背景:由于公司项目需要绘制曲线图,目前只有用到折线图这一种,一开始并没有自己绘制折线图的想法,只是找了一个第三方的库来用,图一个简单方便。但是找了很久,找到一个勉强能够使用,但是效率太差,数据量大的时候效率非常的差,并不能在修改数据的时候时时的显示出最新的绘制效果。
基本需求:
- 实现折线图
- 连接点大小可自定义
- 可以绘制多条折线
- 可以给不同折线标记颜色
- 可以添加图例
- 实现 x 轴,y 轴
代码如下:
//
// splotview.swift
////
// created by mac min on 2021/1/29.
// copyright © 2021 inledco. 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()
}
}

发表评论