// // 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 = [] fileprivate var layers: Array = [] fileprivate var valueLabels: Array = [] fileprivate var views: Array = [] fileprivate var legendImageViews: Array = [] fileprivate var legendLabels: Array = [] 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?) { 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) { /// 添加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?, 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?, 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?) { 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?) { 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?) { 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?) { 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([]) 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?) { 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?){ 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?){ 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?) { 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?) { 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?) { 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([]) 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?){ 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?) { 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 { return Binder(self.base, binding: { (stickView, vm) in stickView.model = vm }) } }