背景:由于公司项目需要绘制曲线图,目前只有用到折线图这一种,一开始并没有自己绘制折线图的想法,只是找了一个第三方的库来用,图一个简单方便。但是找了很久,找到一个勉强能够使用,但是效率太差,数据量大的时候效率非常的差,并不能在修改数据的时候时时的显示出最新的绘制效果。
基本需求:
代码如下:
//
// SPlotView.swift
//
//
// Created by mac min on 2021/1/29.
// Copyright © 2021. All rights reserved.
//
import UIKit
class PlotView: UIView {
// 顶部边距
var topMargin: CGFloat = 5.0
// 左边距
var leftMargin: CGFloat = 40.0
// 底部边距
var bottomMargin: CGFloat = 30.0
// 右边距
var rightMargin: CGFloat = 20.0
// X轴最小值
var xMinValue: CGFloat = 0.0
// X轴最大值
var xMaxValue: CGFloat = 100.0
// Y轴最小值
var yMinValue: CGFloat = 0.0
// Y轴最大值
var yMaxValue: CGFloat = 100.0
// Y轴间隔
var yInterval: CGFloat = 25.0
// X间隔
var xInterval: CGFloat = 120.0
// 是否显示Y轴
var yAxisEnable: Bool = false
// 曲线图宽度:除去边距
var plotWidth: CGFloat = 0.0
// 曲线图高度:除去边距
var plotHeight: CGFloat = 0.0
// 标记是否已经添加横坐标 纵坐标 图例等
var isAddLabel: Bool = false
/**
* 数据点
* 格式:
* 1. 一层数组包含每种颜色的数据
* 2. 数组中的每个对象包含对应颜色的所有数据
*/
var dataPointArray: [[CGPoint]]?
// 线条颜色
var lineColorArray: [UIColor]?
// 线条颜色名称
var lineColorTitleArray: [String]?
// 预览指示器
var indicatorLabel: UILabel = UILabel.init(frame: CGRect.zero)
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .clear
self.plotWidth = frame.size.width - self.leftMargin - self.rightMargin
self.plotHeight = frame.size.height - self.topMargin - self.bottomMargin
self.isAddLabel = false
self.indicatorLabel.frame = CGRect.init(x: 0.0, y: self.topMargin, width: 1.0, height: self.plotHeight)
self.indicatorLabel.isHidden = true
self.indicatorLabel.backgroundColor = .green
self.addSubview(self.indicatorLabel)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
// 添加图例
func addLegend(colorArray: [UIColor], colorTitleArray: [String]) -> Void {
var colorLableWidth = self.leftMargin
var previousTitleLabel: UILabel?
for i in 0..<colorArray.count {
if i != 0 {
colorLableWidth = colorLableWidth + 20
}
let label = UILabel.init(frame: CGRect(x: colorLableWidth, y: self.frame.size.height - 10, width: 10, height: 10))
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = colorArray[i]
self.addSubview(label)
// 添加约束
var labelLeadingLayoutConstraint = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: self.leftMargin)
let labelBottomLayoutConstraint = NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -8.0)
let labelHeightLayoutConstraint = NSLayoutConstraint(item: label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 10.0)
let labelWidthLayoutConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 10.0)
if previousTitleLabel != nil {
labelLeadingLayoutConstraint = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: previousTitleLabel, attribute: .trailing, multiplier: 1.0, constant: 8.0)
}
self.addConstraints([labelLeadingLayoutConstraint, labelBottomLayoutConstraint, labelHeightLayoutConstraint, labelWidthLayoutConstraint])
let titleLabel = UILabel(frame: CGRect(x: 15 + colorLableWidth, y: self.frame.size.height - 10, width: SystemInfo.screenWidth / CGFloat((colorArray.count + 1)), height: 10.0))
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.font = UIFont.ex_systemFontOfSize(fontSize: 8.0)
titleLabel.text = colorTitleArray[i]
titleLabel.textColor = .white
self.addSubview(titleLabel)
// 添加约束
let titleLabelLeadingLayoutConstraint = NSLayoutConstraint(item: titleLabel, attribute: .leading, relatedBy: .equal, toItem: label, attribute: .trailing, multiplier: 1.0, constant: 4.0)
let titleLabelBottomLayoutConstraint = NSLayoutConstraint(item: titleLabel, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -8.0)
self.addConstraints([titleLabelLeadingLayoutConstraint, titleLabelBottomLayoutConstraint])
previousTitleLabel = titleLabel
}
}
// 刷新视图
func refreshPlotView() -> Void {
self.plotWidth = self.frame.size.width - self.leftMargin - self.rightMargin
self.plotHeight = self.frame.size.height - self.topMargin - self.bottomMargin
// KMYLOG(@"self.plotHeight = %f", self.frame.size.height - self.topMargin - self.bottomMargin);
// 是否添加横纵坐标
if (self.isAddLabel == false && self.plotHeight > 0) {
self.addXLabel()
self.addYLabel()
self.isAddLabel = true
}
self.setNeedsDisplay()
}
// MARK: 添加 X 轴刻度
private func addXLabel() -> Void {
let xUint = self.plotWidth / (self.xMaxValue - self.xMinValue)
for i in 0..<GlobalConstant.DAY_SEPARATE_BY_MINUTE / Int(self.xInterval) + 1 {
if i % 3 == 0 {
let label = UILabel.init(frame: CGRect.init(x: self.leftMargin + CGFloat(i) * xUint * self.xInterval - 10, y: self.frame.size.height - 38, width: self.frame.size.width / 5, height: 20))
label.text = String.init(format: "%02d:00", i * 2)
if i == GlobalConstant.DAY_SEPARATE_BY_MINUTE / Int(self.xInterval) {
label.text = "00:00"
}
label.textColor = .white
label.textAlignment = .left
self.addSubview(label)
}
}
}
// MARK: 添加 Y 轴刻度
private func addYLabel() -> Void {
let yUint = self.plotHeight / (self.yMaxValue - self.yMinValue)
// 线条数
let labelCount: Int = Int((self.yMaxValue - self.yMinValue) / self.yInterval + 1)
for i in 0..<labelCount {
let label = UILabel.init(frame: CGRect(x: 0, y: yUint * CGFloat(i) * self.yInterval + self.topMargin - 8.0, width: self.leftMargin, height: 16.0))
label.text = String(format: "%2d", 100 - i * 25)
label.textColor = .white
label.textAlignment = .center
self.addSubview(label)
}
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
if self.dataPointArray == nil {
return
}
// 绘制坐标图
let context = UIGraphicsGetCurrentContext()
context?.setStrokeColor(UIColor.white.cgColor)
// X轴与Y轴单位长度
let xUint = self.plotWidth / self.xMaxValue
let yUint = self.plotHeight / self.yMaxValue
// 绘制Y轴
if self.yAxisEnable == true {
context?.beginPath()
context?.move(to: CGPoint(x: self.leftMargin, y: self.topMargin))
context?.addLine(to: CGPoint(x: self.leftMargin, y: self.plotHeight + self.topMargin))
context?.closePath()
context?.strokePath()
}
// 绘制X轴刻度
for i in 0..<GlobalConstant.DAY_SEPARATE_BY_MINUTE / Int(self.xInterval) + 1 {
context?.beginPath()
context?.move(to: CGPoint(x: self.leftMargin + CGFloat(i) * xUint * self.xInterval, y: self.plotHeight + self.topMargin - 10))
context?.addLine(to: CGPoint(x: self.leftMargin + CGFloat(i) * xUint * self.xInterval, y: self.plotHeight + self.topMargin))
context?.closePath()
context?.strokePath()
}
// 绘制X轴及横线
let horizonalCount: Int = Int(self.yMaxValue / self.yInterval + 1)
for i in 0..<horizonalCount {
context?.beginPath()
context?.move(to: CGPoint(x: self.leftMargin, y: self.plotHeight / 4.0 * CGFloat(i) + self.topMargin))
context?.addLine(to: CGPoint(x: self.plotWidth + self.leftMargin, y: self.plotHeight / 4.0 * CGFloat(i) + self.topMargin))
context?.closePath()
context?.strokePath()
}
// 绘制曲线
context?.setLineWidth(1.5)
context?.beginPath()
for i in 0..<self.dataPointArray!.count {
if self.lineColorArray == nil || i > self.lineColorArray!.count - 1 {
context?.setStrokeColor(UIColor.white.cgColor)
context?.setFillColor(UIColor.white.cgColor)
} else {
context?.setStrokeColor(self.lineColorArray![i].cgColor)
context?.setFillColor(self.lineColorArray![i].cgColor)
}
let lineDataArray = self.dataPointArray![i]
if lineDataArray.count < 1 {
return
}
for j in 0..<lineDataArray.count - 1 {
let prePoint: CGPoint = lineDataArray[j]
let nextPoint: CGPoint = lineDataArray[j + 1]
let preX: CGFloat = prePoint.x * xUint + self.leftMargin
let preY: CGFloat = self.plotHeight - prePoint.y * yUint + self.topMargin
let nextX: CGFloat = nextPoint.x * xUint + self.leftMargin
let nextY: CGFloat = self.plotHeight - nextPoint.y * yUint + self.topMargin
context?.move(to: CGPoint(x: preX, y: preY))
context?.addLine(to: CGPoint(x: nextX, y: nextY))
context?.strokePath()
context?.addArc(center: CGPoint(x: preX, y: preY), radius: 3.0, startAngle: 0.0, endAngle: CGFloat.pi * 2, clockwise: true)
if j == lineDataArray.count - 2 {
context?.addArc(center: CGPoint(x: nextX, y: nextY), radius: 3.0, startAngle: 0.0, endAngle: CGFloat.pi, clockwise: true)
}
context?.fillPath()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
// 使用约束时刷新曲线图
self.refreshPlotView()
}
}