Skip to content

ADR-0028: ComposeCharts for report visuals

  • Status: Accepted
  • Date: 2026-08-22
  • Deciders: maintainers
  • Supersedes: ADR-0013
  • Related: ui.md, issue #51

Context

ADR-0013 hand-rolled the reports donut and trend bars with Compose Canvas. That kept dependencies at zero but left animations, gridlines, axis/value labels and adaptive sizing unimplemented, and every new chart type would re-implement drawing + gesture + tooltip logic by hand.

Decision

Report charts are rendered with io.github.ehsannarmani:compose-charts (Apache-2.0):

  • Category breakdown → PieChart with Pie.Style.Stroke (donut).
  • 6-month trend → ColumnChart with gridlines and a base-currency value scale.

Version is 1.0.0 (amended 2026-08-22). The original pin to 0.2.0 existed because 0.2.5+ releases are compiled with Kotlin 2.3.10, which the then-toolchain (Kotlin 2.2.10 embedded via AGP 9.x built-in Kotlin) could not consume. The toolchain has since lifted KGP (now 2.4.21) through AGP's documented "upgrade to a higher KGP version" route (root build.gradle.kts classpath), unpinning the library. Version bumps must still re-check the Kotlin metadata requirement against the toolchain before upgrading; upstream 1.0.0 enables its pie label helper by default, so the donut passes LabelHelperProperties(enabled = false) to keep the legend app-side.

App-side responsibilities stay app-side:

  • Donut total (centered when it fits, above the ring otherwise), selection bubble (ChartTooltip) and legend rows (icons) — 0.2.0's PieChart has no popup or hole text.
  • Trend month labels and per-bar tap bubbles — the library draws canvas labels as pixels (invisible to TalkBack); labels are Compose Texts below the chart, taps land on an overlay row of per-bar cells that also carry the accessibility summaries.
  • All colors derive from MaterialTheme.colorScheme via reportChartPalette() / screen-level colors — nothing hardcoded.
  • Mapping to library models lives in pure functions (ui/reports/charts/ChartAdapters.kt), JVM-unit-tested; money strings are always formatted from Long minor units through MoneyFormatter, never from the library's Double.

Amended 2026-10-15 (issue #117): measure the total label and amount at the user's font scale using their actual text styles. Keep them in the 104 dp center area only when both natural widths and their combined height fit. Otherwise place the total above the ring, allowing wrapping at the available width. Use the same titleLarge amount style in either position; never shrink text or truncate the financial value to fit the hole. Chart geometry, tooltip bounds, selection, and accessible summaries stay independent of this layout.

Library internals (arc math, animation timing) are not tested.

Consequences

  • Charts animate on entry; trend gains gridlines and a value scale.
  • One extra dependency (+32 KiB APK, R8-minified, no extra keep rules).
  • Version bumps must re-check the Kotlin metadata requirement against the toolchain before upgrading.
  • common/ChartGeometry hit-testing was deleted with its tests; ChartTooltip remains (donut selection).

Alternatives considered

  • Keep hand-rolled Canvas (ADR-0013) — rejected: per-chart reimplementation of interaction/accessibility for every new visual.
  • Vico / MPAndroidChart — rejected: View-interop or heavier theming than a pure-Compose KMP library for two chart types.