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
- X-values map to evenly-spaced positions; y-values map linearly (inverted: higher value = lower SVG y).
- Path generation:
linear = straight segments; monotone = monotone cubic Hermite spline (no overshoot); step = hold-until-next-point.
- 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.
- Multi-series: each series is a separate
Path with its own color, layered in array order.
- Null y-values:
connectNulls=false (default) breaks the path with a visible gap; true skips nulls and connects adjacent points.
- Dots:
auto shows only if fewer than 20 data points; r=3, filled with series color.
- Axis: nice-number ticks (same algorithm as Bar); y extends 10% beyond data range; x labels thin adaptively to avoid overlap.
- Grid: horizontal only, subtle dashed (same treatment as Bar).
- Reference lines: horizontal at given y-value, optional right-aligned label, 1px solid,
$colorDim or custom color.
- 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.)
- 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
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:
packages/ui/src/components/LineAreaChart.tsx
packages/ui/src/index.ts (add export under // Analytics)
examples/storybook/src/stories/design-system/LineAreaChart.stories.tsx
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
Feature summary
Add a
LineAreaChartcomponent topackages/uifor 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)
Array<{ x: string | number; y: number; series?: string }>Array<{ key, label, color?, strokeDasharray? }>[number | 'auto', number | 'auto']{ key, label?, formatter? }Array<{ value, label?, color? }>(point, seriesKey) => void{ top, right, bottom, left }{16,16,40,48}Behavioral rules
linear= straight segments;monotone= monotone cubic Hermite spline (no overshoot);step= hold-until-next-point.Defs+LinearGradient+Stop) — series color at 0.3 opacity near the line fading to 0.05 near the baseline.Pathwith its own color, layered in array order.connectNulls=false(default) breaks the path with a visible gap;trueskips nulls and connects adjacent points.autoshows only if fewer than 20 data points;r=3, filled with series color.$colorDimor custom color.Shared constraints (apply to all 4 remaining chart components — see Pie/Donut issue #143 for full text):
react-native-svgonly, 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
connectNulls=false)connectNulls=true)variant="card"worksAdditional context
DO NOT:
'natural'or'basis'interpolation (overshoot, implies non-existent values)requestAnimationFrame, no CSS transitions)Card.ts,Text.ts,Icon.tsx,theme.ts,presets.ts,config.ts, orgovernance-widgetScope boundary — only create/modify:
packages/ui/src/components/LineAreaChart.tsxpackages/ui/src/index.ts(add export under// Analytics)examples/storybook/src/stories/design-system/LineAreaChart.stories.tsxtests/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
(
connectNullsdemo), empty, and a 1095-point (3-year daily) stress test — full snippetsin the spec (link below).
References:
feat/analytics-components)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:
[DRAFT][PLAN] <what issue is being planned>Task<sub-issue title>Instructions when creating the plan:
Plan specification required sections: