はじめに
こんにちは、suzukiです。今回はChartsライブラリを使い移動平均線を描画します。
ローソク足+テクニカルチャートの描画を行うため、ライブラリを調査中にChartsライブラリに出会いました。
実装したい事は色々あるため調査も兼ねて少しずつ遊んでいきます。
Chartsライブラリについて
今回SwiftでChartsを利用してチャートを書きます。
利用する理由として、このライブラリのAndroid版であるMPAndroidChartが提供されているためです。
サンプルはiOSはGitHub上のdemoから、Androidであればストアからご確認ください。
ライブラリの導入
それでは実際にライブラリを導入しましょう。今回はCocoaPodsを利用しインストールしました。
pod 'Charts'
上記をPodfileに記述し、pod installを行います。
描画の準備
次にStoryBoardでチャートの描画範囲を決めましょう。
- LineChartView CandleStickChartView CombinedChartViewを追加
- 上下左右に制約を追加
- 三つのChartViewの高さを等しく
- それぞれのChartViewを関連付け
こちらでStoryBoardで行いたいことができました。
折れ線グラフを描画する
シンプルな折れ線グラフを描画しようと思います。
完成予定
LineChartViewの設定
LineChartViewに設定できる内容はたくさんあります。
今回は画像のようなシンプルな線グラフを表示するための項目を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // サンプル用のコードから移植 セットするインスタンス、表示するデータ func setupLineChart(_ chart: LineChartView, data: LineChartData) { // デリゲートの受け取り chart.delegate = self // チャートの項目表示を行うか chart.chartDescription?.enabled = true // ドラッグの操作の有効化 chart.dragEnabled = true // チャートの拡大表示を行うか chart.setScaleEnabled(true) // 目盛り表示する場合はその分確保する chart.setViewPortOffsets(left: 30, top: 0, right: 0, bottom: 30) chart.legend.enabled = true // 左の目盛り chart.leftAxis.enabled = true chart.leftAxis.spaceTop = 0.4 chart.leftAxis.spaceBottom = 0.4 // 右の目盛り chart.rightAxis.enabled = false // 目盛り線の表示 chart.xAxis.enabled = true chart.data = data // 描画アニメーションを行うか // chart.animate(xAxisDuration: 2.5) } |
LineChart用のデータ作成
グラフ用のデータはサンプルにランダムなチャート用の作成関数があったため利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // LineChart用のデータ作成 func lineDataWithCount(_ count: Int, range: UInt32) -> LineChartData { let yVals = (0..<count).map { i -> ChartDataEntry in // ランダムな数値を作成 let val = Double(arc4random_uniform(range)) + 3 // チャートのデータ型を作成 return ChartDataEntry(x: Double(i), y: val) } // チャート用のデータ名 let set1 = LineChartDataSet(values: yVals, label: "SampleLineData") set1.lineWidth = 1.75 // 線の色 set1.setColor(.black) set1.highlightColor = .yellow // それぞれの値の表示 set1.drawValuesEnabled = false // プロット用サークルの描画 set1.drawCirclesEnabled = false return LineChartData(dataSet: set1) } |
LineChartViewに描画
先ほどの二つの関数を使い折れ線を描画します。
1 2 3 4 5 6 7 | let chartDataCount = 20 override func viewDidLoad() { super.viewDidLoad() //サンプル用のデータ作成 let lineData = lineDataWithCount(chartDataCount, range: 100) setupLineChart(lineChartView, data: lineData) } |
折れ線グラフの描画が完了しました。
シンプルですが問題なく表示できました。
ローソク足を描画する
次にローソク足の描画を行います。
完成予定
CandleStickChartViewの設定
CandleStickChartViewでは下記のような設定を行います。
BarLineChartViewBaseというクラスをLineChartViewもCandleStickChartViewも継承しています。
そのため設定できる内容はほぼ同じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // ローソク足のチャートの設定 func setupCandleChart(_ chart: CandleStickChartView, data: CandleChartData){ candleStickChartView.delegate = self candleStickChartView.chartDescription?.enabled = false candleStickChartView.dragEnabled = false candleStickChartView.setScaleEnabled(true) // candleStickChartView.maxVisibleCount = 200 candleStickChartView.pinchZoomEnabled = true // legendの設定 candleStickChartView.legend.horizontalAlignment = .left candleStickChartView.legend.verticalAlignment = .bottom candleStickChartView.legend.orientation = .horizontal candleStickChartView.legend.drawInside = false candleStickChartView.legend.font = UIFont(name: "HelveticaNeue-Light", size: 10)! // 左の目盛りの設定 candleStickChartView.leftAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10)! candleStickChartView.leftAxis.spaceTop = 0.3 candleStickChartView.leftAxis.spaceBottom = 0.3 // グラフの下限を設定 candleStickChartView.leftAxis.axisMinimum = 50 candleStickChartView.rightAxis.enabled = false candleStickChartView.xAxis.labelPosition = .bottom candleStickChartView.xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10)! chart.data = data } |
CandleStickChartView用のデータ作成
サンプル用のデータを流用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // ローソク足用のデータの取得 func candleDataWithCount(_ count: Int, range: UInt32) -> CandleChartData{ let yVals1 = (0..<count).map { (i) -> CandleChartDataEntry in let mult = range + 1 let val = Double(arc4random_uniform(40) + mult) let high = Double(arc4random_uniform(9) + 8) let low = Double(arc4random_uniform(9) + 8) let open = Double(arc4random_uniform(6) + 1) let close = Double(arc4random_uniform(6) + 1) let even = i % 2 == 0 //ローソク足用のデータ作成 return CandleChartDataEntry(x: Double(i), shadowH: val + high, shadowL: val - low, open: even ? val + open : val - open, close: even ? val - close : val + close, icon: nil) } let set1 = CandleChartDataSet(values: yVals1, label: "SampleChandleData") set1.axisDependency = .left set1.setColor(UIColor(white: 80/255, alpha: 1)) //アイコン描画 set1.drawIconsEnabled = false set1.shadowColor = .darkGray set1.shadowWidth = 0.7 // color for open < close set1.decreasingColor = .red set1.decreasingFilled = true //color for open > close set1.increasingColor = UIColor(red: 122/255, green: 242/255, blue: 84/255, alpha: 1) set1.increasingFilled = false set1.neutralColor = .blue return CandleChartData(dataSet: set1) } |
CandleStickChartViewに描画
先ほど同様にViewDidLoadにローソク足用のデータを追加しました。
1 2 3 4 5 6 7 8 9 | override func viewDidLoad() { super.viewDidLoad() //サンプル用のデータ作成 let lineData = lineDataWithCount(chartDataCount, range: 100) setupLineChart(lineChartView, data: lineData) //こちらを追加 let candleStickData = candleDataWithCount(chartDataCount, range: 100) setupCandleChart(candleStickChartView, data: candleStickData) } |
Combined-Chartをローソク足と移動平均線を描画
それでは最後に複合グラフを描画します。
※Chartsのライブラリ3.2.1で複合チャートの描画でバグの修正がありました。
私の環境で2019/02/23に起きたので一応対応を記述して起きます。
- XCodeを最新にする
- pod ‘ChartsRealm’を使っている場合は消去
- pod updateを行う
podでインストールされたChartsのバージョンが3.2.1のときは気をつけましょう。
as? CandleStickChartViewで検索してライブラリ内で複数ヒットしてたらバグの残っているフレームワークの可能性があります。
完成予定
CombinedChartViewの設定
下記の設定を行います。
BarLineChartViewBaseというクラスをCombinedChartViewも継承しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 複合チャートの描画 func setupCombineChart(_ chart: CombinedChartView, data: CombinedChartData){ chart.dragEnabled = false chart.setScaleEnabled(true) chart.pinchZoomEnabled = true // legendの設定 chart.legend.horizontalAlignment = .left chart.legend.verticalAlignment = .bottom chart.legend.orientation = .horizontal chart.legend.drawInside = false chart.legend.font = UIFont(name: "HelveticaNeue-Light", size: 10)! // 左の目盛りの設定 chart.leftAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10)! chart.leftAxis.spaceTop = 0.3 chart.leftAxis.spaceBottom = 0.3 chart.leftAxis.axisMinimum = 90 // chart.rightAxis.enabled = false chart.xAxis.labelPosition = .bottom chart.xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10)! chart.xAxis.axisMaximum = data.xMax + 0.25 chart.data = data } |
CombinedChartView用のデータ作成
移動平均線用の計算ロジックを組み込みます。
var endPoints: [Double] = []
を宣言します。
宣言したendPointsにキャンドルデータの作成のロジック部分で終値を保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | func generateCandleData(_ count: Int, range: UInt32) -> CandleChartData { //endPointsを初期化ローソク足のチャートでも読んでいるため endPoints = [] let yVals1 = (0..<count).map { (i) -> CandleChartDataEntry in let mult = range + 1 let val = Double(arc4random_uniform(40) + mult) let high = Double(arc4random_uniform(9) + 8) let low = Double(arc4random_uniform(9) + 8) let open = Double(arc4random_uniform(6) + 1) let close = Double(arc4random_uniform(6) + 1) let even = i % 2 == 0 //終値のデータを保存 endPoints.append( even ? val - close : val + close) //ローソク足用のデータ作成 return CandleChartDataEntry(x: Double(i), shadowH: val + high, shadowL: val - low, open: even ? val + open : val - open, close: even ? val - close : val + close, icon: nil) } let set = CandleChartDataSet(values: yVals1, label: "Candle DataSet") set.setColor(UIColor(red: 80/255, green: 80/255, blue: 80/255, alpha: 1)) set.decreasingColor = UIColor(red: 142/255, green: 150/255, blue: 175/255, alpha: 1) set.shadowColor = .darkGray set.valueFont = .systemFont(ofSize: 10) set.drawValuesEnabled = false return CandleChartData(dataSet: set) } |
移動平均線は5つのデータ分の終値の平均でデータをもとに折れ線グラフを描画作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | func movingAverageData() -> LineChartData{ //幾つのデータの平均値を取得するか let movingDataCount = 5 var movingLineChartData:[ChartDataEntry] = [] endPoints.enumerated().forEach { (i, data) in //最初の2件と終わりの2件はデータを表示しない guard i >= movingDataCount - 1 else{ return } //移動平均線用にデータを計算頭の4つはデータの取得が出来ていないのでこのような形 let val = endPoints[i - (movingDataCount - 1) ... i].reduce(0, +) / Double(movingDataCount) movingLineChartData.append(ChartDataEntry(x: Double(i), y: val)) } let set1 = LineChartDataSet(values: movingLineChartData, label: "MovingAverageData") set1.lineWidth = 1.75 set1.setColor(.black) set1.highlightColor = .yellow set1.drawValuesEnabled = false set1.drawCirclesEnabled = false return LineChartData(dataSet: set1) } |
CombinedChartViewに描画
最後に下記のコードをViewDidLoadに記述しデータの値をセットします。
1 2 3 4 | let cobinedData = CombinedChartData() cobinedData.candleData = candleDataWithCount(20, range: 100) cobinedData.lineData = movingAverageData() setupCombineChart(combinedChartView,data: cobinedData) |
さいごに
今回はライブラリのバグでハマり、かなり苦戦してしまいました。
ライブラリは正しいという先入観を持たずに、事象を元に早く調べれればよかったと後悔しております。
次回はKotlinで同じようなことをしてみようと思っております。
最後までありがとうございました。