Skip to content

Latest commit

 

History

History
328 lines (241 loc) · 17 KB

015-flutter-echarts-chart-strategy.md

File metadata and controls

328 lines (241 loc) · 17 KB

ADR-015: Flutter Chart Strategy — flutter_echarts & Shared Chart Option Contract

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)

Context

Problem

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:

Audit CustomPaint (current state)

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 ⚠️ Partial _defaultPalette hardcode BcTheme.*, tidak adaptif dark/light change
shouldRepaint ⚠️ Inefficient Compare List by reference → repaint setiap rebuild meski data sama
Stacked math ⚠️ Redundant maxVal dihitung 2x (lines 441-446 & 448-456), logic pertama tidak berguna
Accessibility ⚠️ Minimal 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

Gap yang harus ditutup

Untuk mencapai parity 26/26 dan production-grade, ada 2 jalan:

  1. 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.

  2. Switch to flutter_echarts — bungkus Apache ECharts di WebView Flutter. Langsung dapat 26 type + tooltip + animasi + zoom + toolbox + themes + a11y. Trade-off: WebView overhead.

Deployment context

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.


Decision

1. Flutter chart strategy: Full flutter_echarts, deprecate CustomPaint

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_echarts langsung dapat 26 type + fitur lengkap ECharts (tooltip, animasi, zoom, toolbox, themes, aria a11y)
  • 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

2. Chart Option Contract: single source of truth

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.

3. Scope: 26/26 parity

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).


Consequences

Positive

  • ✅ 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

Negative

  • ❌ 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_echarts maintenance 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).

Mitigations

  • 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 load flutter_echarts.
  • flutter_echarts risk: pin version, fork-able jika upstream abandoned. Alternative: echart_flutter (enechain) sebagai backup.
  • Debugging: expose debugOption prop yang log JSON option ke console.

Implementation

Phase 0: Contract extraction (P0, blocks Phase 1)

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 ONLY
  • packages/ui-flutter/lib/src/components/bc_chart.dart → ADR-015 agent ONLY
  • packages/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-value BcChartType in core/types.dart is replaced by a re-export of the new 26-value enum (CP-001); core/types.dart remains ADR-013 territory but the chart-enum migration is owned by ADR-015.

Phase 1: Flutter flutter_echarts wrapper

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.

Phase 2: Migration & deprecation

Goal: Smooth migration dari CustomPaint ke flutter_echarts.

  • v1.0.0 (this ADR): BcChart default ke flutter_echarts. Tambah flag useLegacyRenderer: false (default false) untuk akses CustomPaint lama selama 1 release.
  • v1.1.0: Remove useLegacyRenderer flag + hapus 912 lines CustomPaint (_BarChartPainter, _LineChartPainter, _PieChartView, dll).
  • Migration guide di packages/ui-flutter/doc/chart-migration.md.

Phase 3: Docs & tests

  • 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

Scope Boundary untuk Paralelisasi

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

Acceptance Criteria

F2 — Contract port (DONE ✅)

  • BcChartType Dart enum: 26 variants + doughnut→pie backward-compat alias (CP-001)
  • buildChartOption() (Dart) produce JSON identik dengan factory.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 (TS JSON.stringify drops undefined; 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.dart compile-clean against new enum (build stays green)
  • dart analyze clean on all F2-touched files; dart format clean (BL-001, BL-003)

Phase 2: Migration & deprecation (F3 — DONE ✅)

  • BcChart Flutter widget default ke flutter_echarts, support 26 BcChartType via the ported contract
  • Tooltip, animasi, legend interactivity, dataZoom, toolbox berfungsi (dapat gratis dari ECharts)
  • aria a11y support via ECharts aria option + Flutter Semantics wrapper (ariaLabel prop)
  • 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.dart candlestick/boxplot fixtures + bc_chart_test.dart WC-008 normalization)
  • Y-axis labels muncul (verifikasi via test — chart_factory_test.dart golden fixtures assert yAxis.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 analyze clean on all F3-touched files (BL-001, BL-003)

F4 — Migration cleanup & docs (DONE ✅)

  • Migration guide packages/ui-flutter/doc/chart-migration.md lengkap
  • docs/components-progress.md updated: chart Flutter → 26/26 ✅
  • CONTRACT_INVENTORY.md updated with chart contract section
  • No orphaned imports of old chart types (verified — bc_chart.dart is the sole consumer)
  • Full test suite green (1237 pass; 7 pre-existing bc_app_config.dart failures from ADR-013 are unrelated)
  • dart format clean on all touched files
  • ADR-015 status → Accepted (all phases complete)

Alternatives Considered

A. Hybrid: CustomPaint (4 dasar) + flutter_echarts (22 lainnya) — REJECTED

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.

B. Full CustomPaint untuk semua 26 type — REJECTED

Estimasi berbulan-bulan, high maintenance. Tidak ada tooltip/animasi/zoom gratis. ECharts sudah solve semua ini.

C. Pakai chart library Flutter native lain (fl_chart, syncfusion_flutter_charts) — REJECTED

  • 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.

D. Web-based chart via WebView tanpa flutter_echarts (raw WebView + ECharts CDN) — REJECTED

flutter_echarts sudah abstract ini dengan API reaktif. Raw WebView = reinvent the wheel + maintenance sendiri.


References

  • flutter_echarts (pub.dev)
  • flutter_echarts (GitHub: entronad)
  • Apache ECharts
  • ADR-003 §1 Chart Taxonomy (BcChartType union, 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)