見出し画像

Jetpack ComposeでMPAndroidChartのグラフを表示する

自己紹介

こんにちは!yosh4t4と申します。
くふうAIスタジオにてAndroidアプリエンジニアとして従事しており、家計簿アプリZaimの開発を担当しています。

Zaim Androidの現状

Zaimはリリースから10年以上経過したサービスで、日々、メンテナンスや機能改善を続けています。
Android版は旧来のAndroidビューで実装された画面がまだまだ多い状態ですが、徐々にJetpack Composeへの置き換えを行い、モダンな技術への移行を進めています。

その中で、家計簿アプリという性質上、当然ながらJetpack Composeでのグラフ表示実装も避けては通れません。

グラフ表示処理をJetpack Composeに置き換えたい

Androidで何らかのグラフを表示する場合には、MPAndroidChartを用いることで、比較的容易に導入することが可能です。
Zaim Androidでもこれまで、グラフ表示にはこちらのライブラリを利用してきました。

しかしながらこちらのライブラリは、現(2024年3月)時点で、Jetpack Composeをサポートしていません。
既存のデザイン、実装の共通化の観点から、できる限りMPAndroidChartで実装したいのですが、こうした場合には、AndroidViewコンポーザブルを用いて、Compose化を図ることができます。

AndroidViewコンポーザブルを用いてMPAndroidChartのグラフを表示する

今回、LineChart、PieChart、RadarChartの三種類のグラフについてサンプルを実装、動作検証を行いました。
下記に、コードとともに結果をまとめます。

LineChart

@Composable
fun LineChart(modifier: Modifier = Modifier) {
  val sinDataSet = createLineDataSet({ x -> sin(x * PI / 180).toFloat() }, "Sin Curve", Color.Red)
  val cosDataSet = createLineDataSet({ x -> cos(x * PI / 180).toFloat() }, "Cos Curve", Color.Blue)
  AndroidView(
    factory = { context ->
      LineChart(context).apply {
        description = Description().apply { text = "" }
        data = LineData(sinDataSet, cosDataSet)
        xAxis.apply {
          mAxisMaximum = 360f
          mAxisMinimum = -360f
          granularity = 90f
          setLabelCount(9, true)
        }
        axisLeft.apply {
          mAxisMaximum = 1.0f
          mAxisMinimum = -1.0f
          granularity = 0.2f
          setLabelCount(11, false)
        }
        axisRight.isEnabled = false
      }
    },
    modifier = modifier
  )
}

// データセットの生成処理
fun createLineDataSet(function: (Float) -> Float, label: String, lineColor: Color): LineDataSet {
// -360°から360°まで、10°おきに値を追加
  val entries = (-360..360 step 10).map { Entry(it.toFloat(), function(it.toFloat())) }
  return LineDataSet(entries, label).apply {
    color = lineColor.toArgb()
    setDrawValues(false)
    setDrawCircles(false)
  }
}

高校数学でおなじみのsinカーブ、cosカーブを表示してみました。

PieChart

@Composable
fun PieChart(modifier: Modifier = Modifier) {
  val pieEntryList = listOf("Red", "Green", "Blue").map { label ->
    // 0〜100のランダムな整数を生成
    PieEntry(round(Random.nextFloat() * 100), label)
  }
  val pieDataSet = PieDataSet(pieEntryList, "").apply {
    colors = listOf(Color.Red, Color.Green, Color.Blue).map { it.toArgb() }
  }
  AndroidView(
    factory = { context ->
      PieChart(context).apply {
        description = Description().apply { text = "" }
        centerText = "Sample"
        setEntryLabelTextSize(11f)
        data = PieData(pieDataSet).apply { setValueTextSize(11f) }
        // アニメーションを指定
        animateXY(1000, 1000)
        invalidate()
      }
    },
    modifier = modifier
  )
}

こちらは家計簿にも用いることの多い円グラフ。
時計回りに動きながら表示するアニメーションや、タップした項目を拡大表示するアクションも、そのまま動作することが確認できました。

RadarChart

@Composable
fun RadarChart(modifier: Modifier = Modifier) {
  val radaDataSetList = listOf(
    createRadarDataSet("StudentA", Color.Red),
    createRadarDataSet("StudentB", Color.Green),
    createRadarDataSet("StudentC", Color.Blue)
  )
  val subjects = listOf("Eng.", "Math.", "Soc.Stu", "Sci", "For.Lang")
  AndroidView(
    factory = { context ->
      RadarChart(context).apply {
        description = Description().apply { text = "" }
        data = RadarData(radaDataSetList)
        xAxis.apply {
          valueFormatter = IndexAxisValueFormatter(subjects)
        }
        yAxis.apply {
          axisMaximum = 100f
          axisMinimum = 0f
          granularity = 20f
          setLabelCount(6, true)
        }
        invalidate()
      }
    },
    modifier = modifier
  )
}

// 学生毎のデータセットの生成処理
fun createRadarDataSet(studentName: String, studentColor: Color): RadarDataSet {
  // 0〜100のランダムな値5つのリストを生成
  val randomValues = List(5) { round(Random.nextFloat() * 100) }
  val radarEntries = randomValues.map { value -> RadarEntry(value) }
  return RadarDataSet(radarEntries, studentName).apply {
    color = studentColor.toArgb()
  }
}

こちらは学生3名の科目毎の点数分布。

まとめ

いずれのグラフも、確認した範囲では特に問題などはありませんでした。
今回はサンプルなので、いずれのグラフの処理もAndroidViewのfactoryに実装してしまいましたが、再表示時に最新のデータを読み直す場合を考慮するならば、データのセット処理はupdateで実装する方が好ましいですね。

AndroidViewの万能さには、ただただ脱帽です。
うまく活用しつつ、今後もZaimのCompose化を推進していきます。

くふうAIスタジオでは、採用活動を行っています。

当社は「AX で 暮らしに ひらめきを」をビジョンに、2023年7月に設立されました。
(AX=AI eXperience(UI/UX における AI/AX)とAI Transformation(DX におけるAX)の意味を持つ当社が唱えた造語)
くふうカンパニーグループのサービスの企画開発運用を主な事業とし、非エンジニアさえも当たり前にAIを使いこなせるよう、積極的なAI利活用を推進しています。
(サービスの一例:累計DL数1,000万以上の家計簿アプリ「Zaim」、月間利用者数1,600万人のチラシアプリ「トクバイ」等)
AXを活用した未来を一緒に作っていく仲間を募集中です。
ご興味がございましたら、以下からカジュアル面談のお申込みやご応募等お気軽にお問合せください。
https://open.talentio.com/r/1/c/kufu-ai-studio/homes/3849

ハッシュタグ

#中途採用 #エンジニア採用 #採用広報 #株式会社くふうAIスタジオ #テックブログ

この記事が気に入ったらサポートをしてみませんか?