You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2139 lines
91 KiB
2139 lines
91 KiB
// |
|
// MatchStickView.swift |
|
// HPlusFit |
|
// |
|
// Created by lemo. on 2019/9/24. |
|
// Copyright © 2019 lemo. All rights reserved. |
|
// |
|
|
|
import UIKit |
|
|
|
fileprivate struct Metric { |
|
static let topHeight = kScaleHeight(65.0) |
|
static let bottomHeight = kScaleHeight(40) |
|
static let dotRadius = kScaleWidth(4.0) |
|
static let rightMargin = kScaleWidth(40.0) |
|
static let lineHeight = kScaleHeight(1.0) |
|
} |
|
|
|
struct Chartpoint { |
|
var x: CGFloat |
|
var y: CGFloat |
|
} |
|
|
|
class MatchStickView: UIView { |
|
|
|
/// 图表模型配置 |
|
var model: ChartViewModel? { |
|
didSet { |
|
setModel() |
|
} |
|
} |
|
|
|
fileprivate var shapeLayers: Array<CAShapeLayer> = [] |
|
fileprivate var layers: Array<CALayer> = [] |
|
fileprivate var valueLabels: Array<UILabel> = [] |
|
fileprivate var views: Array<UIView> = [] |
|
fileprivate var legendImageViews: Array<UIImageView> = [] |
|
fileprivate var legendLabels: Array<UILabel> = [] |
|
fileprivate var noDataView: UIView? |
|
fileprivate var measureLineView: MeasureLineView? |
|
fileprivate var measureResultLabel: UILabel? |
|
/// 虚线 |
|
fileprivate var dotteShapLayers: [CAShapeLayer] = [] |
|
|
|
|
|
fileprivate lazy var leftIconImageView: UIImageView = { |
|
let iconImageView = UIImageView.init() |
|
iconImageView.contentMode = .scaleAspectFit |
|
return iconImageView |
|
}() |
|
|
|
fileprivate lazy var titleLabel: UILabel = { |
|
let label = UILabel.init() |
|
label.font = SystemRegularFont(15.0) |
|
return label |
|
}() |
|
|
|
fileprivate lazy var solidLine: UIView = { |
|
let line = UIView.init() |
|
line.backgroundColor = kHexColor(0xB8C9DC).withAlphaComponent(0.5) |
|
return line |
|
}() |
|
|
|
// 中心值 && 中心单位 |
|
fileprivate lazy var centerLabel: UILabel = { |
|
let label = UILabel.init() |
|
label.font = SystemMediumFont(32) |
|
return label |
|
}() |
|
fileprivate lazy var centerUnitLabel: UILabel = { |
|
let label = UILabel.init() |
|
label.font = SystemRegularFont(12) |
|
label.textColor = kHexColor(0x888888) |
|
return label |
|
}() |
|
// 图例 |
|
fileprivate lazy var legendView: UIStackView = { |
|
let view = UIStackView() |
|
view.axis = .horizontal |
|
view.spacing = 10 |
|
return view |
|
}() |
|
|
|
fileprivate var chartHeight: CGFloat = kScaleHeight(158.0) |
|
|
|
override init(frame: CGRect) { |
|
super.init(frame: frame) |
|
|
|
chartHeight = self.frame.size.height - Metric.topHeight - Metric.bottomHeight |
|
|
|
self.addSubview(leftIconImageView) |
|
self.addSubview(titleLabel) |
|
self.addSubview(solidLine) |
|
self.addSubview(centerLabel) |
|
self.addSubview(centerUnitLabel) |
|
self.addSubview(legendView) |
|
|
|
drawGrid() |
|
} |
|
|
|
required init?(coder aDecoder: NSCoder) { |
|
fatalError("init(coder:) has not been implemented") |
|
} |
|
|
|
override func layoutSubviews() { |
|
super.layoutSubviews() |
|
leftIconImageView.snp.makeConstraints { (make) in |
|
make.left.equalToSuperview().offset(kScaleWidth(8.0)) |
|
make.top.equalToSuperview().offset(kScaleHeight(7.0)) |
|
} |
|
|
|
titleLabel.snp.makeConstraints { (make) in |
|
make.left.equalTo(leftIconImageView.snp.right) |
|
make.centerY.equalTo(leftIconImageView.snp.centerY) |
|
} |
|
|
|
solidLine.snp.makeConstraints { (make) in |
|
make.left.right.equalToSuperview() |
|
make.bottom.equalToSuperview().offset(-Metric.bottomHeight) |
|
make.height.equalTo(Metric.lineHeight) |
|
} |
|
centerLabel.snp.makeConstraints { (make) in |
|
make.centerX.equalToSuperview() |
|
make.top.equalTo(5) |
|
} |
|
centerUnitLabel.snp.makeConstraints { (make) in |
|
make.lastBaseline.equalTo(centerLabel.snp.lastBaseline).offset(-1) |
|
make.left.equalTo(centerLabel.snp.right).offset(kScaleWidth(5)) |
|
} |
|
legendView.snp.makeConstraints { (make) in |
|
make.centerX.equalToSuperview() |
|
make.top.equalTo(centerLabel.snp.bottom).offset(10) |
|
} |
|
} |
|
|
|
fileprivate func drawGrid() { |
|
//绘制两条虚线 |
|
let lineSpace = chartHeight/2.0 |
|
|
|
for i in 0 ..< 2 { |
|
let dotteShapLayer = CAShapeLayer() |
|
dotteShapLayer.fillColor = UIColor.clear.cgColor |
|
dotteShapLayer.strokeColor = kHexColor(0x4E576F).withAlphaComponent(0.2).cgColor |
|
dotteShapLayer.lineWidth = Metric.lineHeight |
|
|
|
let arr: Array = [6, 5] |
|
dotteShapLayer.lineDashPhase = Metric.lineHeight |
|
dotteShapLayer.lineDashPattern = arr as [NSNumber] |
|
self.layer.addSublayer(dotteShapLayer) |
|
dotteShapLayers.append(dotteShapLayer) |
|
let pointy = Metric.topHeight + lineSpace * CGFloat.init(i) |
|
let linePath = UIBezierPath.init() |
|
linePath.move(to: CGPoint.init(x: kScaleWidth(16.0), y: pointy)) |
|
linePath.addLine(to: CGPoint.init(x: kScreenW-Metric.rightMargin, y: pointy)) |
|
dotteShapLayer.path = linePath.cgPath |
|
|
|
/// 中线标签 |
|
let gridLabel = UILabel.init(frame: CGRect.init(x: kScreenW - Metric.rightMargin, y:pointy, width: 0, height: 0)) |
|
gridLabel.text = "0" |
|
gridLabel.tag = 4000 + i |
|
gridLabel.font = SystemRegularFont(11.0) |
|
gridLabel.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
gridLabel.sizeToFit() |
|
gridLabel.center = CGPoint.init(x: gridLabel.center.x, y: pointy) |
|
self.addSubview(gridLabel) |
|
} |
|
} |
|
} |
|
|
|
extension MatchStickView { |
|
/// 设置模型 |
|
fileprivate func setModel() { |
|
if let leftIcon = model?.leftIcon { |
|
leftIconImageView.image = UIImage.init(named: leftIcon) |
|
}else { |
|
titleLabel.snp.updateConstraints { (make) in |
|
make.left.equalToSuperview().offset(kScaleWidth(16.0)) |
|
} |
|
} |
|
|
|
titleLabel.text = model?.title |
|
if let chartLegends = model?.legends { |
|
setLegends(legends: chartLegends) |
|
}else { |
|
for imgView in legendImageViews { |
|
imgView.removeFromSuperview() |
|
} |
|
|
|
for label in legendLabels { |
|
label.removeFromSuperview() |
|
} |
|
} |
|
// 中心值 && 单位 |
|
if let center = model?.centerValue { |
|
centerLabel.isHidden = false |
|
centerLabel.text = center |
|
centerLabel.textColor = model?.centerColor ?? .white |
|
}else { |
|
centerLabel.isHidden = true |
|
} |
|
if let centerUnit = model?.centerUnitValue { |
|
centerUnitLabel.isHidden = false |
|
centerUnitLabel.text = centerUnit |
|
}else { |
|
centerUnitLabel.isHidden = true |
|
} |
|
|
|
if let axisX = model?.xAxis { |
|
setAxisX(axisX: axisX) |
|
} |
|
|
|
setSourceDatas(sourceDatas: model?.source, chartType: (model?.chartType)!) |
|
} |
|
|
|
/// 设置图例 |
|
fileprivate func setLegends(legends: Array<LegendData>?) { |
|
legendView.arrangedSubviews.forEach({ view in |
|
view.removeFromSuperview() |
|
}) |
|
if let legends = legends { |
|
for legend in legends { |
|
let bgView = UIView() |
|
let label = UILabel.init() |
|
label.textColor = kHexColor(0x888888) |
|
label.font = SystemRegularFont(9) |
|
label.text = legend.name |
|
let dotImageView = UIImageView.init() |
|
dotImageView.backgroundColor = legend.docColor |
|
dotImageView.layer.cornerRadius = 5 |
|
dotImageView.layer.masksToBounds = true |
|
bgView.addSubview(dotImageView) |
|
bgView.addSubview(label) |
|
legendView.addArrangedSubview(bgView) |
|
dotImageView.snp.makeConstraints { (make) in |
|
make.left.equalToSuperview() |
|
make.centerY.equalToSuperview() |
|
make.height.width.equalTo(10) |
|
} |
|
label.snp.makeConstraints { (make) in |
|
make.left.equalTo(dotImageView.snp.right).offset(5) |
|
make.centerY.equalToSuperview() |
|
} |
|
bgView.snp.makeConstraints { (make) in |
|
make.left.equalTo(dotImageView.snp.left) |
|
make.right.equalTo(label.snp.right) |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/// 设置X坐标 |
|
/// |
|
/// - Parameter axisX: 坐标数组 |
|
fileprivate func setAxisX(axisX: Array<String>) { |
|
/// 添加x轴坐标 |
|
let count = axisX.count |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
let startPoint = CGPoint.init(x: chartLeftMargin, y: self.frame.size.height - Metric.bottomHeight + kScaleHeight(5.0)) |
|
|
|
/// 移除x坐标 |
|
for view in self.subviews { |
|
if 3000 ..< 4000 ~= view.tag { |
|
view.removeFromSuperview() |
|
} |
|
} |
|
|
|
for i in axisX.indices { |
|
let label = UILabel.init(frame: CGRect.init(x: 0.0, y: startPoint.y, width: 0.0, height: 0.0)) |
|
label.text = axisX[i] |
|
label.font = SystemRegularFont(10.0) |
|
label.textColor = kHexColor(0x888888) |
|
label.textAlignment = .center |
|
label.sizeToFit() |
|
label.tag = 3000+i |
|
label.center = CGPoint.init(x: startPoint.x+CGFloat.init(i)*space, y: label.center.y) |
|
self.addSubview(label) |
|
} |
|
} |
|
|
|
fileprivate func setSourceDatas(sourceDatas: Array<ChartViewData>?, chartType: ChartViewType) { |
|
// 睡眠隐藏虚线 |
|
if chartType == .horizontalSegment || chartType == .segmentBar || chartType == .rectangleSegBar { |
|
if let firstLabel = self.viewWithTag(4000) as? UILabel { |
|
firstLabel.removeFromSuperview() |
|
} |
|
if let middleLabel = self.viewWithTag(4001) as? UILabel { |
|
middleLabel.removeFromSuperview() |
|
} |
|
for layer in self.dotteShapLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
}else { |
|
/// 更新虚线的值 |
|
if chartType == .temp, let floatMaxAxis = model?.floatMaxAxis { |
|
let firstLabel = self.viewWithTag(4000) as! UILabel |
|
firstLabel.text = String(format: "%.1lf", floatMaxAxis) |
|
firstLabel.sizeToFit() |
|
let middleLabel = self.viewWithTag(4001) as! UILabel |
|
middleLabel.text = String(format: "%.1lf", floatMaxAxis / 2) |
|
middleLabel.sizeToFit() |
|
}else { |
|
/// 更新虚线的值 |
|
if let maxAxis = model?.maxAxis { |
|
let firstLabel = self.viewWithTag(4000) as! UILabel |
|
firstLabel.text = "\(maxAxis)" |
|
firstLabel.sizeToFit() |
|
|
|
let middleLabel = self.viewWithTag(4001) as! UILabel |
|
middleLabel.text = "\(maxAxis/2)" |
|
middleLabel.sizeToFit() |
|
|
|
// 配速图表网格线值 |
|
if chartType == .gradentFill && model?.leftIcon == "my_fb_icon_pace" { |
|
firstLabel.text = "\(maxAxis/60)\'\(maxAxis%60)\"" |
|
firstLabel.sizeToFit() |
|
|
|
middleLabel.text = "\(maxAxis/2/60)\'\(maxAxis/2%60)" |
|
middleLabel.sizeToFit() |
|
} |
|
// 燃脂图表网格线值 |
|
if chartType == .gradentFill && model?.leftIcon == "my_icon_kcal" { |
|
firstLabel.text = String(format: "%.1f", Float(maxAxis)/1000.0) |
|
firstLabel.sizeToFit() |
|
|
|
middleLabel.text = String(format: "%.1f", Float(maxAxis/2)/1000.0) |
|
middleLabel.sizeToFit() |
|
} |
|
} |
|
} |
|
} |
|
if chartType != .horizontalSegment && chartType != .gradentFill { |
|
/// 添加x轴坐标 |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space:CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count-1) |
|
} |
|
|
|
let startPoint = CGPoint.init(x: chartLeftMargin, y: self.frame.size.height - Metric.bottomHeight + kScaleHeight(5.0)) |
|
|
|
/// 移除x坐标 |
|
for view in self.subviews { |
|
if 2000 ..< 3000 ~= view.tag { |
|
view.removeFromSuperview() |
|
} |
|
} |
|
|
|
for i in source.indices { |
|
let data = source[i] |
|
let label = UILabel.init(frame: CGRect.init(x: 0.0, y: startPoint.y, width: 0.0, height: 0.0)) |
|
let dateComps = data.date?.components(separatedBy: "-").filter{ $0.count == 2} |
|
label.text = dateComps?.joined(separator: "/") |
|
if source.count > 10 { |
|
if i % 5 != 0 && i != source.count - 1 { |
|
label.text = "" |
|
} |
|
} |
|
label.font = SystemRegularFont(8.0) |
|
label.textColor = kRGBA(50.0, 50.0, 50.0, 1.0) |
|
label.textAlignment = .center |
|
label.sizeToFit() |
|
label.tag = 2000+i |
|
label.center = CGPoint.init(x: startPoint.x+CGFloat.init(i)*space, y: label.center.y) |
|
self.addSubview(label) |
|
} |
|
} |
|
} |
|
|
|
switch chartType { |
|
case .stickMatch: |
|
drawStickMatchChart(sourceDatas: sourceDatas) |
|
case .gradentFill: |
|
drawGradentFillChart(sourceDatas: sourceDatas) |
|
case .polyline: |
|
drawPolylineChart(sourceDatas: sourceDatas) |
|
case .vertical: |
|
drawVerticalLineChart(sourceDatas: sourceDatas) |
|
case .segmentBar: |
|
drawSegmentBarChart(sourceDatas: sourceDatas) |
|
case .rectangleSegBar: |
|
drawRectSegBarChart(sourceDatas: sourceDatas) |
|
case .horizontalSegment: |
|
drawHorizontalSegmentChart(sourceDatas: sourceDatas) |
|
// case .progress: |
|
// drawProgressBarChart(sourceDatas: sourceDatas) |
|
case .roundBar: |
|
drawRoundBarChart(sourceDatas: sourceDatas) |
|
case .regularBar: |
|
drawRegularBarChart(sourceDatas: sourceDatas) |
|
case .stepRoundBar: |
|
drawStepRoundBarChart(sourceDatas: sourceDatas) |
|
case .hrvEcg: |
|
drawHrvEcgChartH(sourceDatas: sourceDatas) |
|
case .temp: |
|
drawFloatGradentFillChart(sourceDatas: sourceDatas) |
|
} |
|
} |
|
|
|
func getLeftStartPosition(space: CGFloat, count: Int) -> CGFloat { |
|
var leftStartPointX: CGFloat = self.center.x |
|
if count % 2 == 0 { |
|
leftStartPointX = self.center.x - (CGFloat(count/2) - 0.5) * space |
|
}else { |
|
leftStartPointX = self.center.x - CGFloat(count/2) * space |
|
} |
|
return leftStartPointX |
|
} |
|
|
|
func createXAxisLabel(sourceDatas: Array<ChartViewData>?, number: Int) { |
|
/// 添加x轴坐标 |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
let space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(number-1) |
|
let leftStartPointX = getLeftStartPosition(space: space, count: count) |
|
let startPoint = CGPoint.init(x: leftStartPointX, y: self.frame.size.height - Metric.bottomHeight + kScaleHeight(5.0)) |
|
|
|
/// 移除x坐标 |
|
for view in self.subviews { |
|
if 5000 ..< 6000 ~= view.tag { |
|
view.removeFromSuperview() |
|
} |
|
} |
|
|
|
for i in source.indices { |
|
let data = source[i] |
|
let label = UILabel.init(frame: CGRect.init(x: 0.0, y: startPoint.y, width: 0.0, height: 0.0)) |
|
label.text = data.date |
|
label.font = SystemRegularFont(10.0) |
|
label.textColor = kRGBA(50.0, 50.0, 50.0, 1.0) |
|
label.textAlignment = .center |
|
label.sizeToFit() |
|
label.tag = 5000+i |
|
label.center = CGPoint.init(x: startPoint.x+CGFloat.init(i)*space, y: label.center.y) |
|
self.addSubview(label) |
|
} |
|
|
|
} |
|
} |
|
|
|
/// 睡眠日详情图表 |
|
fileprivate func drawHorizontalSegmentChart(sourceDatas: Array<ChartViewData>?) { |
|
|
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
|
|
if let source = sourceDatas { |
|
let hours = 12 |
|
|
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space:CGFloat = 0.0 |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(hours) |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
// 无数据遮罩 |
|
let valueSum = source.map{ $0.sleep?.value ?? 0 }.reduce(0, +) |
|
guard valueSum > 0 else { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
return |
|
} |
|
|
|
for i in source.indices { |
|
let sr = source[i] |
|
var color: UIColor? |
|
switch sr.sleep?.type { |
|
case 2: /// 深睡 |
|
color = model?.legends?.last?.docColor |
|
case 1: /// 浅睡 |
|
color = (model?.legends?[1].docColor)! |
|
case 0: /// 醒来 |
|
color = model?.legends?.first?.docColor |
|
default: |
|
color = .clear |
|
} |
|
|
|
let shapeLayer = CAShapeLayer.init() |
|
shapeLayer.fillColor = color?.cgColor |
|
self.layer.addSublayer(shapeLayer) |
|
self.shapeLayers.append(shapeLayer) |
|
|
|
let originX = chartLeftMargin + CGFloat.init(timeInterval(start: (model?.xAxis?.first)!, end: (sr.sleep?.begin)!)) * space |
|
let width: CGFloat = CGFloat((sr.sleep?.value)!) * space |
|
let bezierPath = UIBezierPath.init(roundedRect: CGRect.init(x: originX, y: Metric.topHeight + 20, width: width, height: chartHeight - 20), cornerRadius: 0.0) |
|
shapeLayer.path = bezierPath.cgPath |
|
} |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
|
|
let hour_f = Float(value/space) |
|
for sr in source { |
|
if let begin = sr.sleep?.begin ,let end = sr.sleep?.end { |
|
let begin_f = self.timeInterval(start: "21:00", end: begin) |
|
let end_f = self.timeInterval(start: "21:00", end: end) |
|
if hour_f >= begin_f && hour_f <= end_f { |
|
self.measureLineView?.measureLineView.isHidden = false |
|
var typeStr: String = "" |
|
if let type = sr.sleep?.type { |
|
typeStr = self.model?.legends?[type].name ?? "" |
|
} |
|
var resultStr: String = "" |
|
if typeStr.count > 0 { |
|
resultStr += typeStr |
|
resultStr += ":" |
|
} |
|
|
|
if let value = sr.sleep?.value { |
|
let duration = value * 60 |
|
resultStr += String(format: "%.0f", duration) |
|
resultStr += MultiLanguageKey_FB.minFB.localized |
|
} |
|
|
|
resultStr += " \(begin)-\(end)" |
|
self.measureResultLabel?.text = resultStr |
|
break |
|
}else { |
|
self.measureResultLabel?.text = "" |
|
self.measureLineView?.measureLineView.isHidden = true |
|
} |
|
} |
|
|
|
} |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/// 睡眠月统计图表 |
|
fileprivate func drawRectSegBarChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
|
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.sum }.reduce(0, +) |
|
guard valueSum > 0 else { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
return |
|
} |
|
|
|
/// 绘制分段条形图 |
|
for i in source.indices { |
|
let sr = source[i] |
|
if sr.sum > 0 { |
|
if let sleeps = source[i].sleeps { |
|
var lastPointy: CGFloat = baseH |
|
lastPointy = baseH - CGFloat.init(sr.sum) * dotPerPixels |
|
for sleep in sleeps.reversed() { |
|
var color: UIColor? |
|
switch sleep.type { |
|
case 2: /// 深睡 |
|
color = model?.legends?.last?.docColor |
|
case 1: /// 浅睡 |
|
color = (model?.legends?[1].docColor)! |
|
case 0: /// 醒来 |
|
color = model?.legends?.first?.docColor |
|
default: |
|
color = .clear |
|
} |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = color?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(2.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: lastPointy)) |
|
lastPointy = lastPointy + CGFloat.init(sleep.value!) * dotPerPixels |
|
// 绘制最后一段确保不超过图标底线 |
|
if sleep.type == 2 { |
|
lastPointy = baseH |
|
} |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: lastPointy)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
let day = value/space |
|
var idx: Int = 0 |
|
if day < 0 { |
|
idx = 0 |
|
}else if (day >= CGFloat(source.count)) { |
|
idx = source.count - 1 |
|
}else { |
|
idx = lroundf(Float(day)) |
|
} |
|
|
|
let sr = source[idx] |
|
var duration: Float = 0.0 |
|
if let sleeps = sr.sleeps { |
|
for sleep in sleeps { |
|
if sleep.type == 2 || sleep.type == 1 { |
|
duration += (sleep.value ?? 0.0) |
|
} |
|
} |
|
if duration > 0 { |
|
self.measureResultLabel?.text = "睡眠: " + String(format: "%.1f", duration) + "小时" |
|
let topSpace = CGFloat.init(Float(maxAxis!) - sr.sum) * dotPerPixels |
|
self.handleMeasureResult(topSpace) |
|
}else { |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
|
|
} |
|
} |
|
} |
|
} |
|
|
|
/// 睡眠周统计 |
|
fileprivate func drawSegmentBarChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = (chartHeight - CGFloat.init(2))/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.sum }.reduce(0, +) |
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
/// 绘制分段条形图 |
|
for i in source.indices { |
|
let sr = source[i] |
|
|
|
if sr.sum > 0 { |
|
if let sleeps = source[i].sleeps { |
|
var lastPointy: CGFloat = baseH |
|
lastPointy = baseH - CGFloat.init(sr.sum) * dotPerPixels |
|
var totalSleep: Float32 = 0.0 |
|
for sleep in sleeps.reversed() { |
|
var color: UIColor? |
|
switch sleep.type { |
|
case 2: /// 深睡 |
|
color = model?.legends?.last?.docColor |
|
case 1: /// 浅睡 |
|
color = (model?.legends?[1].docColor)! |
|
case 0: /// 醒来 |
|
color = model?.legends?.first?.docColor |
|
default: |
|
color = .clear |
|
} |
|
totalSleep += sleep.value ?? 0 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = color?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(7.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: lastPointy)) |
|
lastPointy = lastPointy + CGFloat.init(sleep.value!) * dotPerPixels |
|
// 绘制最后一段确保不超过图标底线 |
|
if sleep.type == 2 { |
|
lastPointy = baseH |
|
} |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: lastPointy)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
|
|
// // 数值标注 |
|
// let label = UILabel.init(frame: CGRect.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(sr.sum) * dotPerPixels - kScaleHeight(15.0), width: 0, height: 0)) |
|
// label.text = totalSleep.float2String() |
|
// label.textAlignment = .center |
|
// label.font = SystemRegularFont(11.0) |
|
// label.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
// label.sizeToFit() |
|
// self.addSubview(label) |
|
// label.center = CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: label.center.y) |
|
// valueLabels.append(label) |
|
} |
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
fileprivate func drawVerticalLineChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.maxValue }.reduce(0, +) |
|
guard valueSum > 0 else { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
return |
|
} |
|
|
|
/// 绘制竖线图 |
|
for i in source.indices { |
|
let data = source[i] |
|
if data.maxValue > 0 && data.minValue > 0 { |
|
|
|
/// 最大值与最小值相等的情况 |
|
if data.maxValue == data.minValue { |
|
let circleShapeLayer = CAShapeLayer() |
|
circleShapeLayer.fillColor = model?.lineColor?.cgColor |
|
circleShapeLayer.strokeColor = UIColor.clear.cgColor |
|
self.layer.addSublayer(circleShapeLayer) |
|
shapeLayers.append(circleShapeLayer) |
|
|
|
let center = CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.minValue)*dotPerPixels) |
|
let circlePath = UIBezierPath(arcCenter: center, radius: 1.5, startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) |
|
circleShapeLayer.path = circlePath.cgPath |
|
}else { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(2.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.minValue)*dotPerPixels)) |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.maxValue)*dotPerPixels)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
/// 绘制平均值折线 |
|
if let isDrawAvgLine = model?.isDrawAvgLine, isDrawAvgLine { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = UIColor.red.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(1.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
var i = 0 |
|
var points = Array<Chartpoint>([]) |
|
for data in source { |
|
if data.aveValue > 0 { |
|
points.append(Chartpoint(x: CGFloat(i), y: CGFloat(data.aveValue))) |
|
} |
|
i += 1 |
|
} |
|
|
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
for i in points.indices { |
|
let point = points[i] |
|
if i == 0 { |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+point.x*space, y: baseH - point.y*dotPerPixels)) |
|
}else { |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+point.x*space, y: baseH - point.y*dotPerPixels)) |
|
} |
|
} |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
|
|
let index_f = value/space |
|
var idx: Int = 0 |
|
if index_f < 0 { |
|
idx = 0 |
|
}else if (index_f >= CGFloat(source.count)) { |
|
idx = source.count - 1 |
|
}else { |
|
idx = lroundf(Float(index_f)) |
|
} |
|
|
|
let sr = source[idx] |
|
if sr.maxValue > 0 && sr.minValue > 0 { |
|
// 血压 |
|
if self.model?.leftIcon == "my_icon_blood_pressure" { |
|
self.measureResultLabel?.text = "收缩压:"+"\(sr.maxValue)mmhg" + " " + "舒张压:"+"\(sr.minValue)mmhg" |
|
} |
|
// 心率 |
|
if self.model?.leftIcon == "my_icon_hrart_rate" { |
|
self.measureResultLabel?.text = "最高:"+"\(sr.maxValue)bpm" + " " + "最低:"+"\(sr.minValue)bpm" |
|
} |
|
let topSpace = CGFloat(maxAxis! - sr.maxValue)*dotPerPixels |
|
self.handleMeasureResult(topSpace) |
|
}else { |
|
self.measureResultLabel?.text = "" |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/// 绘制常规条形图(月血氧) |
|
/// |
|
/// - Parameter sourceDatas: 数据源 |
|
fileprivate func drawRegularBarChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
let valueSum = source.map{ $0.value }.reduce(0, +) |
|
guard valueSum > 0 else { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
return |
|
} |
|
|
|
/// 绘制竖线图 |
|
for i in source.indices { |
|
let data = source[i] |
|
if data.value > 0 { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(2.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH)) |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.value)*dotPerPixels)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
} |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
let index_f = value/space |
|
var idx: Int = 0 |
|
if index_f < 0 { |
|
idx = 0 |
|
}else if (index_f >= CGFloat(source.count)) { |
|
idx = source.count - 1 |
|
}else { |
|
idx = lroundf(Float(index_f)) |
|
} |
|
|
|
let sr = source[idx] |
|
if sr.value > 0 { |
|
self.measureResultLabel?.text = "血氧:" + "\(sr.value)%" |
|
let topSpace = CGFloat(maxAxis! - sr.value)*dotPerPixels |
|
self.handleMeasureResult(topSpace) |
|
}else { |
|
self.measureResultLabel?.text = "" |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
/// 绘制圆角条形图(血氧) |
|
/// |
|
/// - Parameter sourceDatas: 数据源 |
|
fileprivate func drawRoundBarChart(sourceDatas: Array<ChartViewData>?){ |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let margin = kScaleWidth(2.0) |
|
let dotPerPixels = (chartHeight - CGFloat.init(2)*margin)/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight - margin // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.value }.reduce(0, +) |
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
/// 圆角柱状图 |
|
for i in source.indices { |
|
let data = source[i] |
|
if data.value > 0 { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(4.0) |
|
lineShapeLayer.lineCap = CAShapeLayerLineCap.round |
|
lineShapeLayer.lineJoin = CAShapeLayerLineJoin.round |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH)) |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.value)*dotPerPixels)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
|
|
// 数值标注 |
|
let label = UILabel.init(frame: CGRect.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.value) * dotPerPixels - kScaleHeight(15.0), width: 0, height: 0)) |
|
label.text = "\(data.value)%" |
|
label.textAlignment = .center |
|
label.font = SystemRegularFont(11.0) |
|
label.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
label.sizeToFit() |
|
self.addSubview(label) |
|
label.center = CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: label.center.y) |
|
valueLabels.append(label) |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// 绘制圆角条形图(今日步数) |
|
/// |
|
/// - Parameter sourceDatas: 数据源 |
|
fileprivate func drawStepRoundBarChart(sourceDatas: Array<ChartViewData>?){ |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let margin = kScaleWidth(2.0) |
|
let dotPerPixels = (chartHeight - CGFloat.init(2)*margin)/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight - margin // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 添加X坐标 |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(count - 1) |
|
} |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.value }.reduce(0, +) |
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
/// 圆角柱状图 |
|
for i in source.indices { |
|
let data = source[i] |
|
if data.value > 0 { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(4.0) |
|
lineShapeLayer.lineCap = CAShapeLayerLineCap.round |
|
lineShapeLayer.lineJoin = CAShapeLayerLineJoin.round |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH)) |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat.init(data.value)*dotPerPixels)) |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
} |
|
} |
|
} |
|
|
|
func handleMeasureResult(_ value: CGFloat) { |
|
self.measureLineView?.drawMeasureLine(topSpace: value) |
|
} |
|
|
|
fileprivate func drawGradentFillChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for layer in layers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
let valueArr: [Int] = source.map{ $0.value } |
|
let valueSum = valueArr.reduce(0, +) |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
|
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
guard valueSum > 0 else { |
|
return |
|
} |
|
let valueCount = valueArr.filter { $0 > 0 }.count |
|
if valueCount == 1 { // 一个点的情况 |
|
let positiveNumbers = valueArr.filter { $0 > 0 } |
|
let circelShapeLayer = CAShapeLayer() |
|
circelShapeLayer.strokeColor = UIColor.clear.cgColor |
|
circelShapeLayer.fillColor = model?.lineColor?.cgColor |
|
self.layer.addSublayer(circelShapeLayer) |
|
shapeLayers.append(circelShapeLayer) |
|
let circlePath = UIBezierPath() |
|
let point = CGPoint(x: chartLeftMargin, y:baseH - CGFloat.init(positiveNumbers[0]) * dotPerPixels) |
|
circlePath.addArc(withCenter: point, radius: 3.0, startAngle: 0, endAngle: CGFloat(Double.pi * 2.0), clockwise: true) |
|
circelShapeLayer.path = circlePath.cgPath |
|
return |
|
} |
|
|
|
/// 绘图 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = UIColor.clear.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(0.2) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
let path = UIBezierPath() |
|
|
|
// 能显示最多的点个数 |
|
var maxPoints: Int = source.count |
|
if let maxPs = model?.maxpoints, maxPs > 0 { |
|
maxPoints = maxPs |
|
} |
|
|
|
/// 添加X坐标 |
|
var dotSpace: CGFloat = 0.0 |
|
if maxPoints > 1 { |
|
dotSpace = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(maxPoints - 1) |
|
} |
|
|
|
// 实际显示的点个数 |
|
var pointCount = source.count |
|
if let points = model?.points, points > 0 { |
|
pointCount = points |
|
} |
|
|
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
path.move(to: CGPoint.init(x: chartLeftMargin, y:baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
}else { |
|
path.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
lineShapeLayer.path = path.cgPath |
|
|
|
|
|
/// 绘制渐变图的轮廓 |
|
let contourShapeLayer = CAShapeLayer.init() |
|
contourShapeLayer.fillColor = UIColor.clear.cgColor |
|
contourShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
contourShapeLayer.lineWidth = kScaleWidth(1.2) |
|
self.layer.addSublayer(contourShapeLayer) |
|
shapeLayers.append(contourShapeLayer) |
|
|
|
//轮廓的路径 |
|
let contourPath = UIBezierPath() |
|
var isAddLine: Bool = false |
|
var numberOfPoints = 0 |
|
// 折线不连续 |
|
if let isContinuous = model?.isContinuous, !isContinuous { |
|
for i in 0 ..< pointCount { |
|
if source[i].value > 0 { |
|
numberOfPoints += 1 |
|
if !isAddLine { |
|
contourPath.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
isAddLine = true |
|
continue |
|
} |
|
contourPath.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
}else { |
|
//连续 |
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
contourPath.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
|
|
}else { |
|
contourPath.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
} |
|
// TODO 一个点的处理 |
|
if numberOfPoints == 1 { |
|
} |
|
contourShapeLayer.path = contourPath.cgPath |
|
|
|
/// 遮罩层形状 |
|
let firstPoint: CGPoint = CGPoint.init(x: chartLeftMargin, y: baseH - CGFloat.init(source[0].value) * dotPerPixels) |
|
let lastPoint: CGPoint = CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(pointCount-1), y: baseH - CGFloat.init(source[pointCount-1].value) * dotPerPixels) |
|
|
|
let bezier1 = UIBezierPath() |
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
bezier1.move(to: CGPoint.init(x: chartLeftMargin, y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
}else { |
|
bezier1.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
|
|
bezier1.addLine(to: CGPoint.init(x: lastPoint.x, y: baseH)) |
|
bezier1.addLine(to: CGPoint.init(x: chartLeftMargin, y: baseH)) |
|
bezier1.addLine(to: firstPoint) |
|
|
|
let shadeLayer = CAShapeLayer.init() |
|
shadeLayer.path = bezier1.cgPath |
|
shadeLayer.fillColor = UIColor.green.cgColor |
|
|
|
/// 绘制渐变层 |
|
let colorA: UIColor = (model?.lineColor?.withAlphaComponent(0.5))! |
|
let colorB: UIColor = (model?.lineColor?.withAlphaComponent(0.0))! |
|
|
|
let gradientColors = [colorA.cgColor, |
|
colorB.cgColor |
|
] |
|
let gradientLayer = CAGradientLayer() |
|
gradientLayer.startPoint = CGPoint.init(x: 0, y: 0) |
|
gradientLayer.endPoint = CGPoint.init(x: 0, y: 1) |
|
gradientLayer.locations = [0.0, 1.0] |
|
gradientLayer.frame = CGRect.init(x: 0, y: kScaleHeight(65.0), width: kScreenW, height: chartHeight) |
|
gradientLayer.colors = gradientColors |
|
|
|
let baseLayer = CALayer.init() |
|
baseLayer.addSublayer(gradientLayer) |
|
baseLayer.mask = shadeLayer |
|
self.layer.insertSublayer(baseLayer, at: 0) |
|
layers.append(baseLayer) |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
var idx = 0 |
|
let idx_f = value/dotSpace |
|
if idx_f < 0 { |
|
idx = 0 |
|
}else if (idx_f > CGFloat(count - 1)) { |
|
idx = count - 1 |
|
}else { |
|
idx = Int(idx_f) |
|
} |
|
if source[idx].value > 0 { |
|
self.measureLineView?.measureLineView.isHidden = false |
|
self.measureResultLabel?.text = "\(source[idx].value)" |
|
let topSpace = CGFloat(maxAxis! - source[idx].value) * dotPerPixels |
|
self.handleMeasureResult(topSpace) |
|
}else { |
|
self.measureResultLabel?.text = "" |
|
self.measureLineView?.measureLineView.isHidden = true |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
func createMeasureResultLabel() -> UILabel { |
|
let measureResultLabel = UILabel() |
|
measureResultLabel.frame = CGRect(x: 0, y: kScaleHeight(65.0) - kScaleHeight(20.0), width: self.width, height: kScaleHeight(20.0)) |
|
measureResultLabel.center = CGPoint(x: self.center.x, y: measureResultLabel.center.y) |
|
measureResultLabel.font = SystemRegularFont(15.0) |
|
measureResultLabel.textAlignment = .center |
|
measureResultLabel.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
return measureResultLabel |
|
} |
|
|
|
func drawPolylineChart(sourceDatas: Array<ChartViewData>?) { |
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
for view in views { |
|
view.removeFromSuperview() |
|
} |
|
// 收缩压 && 舒张压标签 |
|
let dbpView = UIView(frame: CGRect(x: 25, y: 20 + 6, width: 10, height: 2)) |
|
dbpView.backgroundColor = kHexColor(0xCA1E44) |
|
let dbpLabel = UILabel(frame: CGRect(x: 35 + 6, y: 20, width: 28, height: 14)) |
|
dbpLabel.text = "收缩压".localized |
|
dbpLabel.textColor = kHexColor(0xCA1E44) |
|
dbpLabel.font = SystemRegularFont(9) |
|
self.addSubview(dbpView) |
|
self.addSubview(dbpLabel) |
|
let sbpView = UIView(frame: CGRect(x: dbpLabel.mj_x + 28 + 18, y: dbpView.mj_y, width: 10, height: 2)) |
|
sbpView.backgroundColor = kHexColor(0x76BA3F) |
|
let sbpLabel = UILabel(frame: CGRect(x: sbpView.mj_x + 10 + 6, y: dbpLabel.mj_y, width: 28, height: 14)) |
|
sbpLabel.text = "舒张压".localized |
|
sbpLabel.textColor = kHexColor(0x76BA3F) |
|
sbpLabel.font = SystemRegularFont(9) |
|
self.addSubview(sbpView) |
|
self.addSubview(sbpLabel) |
|
views.append(dbpView) |
|
valueLabels.append(dbpLabel) |
|
views.append(sbpView) |
|
valueLabels.append(sbpLabel) |
|
// 绘制数据 |
|
if let source = sourceDatas { |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
for layer in layers { |
|
layer.removeFromSuperlayer() |
|
} |
|
let valueArr: [Int] = source.map{ $0.value } |
|
let valueSum = valueArr.reduce(0, +) |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
guard valueSum > 0 else { |
|
return |
|
} |
|
// 能显示最多的点个数 |
|
var maxPoints: Int = source.count |
|
if let maxPs = model?.maxpoints, maxPs > 0 { |
|
maxPoints = maxPs |
|
} |
|
/// 添加X坐标 |
|
var dotSpace: CGFloat = 0.0 |
|
if maxPoints > 1 { |
|
dotSpace = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(maxPoints - 1) |
|
} |
|
// 实际显示的点个数 |
|
var pointCount = source.count |
|
if let points = model?.points, points > 0 { |
|
pointCount = points |
|
} |
|
/// 绘图 |
|
var realPointCount = 0 // 实际绘制的点数量 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = kHexColor(0xCA1E44).cgColor |
|
lineShapeLayer.lineWidth = 1 |
|
let path = UIBezierPath() |
|
var isMove = false |
|
for i in 0 ..< pointCount { |
|
if source[i].value > 0 { |
|
realPointCount += 1 |
|
if !isMove { |
|
path.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
isMove = true |
|
continue |
|
} |
|
path.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
// 第二根线 |
|
let lineShapeLayer2 = CAShapeLayer.init() |
|
lineShapeLayer2.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer2.strokeColor = kHexColor(0x76BA3F).cgColor |
|
lineShapeLayer2.lineWidth = 1 |
|
let path2 = UIBezierPath() |
|
var isMove2 = false |
|
for i in 0 ..< pointCount { |
|
if source[i].value2 > 0 { |
|
if !isMove2 { |
|
path2.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - CGFloat.init(source[i].value2) * dotPerPixels)) |
|
isMove2 = true |
|
continue |
|
} |
|
path2.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value2) * dotPerPixels)) |
|
} |
|
} |
|
// 仅有一个点 |
|
if realPointCount == 1 { |
|
let radius: CGFloat = 2 |
|
path.addArc(withCenter: path.currentPoint, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true) |
|
lineShapeLayer.fillColor = kHexColor(0xCA1E44).cgColor |
|
path2.addArc(withCenter: path2.currentPoint, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true) |
|
lineShapeLayer2.fillColor = kHexColor(0x76BA3F).cgColor |
|
} |
|
lineShapeLayer.path = path.cgPath |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
lineShapeLayer2.path = path2.cgPath |
|
self.layer.addSublayer(lineShapeLayer2) |
|
shapeLayers.append(lineShapeLayer2) |
|
} |
|
} |
|
|
|
fileprivate func drawStickMatchChart(sourceDatas: Array<ChartViewData>?) { |
|
|
|
if let source = sourceDatas { |
|
|
|
let count = source.count |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
var space: CGFloat = 0.0 |
|
if count > 1 { |
|
space = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat(count - 1) |
|
} |
|
|
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
let valueSum = source.map{ $0.maxValue }.reduce(0, +) |
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
for i in source.indices { |
|
let data = source[i] |
|
|
|
/// 数据点不为0时绘制火柴 |
|
if data.minValue > 0 && data.maxValue > 0 { |
|
|
|
// 绘制下圆点 |
|
let downDotShapeLayer = CAShapeLayer() |
|
downDotShapeLayer.fillColor = model?.legends?.first?.docColor.cgColor |
|
downDotShapeLayer.strokeColor = UIColor.clear.cgColor |
|
self.layer.addSublayer(downDotShapeLayer) |
|
shapeLayers.append(downDotShapeLayer) |
|
|
|
let downPoint = CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat(data.minValue)*dotPerPixels) |
|
let downDotPath = UIBezierPath(arcCenter: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat(data.minValue)*dotPerPixels), radius: kScaleWidth(3.0), startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) |
|
|
|
downDotShapeLayer.path = downDotPath.cgPath |
|
|
|
// 绘制上圆点 |
|
let upDotShapeLayer = CAShapeLayer() |
|
upDotShapeLayer.fillColor = model?.legends?.last?.docColor.cgColor |
|
upDotShapeLayer.strokeColor = UIColor.clear.cgColor |
|
self.layer.addSublayer(upDotShapeLayer) |
|
shapeLayers.append(upDotShapeLayer) |
|
|
|
let upPoint = CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat(data.maxValue)*dotPerPixels) |
|
let upDotPath = UIBezierPath(arcCenter: CGPoint.init(x: chartLeftMargin+CGFloat.init(i)*space, y: baseH - CGFloat(data.maxValue)*dotPerPixels), radius: kScaleWidth(3.0), startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) |
|
|
|
upDotShapeLayer.path = upDotPath.cgPath |
|
|
|
// 绘制直线 |
|
let lineShapeLayer = CAShapeLayer() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = kHexColor(0x4E576F).cgColor |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
let linePath = UIBezierPath() |
|
linePath.move(to: CGPoint.init(x: downPoint.x, y: downPoint.y - kScaleWidth(3.0))) |
|
linePath.addLine(to: CGPoint.init(x: upPoint.x, y: upPoint.y + kScaleWidth(3.0))) |
|
lineShapeLayer.path = linePath.cgPath |
|
//舒张压数据显示标签 |
|
let diastolicLabel = UILabel.init(frame: CGRect.init(x: downPoint.x, y: downPoint.y + kScaleHeight(5.0), width: 0, height: 0)) |
|
diastolicLabel.text = "\(data.minValue)" |
|
diastolicLabel.textAlignment = .center |
|
diastolicLabel.font = SystemRegularFont(11.0) |
|
diastolicLabel.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
diastolicLabel.sizeToFit() |
|
self.addSubview(diastolicLabel) |
|
diastolicLabel.center = CGPoint.init(x: downPoint.x, y: diastolicLabel.center.y) |
|
valueLabels.append(diastolicLabel) |
|
//收缩压数据显示标签 |
|
let systolicLabel = UILabel.init(frame: CGRect.init(x: 0, y: upPoint.y - kScaleHeight(18.0), width: 0, height: 0)) |
|
systolicLabel.text = "\(data.maxValue)" |
|
systolicLabel.textAlignment = .center |
|
systolicLabel.font = SystemRegularFont(11.0) |
|
systolicLabel.textColor = kRGBA(50.0, 50.0, 50.0, 0.5) |
|
systolicLabel.sizeToFit() |
|
self.addSubview(systolicLabel) |
|
systolicLabel.center = CGPoint.init(x: upPoint.x, y: systolicLabel.center.y) |
|
valueLabels.append(systolicLabel) |
|
} |
|
|
|
} |
|
|
|
/// 绘制平均值折线 |
|
if let isDrawAvgLine = model?.isDrawAvgLine, isDrawAvgLine { |
|
/// 图层 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = UIColor.red.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(1.0) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
var i = 0 |
|
var points = Array<Chartpoint>([]) |
|
for data in source { |
|
if data.aveValue > 0 { |
|
points.append(Chartpoint(x: CGFloat(i), y: CGFloat(data.aveValue))) |
|
} |
|
i += 1 |
|
} |
|
|
|
/// 路径 |
|
let bezierPath = UIBezierPath.init() |
|
for i in points.indices { |
|
let point = points[i] |
|
if i == 0 { |
|
bezierPath.move(to: CGPoint.init(x: chartLeftMargin+point.x*space, y: baseH - point.y*dotPerPixels)) |
|
}else { |
|
bezierPath.addLine(to: CGPoint.init(x: chartLeftMargin+point.x*space, y: baseH - point.y*dotPerPixels)) |
|
} |
|
} |
|
lineShapeLayer.path = bezierPath.cgPath |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/// 绘制HRV/心电图 |
|
/// |
|
/// - Parameter sourceDatas: 数据源 |
|
fileprivate func drawHrvEcgChartH(sourceDatas: Array<ChartViewData>?){ |
|
for label in valueLabels { |
|
label.removeFromSuperview() |
|
} |
|
for view in views { |
|
view.removeFromSuperview() |
|
} |
|
// 绘制数据 |
|
if let source = sourceDatas { |
|
let maxAxis = model?.maxAxis |
|
let dotPerPixels = chartHeight/CGFloat.init(maxAxis!) |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
for layer in layers { |
|
layer.removeFromSuperlayer() |
|
} |
|
let valueArr: [Int] = source.map{ $0.value } |
|
let valueSum = valueArr.reduce(0, +) |
|
|
|
// 网格 |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
guard valueSum > 0 else { |
|
return |
|
} |
|
// 能显示最多的点个数 |
|
var maxPoints: Int = source.count |
|
if let maxPs = model?.maxpoints, maxPs > 0 { |
|
maxPoints = maxPs |
|
} |
|
/// 添加X坐标 |
|
var dotSpace: CGFloat = 0.0 |
|
if maxPoints > 1 { |
|
dotSpace = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(maxPoints - 1) |
|
} |
|
// 实际显示的点个数 |
|
var pointCount = source.count |
|
if let points = model?.points, points > 0 { |
|
pointCount = points |
|
} |
|
/// 绘图 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = kHexColor(0xCA1E44).cgColor |
|
lineShapeLayer.lineWidth = 1 |
|
let path = UIBezierPath() |
|
var isMove = false |
|
for i in 0 ..< pointCount { |
|
if source[i].value > 0 { |
|
if !isMove { |
|
path.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
isMove = true |
|
continue |
|
} |
|
path.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - CGFloat.init(source[i].value) * dotPerPixels)) |
|
} |
|
} |
|
lineShapeLayer.path = path.cgPath |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
} |
|
} |
|
|
|
/// 体温小数点折线图 |
|
fileprivate func drawFloatGradentFillChart(sourceDatas: Array<ChartViewData>?) { |
|
if let source = sourceDatas { |
|
let count = source.count |
|
let maxAxis = model?.floatMaxAxis |
|
let dotPerPixels = chartHeight/maxAxis! |
|
let baseH = self.frame.size.height - Metric.bottomHeight // 图像启始位置(x轴位置) |
|
let chartLeftMargin = kScaleWidth(49.0) |
|
let chartRightMargin = kScaleWidth(72.0) |
|
|
|
/// 先移除图层 |
|
for layer in shapeLayers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
for layer in layers { |
|
layer.removeFromSuperlayer() |
|
} |
|
|
|
let valueArr: [CGFloat] = source.map{ CGFloat($0.floatValue) } |
|
let valueSum = valueArr.reduce(0, +) |
|
|
|
//无数据遮罩 |
|
if noDataView != nil { |
|
noDataView?.removeFromSuperview() |
|
} |
|
|
|
// 测量线 |
|
if self.measureLineView != nil { |
|
measureLineView?.removeFromSuperview() |
|
} |
|
|
|
// 测量值 |
|
if self.measureResultLabel != nil { |
|
measureResultLabel?.removeFromSuperview() |
|
} |
|
|
|
if valueSum == 0 { |
|
let noDataView = NoDataChartView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: kScaleWidth(251.0), height: kScaleHeight(160.0))) |
|
self.addSubview(noDataView) |
|
self.noDataView = noDataView |
|
} |
|
|
|
guard valueSum > 0 else { |
|
return |
|
} |
|
|
|
if count == 1 { // 一个点的情况 |
|
let circelShapeLayer = CAShapeLayer() |
|
circelShapeLayer.strokeColor = UIColor.clear.cgColor |
|
circelShapeLayer.fillColor = model?.lineColor?.cgColor |
|
self.layer.addSublayer(circelShapeLayer) |
|
shapeLayers.append(circelShapeLayer) |
|
|
|
let circlePath = UIBezierPath() |
|
let point = CGPoint(x: chartLeftMargin, y:baseH - CGFloat.init(source[0].value) * dotPerPixels) |
|
circlePath.addArc(withCenter: point, radius: 3.0, startAngle: 0, endAngle: CGFloat(Double.pi * 2.0), clockwise: true) |
|
circelShapeLayer.path = circlePath.cgPath |
|
return |
|
} |
|
|
|
/// 绘图 |
|
let lineShapeLayer = CAShapeLayer.init() |
|
lineShapeLayer.fillColor = UIColor.clear.cgColor |
|
lineShapeLayer.strokeColor = UIColor.clear.cgColor |
|
lineShapeLayer.lineWidth = kScaleWidth(0.2) |
|
self.layer.addSublayer(lineShapeLayer) |
|
shapeLayers.append(lineShapeLayer) |
|
|
|
let path = UIBezierPath() |
|
|
|
// 能显示最多的点个数 |
|
var maxPoints: Int = source.count |
|
if let maxPs = model?.maxpoints, maxPs > 0 { |
|
maxPoints = maxPs |
|
} |
|
|
|
/// 添加X坐标 |
|
var dotSpace: CGFloat = 0.0 |
|
if maxPoints > 1 { |
|
dotSpace = (kScreenW - chartLeftMargin - chartRightMargin)/CGFloat.init(maxPoints - 1) |
|
} |
|
|
|
// 实际显示的点个数 |
|
var pointCount = source.count |
|
if let points = model?.points, points > 0 { |
|
pointCount = points |
|
} |
|
|
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
path.move(to: CGPoint.init(x: chartLeftMargin, y:baseH - source[i].floatValue * dotPerPixels)) |
|
}else { |
|
path.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - source[i].floatValue * dotPerPixels)) |
|
} |
|
} |
|
lineShapeLayer.path = path.cgPath |
|
|
|
|
|
/// 绘制渐变图的轮廓 |
|
let contourShapeLayer = CAShapeLayer.init() |
|
contourShapeLayer.fillColor = UIColor.clear.cgColor |
|
contourShapeLayer.strokeColor = model?.lineColor?.cgColor |
|
contourShapeLayer.lineWidth = kScaleWidth(1.2) |
|
self.layer.addSublayer(contourShapeLayer) |
|
shapeLayers.append(contourShapeLayer) |
|
|
|
//轮廓的路径 |
|
let contourPath = UIBezierPath() |
|
var isAddLine: Bool = false |
|
|
|
// 折线不连续 |
|
if let isContinuous = model?.isContinuous, !isContinuous { |
|
for i in 0 ..< pointCount { |
|
// 点为0指向该点,但不再连线 |
|
if source[i].floatValue == 0 { |
|
contourPath.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - source[i].floatValue * dotPerPixels)) |
|
isAddLine = false |
|
}else { |
|
// 第一个不为0的点 |
|
if isAddLine { |
|
contourPath.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - source[i].floatValue * dotPerPixels)) |
|
}else { |
|
contourPath.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - source[i].floatValue * dotPerPixels)) |
|
isAddLine = true |
|
} |
|
} |
|
} |
|
|
|
}else { |
|
//连续 |
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
contourPath.move(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y:baseH - source[i].floatValue * dotPerPixels)) |
|
|
|
}else { |
|
contourPath.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - source[i].floatValue * dotPerPixels)) |
|
} |
|
} |
|
} |
|
|
|
contourShapeLayer.path = contourPath.cgPath |
|
|
|
/// 遮罩层形状 |
|
let firstPoint: CGPoint = CGPoint.init(x: chartLeftMargin, y: baseH - source[0].floatValue * dotPerPixels) |
|
let lastPoint: CGPoint = CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(pointCount-1), y: baseH - CGFloat.init(source[pointCount-1].value) * dotPerPixels) |
|
|
|
let bezier1 = UIBezierPath() |
|
for i in 0 ..< pointCount { |
|
if i == 0 { |
|
bezier1.move(to: CGPoint.init(x: chartLeftMargin, y: baseH - source[i].floatValue * dotPerPixels)) |
|
}else { |
|
bezier1.addLine(to: CGPoint.init(x: chartLeftMargin+dotSpace*CGFloat(i), y: baseH - source[i].floatValue * dotPerPixels)) |
|
} |
|
} |
|
|
|
bezier1.addLine(to: CGPoint.init(x: lastPoint.x, y: baseH)) |
|
bezier1.addLine(to: CGPoint.init(x: chartLeftMargin, y: baseH)) |
|
bezier1.addLine(to: firstPoint) |
|
|
|
let shadeLayer = CAShapeLayer.init() |
|
shadeLayer.path = bezier1.cgPath |
|
shadeLayer.fillColor = UIColor.green.cgColor |
|
|
|
/// 绘制渐变层 |
|
let colorA: UIColor = (model?.lineColor?.withAlphaComponent(0.5))! |
|
let colorB: UIColor = (model?.lineColor?.withAlphaComponent(0.0))! |
|
|
|
let gradientColors = [colorA.cgColor, |
|
colorB.cgColor |
|
] |
|
let gradientLayer = CAGradientLayer() |
|
gradientLayer.startPoint = CGPoint.init(x: 0, y: 0) |
|
gradientLayer.endPoint = CGPoint.init(x: 0, y: 1) |
|
gradientLayer.locations = [0.0, 1.0] |
|
gradientLayer.frame = CGRect.init(x: 0, y: kScaleHeight(65.0), width: kScreenW, height: chartHeight) |
|
gradientLayer.colors = gradientColors |
|
|
|
let baseLayer = CALayer.init() |
|
baseLayer.addSublayer(gradientLayer) |
|
baseLayer.mask = shadeLayer |
|
self.layer.insertSublayer(baseLayer, at: 0) |
|
layers.append(baseLayer) |
|
|
|
if let isShowMeasureLine = model?.isShowMeasureLine, isShowMeasureLine { |
|
let width = self.width - chartLeftMargin - chartRightMargin |
|
let heght = self.height - kScaleHeight(65.0) - Metric.bottomHeight |
|
//创建显示测量结果标签 |
|
let measureResultLabel = createMeasureResultLabel() |
|
self.addSubview(measureResultLabel) |
|
self.measureResultLabel = measureResultLabel |
|
//创建测量线视图 |
|
let measureLine = MeasureLineView(frame: CGRect(x: chartLeftMargin, y: kScaleHeight(65.0), width: width, height: heght)) |
|
self.addSubview(measureLine) |
|
self.measureLineView = measureLine |
|
self.measureLineView?.measureLineClosure = { [weak self] (value, isHidden) in |
|
guard let `self` = self else { return } |
|
if isHidden { |
|
self.measureResultLabel?.text = "" |
|
return |
|
} |
|
var idx = 0 |
|
let idx_f = value/dotSpace |
|
if idx_f < 0 { |
|
idx = 0 |
|
}else if (idx_f > CGFloat(count - 1)) { |
|
idx = count - 1 |
|
}else { |
|
idx = Int(idx_f) |
|
} |
|
if source[idx].value > 0 { |
|
self.measureLineView?.measureLineView.isHidden = false |
|
self.measureResultLabel?.text = "\(source[idx].floatValue)" |
|
let topSpace = CGFloat(maxAxis! - source[idx].floatValue) * dotPerPixels |
|
self.handleMeasureResult(topSpace) |
|
}else { |
|
self.measureResultLabel?.text = "" |
|
self.measureLineView?.measureLineView.isHidden = true |
|
self.handleMeasureResult(0) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
extension MatchStickView { |
|
func timeInterval(start: String, end: String) -> Float { |
|
|
|
if start.decomposeTime() - end.decomposeTime() > 0 { |
|
return "24:00".decomposeTime() - start.decomposeTime() + end.decomposeTime() |
|
} |
|
|
|
return end.decomposeTime() - start.decomposeTime() |
|
} |
|
} |
|
|
|
|
|
// MARK: - 符点型数据转字符串 |
|
extension Float { |
|
func float2String() -> String { |
|
let iValue = Int.init(self) |
|
if Float.init(iValue) == self { |
|
return "\(iValue)" |
|
} |
|
// 保留1位小数 |
|
return String(format: "%.1f", self) |
|
} |
|
} |
|
|
|
|
|
// MARK: - 将字符串的时间转成带小数点的时间 |
|
extension String { |
|
func decomposeTime() -> Float { |
|
guard self.contains(":") else { |
|
return 0.0 |
|
} |
|
let comps = self.components(separatedBy: ":") |
|
let result = Int(comps.first ?? "0")! * 60 + Int(comps.last ?? "0")! |
|
return Float(result) / 60.0 |
|
} |
|
} |
|
|
|
extension Reactive where Base: MatchStickView { |
|
func chartDatsource() -> Binder<ChartViewModel> { |
|
return Binder(self.base, binding: { (stickView, vm) in |
|
stickView.model = vm |
|
}) |
|
} |
|
}
|
|
|