Skip to content

[Feature]: Add Line/Area chart component #145

Description

@goodbounties-nanoclaw-agent

Feature summary

Add a LineAreaChart component to packages/ui for time-series and continuous data —
communicates trends, velocity, and cumulative patterns. Area mode (shaded fill below the
line) emphasizes volume/accumulation. The most complex of the 4 remaining analytics chart
components completing GoodWidget's analytics visualization layer (Scorecard already
shipped in PR #142).

Problem or opportunity

GoodWidget has no reusable line/area chart primitive for showing trends over time (daily
claims, member growth, reserve balance history) or rate of change. This is the component
most widgets will reach for to answer "is it trending up or down," and currently requires
a one-off implementation each time.

Proposed solution

Follow Scorecard.tsx's patterns (createComponent, useTheme, golden-ratio spacing,
formatMetricValue).

Props / API surface (MVP)

Prop Type Required Default Description
data Array<{ x: string | number; y: number; series?: string }> Yes - Data points
title string No undefined Chart heading
series Array<{ key, label, color?, strokeDasharray? }> No auto-detect Series definitions
type 'linear' | 'monotone' | 'step' No 'linear' Interpolation curve
showArea boolean No false Fill area below line
areaOpacity number No 0.15 Area fill transparency
showDots boolean | 'auto' No 'auto' Point markers (auto = show if <20 points)
showGrid boolean No true Show grid lines
connectNulls boolean No false Bridge gaps in data (false = visible gap)
strokeWidth number No 2 Line thickness
xAxisLabel / yAxisLabel string No undefined Axis titles
xAxisFormatter / yAxisFormatter function No identity / formatMetricValue Axis label formatting
yAxisDomain [number | 'auto', number | 'auto'] No ['auto','auto'] Y-axis range
secondaryYAxis { key, label?, formatter? } No undefined Secondary right-side y-axis for one series
referenceLines Array<{ value, label?, color? }> No [] Horizontal marker lines
onPointPress (point, seriesKey) => void No undefined Point tap callback
variant 'bare' | 'card' No 'bare' Visual variant
testID string No undefined Testing identifier
accessibilityLabel string No auto-generated Screen reader description
width number | string No '100%' Chart width
height number No 200 Chart height
padding { top, right, bottom, left } No {16,16,40,48} Internal padding

Behavioral rules

  1. X-values map to evenly-spaced positions; y-values map linearly (inverted: higher value = lower SVG y).
  2. Path generation: linear = straight segments; monotone = monotone cubic Hermite spline (no overshoot); step = hold-until-next-point.
  3. Area fill: duplicate line path extended to bottom, filled with a vertical LinearGradient (Defs+LinearGradient+Stop) — series color at 0.3 opacity near the line fading to 0.05 near the baseline.
  4. Multi-series: each series is a separate Path with its own color, layered in array order.
  5. Null y-values: connectNulls=false (default) breaks the path with a visible gap; true skips nulls and connects adjacent points.
  6. Dots: auto shows only if fewer than 20 data points; r=3, filled with series color.
  7. Axis: nice-number ticks (same algorithm as Bar); y extends 10% beyond data range; x labels thin adaptively to avoid overlap.
  8. Grid: horizontal only, subtle dashed (same treatment as Bar).
  9. Reference lines: horizontal at given y-value, optional right-aligned label, 1px solid, $colorDim or custom color.
  10. Secondary y-axis: the specified series maps to a right-side axis with its own scale/domain/formatter, labeled in that series' color; all other series use the left axis. (Dual axes can mislead readers — flagged as a caveat, not a bug, in the acceptance criteria.)
  11. Empty state: axes only + "No data" centered. Single data point: render only a dot.

Shared constraints (apply to all 4 remaining chart components — see Pie/Donut issue #143 for full text): react-native-svg only, Tamagui + useTheme() for layout/color, createComponent(), Scorecard spacing/type-scale constants, no animation, no new dependencies, Nivo-quality visual target, full accessibility baseline (accessibilityRole="image", testID/data-testid).

Visual quality target: Nivo Line and Nivo stacked Area.

Acceptance criteria

  • Renders line connecting data points proportionally
  • Y-axis nice-number ticks with auto domain
  • X-axis labels display without overlapping (adaptive thinning)
  • Grid lines render correctly
  • Area fill below line at correct gradient/opacity
  • Multi-series with distinct colors and legend
  • Null y-values create a visible gap (connectNulls=false)
  • Null y-values bridge (connectNulls=true)
  • Dots show at auto threshold (< 20 points)
  • Reference line renders at correct position with label
  • Step interpolation produces a staircase
  • Monotone interpolation produces a smooth curve
  • Secondary y-axis renders on the right with correct scale
  • Empty state: axes + "No data"
  • Single point: dot only
  • 1000-point stress test: renders without crash or hang
  • variant="card" works
  • Accessibility attributes present
  • No hardcoded colors

Additional context

DO NOT:

  • Use 'natural' or 'basis' interpolation (overshoot, implies non-existent values)
  • Add entrance animations on path drawing
  • Use web-only APIs (no requestAnimationFrame, no CSS transitions)
  • Modify Card.ts, Text.ts, Icon.tsx, theme.ts, presets.ts, config.ts, or governance-widget

Scope boundary — only create/modify:

  1. packages/ui/src/components/LineAreaChart.tsx
  2. packages/ui/src/index.ts (add export under // Analytics)
  3. examples/storybook/src/stories/design-system/LineAreaChart.stories.tsx
  4. tests/design-system/smoke.spec.ts (add cases)

Mock data: 14-day daily-claims series (single series + area + reference line),
multi-series with secondary y-axis (claims vs. G$ price), a series with a gap
(connectNulls demo), empty, and a 1095-point (3-year daily) stress test — full snippets
in the spec (link below).

References:

Create the plan

Based on the above description create an execution plan in a new sub-issue and preserve the original issue content unchanged.
Do not execute the plan until given instructions to do so.

Sub-issue requirements:

  • Title format: [DRAFT][PLAN] <what issue is being planned>
  • Type: Task
  • Description must start with: <sub-issue title>
  • Link the sub-issue back to this parent issue.

Instructions when creating the plan:

  • Map relevant files that are too be used as reference from all repos mentioned
  • import existing @GoodDollar packages
  • Map new components that should be created. Assess when a new component should be created in the new savings widget package or made part of the reusable packages/ui

Plan specification required sections:

  • Required states, flows, and behaviors
  • Execution plan
  • acceptance criteria
  • human-reviewer checklist

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Prepare AI Task

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions