当前位置: 代码网 > it编程>App开发>苹果IOS > iOS - 折线图 Swift 实现

iOS - 折线图 Swift 实现

2024年07月28日 苹果IOS 我要评论
背景:由于公司项目需要绘制曲线图,目前只有用到折线图这一种,一开始并没有自己绘制折线图的想法,只是找了一个第三方的库来用,图一个简单方便。但是找了很久,找到一个勉强能够使用,但是效率太差,数据量大的时候效率非常的差,并不能在修改数据的时候时时的显示出最新的绘制效果。基本需求:实现折线图连接点大小可自定义可以绘制多条折线可以给不同折线标记颜色可以添加图例实现 X 轴,Y 轴代码...

背景:由于公司项目需要绘制曲线图,目前只有用到折线图这一种,一开始并没有自己绘制折线图的想法,只是找了一个第三方的库来用,图一个简单方便。但是找了很久,找到一个勉强能够使用,但是效率太差,数据量大的时候效率非常的差,并不能在修改数据的时候时时的显示出最新的绘制效果。

基本需求:

  • 实现折线图
  • 连接点大小可自定义
  • 可以绘制多条折线
  • 可以给不同折线标记颜色
  • 可以添加图例
  • 实现 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()
    }

}
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com