目前我们已经做出台股加权指数的 K 线图,但目前进度的线图的 x 轴没有时间,所以当使用者看到这张图,无法判断这张图每根 K 线的日期,是哪一天。所以我们需要转换 x 轴的 index 到 人类可阅读的日期。
开始之前
因为一个月的交易量大约是 20 日左右,所以当月加上个月的量,也在 40 根左右。所以再加上 2 个月前的 K 线资料,让资料的数量比较充足。
先扩充 DateUtility ,补上一个 func,输入 n,回传与现在日期差 n 个月的第一天 Date。
func getMonthStartDate(date: Date = Date(), add month: Int) -> Date {
let calendar = isoCalendar
let startOfMonth = getStartOfMonth(date: date)
return calendar.date(byAdding: DateComponents(month: month), to: startOfMonth) ?? Date()
}
所以要拿两个月前的资料,就只是下面这一行
let date = dateUtility.getMonthStartDate(date: Date(), add: -2)
然後在 TwStockMarketKLineModel 发动拿取前三个月
/// 会取这个月和前一个月台股加权指的 KLine data,单一个月,有可能 k 棒数量太少
func requestTwExKLineInfo() {
requestTwExThisMonthKLineInfo() //实作在前面的文章已有
requestTwExLastMonthKLineInfo() //实作在前面的文章已有
requestTwExBefore2MonthKLineInfo()
}
/// 拿前两个月的 k line
private func requestTwExBefore2MonthKLineInfo() {
let date = dateUtility.getMonthStartDate(date: Date(), add: -2)
manager.requestTwStockKLine(date: date) { [weak self] kLineDataSet, error in
self?.update(kLineDataSet)
self?.delegate?.didRecieveTaiEx(kLineDataSet: kLineDataSet, error: error)
}
}
当画线的实作已经确认完成了之後,就是整理程序码。
先开一个 ChartsAdapter,让这个物件负责整个专案和 Charts 沟通。那首先,把 KLine VC 中和 Charts 相关功能,放进去。
import UIKit
import Charts
class ChartsAdapter {
}
// MARK: - 这一段的程序码做 K Line charts
extension ChartsAdapter {
/// 让 VC 在需要 K Line 图的时候直接拿到一个 K Line View,但为了不让外部看到 Charts,回传 UIView
/// - Returns: 因 CandleStickChartView 继承 UIView,封装起来,不让外部看到 Charts
func getCandleStickChartView() -> UIView {
let candleView = CandleStickChartView()
setupCandleStickView(candleView)
return candleView
}
func update(stockSticks: [StockKLine], on candleView: UIView) {
let dateUtility = DateUtility()
if let candleView = candleView as? CandleStickChartView {
let dataEntry = convert(stockStick: stockSticks)
let dataSet = convert(dataEntry: dataEntry)
let data = convert(dataSet: dataSet)
candleView.data = data
updateMaxMin(candleView, dataSet: dataSet)
}
}
private func updateXAxis(_ chartView: CandleStickChartView, indexDateLabels: [Int: String]) {
chartView.xAxis.valueFormatter = CandleXAxisValueFormatter(indexLabelMap: indexDateLabels)
chartView.xAxis.granularity = 1.0
}
private func setupCandleStickView(_ chartView: CandleStickChartView) {
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.xAxis.labelCount = 10
}
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 = .black
dataSet.drawValuesEnabled = false
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
}
}
然後 K Line VC 的程序码就会少到变成这样,低於 50 行,而且不会 import Charts,不会和套件耦合。
import UIKit
class KLineViewController: UIViewController {
@IBOutlet weak var chartContainer: UIView!
private lazy var chartsAdapter: ChartsAdapter = {
return ChartsAdapter()
}()
private lazy var chartView: UIView = {
let view = chartsAdapter.getCandleStickChartView()
return view
}()
var kLineDataSet = [StockKLine]()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupBasicUI()
setupCandleView()
}
// 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 setupCandleView() {
chartsAdapter.update(stockSticks: kLineDataSet, on: chartView)
}
}
在Charts 套件中,可以用 IAxisValueFormatter 这个类别,来告诉 Chart View 在哪个位置要显示什麽样的 String。只要该类别 Conform IAxisValueFormatter,并实作 func stringForValue,告诉 Charts,就可以在 x value 显示你要的值。
在 ChartsAdapter 内宣告 CandleXAxisValueFormatter,要求 init 代入 [Int: String]。
import UIKit
import Charts
extension ChartsAdapter {
class CandleXAxisValueFormatter: IAxisValueFormatter {
private let indexLabelMap: [Int: String]
/// 因为 candle charts 是用 index 来当 x 轴,但是 index 需要 mapping 成 date string,才可以让人类识别每个 candle stick 代表的意义
/// - Parameter indexLabelMap: index vs. date string
init(indexLabelMap: [Int: String]) {
self.indexLabelMap = indexLabelMap
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
guard let string = indexLabelMap[Int(value)] else {
return ""
}
return string
}
}
}
将 Charts 的 x 轴更新的 func 如下
private func updateXAxis(_ chartView: CandleStickChartView, indexDateLabels: [Int: String]) {
chartView.xAxis.valueFormatter = CandleXAxisValueFormatter(indexLabelMap: indexDateLabels)
chartView.xAxis.granularity = 1.0
}
在 ChartsAdapter 的对外 func,将 func update(stockSticks: [StockKLine], on candleView: UIView),里面,在完成 update 後,呼叫更新 XAxis。在 ChartsAdapter 内的 func 更改成下面这样。
func update(stockSticks: [StockKLine], on candleView: UIView) {
let dateUtility = DateUtility()
var indexDateLabels = [Int: String]()
for (index, stick) in stockSticks.enumerated() {
if let date = stick.date {
let dateString = dateUtility.getString(date: date, format: "MM/dd")
indexDateLabels[index] = dateString
}
}
if let candleView = candleView as? CandleStickChartView {
let dataEntry = convert(stockStick: stockSticks)
let dataSet = convert(dataEntry: dataEntry)
let data = convert(dataSet: dataSet)
candleView.data = data
updateXAxis(candleView, indexDateLabels: indexDateLabels)
updateMaxMin(candleView, dataSet: dataSet)
}
}
完成的图案如下,目标完成,剩下的间距,可以再自行细调。
下方是这次 D1 ~ D12 的完成品,可以下载来试
玩OpenWrt第一步当然是制作系统,下载系统映像的入口其实很多,但这个路径我觉得最直观与便捷。可以...
「鲑鱼均,因为一场鲑鱼之乱被主管称为鲑鱼世代,广义来说以年龄和脸蛋分类的话这应该算是一种 KNN 的...
今天我们要来完成 Banner 的效果啦!! 修改 CustomListRowPresenter 我...
前言 假日没有行情,所以只能平日来做取得行情资料的工作,所以今天的文章是根据期货行情,模拟价格修改的...
云服务器(Elastic Compute Service,ECS) ECS是阿里云上提供服务器租用的...