LoginSignup
21
12

More than 3 years have passed since last update.

スマホアプリ向けお手軽グラフライブラリを作ってみた【MPAndroidChart改】

Last updated at Posted at 2020-09-27

スマホアプリとグラフ

Androidアプリでグラフを表示させたい場合、
MPAndroidChart
というライブラリがよく使われます。 (iOS版のChartsライブラリも存在)

多機能かつUIも優れた素晴らしいライブラリですが、
日本語情報の不足もあり、実装難度は結構高いと感じます。
そこで、簡易にグラフを作成するための追加パッケージを作成してみました!
Github

下のようなグラフを簡単に作る事ができます

image.png
(題材が重い!(笑))

なお、本パッケージはKotlinでの実装を前提としておりますが、
希望があればJava・Swift(iOS)バージョンも作ろうと思います。
その他質問、メソッド追加要望等あれば、気軽にコメント頂けますとありがたいです!

MPAndroidChartをより使いやすくするには?

MPAndroidChartでグラフを含んだアプリを作成して、私が苦戦したのは以下の部分です。
1. 時系列グラフの作成
2. フォーマット指定(特に色)
3. ツールヒントの作成
4. 日本語ドキュメントが少ない

1. 時系列グラフの作成

詳しくは補足に書きますが、時系列グラフの作成にはデータの格納や軸のフォーマット等、相当な手間が掛かります。
image.png

本パッケージでの対応

時系列グラフ専用のメソッドを作成し、簡単な操作で作成できるようにしました。

また、下記のように時間が等間隔でない場合、

        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),//ここのみ時間間隔が飛んでいる
        )

上記の方法ではインデックス情報のみに基づいて等間隔にプロットされてしまい、
時間の間隔が横軸上で正確に表現されません。
image.png

本パッケージでは、横軸が等間隔でない場合も正確にプロットされるような表示モードも準備しました
image.png
(ラベルは最初と最後のみ表示されます)

2. UIフォーマット指定

MPAndroidChartでは、グラフ表示のためにおおざっぱに
①データを入力する処理
②UIフォーマットを指定する処理
の2種類が必要となります。
コード上でもこの2種類の処理をまとめて別個に指定できることが、独立性の観点から望ましいです。

しかし折れ線グラフのように複数のY軸の値を指定する場合、
下のように①と②に処理が入り組んだ指定方法となってしまいます。

        //Entryにデータ格納 → ①データ入力処理
        var entryList1 = mutableListOf<Entry>()//1本目の線
        var entryList2 = mutableListOf<Entry>()//2本目の線
        for(i in x.indices){
            entryList1.add(
                Entry(x[i], y1[i])
            )
            entryList2.add(
                Entry(x[i], y2[i])
            )
        }

        //X軸の設定 → ②UIフォーマット指定処理
        lineChart.xAxis.apply {
            isEnabled = true
            textColor = Color.BLACK
        }
        //左Y軸の設定 → ②UIフォーマット指定処理
        lineChart.axisLeft.apply {
            isEnabled = true
            textColor = Color.BLACK
        }
        //右Y軸の設定 → ②UIフォーマット指定処理
        lineChart.axisLeft.apply {
            isEnabled = false
        }

        //LineDataSet(線1本ごとの)のリストを作成 → ①データ入力処理
        val lineDataSets = mutableListOf<ILineDataSet>()
        //線1本目のデータ格納 → ①データ入力処理
        val lineDataSet1 = LineDataSet(entryList1, "linear")
        //線1本目の色 → ②UIフォーマット指定処理
        lineDataSet1.color = Color.BLUE
        //リストに格納 → ①データ入力処理
        lineDataSets.add(lineDataSet1)
        //線2本目のデータ格納 → ①データ入力処理
        val lineDataSet2 = LineDataSet(entryList2, "square")
        //線2本目の色 → ②UIフォーマット指定処理
        lineDataSet2.color = Color.RED

        //LineDataにLineDataSetのリスト格納 → ①データ入力処理
        lineDataSets.add(lineDataSet2)
        //LineChartにData格納 → ①データ入力処理
        lineChart.data = LineData(lineDataSets)

本パッケージでの対応

上記のような指定法は独立性やコードの可読性の観点から好ましい状態ではないので、
・UIフォーマット指定用クラス(ChartとDataSetの2種類。両者の違いはこちら参照)
・データ入力用メソッド
・上記UI指定&データを基にグラフ描画するメソッド
の順で、独立して指定できるような構成としました。

また、フォーマット指定ようわからん!という方のために、
フォーマット指定をしなかった(コンストラクタに引数を入れない)場合も、
私の主観でいい感じ(抽象的な表現ですが‥笑)に設定してくれるような機能を加えています。

特に色設定は設定箇所が多く、手動設定が面倒なので、
カラーユニバーサルデザインに基づき、線や棒の色を自動指定
image.png
・背景が黒(輝度が0.5以下)のときは、文字を白に自動変更
image.png

という機能を追加しました

3. ツールヒント作成

データ点をタップした際にデータの詳細を表示してくれる「ツールヒント」
image.png

があると、UIの見やすさが格段に向上します。

しかし公式ドキュメントにも実装法がまともに記載されておらず。
サンプルコードを見ながら手探りでの実装が求められます。

本パッケージでの対応

簡単にツールヒントを表示できるよう、フォーマット指定用クラスで下記の指定を可能としました
A. ツールヒント表示の有無
ツールヒントの表示有無を指定します

B. 表示するデータの軸方向(X、Y、XY両方)
下の図のように表示するデータの軸方向を選択します(左から表示なし、Xのみ、Yのみ、XY両方)
toolhint.png

C. 時系列グラフの場合、時刻表示のフォーマット(例:"M/d HH:mm")
X軸が時系列のとき、時刻表示のフォーマットを指定できるようにしました。
下の図では、"d日H時"というフォーマットを指定しています
image.png

D. データに付与する単位(例:℃、%、Paなど)
X軸、Y軸ともに、表示するデータに単位を付加できます。
下の図では、Y軸に"円"という単位を付加しています
image.png

4. 日本語ドキュメントが少ない

日本語で網羅的に解説した記事は皆無といっても良い状況です。
特にUIフォーマット指定系は公式英語ドキュメントに説明が記載されていないメソッド・プロパティも多いです。

本パッケージでの対応

本記事に、UIフォーマット指定プロパティの一覧と、指定による変化を図示したリンクを記載しました
(DataSetのUIフォーマット指定プロパティ一覧はこちら)

どのプロパティを設定すれば、どのようにグラフ形状が変わるかを、
日本語+画像で簡便に追えるかと思います

必要なもの

下記の開発環境を構築してください
・開発用のPC(今回はWindows10を使用)
・Android Studio(今回は4.0.1を使用、AndroidバージョンはAPI26以降推奨)
・動作させるAndroidスマホ(今回はPixel 3aを使用)

導入方法

下記のような手順となります
1. MPAndroidChartの導入
2. CustomMPAndroidChartパッケージの導入

1. MPAndroidChartの導入

プロジェクト内に、グラフ描画ライブラリであるMPAndroidChartを導入します

build.grandle(Project)に、

allprojects {
    repositories{
        :
        maven { url 'https://jitpack.io' }
        :

という記載を加えます。
9_mpandroidchart_build.gradle.png

build.grandle(Module:app)に、

dependencies {
    :
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
    :

という記載を加えます。
10_mpandroidchart_build.gradle_app.png

メニューバーの「File → Close Project」でプロジェクトを閉じて開き直すと、
ライブラリが反映されます。

2. CustomMPAndroidChartパッケージの導入

上記MPAndroidChartのグラフを簡易的に作成するためのメソッド・クラス集を、
「CustomMPAndroidChart」としてパッケージ化しました。
Githubにもアップロードしております

パッケージ概要

下記の6つのモジュールからなります
・LineChartMethods.kt:折れ線グラフ用メソッド集
・BarChartMethods.kt:棒グラフ用メソッド集
・CandleStickChartMethods.kt:ローソク足グラフ(株価のチャートのようなグラフ)用メソッド集
・PieChartMethods.kt:折れ線グラフ用メソッド集
・MarkerViews.kt:ツールヒント表示用クラス
・ChartFormats.kt:UIフォーマット指定用クラスを集めたモジュール

また、ツールヒントで使用するレイアウトファイル
simple_marker_view.xmlの導入も必要となります。

パッケージの導入方法

グラフを作成するプロジェクト内に、下記の手順で導入します。
※手動操作が多いので、より良い提供方法ご存知であればご教示いただけるとありがたいです

javaフォルダ直下のパッケージフォルダを右クリックし、New → Packageを選択し、"chart"と名前をつけます
11_chart.png

折れ線グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"LineChartMethods"と名前を付け、GitHub上のLineChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

棒グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"BarChartMethods"と名前を付け、GitHub上のBarChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ローソク足グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"CandleStickChartMethods"と名前を付け、GitHub上のCandleStickChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

円グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"PieChartMethods"と名前を付け、GitHub上のPieChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ツールヒント表示用クラスの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"MarkerViews"と名前を付け、GitHub上のMarkerViews.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

UIフォーマット指定用クラスの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"ChartFormats"と名前を付け、GitHub上のChartFormats.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ツールヒント用レイアウトファイルの作成

クリック時のツールヒント用レイアウトファイルを、下記手順で作成します。

res/layoutを右クリックし、New → Layout Resource Fileを選択し、"simple_marker_view"と名前をつけます
24_make_fragment_xml1.png

作成されたxmlファイルを、下記内容に書き換えます

simple_marker_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="40dp"
    android:background="@color/toolTipBgColor"
    tools:ignore="Overdraw">

    <TextView
        android:id="@+id/tvSimple"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="7dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:text=""
        android:textSize="12sp"
        android:textColor="@color/toolTipTextColor"
        android:ellipsize="end"
        android:gravity="center_vertical|center_horizontal"
        android:textAppearance="?android:attr/textAppearanceSmall" />

</RelativeLayout>

res/values/colors.xmlに、下記内容を追記します。
(色コードはこちらを参考に適宜変更してください)

colors.xml
    :
    <color name="toolTipBgColor">#999999</color>//背景色コード
    <color name="toolTipTextColor">#ffffff</color>//テキスト色コード
    :

以上で、パッケージの導入が完了しました

使用方法

折れ線グラフ、棒グラフ、ローソク足グラフ、円グラフそれぞれに関して、
実装方法を解説します。

GitHubにサンプルコードをアップしていますので、こちらも参照頂けると分かりやすいかと思います

1. 折れ線グラフの実装方法

折れ線グラフの実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

下記のように、LineChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.LineChart
    android:id="@+id/lineChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

折れ線グラフ作成メソッド呼び出し処理の実装

折れ線グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・線が1本のとき
・横軸を時系列にしたいとき
・複数のとき
で例を分けて解説します。

線が1本のとき

基本的な流れとしては
・Chartフォーマットの指定
・DataSetフォーマットの指定
・EntryにmakeLineChartDataメソッドでデータ格納
・setupLineChartメソッドでグラフ描画
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//X軸データ
        val y = x.map{it*it}//Y軸データ(X軸の2乗)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeLineChartData(x, y)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, findViewById(R.id.lineChartExample), lineChartFormat, lineDataSetFormat, context)

ChartフォーマットやDataSetフォーマットはグラフのUIを指定します。詳しくは後述します
上記コードを実行すると、下図のようなグラフが表示されます
image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeLineChartData() → makeDateLineChartData()
に変更します

//表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//Y軸データ(数値)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeDateLineChartData(x, y, lineChartFormat.timeAccuracy)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, findViewById(R.id.lineChartExample), lineChartFormat, lineDataSetFormat, context)

image.png

非等間隔な時間を正確に表現したいとき

前述のように、上記の方法はデータ点がX方向に等間隔にプロットされるため、
データの取得間隔が一定でないときは、時間が横軸上で正確に表現されません。

このようなときに時間を正確に表現したい時は、Chartフォーマットで
timeAccuracy = true
と指定します。

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(
        timeAccuracy = true,
        /*ここでその他のChartフォーマット指定*/
        )

image.png
※ラベルは最初と最後のみ表示されます

線が複数本のとき

1本の時との違いは、
・DataSetフォーマットを線の本数だけ指定
・Entryに線の本数だけデータ格納
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//X軸データ
        val y1 = x.map{it}//Y軸データ1(X軸の1乗)
        val y2 = x.map{it*it}//Y軸データ2(X軸の2乗)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/),
            "square" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeLineChartData(x, y1),
            "square" to makeLineChartData(x, y2)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, lineChart, lineChartFormat, lineDataSetFormat, context)

image.png
なお、データ格納時のメソッドをmakeDateLineChartDataにすれば、複数線かつ時系列のグラフも作成可能です

2. 棒グラフの実装方法

棒グラフの実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、BarChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.BarChart
    android:id="@+id/barChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

棒グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・棒が1本のとき
・横軸を時系列にしたいとき
・複数の棒を積み上げ表示するとき
・複数の棒を横に並べて表示するとき
で例を分けて解説します。

棒が1本のとき

折れ線グラフのときとほぼ同様です。
"Line~"という名前になっているクラス名を"Bar~"と変えるだけでいけるかと思います。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 4f, 6f, 7f, 8f, 9f)//X軸データ
        val y = x.map{it*it}//Y軸データ(X軸の2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "square" to makeBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeBarChartData() → makeDateBarChartData()
に変更します

※折れ線グラフのときと異なり、timeAccuracyプロパティ指定はできません

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//Y軸データ(数値)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "square" to makeDateBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

複数の棒を積み上げ表示するとき

1本の時との違いは、
・Entryに格納するY軸データは、List<MutableList<Float>>で積み上げたいデータをまとめて格納
・棒ごとのカテゴリ名は、DataSetフォーマットのプロパティstackLabelsに、Listで指定
・棒ごと色指定は、プロパティ"color"ではなく、"colors"にリストで指定が必要(参考)
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 4f, 6f, 7f, 8f, 9f)//X軸データ
        val y = x.map{ mutableListOf(it, it*it)}//Y軸データ(1項目:X軸の1乗、2項目:Xの2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "stack" to BarDataSetFormat(
                stackLabels = listOf("linear","square"),
                /*ここでその他のDataSetフォーマット指定*/
            )
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "stack" to makeStackBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

複数の棒を横に並べて表示するとき

複数折れ線と同様、1本の時との違いは、
・DataSetフォーマットを棒の本数だけ指定
・Entryに棒の本数だけデータ格納
となります。
※横軸間隔が一定のときのみ使用可能なので、ご注意ください

        //表示用サンプルデータの作成
        val x = listOf<Float>(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)//X軸データ
        val y1 = x.map{it}//Y軸データ1(X軸の1乗)
        val y2 = x.map{it*it}//Y軸データ2(X軸の2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "linear" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/),
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "linear" to makeBarChartData(x, y1),
            "square" to makeBarChartData(x, y2)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

3. ローソク足グラフの実装方法

「ローソク足グラフ」とは株価のチャートで使われているグラフで、
箱ひげ図の代用にも使えます

実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、CandleStickChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.CandleStickChart
    android:id="@+id/candleStickChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

ローソク足グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・横軸が数値のとき
・横軸を時系列にしたいとき
で例を分けて解説します。

横軸が数値のとき

Entryへのデータ格納時に、X軸の値、Y最大値、Y最小値、Y開始値、Y終了値の5種類の引数を指定する必要があることに注意してください

        //表示用サンプルデータの作成//
        val x = listOf<Float>(2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f)//X軸データ
        val yHigh = x.map{it * 2}//Y軸データ(最大値)
        val yLow = x.map{it}//Y軸データ(最小値)
        val yOpen = x.map{it + 1}//Y軸データ(開始値)
        val yClose = x.map{it + 2}//Y軸データ(終了値)

        //Chartフォーマット
        var candleChartFormat = CandleChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var candleDataSetFormat = CandleDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納(カテゴリ名のMap)
        val candleEntries = makeCandleChartData(x, yHigh, yLow, yOpen, yClose)

        //②~⑦グラフの作成
        setupCandleStickChart(candleEntries, candleStickChart, candleChartFormat, candleDataSetFormat, context)

image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeCandleChartData() → makeDateCandleChartData()
に変更します

※timeAccuracyプロパティ指定はできません

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val ySeed = listOf<Float>(2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f)//Y軸データ生成用
        val yHigh = ySeed.map{it * 2}//Y軸データ(最大値)
        val yLow = ySeed.map{it}//Y軸データ(最小値)
        val yOpen = ySeed.map{it + 1}//Y軸データ(開始値)
        val yClose = ySeed.map{it + 2}//Y軸データ(終了値)

        //Chartフォーマット
        var candleChartFormat = CandleChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット
        var candleDataSetFormat = CandleDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納
        val candleEntries = makeDateCandleChartData(x, yHigh, yLow, yOpen, yClose)

        //②~⑦グラフの作成
        setupCandleStickChart(candleEntries, candleStickChart, candleChartFormat, candleDataSetFormat, context)

image.png

4. 円グラフの実装方法

円グラフの実装方法を、レイアウト(.xml)と処理部(.ktあるいは.java)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、PieChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.PieChart
    android:id="@+id/pieChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

円グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。

        //表示用サンプルデータの作成//
        val dimensions = listOf("A", "B", "C", "D")//分割円の名称(String型)
        val values = listOf(1f, 2f, 3f, 4f)//分割円の大きさ(Float型)

        //Chartフォーマット
        var pieChartFormat = PieChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット
        var pieDataSetFormat = PieDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納
        val pieEntries = makePieChartEntries(dimensions, values)

        //②~⑦グラフの作成
        setupPieChart(pieEntries, pieChart, "PieChart", pieChartFormat, pieDataSetFormat)

image.png

UIフォーマットの指定方法

グラフ全体(Chart)に適用するものと、カテゴリ毎(DataSet)に適用するものを分けて解説します。

適用方法は、下のようにコンストラクタに引数を与えることで各プロパティ内容が指定できます。
(指定がない場合は前章「使用方法」で図示しているようなUIとなります)

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/M")
        val x = listOf<Date>(
            sdf.parse("1990/1"),
            sdf.parse("1995/1"),
            sdf.parse("2000/1"),
            sdf.parse("2005/1"),
            sdf.parse("2010/1"),
            sdf.parse("2015/1"),
            sdf.parse("2018/1")
        )
        val y1 = listOf(6.0f, 7.6f, 10.3f, 13.0f, 15.0f, 18.2f, 20.6f)//Y軸データ1(アメリカ)
        val y2 = listOf(0.4f, 0.7f, 1.2f, 2.3f, 6.1f, 11.2f, 13.4f)//Y軸データ2(中国)
        val y3 = listOf(3.1f, 5.4f, 4.8f, 4.8f, 5.7f, 4.4f, 5.0f)//Y軸データ3(日本)
        val y4 = listOf(1.6f, 2.6f, 1.9f, 2.8f, 3.4f, 3.4f, 4.0f)//Y軸データ4(ドイツ)

        ///////////ここでChartフォーマットの指定///////////
        var lineChartFormat = LineChartFormat(
            legendTextSize = 10f,
            description = "主要国GDP推移",
            descriptionTextSize = 15f,
            descriptionYOffset = -10f,
            bgColor = Color.DKGRAY,
            xAxisDateFormat = SimpleDateFormat("yyyy年"),
            toolTipDateFormat = SimpleDateFormat("yyyy年"),
            toolTipDirection = "xy",
            toolTipUnitY = "兆ドル"
        )
        ///////////ここでDataSetフォーマットの指定(カテゴリ名のMap)///////////
        var lineDataSetFormat =  mapOf(
            "アメリカ" to LineDataSetFormat(//線1のDataSetフォーマット指定
                lineColor = UNIVERSAL_BLUE,
                lineWidth = 2f
            ),
            "中国" to LineDataSetFormat(//線2のDataSetフォーマット指定
                lineColor = UNIVERSAL_RED,
                lineWidth = 2f
            ),
            "日本" to LineDataSetFormat(//線3のDataSetフォーマット指定
                lineColor = UNIVERSAL_SKYBLUE,
                lineWidth = 2f
            ),
            "ドイツ" to LineDataSetFormat(//線4のDataSetフォーマット指定
                lineColor = Color.LTGRAY,
                lineWidth = 2f
            )
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "アメリカ" to makeDateLineChartData(x, y1, false),
            "中国" to makeDateLineChartData(x, y2, false),
            "日本" to makeDateLineChartData(x, y3, false),
            "ドイツ" to makeDateLineChartData(x, y4, false)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, lineChart, lineChartFormat, lineDataSetFormat, context)

image.png

GitHubのMainActivity.ktに実装例がいくつかある(特に後半の4メソッド)ので、こちらを参照頂けると分かりやすいかと思います

グラフ全体(Chart)に適用するUIフォーマット一覧

下記に、Chartフォーマットにおいて指定可能なプロパティ一覧を記載します。

「MPAndroidChartでのプロパティ名」列のリンクで、実際にどのようにUIが変わるかを図示しました(別記事)
※MPAndroidChartに対応が存在しない本パッケージオリジナルプロパティに関しては、本記事内で別途変化を図示しております。

適用対象 変更項目 Chartフォーマットでのプロパティ名 備考 MPAndroidChartでのプロパティ名 折れ線 棒グラフ ローソク足 円グラフ
凡例 マーク形状 legendFormat Legend.LegendForm? nullなら凡例表示なし .legend.form
凡例 文字色 legentTextColor Int? nullならデフォルト(黒) .legend.textColor
凡例 文字サイズ legendTextSize Float? nullならデフォルト .legend.textSize
説明ラベル 表示文字列 description String? nullなら説明ラベルなし .description.text
説明ラベル 文字色 descriptionTextColor Int? nullならデフォルト(黒) .description.textColor
説明ラベル 文字サイズ descriptionTextSize Float? nullならデフォルト .description.textSize
説明ラベル 横位置微調整 descriptionXOffset Float? nullならデフォルト .description.xOffset
説明ラベル 縦位置微調整 descriptionYOffset Float? nullならデフォルト .description.yOffset
背景 背景色 bgColor Int? nullならデフォルト(白) .setBackgroundColor()
タッチ操作 有効無効 touch Boolean .setTouchEnabled()
X軸ラベル 表示有無 xAxisEnabled Boolean .xAxis.isEnabled
X軸ラベル 文字色 xAxisTextColor Int? nullならデフォルト(黒) .xAxis.textColor
X軸ラベル 文字サイズ xAxisTextSize Float? nullならデフォルト .xAxis.textSize
X軸ラベル 時刻表示フォーマット xAxisDateFormat SimpleDateFormat? nullならM/d H:mm -
左Y軸ラベル 表示有無 yAxisLeftEnabled Boolean .axisLeft.isEnabled
左Y軸ラベル 文字色 yAxisLeftTextColor Int? nullならデフォルト(黒) .axisLeft.textColor
左Y軸ラベル 文字サイズ yAxisLeftTextSize Float? nullならデフォルト .axisLeft.textSize
左Y軸ラベル 表示下限 yAxisLeftMin Float? nullなら下限なし .axisLeft.axisMinimum
左Y軸ラベル 表示上限 yAxisLeftMax Float? nullなら上限なし .axisLeft.axisMaximam
右Y軸ラベル 表示有無 yAxisRightEnabled Boolean .axisRight.isEnabled
右Y軸ラベル 文字色 yAxisRightTextColor Int? nullならデフォルト(黒) .axisRight.textColor
右Y軸ラベル 文字サイズ yAxisRightTextSize Float? nullならデフォルト .axisRight.textSize
右Y軸ラベル 表示下限 yAxisRightMin Float? nullなら下限なし .axisRight.axisMinimum
右Y軸ラベル 表示上限 yAxisRightMax Float? nullなら上限なし .axisRight.axisMaximam
拡大操作 拡大方向 zoomDirection String? "x", "y", "xy"
nullなら拡大無効
.isScaleXEnabled
.isScaleYEnabled
.setScaleEnabled()
拡大操作 ピンチ操作有効 zoomPinch Boolean .setPinchZoom()
ツールヒント 表示する軸 toolTipDirection String? "x", "y", "xy"
nullならツールヒントなし
.marker
ツールヒント 時系列グラフのときのフォーマット toolTipDateFormat SimpleDateFormat? nullならM/d H:mm .marker
ツールヒント X軸の単位 toolTipUnitX String デフォルトは単位なし("") .marker
ツールヒント Y軸の単位 toolTipUnitY String デフォルトは単位なし("") .marker
X軸表示法 時間軸スケールの正確性 timeAccuracy Boolean Trueなら時間軸を正確表示(ラベルは最大最小値のみ) -
ラベル 文字色 labelColor Int? nullならデフォルト(黒) .setEntryLabelColor()
ラベル 文字サイズ labelTextSize Float? nullならデフォルト .setEntryLabelTextSize()
中央のテキスト 表示文字列 centerText String? nullなら中央テキストなし .centerText
中央のテキスト 文字色 centerTextColor Int? nullならデフォルト(黒) .setCenterTextColor()
中央のテキスト 文字サイズ centerTextSize Float? nullならデフォルト .setCenterTextSize()
中央の穴 穴の半径 holeRadius Float? nullならデフォルト .holeRadius
中央の穴 穴周辺の色が薄い部分の幅 transparentCircleRadius Float? nullならデフォルト .transparentCircleRadius
中央の穴 穴の塗りつぶし色 holeColor Int? nullならデフォルト(黒) .setHoleColor()
______________ ____________ ________________

本パッケージオリジナルメソッド

xAxisDateFormat

X軸の時刻表示フォーマットを変更します(時系列グラフのときのみ。デフォルトは"M/d H:mm")

var lineChartFormat = LineChartFormat(xAxisDateFormat = SimpleDateFormat("d日H時"))

image.png

timeAccuracy

前述した内容と同様に、時間軸スケールの正確性を指定します(時系列折れ線グラフのみ)

スケールを正確に表示し、時間ラベルは最大最小のみ表示
var lineChartFormat = LineChartFormat(timeAccuracy=true)

image.png

正確性は下がるが、時間ラベルを全て表示
var lineChartFormat = LineChartFormat(timeAccuracy=false)

image.png

toolTipDirection

ツールヒント表示なし
var lineChartFormat = LineChartFormat(toolTipDirection=null)

image.png

ツールヒントにX軸の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="x")

image.png

ツールヒントにY軸の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="y")

image.png

ツールヒントにX軸Y軸両方の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="xy")

image.png

toolTipDateFormat

ツールヒントの時刻表示フォーマットを変更します(時系列グラフのときのみ。デフォルトは"M/d H:mm")

ツールヒントの時刻表示フォーマットを"d日H時"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipDateFormat = SimpleDateFormat("d日H時")
        )

image.png

toolTipUnitX

ツールヒントのX軸表示に付加する単位を指定します(デフォルトは単位なし)

ツールヒントのX軸単位を"日目"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipUnitX = "日目"
        )

image.png

toolTipUnitY

ツールヒントのY軸表示に付加する単位を指定します(デフォルトは単位なし)

ツールヒントのY軸単位を"円"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipUnitY = "円"
        )

image.png

カテゴリごと(DataSet)に適用するUIフォーマット一覧

下記に、DataSetフォーマット(線ごと、棒ごとetc.に指定)において指定可能なプロパティ一覧を記載します。

「MPAndroidChartでのプロパティ名」列のリンクで、実際にどのようにUIが変わるかを図示しました(別記事)
※MPAndroidChartに対応が存在しない本パッケージオリジナルプロパティに関しては、本記事内で別途変化を図示しております。

適用対象 変更項目 DataSetフォーマットでのプロパティ名 備考 MPAndroidChartでのプロパティ名 折れ線 棒グラフ ローソク足 円グラフ
値表示 値表示の有無 drawValue Boolean .setDrawValues()
値表示 値表示の文字色 valueTextColor Int? nullならデフォルト(黒) .valueTextColor
値表示 値表示の文字サイズ valueTextSize Float? nullならデフォルト .valueTextSize
値表示 値表示のフォーマット valueTextFormatter String? nullならデフォルト .valueFormatter
左右軸どちらを使用するか axisDependency YAxis.AxisDependency? nullなら左Y軸 .axisDependency
線の色 lineColor Int? nullならデフォルト(水色) .color
lineWidth Float? nullならデフォルト .lineWidth
補完方法 fittingMode LineDataSet.Mode? nullならデフォルト(直線補完) .mode
データ点 表示有無 drawCircles Boolean .setDrawCircles()
データ点 枠の色 circleColor Int? nullならデフォルト(水色) .setCircleColor()
データ点 枠の半径 circleRadius Float? nullならデフォルト .circleRadius
データ点 穴の塗りつぶし色 circleHoleColor Int? nullならデフォルト(白) .circleHoleRadius
データ点 穴の半径 circleHoleRadius Float? nullならデフォルト .circleHoleColor
棒の色 barColor Int? nullならデフォルト(水色) .color 積み上げ以外
各積み上げ棒の色リスト barColors List .colors 積み上げ
積み上げ棒のカテゴリ名 stackLabels List? nullならデフォルト(ラベル名で埋める) .stackLabels 積み上げ
ローソク細線 線の色 shadowColor Int .shadowColor
ローソク細線 shadowWidth Float? nullならデフォルト .shadowWidth
ローソク太線 減少時の色 decreasingColor Int .decreasingColor
ローソク太線 減少時の塗りつぶし形式 decreasingPaint Paint.Style? nullなら塗りつぶしあり .decreasingPaintStyle
ローソク太線 増加時の色 increasingColor Int? nullなら色なし .increasingColor
ローソク太線 増加時の塗りつぶし形式 increasingPaint Paint.Style? nullなら塗りつぶしなし .increasingPaintStyle
分割円 分割円の色 colors List .colors
______________ ____________ ________________

本パッケージオリジナルメソッド

valueTextFormatter

値表示のフォーマットを指定します
(MPAndroidChartではValueFormatterクラス内をオーバーライドして指定しますが、本パッケージではString型で指定します)

整数(小数点以下0位)で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.0f"
            )
        )

image.png

小数点以下2位で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.2f"
            )
        )

image.png

小数点以下1位+"円"で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.1f円"
            )
        )

image.png

参考

MPAndroidChartにおける時系列グラフの作り方

時系列でないグラフの場合

時系列ではない(X軸がFloat型の数値)グラフでは、

val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)
val y = x.map{it*it}
var entryList = mutableListOf<Entry>()
for(i in x.indices){
    entryList.add(Entry(x[i], y[i]))
}

というように、Entryの第一項にX軸の値を、第二項にY軸の値を入れれば、入力したX軸の値に合わせて

val lineDataSets = ListOf<ILineDataSet>(LineDataSet())
lineChart.data = LineData(lineDataSets)

とすれば、下図のように両軸共に正しくプロットされます。
image.png

時系列グラフの場合

X軸がDate(java.util.Date)型の数値のとき、Entryの第一項にX軸の値を入力することができません。

        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/02 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)
        //Entryにデータ格納
        var entryList = mutableListOf<Entry>()
        for(i in x.indices){
            entryList.add(
                Entry(x[i], y[i])//←ここでエラーが出る
            )
        }
        val lineDataSets = listOf<ILineDataSet>(LineDataSet(entryList,"label"))
        lineChart.data = LineData(lineDataSets)

Entryの第一項にはFloat型を格納する必要があるので、indexをFloat型に変換して格納します(代わりに第3項に日付データを保持しておく)

          :
        //Entryにデータ格納
        var entryList = mutableListOf<Entry>()
        for(i in x.indices){
            entryList.add(
                Entry(i.toFloat(), y[i], x[i])
            )
        }
          :

しかしこのままでは
image.png!

のように、X軸のラベルがただのインデックスとなってしまい、時刻が何時だか分かりません。
下記のように、時刻を文字列リストに変換してX軸ラベルに指定することで、ラベルを表示することができます。

          :
        //X軸の値を日付型→文字列に変換し、X軸ラベルに指定
        val xStr = x.map { SimpleDateFormat("M/d H:mm").format(it)}
        lineChart.xAxis.valueFormatter = IndexAxisValueFormatter(xStr)

        val lineDataSets = listOf<ILineDataSet>(LineDataSet(entryList,"label"))
        lineChart.data = LineData(lineDataSets)

image.png

21
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
12