6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Android] MPAndroidChart で X 軸に日付を表示させる

Posted at

概要

タイトルのとおりですが、Android のグラフライブラリ MPAndroidChart の X 軸に日付を表示する方法させる方法になります。

スクリーンショット 2021-03-08 18.15.31.png

「Android MPAndroidChart」 で検索するとざっくりとした使用方法は出てくるのですが
こういう細かい内容のものがあまりなかったので記事にしておきます。

参考になると幸いです。

実装

さっそくサンプルコードです。

MainActivity.kt
open class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        // X 軸ごとの Y 軸
        val entries: MutableList<Int> = mutableListOf(
            105,
            129,
            85,
            104,
            52,
            84,
            114
        )

        // X 軸のタイムスタンプ
        val entriesTimestampMills: MutableList<Long> = mutableListOf(
            1614524400000,  // 2021/03/01 00:00:00
            1614610800000,  // 2021/03/02 00:00:00
            1614697200000,  // 2021/03/03 00:00:00
            1614783600000,  // 2021/03/04 00:00:00
            1614870000000,  // 2021/03/05 00:00:00
            1614956400000,  // 2021/03/06 00:00:00
            1615042800000   // 2021/03/07 00:00:00
        )

        // グラフに描画するデータの設定
        val entryList = entries.mapIndexed { index, entry ->
            BarEntry(
                index.toFloat(),    // X 軸 ここに渡すのはあくまで 0, 1, 2... という index
                entry.toFloat()     // Y 軸
            )
        }

        val barDataSet = BarDataSet(entryList, "barChart")
        barDataSet.setDrawValues(false)

        barChart.data = BarData(mutableListOf<IBarDataSet>(barDataSet))
        barChart.setDrawGridBackground(false)
        barChart.description.isEnabled = false

        barChart.legend.apply {
            isEnabled = false
        }

        // X 軸のフォーマッター
        val xAxisFormatter = object : ValueFormatter() {
            private var simpleDateFormat: SimpleDateFormat =
                SimpleDateFormat("M/d", Locale.getDefault())

            override fun getFormattedValue(value: Float): String {
                // value には 0, 1, 2... という index が入ってくるので
                // index からタイムスタンプを取得する
                val timestampMills = entriesTimestampMills[value.toInt()]
                val date = Date(timestampMills)
                return simpleDateFormat.format(date)
            }
        }

        // X 軸の設定
        barChart.xAxis.apply {
            position = XAxis.XAxisPosition.BOTTOM
            valueFormatter = xAxisFormatter
            setDrawGridLines(false)
        }

        // Y 軸(左)の設定
        barChart.axisLeft.apply {
            setDrawGridLines(true)
            axisMinimum = 0f
        }

        // Y 軸(右)の設定
        barChart.axisRight.apply {
            isEnabled = false
        }

        // グラフ描画
        barChart.invalidate()
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.github.mikephil.charting.charts.BarChart
        android:id="@+id/barChart"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

上記サンプルコードを実行すると以下のような画面が表示できると思います。

Screenshot_1615194645.png

解説

ポイントとなるのは X 軸のタイムスタンプを別で管理する というところです。

順に解説していきます。

        // X 軸ごとの Y 軸
        val entries: MutableList<Int> = mutableListOf(
            105,
            129,
            85,
            104,
            52,
            84,
            114
        )

        // X 軸のタイムスタンプ
        val entriesTimestampMills: MutableList<Long> = mutableListOf(
            1614524400000,  // 2021/03/01 00:00:00
            1614610800000,  // 2021/03/02 00:00:00
            1614697200000,  // 2021/03/03 00:00:00
            1614783600000,  // 2021/03/04 00:00:00
            1614870000000,  // 2021/03/05 00:00:00
            1614956400000,  // 2021/03/06 00:00:00
            1615042800000   // 2021/03/07 00:00:00
        )

テストデータです。
entriesTimestampMills でタイムスタンプを管理しています。

        // グラフに描画するデータの設定
        val entryList = entries.mapIndexed { index, entry ->
            BarEntry(
                index.toFloat(),    // X 軸 ここに渡すのはあくまで 0, 1, 2... という index
                entry.toFloat()     // Y 軸
            )
        }

グラフに描画するデータ BarEntry に X 軸と Y 軸の値をセットしてます。
このサンプルコードの場合 entryList には 7つ の BarEntry が格納されます。

        // X 軸のフォーマッター
        val xAxisFormatter = object : ValueFormatter() {
            private var simpleDateFormat: SimpleDateFormat =
                SimpleDateFormat("M/d", Locale.getDefault())

            override fun getFormattedValue(value: Float): String {
                // value には 0, 1, 2... という index が入ってくるので
                // index からタイムスタンプを取得する
                val timestampMills = entriesTimestampMills[value.toInt()]
                val date = Date(timestampMills)
                return simpleDateFormat.format(date)
            }
        }

        // X 軸の設定
        barChart.xAxis.apply {
            position = XAxis.XAxisPosition.BOTTOM
            valueFormatter = xAxisFormatter
            setDrawGridLines(false)
        }

interface ValueFormatter() を実装し、 X 軸の valueFormatter に設定します。

コード内のコメントにも記載していますが、getFormattedValue(value: Float)value には BarEntry の X 軸として設定した値が渡されます。
BarEntry の X 軸には entries の index である 0, 1, 2... を設定しています。
X 軸: 0 に格納されているタイムスタンプを entriesTimestampMills から取得し、日付フォーマットして 3/1 と表示する、といった具合です。

なぜ別で管理するのか

なぜこのような実装にしたのかを備忘録として残しておきます。
興味がある方はこちらもご覧ください。

最初に思いついたのは以下のような実装でした。

        ...
        // `MutableMap` にして Key にタイムスタンプを持たせる
        val entries: MutableMap<Long, Int> = mutableMapOf(
            1614524400000 to 105,
            1614610800000 to 129,
            1614697200000 to 85,
            1614783600000 to 104,
            1614870000000 to 52,
            1614956400000 to 84,
            1615042800000 to 114
        )
        ...
        // `BarEntry` の X 軸にタイムスタンプを設定
        val entryList = entries.map { (timestampMills, entry) ->
            BarEntry(
                timestampMills.toFloat(),
                entry.toFloat()
            )
        }
        ...
        // X 軸のフォーマッターでタイムスタンプを受け取って日付にフォーマット
        val xAxisFormatter = object : ValueFormatter() {
            private var simpleDateFormat: SimpleDateFormat =
                SimpleDateFormat("M/d", Locale.getDefault())

            override fun getFormattedValue(value: Float): String {
                val date = Date(value.toLong())
                return simpleDateFormat.format(date)
            }
        }
        ...

上記コードを実行すると

Screenshot_1615202136.png

真っ白なグラフになってしまいました。

なぜ真っ白になるのか調査した結果、問題となっていたコードは

timestampMills.toFloat(),

タイムスタンプを Float に変換している箇所でした。

タイムスタンプを Float に変換することで値が丸められ、Float から Long へ戻したときに

1614524400000 (2021/03/01 00:00:00) → 1614599946240 (2021/03/01 20:59:06)
1614610800000 (2021/03/02 00:00:00) → 1614699954176 (2021/03/03 00:45:54)

のように実際の値と変わっていました。

そのせいでうまくグラフの描画ができなかったのだと思われます。

タイムスタンプを Long ではなく Int でも試してみましたが結果は変わらず真っ白でした。

Float の変換のところでおかしな値になってるので、 BarEntryFloat 以外の値を渡せないかも調べましたが見当たらず、、、
試行錯誤した結果 X 軸のタイムスタンプを別で管理する に落ち着いた次第です。

参考

MPAndroidChart

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?