如果你真的對畫圖很有興趣,而且很想自己做圖表的類別,那你可以使用程式在拿到資料後,用 UIView, CALayer 這些物件,畫出你要的圖案。但這邊介紹 Android, iOS 雙平台都知名的 Charts 套件,作者不同人,但 iOS Charts 的套件在 readme 上有寫,iOS 的套件是參考 Android 的 API 寫的,所以在說明文件的連結上,也直接指向 Android Charts 套件的說明文件。
iOS 套件的 GitHub 如下
https://github.com/danielgindi/Charts
Android 套件的 GitHub 如下
https://github.com/PhilJay/MPAndroidChart
說明文件
https://weeklycoding.com/mpandroidchart/
安裝套件的方法
先更新 Podfile
# Uncomment the next line to define a global platform for your project
platform :ios, '14.0'
target 'ITIronMan' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for ITIronMan
pod 'Alamofire'
pod 'SwiftCSV'
pod 'Charts'
target 'ITIronManTests' do
inherit! :search_paths
# Pods for testing
end
end
然後用 terminal 進入專案資料夾,輸入 pod install
等到跑完,Charts 就裝好了。
獨立開一個 KLineViewController,先放在 KLine.storyboard 裡,並開出放 K Line Model 的 array,讓 parent 傳進來。目前 parent 暫定為下載台股加權開高低收的 VC,因為那個 VC 會有資料。
下方的藍色區域,就是 Chart 圖要放的位置
import UIKit
import Charts
class KLineViewController: UIViewController {
@IBOutlet weak var chartContainer: UIView!
var kLineDataSet = [StockKLine]()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
在 TwStockMarketKLineViewController 加一個按鈕,並讓按鈕發動轉場,推入 KLineViewController
@IBAction func pushKLineButtonDidTap(_ sender: Any) {
let storyboard = UIStoryboard(name: "KLine", bundle: nil)
if let vc = storyboard.instantiateViewController(withIdentifier: "KLineViewController") as? KLineViewController {
vc.kLineDataSet = model.twExStockDataSet //將 K 線資料傳進 KLineViewController
navigationController?.pushViewController(vc, animated: true)
}
}
在 viewDidLoad() 時,將 chartView 放進 container 裡面,並把 autolayout 設定好。
整個 VC 的程式碼如下
import UIKit
import Charts
class KLineViewController: UIViewController {
@IBOutlet weak var chartContainer: UIView!
private lazy var chartView: CandleStickChartView = {
let view = CandleStickChartView()
return view
}()
var kLineDataSet = [StockKLine]()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupBasicUI()
setupCandleStickView()
}
// MARK: - private methods
private func setupBasicUI() {
chartContainer.backgroundColor = .clear
chartContainer.addSubview(chartView)
chartView.translatesAutoresizingMaskIntoConstraints = false
chartView.leadingAnchor.constraint(equalTo: chartContainer.leadingAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: chartContainer.topAnchor).isActive = true
chartView.trailingAnchor.constraint(equalTo: chartContainer.trailingAnchor).isActive = true
chartView.bottomAnchor.constraint(equalTo: chartContainer.bottomAnchor).isActive = true
}
// 調整 Candle Stick View 的設定
private func setupCandleStickView() {
chartView.dragEnabled = false
chartView.setScaleEnabled(true)
chartView.maxVisibleCount = 200
chartView.pinchZoomEnabled = true
chartView.legend.horizontalAlignment = .right
chartView.legend.verticalAlignment = .top
chartView.legend.orientation = .vertical
chartView.legend.drawInside = false
chartView.legend.font = UIFont.systemFont(ofSize: 10)
chartView.leftAxis.labelFont = UIFont.systemFont(ofSize: 10)
chartView.leftAxis.spaceTop = 0.3
chartView.leftAxis.spaceBottom = 0.3
chartView.leftAxis.axisMinimum = 0
chartView.rightAxis.enabled = false
chartView.xAxis.labelPosition = .bottom
chartView.xAxis.labelFont = UIFont.systemFont(ofSize: 10)
chartView.maxVisibleCount = 20
}
}
接下來實作將 K 線的資料轉換成 CandleChart 的部分。
在 Charts 套件裡面,將 Data Model 轉成 ChartView 可以用的 data 類別,會需要經過下列的轉換。
step 1 → 將你的 Data Model 轉換成對應的 DataEntry 類別
蠟燭圖的類別為 CandleChartDataEntry
而他有下列的建構子
x: Chart View 的 位置,但要注意的是,CandleStickChart 的 x 不建議使用 timeIntervalSince1970,建議使用 Charts Demo 的 index。因為在 render 的時候, Candle Stick Chart 是有他自己的邏輯。如果用 timeInterval ,我個人測試的結果,Candle 的燭身會畫不出來。
open: 開市價格
shadowH: 當日最高點
shadowL: 當日最低點
close: 當日收盤價
所以,需要一個轉換 StockKLine 到 CandleChartDataEntry 的 func
private func convert(stockStick: [StockKLine]) -> [CandleChartDataEntry] {
var dataEntry = [CandleChartDataEntry]()
for (i, each) in stockStick.enumerated() {
let x = Double(i)
if let open = each.open,
let highest = each.highest,
let lowest = each.lowest,
let close = each.close {
let candleData = CandleChartDataEntry(x: x, shadowH: highest, shadowL: lowest, open: open, close: close)
dataEntry.append(candleData)
}
}
return dataEntry
}
step 2 → 將 DataEntry 類別轉成 DataSet 類別
在 DataSet 是設定這一組數據畫在 charts 上的設定,像是顏色、照哪一軸的比例。以
private func convert(dataEntry: [CandleChartDataEntry]) -> CandleChartDataSet {
let dataSet = CandleChartDataSet(entries: dataEntry)
dataSet.axisDependency = .left
dataSet.setColor(.red)
dataSet.drawIconsEnabled = false
dataSet.shadowColor = .darkGray
dataSet.shadowWidth = 0.5
dataSet.decreasingColor = .systemGreen //注意下跌的顏色在該地區的習慣
dataSet.decreasingFilled = true // 蠟燭線可以是實心,也可以是空心
dataSet.increasingColor = .systemRed //注意下跌的顏色在該地區的習慣
dataSet.increasingFilled = true
dataSet.neutralColor = .black //當開盤 == 收盤的時候顏色
return dataSet
}
step 3 → 將 DataSet 轉換成 Data,ChartView 中的 data 會依照這樣的 Data 畫。
private func convert(dataSet: CandleChartDataSet) -> CandleChartData {
return CandleChartData(dataSet: dataSet)
}
將 1、2、3 步驟連起來,就完成了
private func update(_ chartView: CandleStickChartView, stockStickList: [StockKLine]) {
let dataEntry = convert(stockStick: stockStickList)
let dataSet = convert(dataEntry: dataEntry)
let data = convert(dataSet: dataSet)
chartView.data = data
}
整個 VC 的程式碼如下
import UIKit
import Charts
class KLineViewController: UIViewController {
@IBOutlet weak var chartContainer: UIView!
private lazy var chartView: CandleStickChartView = {
let view = CandleStickChartView()
return view
}()
var kLineDataSet = [StockKLine]()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupBasicUI()
setupCandleStickView()
update(self.chartView, stockStickList: kLineDataSet)
}
// MARK: - private methods
private func setupBasicUI() {
chartContainer.backgroundColor = .clear
chartContainer.addSubview(chartView)
chartView.translatesAutoresizingMaskIntoConstraints = false
chartView.leadingAnchor.constraint(equalTo: chartContainer.leadingAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: chartContainer.topAnchor).isActive = true
chartView.trailingAnchor.constraint(equalTo: chartContainer.trailingAnchor).isActive = true
chartView.bottomAnchor.constraint(equalTo: chartContainer.bottomAnchor).isActive = true
}
private func setupCandleStickView() {
chartView.dragEnabled = false
chartView.setScaleEnabled(true)
chartView.maxVisibleCount = 200
chartView.pinchZoomEnabled = true
chartView.legend.enabled = false
chartView.leftAxis.labelFont = UIFont.systemFont(ofSize: 10)
chartView.leftAxis.spaceTop = 0.3
chartView.leftAxis.spaceBottom = 0.3
chartView.leftAxis.axisMinimum = 0
chartView.rightAxis.enabled = false
chartView.xAxis.labelPosition = .bottom
chartView.xAxis.labelFont = UIFont.systemFont(ofSize: 10)
chartView.maxVisibleCount = 20
}
private func convert(stockStick: [StockKLine]) -> [CandleChartDataEntry] {
var dataEntry = [CandleChartDataEntry]()
for (i, each) in stockStick.enumerated() {
let x = Double(i)
if let open = each.open,
let highest = each.highest,
let lowest = each.lowest,
let close = each.close {
let candleData = CandleChartDataEntry(x: x, shadowH: highest, shadowL: lowest, open: open, close: close)
dataEntry.append(candleData)
}
}
return dataEntry
}
private func convert(dataEntry: [CandleChartDataEntry]) -> CandleChartDataSet {
let dataSet = CandleChartDataSet(entries: dataEntry)
dataSet.axisDependency = .left
dataSet.setColor(.red)
dataSet.drawIconsEnabled = false
dataSet.shadowColor = .darkGray
dataSet.shadowWidth = 0.5
dataSet.decreasingColor = .systemGreen
dataSet.decreasingFilled = true
dataSet.increasingColor = .systemRed
dataSet.increasingFilled = true
dataSet.neutralColor = .blue
return dataSet
}
private func convert(dataSet: CandleChartDataSet) -> CandleChartData {
return CandleChartData(dataSet: dataSet)
}
private func updateMaxMin(_ chartView: CandleStickChartView, dataSet: CandleChartDataSet) {
let max = dataSet.yMax
let min = dataSet.yMin
chartView.leftAxis.axisMaximum = max * 1.05
chartView.leftAxis.axisMinimum = min * 0.95
}
private func update(_ chartView: CandleStickChartView, stockStickList: [StockKLine]) {
let dataEntry = convert(stockStick: stockStickList)
let dataSet = convert(dataEntry: dataEntry)
let data = convert(dataSet: dataSet)
chartView.data = data
updateMaxMin(chartView, dataSet: dataSet)
}
}
完成上述步驟後,Candle Chart 成品如下
目前這張圖,是沒辦法提供比較有效的資訊,最大原因,就是 x 軸並不是有效資訊。在第一步 Convert Data Entry 那邊,我們要使用 index 來畫 k 線,就喪失了日期這一訊息。
下一篇: 補上 x 軸遺失的資訊
下方是這次 D1 ~ D12 的完成品,可以下載來試
App Store - 台股申購日曆