座標圖,經常會在各種各樣的App中使用,最常用的一種座標圖就是折線圖,根據給定的點繪製出對應的座標圖是最基本的需求。由於本人的項目需要使用折線圖,第一反應就是搜索已經存在的解決方案,因爲這種需求應該很常見,一定存在不少方案。確實也找到不少,但是沒有一個能完全滿足需求的,而且一般寫的好的都是庫很大,包含各種各樣的圖表,而我這裏只需要折線圖這一種,也不需要其它功能,於是就決定自己寫一個簡單的折線圖。
基本要求:
1.性能要好,能夠快速繪製,改變點時能夠時時繪製,有些在數據多的情況下性能實在是差,重新繪製卡頓明顯;
2.同一個橫座標可以對應不同的縱座標(有很多其它的都是根據很座標計算縱座標,而這種就不能繪製垂直的線條),這個也是我的項目中需要的
3.能夠繪製多條不同顏色的折線
項目地址:https://github.com/huangzhengguo/iOSSamples/tree/master/SwiftTools/SwiftTools/Tools/PlotView
效果圖:
大量數據點:
原理:之前一直以爲曲線圖這種很難做,由於找不到合適的,只能自己動手,才發現若只是實現我這裏的需要,還是很簡單的。由於我這只是曲線圖,所以只用在視圖上直接畫圖即可。由於我這裏需求簡單,用到了Quartz 2D框架畫線即可。
其實我們只要在一個View上面把給定的點使用線連接起來並標記給定的點即可,這樣就實現了一條線的繪製。
繪圖都是在一個圖形上下文上繪製,圖形上下文包含繪圖系統執行任何繪製命令的繪圖參數和所有的特定的設備信息。圖形上下文定義了基本的繪圖屬性,像繪圖使用的顏色、繪圖剪切的區域、線條寬度以及樣式信息、字體信息、合成選項及其他信息。
在iOS的視圖圖形上下文中繪圖
要想iOS應用程序中往屏幕上繪製圖形,需要在UIView對象中實現drawRect:方法:執行繪製的方法。這個方法在視圖在屏幕上可見或者視圖的內容需要更新時調用。在調用自定義的drawRect:方法前,視圖會自動配置繪製環境,以便你的代碼可以立即開始繪製。作爲配置的一部分,視圖會爲當前的繪製環境創建一個圖形上下文。你可以在drawRect:中調用UIGraphicsGetCurrentContext獲取圖形上下文。
UIKit使用的默認的座標系統和Quartz使用的不同。在UIKit中,座標原點在左上角,正的Y值在原點下面。UIView對象會轉換Quartz圖形上下文以滿足UIKit的座標系統。
下面開始繪製座標圖
首先我們定義一個UIView類,在drawRect中繪製座標圖。我們需要定義一些常用的屬性,比如X軸最小最大值,Y軸最小最大值以及線條的顏色等等。
類的初始狀態如下:
import UIKit class PlotView: UIView { // 頂部邊距 var topPadding: CGFloat = 20.0 // 左邊距 var leftPadding: CGFloat = 40.0 // 底部邊距 var bottomPadding: CGFloat = 30.0 // 右邊距 var rightPadding: CGFloat = 20.0 // X軸最小值 var xMinValue: CGFloat = 0.0 // X軸最大值 var xMaxValue: CGFloat = 1439.0 // Y軸最小值 var yMinValue: CGFloat = 0 // Y軸最大值 var yMaxValue: CGFloat = 100.0 // Y軸間隔 var yInterval: CGFloat = 25.0 // X軸間隔 var xInterval: CGFloat = 120.0 // 點的半徑 var markRadius: CGFloat = 2.0 // 是否顯示Y軸 var yAxisEnable: Bool = true // 是否顯示Y軸標題 var yAxisLabelEnable: Bool = true // 要繪製的點: 類型爲數組中包含數組,每個數組是一條線,數組中的元素爲點,包含座標點的X座標和Y座標;可以省略前面的類型指定,swift可以自動推斷變量類型 var dataPointArray: [Array<CGPoint>] = [Array<CGPoint>]() // 線條顏色:數組的簡寫語法 var lineColorArray = [UIColor]() // 線條顏色名稱 var lineColorTitleArray = [String]() // 去掉邊距後的寬度 var plotWidth: CGFloat? // 去掉邊距後的高度 var plotHeight: CGFloat? override init(frame: CGRect) { super.init(frame: frame) self.plotWidth = self.frame.size.width - self.leftPadding - self.rightPadding self.plotHeight = self.frame.size.height - self.topPadding - self.bottomPadding } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { }
獲取圖形上下文並設置繪圖使用的默認顏色值:draw方法實現
override func draw(_ rect: CGRect) { // 獲取當前圖像上下文 let context = UIGraphicsGetCurrentContext() context?.setStrokeColor(UIColor.white.cgColor) context?.setFillColor(UIColor.red.cgColor) // X軸單位長度 let xUnit = self.plotWidth! / self.xMaxValue // Y軸單位長度 let yUnit = self.plotHeight! / self.yMaxValue // 繪製Y軸 if self.yAxisEnable == true { context?.beginPath() context?.move(to: CGPoint(x: self.leftPadding, y: self.topPadding)) context?.addLine(to: CGPoint(x: self.leftPadding, y: self.topPadding + self.plotHeight!)) context?.strokePath() } // 繪製X軸刻度 for i in 0...((Int)((self.xMaxValue + 1) / self.xInterval) + 1) { context?.move(to: CGPoint(x: self.leftPadding + CGFloat(i) * xUnit * self.xInterval, y: self.plotHeight! + self.topPadding - 10)) context?.addLine(to: CGPoint(x: self.leftPadding + CGFloat(i) * xUnit * self.xInterval, y: self.plotHeight! + self.topPadding)) context?.strokePath() } // 繪製X軸 context?.beginPath() context?.move(to: CGPoint(x: self.leftPadding, y: self.topPadding + self.plotHeight!)) context?.addLine(to: CGPoint(x: self.leftPadding + self.plotWidth!, y: self.topPadding + self.plotHeight!)) context?.strokePath() // 根據給定的數據點繪製曲線 context?.beginPath() for lineIndex in 0...self.dataPointArray.count-1 { if lineIndex < self.lineColorArray.count { context?.setStrokeColor(self.lineColorArray[lineIndex].cgColor) context?.setFillColor(self.lineColorArray[lineIndex].cgColor) } else { context?.setStrokeColor(UIColor.white.cgColor) context?.setFillColor(UIColor.white.cgColor) } for pointIndex in 0...self.dataPointArray[lineIndex].count-2 { // 轉換座標爲當前視圖的點 let currentPoint = CGPoint(x: self.leftPadding + self.dataPointArray[lineIndex][pointIndex].x * xUnit, y: self.plotHeight! - self.dataPointArray[lineIndex][pointIndex].y * yUnit + self.topPadding) let nextPoint = CGPoint(x: self.leftPadding + self.dataPointArray[lineIndex][pointIndex + 1].x * xUnit, y: self.plotHeight! - self.dataPointArray[lineIndex][pointIndex + 1].y * yUnit + self.topPadding) context?.move(to: currentPoint) // 畫線 context?.addLine(to: nextPoint) context?.strokePath() // 標記點 context?.addArc(center: currentPoint, radius: markRadius, startAngle: 0.0, endAngle: CGFloat.pi * 2, clockwise: false) context?.fillPath() // 如果是最後一段線條,則標記最後一個點 if pointIndex == self.dataPointArray[lineIndex].count-2 { context?.addArc(center: nextPoint, radius: markRadius, startAngle: 0.0, endAngle: CGFloat.pi * 2, clockwise: false) context?.fillPath() } } } }