| Status | Accepted (F1 contract extraction ✅, F2 Dart port ✅, F3 flutter_echarts rewrite ✅, F4 docs cleanup ✅) |
| Date | 2026-06-26 |
| Deciders | Stakeholder BitCode |
| Supersedes | ADR-011 §"Chart widget" (M5 data-display CustomPaint approach) — partially |
| Related | ADR-002 (Cross-Platform), ADR-003 (Component Roadmap §1 Chart Taxonomy), ADR-011 (Flutter Adapter), ADR-013 (Cross-tier Infra), ADR-014 (Engine UI Wiring) |
| Parallel-safe with | ADR-013 (✅ zero file overlap), ADR-014 (✅ zero file overlap) |
BitCode UI memiliki 3 layer chart dengan parity yang tidak seimbang:
| Layer | Komponen | Chart Types | Engine |
|---|---|---|---|
| Stencil (legacy) | 26 komponen bc-chart-* + KPI + Progress + Pivot |
26/26 | Apache ECharts ✅ |
Mitosis (@bitcode/ui) |
1 komponen BcChart (discriminator type) + BcKpi + BcProgress + BcSparkline |
26/26 | Apache ECharts (lazy import) ✅ |
Flutter (@bitcode/ui-flutter) |
1 komponen BcChart |
4/26 (bar, line, pie, doughnut) | CustomPaint manual ❌ |
Flutter chart saat ini (bc_chart.dart, 912 lines) menggunakan CustomPaint dengan CustomPainter hand-drawn. Hasil audit jujur menunjukkan implementasi ini hobby-grade, bukan production-grade:
| Aspek | Status | Detail |
|---|---|---|
| Interactivity (tooltip, hover, click) | ❌ Tidak ada | tooltipEnabled prop ada tapi diabaikan |
| Animation | ❌ Tidak ada | animate prop ada tapi render static |
| Y-axis labels | ❌ Tidak ada | Grid lines ada, tapi tidak ada angka skala |
| Negative values | ❌ Bug | barH = (value/maxVal)*plotH → height negatif → crash/render kosong, tidak ada baseline 0 |
| Nice-number axis scaling | ❌ Naive | maxVal manual, tidak ada algoritma nice scale (ECharts punya) |
| Legend interactivity | ❌ Tidak ada | Legend statis, tidak bisa hide/show series |
| Axis label truncation/rotation | ❌ Tidak ada | Label panjang ketabrak |
| Theme runtime adaptation | _defaultPalette hardcode BcTheme.*, tidak adaptif dark/light change |
|
shouldRepaint |
Compare List by reference → repaint setiap rebuild meski data sama | |
| Stacked math | maxVal dihitung 2x (lines 441-446 & 448-456), logic pertama tidak berguna |
|
| Accessibility | Hanya Semantics(label), tidak ada data table fallback (ECharts punya aria) |
|
| Rendering dasar | ✅ OK | bar/line/pie/doughnut memang tampil |
| Loading / No-data state | ✅ OK | Ada |
Untuk mencapai parity 26/26 dan production-grade, ada 2 jalan:
-
Fix & extend CustomPaint — fix semua bug di atas + implement 22 chart type lain secara manual. Estimasi: weeks per chart type (heatmap, sankey, treemap, sunburst, candlestick, graph, radar, gauge, funnel, parallel, themeriver, tree, boxplot, scatter, polar, pictorialbar, scorecard, mixed, pivot, custom, area). Total: berbulan-bulan, high maintenance burden.
-
Switch to
flutter_echarts— bungkus Apache ECharts di WebView Flutter. Langsung dapat 26 type + tooltip + animasi + zoom + toolbox + themes + a11y. Trade-off: WebView overhead.
Chart di ERP app Flutter tipikal 1-3 chart per screen (dashboard). Bukan use case 50 chart realtime. WebView overhead (~10-50MB/chart, 200-500ms startup) acceptable.
Adopsi flutter_echarts (by entronad) sebagai satu-satunya engine chart Flutter. Deprecate 912 lines CustomPaint di bc_chart.dart.
Rationale:
- CustomPaint saat ini belum production-grade (bug value negatif, no tooltip, no axis label, no animasi)
flutter_echartslangsung dapat 26 type + fitur lengkap ECharts (tooltip, animasi, zoom, toolbox, themes,ariaa11y)- Fix CustomPaint + implement 22 chart manual = berbulan-bulan, tidak worth it
- WebView overhead acceptable untuk 1-3 chart/dashboard
- Contract bisa shared — ECharts option adalah JSON, identik cross-platform
buildChartOption() yang sudah ada di packages/ui/src/contracts/chart-option/factory.ts (diekstrak dari Mitosis component tree, lihat F1 contract-extraction) menjadi single source of truth untuk ECharts option generation. Flutter menerima option yang sama sebagai JSON string → kirim ke WebView.
Tidak ada duplikasi logika chart antara Mitosis dan Flutter. Logika (nice scale, color palette, axis config, series mapping) hidup di 1 tempat: factory.ts.
Semua 26 BcChartType dari ADR-003 §1 Chart Taxonomy harus support di Flutter via flutter_echarts:
area, bar, boxplot, candlestick, custom, funnel, gauge, graph, heatmap,
kpi, line, mixed, parallel, pictorialbar, pie, pivot, polar, progress,
radar, sankey, scatter, scorecard, sunburst, themeriver, tree, treemap
Plus doughnut (Flutter variant, subset pie dengan doughnutInnerRadiusFactor).
- ✅ Parity 26/26 antara Stencil ↔ Mitosis ↔ Flutter tercapai
- ✅ Zero duplication — logika chart hidup di
factory.ts, Flutter hanya konsumsi JSON - ✅ Production-grade fitur gratis — tooltip, animasi, zoom, toolbox, themes, a11y
- ✅ Visual consistency — chart Flutter 100% identik dengan chart web
- ✅ Maintainable — update chart behavior cukup di 1 tempat (
factory.ts) - ✅ 912 lines buggy code dihapus — reduce maintenance burden
- ❌ WebView overhead — ~10-50MB memory per chart, 200-500ms startup. Acceptable untuk 1-3 chart/dashboard, tapi tidak ideal untuk chart-heavy screen.
- ❌ Platform WebView dependency — butuh WebView tersedia di target platform (Android/iOS default ada; desktop perlu verifikasi).
- ❌
flutter_echartsmaintenance risk — package by entronad, perlu monitor update & ECharts version alignment. - ❌ Debugging lebih sulit — chart render di WebView, inspect berbeda dari Flutter widget tree.
- ❌ Native feel berkurang — chart tidak se-native CustomPaint murni (font rendering, gesture, theming platform).
- Memory: dispose WebView ketika chart off-screen (
AutomaticKeepAliveClientMixin+ lifecycle). Pool WebView instance jika banyak chart. - Startup: pre-warm WebView di app init (
WebView.platform = ...), lazy loadflutter_echarts. flutter_echartsrisk: pin version, fork-able jika upstream abandoned. Alternative:echart_flutter(enechain) sebagai backup.- Debugging: expose
debugOptionprop yang log JSON option ke console.
Goal: Ekstrak buildChartOption() + types ke lokasi shared agar Flutter bisa konsumsi tanpa import TS langsung.
Approach: Karena ADR-012 (extract @bitcode/ui-schema) masih proposed, gunakan interim approach: publish factory.ts output sebagai JSON schema contract yang Flutter parse di build-time.
packages/ui/src/components/charts/options/
├── types.ts ← BcChartType, ChartData, BuildOptionContext (existing)
├── helpers.ts ← buildTitleOption, buildTooltipOption, etc. (existing)
├── factory.ts ← buildChartOption() (existing, single source of truth)
└── contract.json ← NEW: auto-generated schema of BcChartType + option shape
File ownership (parallel-safe):
packages/ui/src/components/charts/options/*→ ADR-015 agent ONLYpackages/ui-flutter/lib/src/components/bc_chart.dart→ ADR-015 agent ONLYpackages/ui-flutter/lib/src/core/chart/→ ADR-015 territory (NEW dir: 4 files —chart_types.dart,chart_helpers.dart,chart_factory.dart,chart_contract.dart). The legacy 4-valueBcChartTypeincore/types.dartis replaced by a re-export of the new 26-value enum (CP-001);core/types.dartremains ADR-013 territory but the chart-enum migration is owned by ADR-015.
Goal: Replace CustomPaint bc_chart.dart dengan flutter_echarts-based implementation.
// bc_chart.dart (rewrite, ~300 lines dari 912)
class BcChart extends StatelessWidget {
final BcChartType type;
final List<BcChartSeries> series;
final List<String> categories;
final List<BcChartItem> items;
final List<Color> colors;
// ... props identik dengan Mitosis BcChartProps
@override
Widget build(BuildContext context) {
final option = BcChartOptionBuilder.build(
type: type,
data: _normalizeData(series, categories, items),
colors: _colorsToHex(colors),
chartTitle: chartTitle,
legend: legend,
// ... mirror BuildOptionContext
); // returns Map<String, dynamic> — same as factory.ts output
return Semantics(
container: true,
label: ariaLabel ?? chartTitle ?? 'Chart',
child: Container(
width: width, height: height,
decoration: BoxDecoration(
color: BcTheme.background,
border: Border.all(color: BcTheme.border),
borderRadius: BorderRadius.circular(BcTheme.radius),
),
child: loading
? _buildLoading()
: Echarts(
option: option, // ← ECharts JSON option, identik dengan web
extraScript: _resizeScript,
),
),
);
}
}BcChartOptionBuilder (Dart port of factory.ts):
- Port
buildChartOption()+ helpers ke Dart - Input:
BuildOptionContext(Dart class mirror TS interface) - Output:
Map<String, dynamic>(ECharts option, identik shape dengan TS output) - Ini adalah duplikasi logic yang UNAVOIDABLE (TS → Dart tidak bisa shared runtime). Tapi karena contract-nya identik, test parity bisa verify output JSON sama.
Parity testing: Generate golden JSON dari factory.ts (TS) untuk setiap BcChartType, bandingkan dengan output BcChartOptionBuilder (Dart). Test harus pass 26/26.
Goal: Smooth migration dari CustomPaint ke flutter_echarts.
- v1.0.0 (this ADR):
BcChartdefault keflutter_echarts. Tambah flaguseLegacyRenderer: false(default false) untuk akses CustomPaint lama selama 1 release. - v1.1.0: Remove
useLegacyRendererflag + hapus 912 lines CustomPaint (_BarChartPainter,_LineChartPainter,_PieChartView, dll). - Migration guide di
packages/ui-flutter/doc/chart-migration.md.
- Update
packages/components/docs/bc-chart.md(note parity achieved) - Update
packages/ui-flutter/CONTRACT_INVENTORY.md(chart section) - Update
docs/components-progress.md(chart row → 26/26 Flutter) - Test: 26 parity tests (1 per chart type) + golden JSON comparison
File ownership (HANYA ADR-015 agent boleh modify):
packages/ui/src/components/charts/ ← ADR-015 ONLY
├── chart/
├── sparkline/
├── BcChart*.lite.tsx
├── BcChart*.rtl.test.tsx
├── BcKpi.lite.tsx
└── BcProgress.lite.tsx
packages/ui-flutter/lib/src/components/
├── bc_chart.dart ← ADR-015 ONLY (rewrite)
└── bc_chart_types.dart ← ADR-015 ONLY (NEW)
packages/ui-flutter/test/
└── bc_chart_test.dart ← ADR-015 ONLY
packages/ui-flutter/doc/
└── chart-migration.md ← ADR-015 ONLY (NEW)
packages/components/src/components/charts/ ← READ-ONLY (reference only, legacy)
File yang TIDAK boleh di-touch (milik ADR lain):
packages/ui/src/core/ ← ADR-013 territory
packages/ui/src/index.ts ← merge coordinator
packages/ui-flutter/lib/src/core/types.dart ← ADR-013 territory
packages/ui-flutter/lib/src/theme/ ← ADR-013 territory
engine/internal/presentation/ ← ADR-014 territory
Shared file (merge coordinator manages):
docs/adr/README.md ← add ADR-015 entry
docs/components-progress.md ← update chart row
packages/ui-flutter/pubspec.yaml ← add flutter_echarts dep
packages/ui-flutter/CONTRACT_INVENTORY.md ← update chart section
-
BcChartTypeDart enum: 26 variants +doughnut→piebackward-compat alias (CP-001) -
buildChartOption()(Dart) produce JSON identik denganfactory.ts(TS) — 36 golden fixtures, 0 diff (CP-002..CP-015) - Pure-function port: no Flutter/widget deps in
chart_factory.dart/chart_helpers.dart/chart_types.dart(XS-002) -
undefined-vs-absent key parity via null-key stripping (TSJSON.stringifydropsundefined; Dart omits nulls) - Parity test: 36 golden JSON, 0 diff (
test/chart_factory_test.dart) - Helpers unit test: 34 assertions, ports
helpers.test.ts(test/chart_helpers_test.dart) - Legacy
bc_chart.dart+bc_chart_test.dartcompile-clean against new enum (build stays green) -
dart analyzeclean on all F2-touched files;dart formatclean (BL-001, BL-003)
-
BcChartFlutter widget default keflutter_echarts, support 26BcChartTypevia the ported contract - Tooltip, animasi, legend interactivity, dataZoom, toolbox berfungsi (dapat gratis dari ECharts)
-
ariaa11y support via EChartsariaoption + FlutterSemanticswrapper (ariaLabelprop) - CustomPaint removed entirely (not deprecated behind a flag) — 912 lines → 288 lines, zero legacy rendering code remains
- Negative values tidak crash (verifikasi via test —
chart_factory_test.dartcandlestick/boxplot fixtures +bc_chart_test.dartWC-008 normalization) - Y-axis labels muncul (verifikasi via test —
chart_factory_test.dartgolden fixtures assertyAxis.type: 'category'and category data) - Widget contract tests: 25 tests (WC-001..WC-012) covering container, loading, empty, semantics, Echarts presence, data normalization, backward-compat (doughnut→pie, List colors)
- Regression tests: negative values (RG-001) + axis labels (RG-002) —
bc_chart_test.dart -
flutter analyzeclean on all F3-touched files (BL-001, BL-003)
- Migration guide
packages/ui-flutter/doc/chart-migration.mdlengkap -
docs/components-progress.mdupdated: chart Flutter → 26/26 ✅ -
CONTRACT_INVENTORY.mdupdated with chart contract section - No orphaned imports of old chart types (verified —
bc_chart.dartis the sole consumer) - Full test suite green (1237 pass; 7 pre-existing
bc_app_config.dartfailures from ADR-013 are unrelated) -
dart formatclean on all touched files - ADR-015 status → Accepted (all phases complete)
Ditambahkan di atas (Analysis fase sebelumnya). Ditolak karena CustomPaint sekarang belum production-grade — tetap perlu investasi besar untuk fix bug sebelum bisa dipakai production. Maintenance 2 sistem tidak worth it.
Estimasi berbulan-bulan, high maintenance. Tidak ada tooltip/animasi/zoom gratis. ECharts sudah solve semua ini.
- fl_chart: open source, tapi chart type terbatas (bar, line, pie, radar, scatter). Tidak ada heatmap, sankey, treemap, sunburst, candlestick, graph, funnel, gauge, parallel, themeriver, tree, boxplot, polar, pictorialbar. Parity 26/26 tidak tercapai.
- syncfusion_flutter_charts: commercial license, type lebih lengkap tapi tidak 26/26. Vendor lock-in. Tidak open source.
- Keduanya tidak share contract dengan web — visual & API beda dengan ECharts yang dipakai Stencil & Mitosis. Inkonsistensi cross-platform.
flutter_echarts sudah abstract ini dengan API reaktif. Raw WebView = reinvent the wheel + maintenance sendiri.
- flutter_echarts (pub.dev)
- flutter_echarts (GitHub: entronad)
- Apache ECharts
- ADR-003 §1 Chart Taxonomy (
BcChartTypeunion, 26 types) - ADR-011 Flutter Adapter Architecture (M5 data-display, CustomPaint approach — superceded by this ADR)
packages/ui/src/contracts/chart-option/factory.ts(single source of truth for ECharts option)packages/ui-flutter/lib/src/components/bc_chart.dart(CustomPaint 912 lines, to be deprecated)