Skip to content

[DRAFT][PLAN] Add Pie/Donut, Bar, Line/Area, and Data Table chart components to packages/ui #147

Description

@goodbounties-nanoclaw-agent

Type: Task
Parent issues: #143 (primary), #144, #145, #146
Sub-issue link: to be created as a sub-issue of #143, cross-referencing #144/#145/#146, once posted


[DRAFT][PLAN] Add Pie/Donut, Bar, Line/Area, and Data Table chart components to packages/ui

This plans the implementation of the 4 remaining analytics chart components described in
#143#146: PieDonutChart, BarChart, LineAreaChart, and DataTable, all for
packages/ui. Combined into one plan per Thales's request — they share one PR (#142),
one file-touch footprint (packages/ui/src/index.ts, tests/design-system/smoke.spec.ts),
and one build order. Scorecard (the first of these 5 components) already shipped in
PR #142 per its own plan, #141.

Reference files mapped

GoodDollar/GoodWidget (this repo):

  • packages/ui/src/components/Scorecard.tsx — the pattern all 4 components must follow:
    createComponent-based internal Frame/Text sub-pieces, useTheme() for every color,
    a private resolveThemeColor(theme, token) helper with a $color fallback, and the
    SCORECARD_BASE_SIZE_PX = 24 / GOLDEN_RATIO = 1.618 constants driving all spacing
    and type-scale math. Confirmed live on feat/analytics-components (renamed branch).
  • packages/governance-widget/src/FundingDistributionChart.tsx — direct precedent for
    PieDonutChart's SVG arc technique (Circle + strokeDasharray/strokeDashoffset,
    G rotation="-90" origin trick, CHART_COLOR_KEYS-style palette). Extract the
    technique; do not import from governance-widget or modify this file (per DO-NOT).
  • packages/ui/src/utils/formatMetricValue.ts — shared number formatter (compact
    K/M/B/T, decimal, none), already exported from packages/ui. All 4 new components
    use this directly for axis/value/cell formatting — no new formatter needed.
  • packages/ui/src/createComponent.ts — styled-component wrapper + theme-manifest
    auto-registration; every internal styled sub-piece in all 4 components uses this.
  • packages/ui/src/components/Card.tsvariant="card" wraps each component's content
    in this existing primitive (same approach as Scorecard), never modified directly.
  • packages/ui/src/theme.ts / presets.ts — confirmed $primary, $success,
    $warning, $colorDim/$placeholderColor, $error, $borderColor all resolve
    under the default preset for the CHART_COLOR_KEYS palette
    (['primary','success','warning','colorDim','error']).
  • packages/ui/src/components/Icon.tsxdo not use. Same web-only DOM-SVG gap
    flagged in [DRAFT][PLAN] Add Scorecard (KPI card) chart component to packages/ui #141; DataTable's sort arrows must be unicode glyphs (per its own
    DO-NOT), and no other component needs iconography.
  • examples/storybook/src/stories/design-system/Scorecard.stories.tsx and
    TokenAmount.stories.tsx — story pattern to follow (title: 'Design System/ Primitives/<Component>', decorators: [withDefaultPreset], tags: ['autodocs', 'showcase'], multi-instance Default story covering all mock-data rows/states).
  • tests/design-system/smoke.spec.ts — Playwright convention for packages/ui
    primitives (gotoStory/getStoryFrame/screenshotStory helpers); this is the
    correct convention here, not tests/widgets/<name>/states.spec.ts (that's for widget
    packages, per standing repo convention).
  • packages/ai-credits-widget/src/CreditsManagementCard.tsxStatCell/stat-grid
    aesthetic reference cited for DataTable, per [Feature]: Add Data Table component #146.

External reference: Nivo Pie/Bar/Line/stacked-Area demos (nivo.rocks) — visual
quality bar for all 4, per each issue and Thales's Notion mockups.

Source of truth: full spec at GoodDollar/data-team (branch
feat/pipeline-v5-hardening) projects/goodwidget-components/spec.md — already inlined
per-component into issues #143#146; re-verified live for this plan (confirmed props,
behavioral rules, scope boundaries, and DO-NOT lists match between spec and issues).

Existing @GoodDollar package imports

  • @goodwidget/uiStack/YStack/XStack/Text/Heading/ScrollView,
    createComponent, formatMetricValue (all 4 components); Card (for variant="card").
  • tamaguiuseTheme().
  • react-native-svgSvg, Path, Circle, Rect, Line, G, Defs,
    LinearGradient, Stop (Pie/Donut, Bar, Line/Area only — DataTable is pure Tamagui
    layout, no SVG).

New components — placement assessment

Component Location Reasoning
PieDonutChart packages/ui/src/components/PieDonutChart.tsx (new) Reusable "parts of a whole" primitive any widget can compose; matches issue's explicit instruction and the FundingDistributionChart/Scorecard precedent of one self-contained file per primitive.
BarChart packages/ui/src/components/BarChart.tsx (new) Same reasoning — categorical comparison is a cross-widget need, not specific to any one widget package.
LineAreaChart packages/ui/src/components/LineAreaChart.tsx (new) Same reasoning — trend/time-series display, the most broadly reusable of the 4.
DataTable packages/ui/src/components/DataTable.tsx (new) Same reasoning — exact-value lookup complement to the 3 chart types; pure Tamagui layout keeps it consistent with the others despite no SVG use.
resolveThemeColor helper New: packages/ui/src/utils/resolveThemeColor.ts, exported from packages/ui Spec's Appendix explicitly leaves this as an open call ("duplicate into each chart component or extract to packages/ui/src/utils/"). Scorecard.tsx already has a private copy; duplicating it 4 more times (5 total) crosses from "acceptable repetition" into a real shared utility — same justification formatMetricValue used when extracted for Scorecard's 2nd-consumer case, and this is now a 5th+ consumer. Proposing extraction; not touching Scorecard.tsx to point at it — that file already shipped under a prior issue and is out of this task's scope boundary. Flagged below as a human-reviewer call, not made silently.

Required states, flows, and behaviors

Shared across all 4: empty state (no data — themed placeholder, never a crash), a
single-item/point state, a stress-test state at each component's documented volume
ceiling (120 items / 150 categories / 1000+ points / 150 rows) that degrades gracefully
(clip/truncate/aggregate) rather than crashing, bare vs card variant, full
testID/data-testid/accessibilityRole="image" (SVG components) baseline, zero
hardcoded colors, no animation, no new dependencies.

PieDonutChart (#143): pie (innerRadius=0) vs donut (innerRadius>0, with optional
center label/value/sublabel) modes; descending-sort-by-default arcs starting at 12
o'clock; maxSlices aggregation into "Other"; percentage formatting (int vs 1-decimal);
legend with swatch+label+percentage; onSegmentPress; NaN/null/negative values silently
excluded from render and total.

BarChart (#144): vertical vs horizontal layout (axes swap); zero-inclusive y-axis
with nice-number ticks (~5); barWidth/gap formula; single-series only ($primary);
optional value labels (hidden below 20px bar height); dashed grid lines; label
truncation/right-alignment; onBarPress; NaN/null excluded silently.

LineAreaChart (#145): linear/monotone/step interpolation; optional area fill
via vertical LinearGradient; multi-series (each own Path+color); connectNulls
true/false gap behavior; auto dot visibility (<20 points); nice-number y-ticks +
adaptive x-label thinning; reference lines; optional secondary y-axis (flagged as a
readability caveat in acceptance criteria, not a bug); onPointPress.

DataTable (#146): typed columns (text/number/date/currency) with per-type
default formatters; center-default alignment; client-side sort (asc → desc → clear,
unicode arrow, defaultSort support); striped rows; sticky header; horizontal
ScrollView overflow (no FlatList/pagination); null cells render "--"; compact mode;
onRowPress.

Execution plan

Build order per spec/issues: Pie/Donut → Bar → Line/Area → Table. All work targets
the already-renamed feat/analytics-components branch (PR #142).

  1. Shared prep: extract resolveThemeColor to packages/ui/src/utils/resolveThemeColor.ts
    (theme token → resolved color string, $color fallback + console warning, mirroring
    Scorecard.tsx's private version) and export it from packages/ui's index. One-time
    step before component 1, since Pie/Donut needs it first.
  2. PieDonutChart.tsx: arc rendering (Circle segments, G rotation="-90"), sort +
    maxSlices/"Other" aggregation, percentage calc/formatting, center content (donut
    mode), legend, empty/stress states, onSegmentPress.
  3. BarChart.tsx: axis/scale math (zero-inclusive domain, nice-number ticks,
    bar/gap width formula), vertical + horizontal layout, value labels, grid, empty/stress
    states, onBarPress.
  4. LineAreaChart.tsx: point-to-SVG-coordinate mapping, 3 interpolation modes, area
    gradient fill, multi-series + connectNulls, dots, reference lines, secondary y-axis,
    empty/single-point/stress states, onPointPress.
  5. DataTable.tsx: column-driven row rendering, per-type formatters, client-side sort
    state + cycle, striped/sticky/compact/scroll behaviors, empty state, onRowPress.
  6. Exports: each component + its prop types added to packages/ui/src/index.ts
    under the existing // Analytics section (alongside Scorecard), in build order.
  7. Storybook stories: one .stories.tsx per component under
    examples/storybook/src/stories/design-system/, each with a Default story covering
    every required state (bare, card, empty, single-item, stress) using the mock datasets
    from spec.md/issues, plus a Controllable story where relevant.
  8. Playwright smoke tests: one test case per component added to
    tests/design-system/smoke.spec.ts (gotoStory/screenshotStory pattern);
    commit baseline PNGs to tests/design-system/test-results/story-<component>-default.png.
  9. Manual QA per component, immediately after each is built (not batched at the
    end): build/lint/typecheck scoped to changed files; visual check in Storybook against
    the Nivo target and Thales's mockups, both light and dark theme.
  10. Push incrementally to feat/analytics-components; do not wait on Laurence's review
    at this stage (confirmed by Thales — full 5-component review happens once all 4 land).
  11. PR description updated to list all 4 new components and note the resolveThemeColor
    extraction decision (so reviewers see it called out, not discovered in the diff).

Acceptance criteria

  • All 4 components render correctly per their issue's full acceptance-criteria list
    ([Feature]: Add Pie/Donut chart component #143, [Feature]: Add Bar chart component #144, [Feature]: Add Line/Area chart component #145, [Feature]: Add Data Table component #146) — combined here at the category level:
  • PieDonutChart: pie/donut modes, sort+maxSlices aggregation, percentage formatting,
    legend, empty state, 120-item stress test, onSegmentPress
  • BarChart: vertical/horizontal layout, zero-inclusive nice-number axis, value
    labels, empty state, 150-category stress test, onBarPress
  • LineAreaChart: all 3 interpolation modes, area gradient, multi-series +
    connectNulls, reference lines, secondary y-axis, empty/single-point states,
    1000+-point stress test, onPointPress
  • DataTable: typed column formatting, sort cycle, striped/sticky/compact/scroll
    behaviors, empty state, 150-row stress test, onRowPress
  • All 4 use react-native-svg only where SVG is needed (Table: none), zero
    hardcoded colors, no animation, no new dependencies
  • All 4 follow Scorecard.tsx's spacing/type-scale constants — no separate spacing
    system invented
  • resolveThemeColor extracted to packages/ui/src/utils/ and used consistently by
    all 4 new components
  • All 4 exported from packages/ui/src/index.ts under // Analytics
  • Storybook stories exist for all 4 (Design System/Primitives/<Component>),
    covering every required state
  • tests/design-system/smoke.spec.ts has a passing case per component, each with a
    committed baseline screenshot
  • None of Card.ts, Text.ts, Icon.tsx, theme.ts, presets.ts, config.ts,
    or any governance-widget file modified
  • Screenshots of bare + card variants for all 4 components in dark theme provided
    (per spec.md Appendix) alongside light-theme equivalents

Human-reviewer checklist

  • Verify the resolveThemeColor extraction is a good call — and whether
    Scorecard.tsx's existing private copy should be migrated to it in a follow-up
    (explicitly out of scope here; flagging so it isn't lost)
  • Verify all 4 components' visual output actually reaches the Nivo-quality bar in
    both light and dark theme, not just "renders without crashing"
  • Verify each stress test (120/150/1000+/150) degrades gracefully rather than
    merely "not crashing" — check legend overflow, sub-pixel bar clipping, and table
    scroll performance specifically
  • Verify LineAreaChart's secondary-y-axis feature doesn't get misused in the
    Storybook mock data in a way that reads as misleading (spec flags dual axes as a
    reader-trust caveat, not a bug)
  • Confirm build order was respected and each component was QA'd before moving to
    the next, not batched at the end
  • Verify no hardcoded colors slipped into any of the 4 diffs
  • Verify Playwright baseline screenshots match the attached design references
    closely enough for visual sign-off
  • Confirm PR description clearly documents the resolveThemeColor extraction
    decision for reviewer visibility

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status
    Prepare AI Task

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions