カテゴリー: iOS

iOS Chartsライブラリを使い移動平均線を描画してみる。

はじめに

こんにちは、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に設定できる内容はたくさんあります。
今回は画像のようなシンプルな線グラフを表示するための項目を設定します。

//    サンプル用のコードから移植 セットするインスタンス、表示するデータ
    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用のデータ作成

グラフ用のデータはサンプルにランダムなチャート用の作成関数があったため利用します。

//    LineChart用のデータ作成
    func lineDataWithCount(_ count: Int, range: UInt32) -> LineChartData {
        let yVals = (0.. 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に描画

先ほどの二つの関数を使い折れ線を描画します。

    
    let chartDataCount = 20
    override func viewDidLoad() {
        super.viewDidLoad()
        //サンプル用のデータ作成
        let lineData = lineDataWithCount(chartDataCount, range: 100)
        setupLineChart(lineChartView, data: lineData)
    }

折れ線グラフの描画が完了しました。
シンプルですが問題なく表示できました。

ローソク足を描画する

次にローソク足の描画を行います。
完成予定

CandleStickChartViewの設定

CandleStickChartViewでは下記のような設定を行います。
BarLineChartViewBaseというクラスをLineChartViewもCandleStickChartViewも継承しています。
そのため設定できる内容はほぼ同じになります。

//  ローソク足のチャートの設定
    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用のデータ作成

サンプル用のデータを流用しています。

//  ローソク足用のデータの取得
    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にローソク足用のデータを追加しました。

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も継承しています。

    //  複合チャートの描画
    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にキャンドルデータの作成のロジック部分で終値を保存します。

    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つのデータ分の終値の平均でデータをもとに折れ線グラフを描画作成します。

    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に記述しデータの値をセットします。

        let cobinedData = CombinedChartData()
        cobinedData.candleData = candleDataWithCount(20, range: 100)
        cobinedData.lineData = movingAverageData()
        setupCombineChart(combinedChartView,data: cobinedData)

さいごに

今回はライブラリのバグでハマり、かなり苦戦してしまいました。
ライブラリは正しいという先入観を持たずに、事象を元に早く調べれればよかったと後悔しております。
次回はKotlinで同じようなことをしてみようと思っております。
最後までありがとうございました。

おすすめ書籍

    

— NORMAL —
suzuki

シェア
執筆者:
suzuki
タグ: Swift

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前