Skip to content

FRD: Trend Comparison

Document Summary

FieldDetails
Feature NameTrend Comparison
StatusDraft
OwnerDavid Holmes
ContributorsDesign, Engineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #48
Last Updated2026-05-26

Introduction

Overview

Trend Comparison is an analytics widget that overlays two data series on a single chart with an integrated legend and period toggle. It enables side-by-side comparison of metrics (e.g., this month vs. last month, plan A vs. plan B) within a single, self-contained card. The widget builds on the existing line and area chart primitives in charts.tsx.

Goals

  • Provide a <TrendComparison> component that renders two series on one chart with distinct visual treatments.
  • Include a built-in legend identifying each series by name and color.
  • Include a period toggle for switching comparison windows.
  • Ship complete Storybook stories covering typical analytics comparison patterns.

Non-Goals

  • More than two series in a single comparison (deferred to a multi-series chart widget).
  • Drill-down or click-to-filter interactions on data points.
  • Statistical analysis (correlation, regression) computed within the component.
  • Server-side data fetching or query integration.

Scope

In Scope

AreaDescription
Component<TrendComparison> widget rendering two overlaid series
LegendColor-coded legend with series names, toggleable visibility
Period ToggleSegmented control for switching comparison time ranges
Visual DifferentiationPrimary series as solid line/area, comparison series as dashed line or lower-opacity area
StatesLoading skeleton, empty state, error state
StoriesStorybook stories for line overlay, area overlay, period switching, and edge cases

Out of Scope

AreaReason
Three or more seriesSeparate multi-series component; two-series keeps the UX focused
Bar chart comparisonGrouped/stacked bar is a different layout pattern
Data normalizationConsumer is responsible for aligning series to the same x-axis
Annotations or markersFuture enhancement, not part of initial comparison widget

Users and Pain Points

User Groups

UserDescriptionNeeds
DevelopersEngineers building analytics dashboardsA ready-made two-series comparison widget
DesignersDesign-system consumersConsistent dual-series visual treatment
AnalystsDashboard end usersQuick visual comparison of two time periods or segments

Pain Points

UserPain PointImpact
DevelopersBuilding dual-series overlays requires manual z-index management, duplicate chart instances, and custom legend wiringSlow development, inconsistent results
AnalystsComparing metrics requires opening two separate cards side-by-sideHarder to spot trends and correlations

Definitions

TermDefinition
SeriesA named sequence of data points rendered as a single line or area on the chart
Primary SeriesThe main metric being examined; rendered with the primary color token
Comparison SeriesThe secondary metric or time-shifted variant; rendered with a muted or dashed treatment
Period ToggleA control that switches which time window both series display

Current State

Existing Behavior

The design system provides single-series line and area charts in charts.tsx. There is no built-in support for rendering two series on a shared axis. MetricCard supports a single sparkline but not a dual-series comparison.

Current Limitations

  • Charts accept a single data: ChartDataPoint[] array with no multi-series concept.
  • No legend component exists for chart series identification.
  • No pattern for visually differentiating a primary vs. comparison series.

Existing Workarounds

  • Developers render two separate chart cards and ask users to compare visually.
  • Some teams overlay two absolute-positioned chart components, which is fragile and inaccessible.

Proposed Solution

Summary

Introduce <TrendComparison> that accepts primarySeries and comparisonSeries props, each containing a name, color token, and data array. The component renders both series on a shared axis using the existing chart rendering logic, adds a legend, and includes an optional period toggle.

Key Capabilities

  • Dual-series overlay on a single chart with shared x-axis and y-axis.
  • Built-in legend with series name, color swatch, and toggle to hide/show each series.
  • Period toggle for time range switching.
  • Visual differentiation: solid vs. dashed line, or full-opacity vs. reduced-opacity area.

User Experience

Users see a chart card with two overlaid lines or areas. A legend below the chart identifies each series. A period toggle in the header switches the time window. Hovering shows a tooltip with values from both series at the hovered x-position.

Developer Experience

<TrendComparison
title="Revenue: This Month vs. Last Month"
chartType="line"
primarySeries={{ name: "This Month", data: currentData, color: "primary" }}
comparisonSeries={{ name: "Last Month", data: previousData, color: "muted" }}
periods={["7d", "30d", "90d"]}
activePeriod="30d"
onPeriodChange={setPeriod}
/>

Requirements

IDRequirementPriorityNotes
FR-001TrendComparison renders two data series on a shared axisMust-
FR-002A legend displays series names with color indicatorsMust-
FR-003Legend items can be toggled to show/hide individual seriesShould-
FR-004Period toggle switches the displayed time rangeMust-
FR-005Primary series uses solid rendering; comparison uses dashed/mutedMust-
FR-006Loading, empty, and error states are handledMust-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001primarySeries and comparisonSeries each accept { name, data, color }Clear API for dual-series dataMust
FUNC-002chartType accepts "line" or "area" to determine renderingFlexibility for different visualization needsMust
FUNC-003Legend renders below the chart with color swatches and series namesUsers can identify which line is whichMust
FUNC-004Clicking a legend item toggles that series visibilityFocused analysis of a single seriesShould
FUNC-005Hover tooltip shows values from both series at the same x-positionEasy point-by-point comparisonShould
FUNC-006Y-axis auto-scales to the visible range of both seriesAccurate visual comparisonMust
FUNC-007Empty state shows when both series have no dataClear feedbackMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Renders in under 50ms for two series of 365 points eachPerformanceMust
NFR-002All interactive elements (legend, toggle, tooltip) are keyboard-navigableAccessibilityMust
NFR-003Works in light and dark themesThemingMust
NFR-004No new runtime dependenciesMaintainabilityMust
NFR-005Visual differentiation between series does not rely solely on colorAccessibilityMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
titlestringCard headingYes
chartType"line" | "area"Visualization typeYes
primarySeries{ name: string; data: ChartDataPoint[]; color?: string }Main data seriesYes
comparisonSeries{ name: string; data: ChartDataPoint[]; color?: string }Comparison data seriesYes
periodsstring[]Available time period optionsNo
activePeriodstringCurrently selected periodNo
onPeriodChange(period: string) => voidPeriod change callbackNo
isLoadingbooleanShow loading skeletonNo
errorstringError messageNo
heightnumberChart height in pixelsNo
classNamestringAdditional CSS classesNo

Example Usage

import { TrendComparison } from "@/components/ui/trend-comparison";
<TrendComparison
title="Signups: This Week vs. Last Week"
chartType="area"
primarySeries={{
name: "This Week",
data: [{ label: "Mon", value: 120 }, { label: "Tue", value: 145 }],
}}
comparisonSeries={{
name: "Last Week",
data: [{ label: "Mon", value: 98 }, { label: "Tue", value: 110 }],
}}
/>

API Notes

  • Both series must share the same label values for x-axis alignment.
  • color defaults to "primary" for primary and "muted-foreground" for comparison.
  • Comparison series renders dashed for line type and reduced opacity for area type.

Accessibility Requirements

IDRequirementNotes
A11Y-001Chart region has role="img" with a label describing both series-
A11Y-002Legend items are focusable and togglable via keyboardEnter/Space to toggle
A11Y-003Series differentiation uses both color and line style (solid vs. dashed)Not color-only
A11Y-004Tooltip content is accessible to screen readersaria-live region for hovered values
A11Y-005Loading state sets aria-busy="true"-

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page with overview and props tableStorybookMust
DOC-002”When to use / When not to use” guidanceStorybook docsMust
DOC-003Stories for line and area comparisons with realistic analytics dataStorybookMust
DOC-004Story demonstrating legend toggle interactionStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
charts.tsx (LineChart, AreaChart)EngineeringDesign SystemReadyBase chart rendering
SegmentedControlEngineeringDesign SystemReadyPeriod toggle
Design tokensDesignDesign SystemReadyColor, spacing, typography

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Two series with very different scales can mislead usersVisual comparison may be inaccurateDocument that series should share the same unit; consider dual y-axis as future enhancement
Dashed-line rendering in CSS may have browser inconsistenciesVisual glitches in some browsersTest across Chrome, Firefox, Safari; use stroke-dasharray on SVG or border-style fallback
Limiting to two series restricts power-user analyticsUsers who need 3+ series cannot use this widgetExplicitly designed for two-series; multi-series is a separate roadmap item

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should dual y-axis be supported for series with different units?David HolmesOpen
Q-002Should the tooltip show the delta between the two series at each point?David HolmesOpen
Q-003Should the comparison series support a time-offset mode (auto-shift labels)?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001Two series render on a shared chart with distinct visual treatmentsFR-001, FR-005
AC-002Legend displays both series names with color indicatorsFR-002
AC-003Clicking a legend item hides/shows the corresponding seriesFR-003
AC-004Period toggle fires onPeriodChange and updates displayFR-004
AC-005Loading skeleton, empty state, and error state render correctlyFR-006
AC-006Component passes axe accessibility auditNFR-002
AC-007All Storybook stories render without errorsDOC-003

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Build on existing chart rendering logic from charts.tsx; extend rather than replace.
  • Use solid/dashed differentiation for line charts and opacity differentiation for area charts.
  • Place stories under the SaaS Widgets Storybook section.
  • Add tests mapping to acceptance criteria.

LLM Should Not

  • Add a third-party charting library.
  • Support more than two series.
  • Modify existing single-series chart components.
  • Invent analytics features not described here.

Decision Log

DateDecisionReasonOwner
2026-05-26Limit to exactly two seriesKeeps UX focused and avoids multi-series complexityDavid Holmes
2026-05-26Use line-style differentiation (solid vs. dashed) rather than color-onlyAccessibility: ensures series are distinguishable without color visionDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft