013 / RECHARTS / AREA

Percent Stream Area

Recharts

100% normalized stacked area chart for tracking how each contributor's share of the visible whole evolves over an ordered domain.

SPEC
#013
ENGINE
Recharts
FAMILY
Area
RENDERER
svg
STATUS
preview

Installation

PLOTCN/REGISTRY/AREA-PERCENT-STREAM/SOURCE
pnpm dlx shadcn@latest add @plotcn/area-percent-stream

Checking public registry…

View local registry JSON
REGISTRY direct URL·ENGINE Recharts·FILES 1·DEPENDENCIES 5

Copied as source into your project (requires recharts).

× 340Container width
Measuring preview...
RECHARTS · SVG · 0 × 340pxMotion enabled · ResizeObserver

Overview

Percent Stream Area is the composition-specialized chart of the Plotcn Area family. It normalizes multiple additive series so that their combined visual height strictly equals 100% of the visible whole at every observation point across an ordered domain.

The primary analytical question answered by Percent Stream Area is:

"How is the composition of the whole changing over time?"

The secondary analytical question is:

"What underlying raw values produced those proportional shares?"

Unlike general stacked area charts that communicate changes in absolute scale, Percent Stream Area deliberately removes total-size information from its geometry. A period with 1,000 total requests and a period with 10,000 total requests produce visually identical geometry if their constituent platform shares are both 50% / 30% / 20%.

TSX
import { PercentStreamArea } from "@/components/charts/recharts/area-percent-stream"const trafficData = [  { month: "Jan", web: 500, ios: 300, android: 200 },  { month: "Feb", web: 5000, ios: 3000, android: 2000 },  { month: "Mar", web: 4800, ios: 3200, android: 2000 },  { month: "Apr", web: 4000, ios: 3600, android: 2400 },  { month: "May", web: 3600, ios: 3700, android: 2700 },  { month: "Jun", web: 3300, ios: 3900, android: 2800 },]export function MonthlyTrafficMix() {  return (    <PercentStreamArea      data={trafficData}      xKey="month"      series={[        { key: "web", label: "Web" },        { key: "ios", label: "iOS" },        { key: "android", label: "Android" },      ]}      showLegend      interactiveLegend      lockableTooltip      valueFormatter={(v) => `${v.toLocaleString()} req/s`}      showGrid    />  )}

Area-Family Positioning

The Plotcn Area family provides three distinct analytical instruments:

Consideration Prism Area (011) Stack Flow Area (012) Percent Stream Area (013)
ConsiderationPrism Area (011)Stack Flow Area (012)Percent Stream Area (013)
Primary Question"How much magnitude exists relative to baseline?""How do parts and total magnitude change?""How does the composition of the whole evolve?"
Input StructureSingle series (series={...})Multiple additive series (series={[...]})Multiple additive series (series={[...]})
Scale DomainAbsolute values (0Max0 \rightarrow \text{Max})Absolute stack sum (0ΣMax0 \rightarrow \Sigma \text{Max})Strictly normalized (0%100%0\% \rightarrow 100\%)
Baseline RuleConfigurable ("zero", "domain-min")Fixed at zero baseline (y=0y = 0)Fixed at zero percentage (0%0\%)
Total MagnitudeExplicitly visible in heightExplicitly visible in top silhouetteDeliberately removed from geometry
Unit ConstraintOne quantitative unitShared additive unitShared additive unit before normalization

Composition Model & Mathematical Normalization

100% NORMALIZED COMPOSITIONShare-of-Total Model

Geometry Represents Relative Shares; Total Magnitude Is Deliberately Removed

Web
iOS
Android
100% Ceiling
100%75%50%25%0%Jan: Total = 1,000Feb: Total = 10,000 (10×)Identical 50% / 30% / 20% MixAndroid: 24.0%iOS: 36.0%Web: 40.0%JanFebMarAprMayJun
Analytical Invariant: Percent Stream deliberately removes total magnitude to focus on share evolution.
Y domain is strictly fixed to 0%–100% without arbitrary truncation

1. Governing Mathematical Model

For all valid visible series at observation xx:

Total(x)=sVisibleRawValue(s,x)\text{Total}(x) = \sum_{s \in \text{Visible}} \text{RawValue}(s, x)

For each individual series ss:

Share(s,x)=RawValue(s,x)Total(x)×100%\text{Share}(s, x) = \frac{\text{RawValue}(s, x)}{\text{Total}(x)} \times 100\%

2. Normalization Is Derived Data

Plotcn never mutates caller data. Your original records (e.g. { month: "Jan", web: 500, ios: 300, android: 200 }) remain completely untouched. Normalization occurs in a pure derived step, converting raw counts into internal percentage shares (01000 \rightarrow 100) for SVG rendering while retaining original quantities for tooltips and screen readers.

3. High Floating-Point Precision

Internal shares retain complete 64-bit floating-point precision throughout path generation. Even if display rounding formats individual rows as 33.3% + 33.3% + 33.3% = 99.9%, the underlying stacked SVG geometry terminates precisely at the 100% boundary.

Missing & Zero-Total Semantics

In 100% normalized composition, Plotcn enforces a strict distinction across three analytical states:

Scenario Raw Observations Resolved State Tooltip Readout Geometry Behavior
ScenarioRaw ObservationsResolved StateTooltip ReadoutGeometry Behavior
Known Zeroweb: 80, ios: 20, android: 0valid0.0% (raw 0)Valid 0-height layer; remaining layers stack to 100%
Missing Contributorweb: 80, ios: null, android: 20incomplete— (Composition Incomplete)Honest break (gap); no deceptive subtotal normalization
Zero Totalweb: 0, ios: 0, android: 0zero-totalshare unavailable (raw 0)Zero stacked height; no divide-by-zero, NaN%, or Infinity%

1. Known Zero Is a Valid Measurement (v=0v = 0)

A contributor recording 0 is not missing. It represents a truthful, known contribution of zero. The series contributes 0 height, and the remaining series normalize accurately.

2. Missing Is Not Zero (null ≠ 0)

Under the default missingValuePolicy="gap", an absent contributor (null or undefined) is unknown. Plotcn never normalizes known partial subtotals to 100% (e.g., calculating 500/700=71.4%500 / 700 = 71.4\% when iOS is missing), because doing so would fabricate a false total and misrepresent market share. Instead, the geometry breaks cleanly, and the tooltip reports Composition Incomplete.

3. Zero Total Is Mathematically Undefined (0/00 / 0)

When all visible contributors record zero, calculating percentage shares would divide by zero. Plotcn handles this gracefully: zero total is classified explicitly as "zero-total", tooltips report raw zeros with share unavailable, and screen readers announce that percentages are undefined.

Legend & Visible-Series Re-Normalization

VISIBLE-SERIES RE-NORMALIZATIONInteractive Legend Semantics

Toggling a Series Changes the Normalization Basis (Visible Total = 100%)

All 3 Series VisibleBaseline Basis

Denominator = 50 + 30 + 20 = 100. Each raw contribution maps directly to its initial share.

100%0%Web: 50.0% (raw 50)iOS: 30.0% (raw 30)Android: 20.0% (raw 20)
Total Visible Sum = 100 · 100% Geometry
Android Toggled OffRe-Normalized

Denominator = 50 + 30 = 80. Remaining layers re-normalize to 100% without color shifts.

100%0%Web: 62.5% (raw 50)iOS: 37.5% (raw 30)
Web & iOS fill 100% · Colors remain Chart-1 & Chart-2
Core Rule: Hiding a series changes the visible denominator; it never reassigns color tokens.
Raw values remain intact; only derived percentage shares are recalculated

When an interactive legend is enabled, clicking a series toggles its visibility. Plotcn implements Model A: Visible-Series Normalization:

  1. Dynamic Visible Basis: Hiding a series recalculates the normalization denominator across remaining visible contributors: Visible Whole=sVisibleRawValue(s,x)=100%\text{Visible Whole} = \sum_{s \in \text{Visible}} \text{RawValue}(s, x) = 100\%
  2. Stable Identity Preservation: Hiding a series never shifts or reassigns color tokens. If Web is assigned --chart-1, iOS --chart-2, and Android --chart-3, hiding iOS leaves Android bound to --chart-3.
  3. Single Visible Series: If only one visible series remains, its share truthfully normalizes to 100%100\% across all positive observations.
  4. All Series Hidden: If all series are toggled off, Percent Stream Area renders an accessible, recoverable prompt ("All Series Hidden") with active legend buttons to restore visibility without page reloading.

Data & Series Contracts

Percent Stream Area accepts an ordered array of data records:

TypeScript
type TrafficMixDatum = {  month: string | Date  web: number | null  ios: number | null  android: number | null}

Series Configuration Object

TypeScript
interface PercentStreamSeries<TData> {  key: NumericKeyOf<TData>  label: string  color?: string  valueFormatter?: (value: number) => string}

Contract Requirements

  • Same Unit Requirement: All contributors must share the identical physical, operational, or financial unit (e.g. all requests, all users, or all dollars). Do not stack disparate metrics like revenue and latency.
  • Non-Negative Values: In V1, additive composition requires non-negative quantities (v0v \ge 0). If negative values are detected, Plotcn halts rendering and presents a truthful ChartErrorState explaining that signed values violate 100% composition semantics. Negative values are never silently clamped.

Installation

Install Percent Stream Area directly into your project using the shadcn CLI:

PLOTCN/REGISTRY/AREA-PERCENT-STREAM/SOURCE
pnpm dlx shadcn@latest add @plotcn/area-percent-stream

Checking public registry…

View local registry JSON
REGISTRY direct URL·ENGINE Recharts·FILES 1·DEPENDENCIES 5

Copied as source into your project (requires recharts).

Component Props

Property Type Default Required Description
PropertyTypeDefaultRequiredDescription
datareadonly TData[][]RequiredReadonly array of observation records. Caller data is never mutated.
xKeykeyof TData & stringRequiredProperty name on data records for horizontal domain coordinates.
seriesreadonly PercentStreamSeries<TData>[]RequiredCanonical array of additive series. Stack order is strictly bottom-to-top.
heightnumber | string320OptionalContainer height in pixels or CSS dimension string.
curve"monotone" | "linear" | "step""monotone"OptionalCurve interpolation algorithm for layer boundaries.
fillOpacitynumber0.75OptionalLayer fill opacity between 0 and 1.
gradientMode"none" | "vertical-fade""none"OptionalDecorative gradient treatment. Never encodes data certainty.
selectionColorstring"var(--chart-selection)"OptionalAccent color for pinned crosshair and inspection markers.
showGridbooleantrueOptionalWhether to render subtle horizontal Cartesian grid reference lines.
showXAxisbooleantrueOptionalWhether to render horizontal category scale.
showYAxisbooleantrueOptionalWhether to render vertical percentage ticks (0%, 25%, 50%, 75%, 100%).
showLegendbooleantrueOptionalWhether to render the series identity legend.
interactiveLegendbooleantrueOptionalEnables pointer and keyboard series visibility toggles.
lockableTooltipbooleantrueOptionalEnables persistent tooltip locking via click or Enter/Space.
missingValuePolicy"gap" | "zero""gap"OptionalHandling of missing values: 'gap' marks incomplete; 'zero' substitutes 0.
animation"draw" | "fade" | "none""draw"OptionalEntry reveal animation. Bypassed automatically under reduced motion.
shareFormatter(share: number) => string${share.toFixed(1)}%OptionalFormatter for normalized percentage readouts in tooltips.
valueFormatter(value: number) => stringn.toLocaleString()OptionalFormatter for raw underlying quantities in tooltips and tables.
titlestring"Percent Stream Area Chart"OptionalAccessible title for screen readers.
descriptionstringundefinedOptionalAccessible description detailing 100% normalized composition.

Keyboard Navigation Reference

Percent Stream Area provides a single focused entry point for Cartesian exploration:

Key Control Target Action
KeyControl TargetAction
/ ArrowRightPlot InspectionInspect next chronological observation
/ ArrowLeftPlot InspectionInspect previous chronological observation
HomePlot InspectionJump inspection to the first observation
EndPlot InspectionJump inspection to the latest observation
Enter or SpacePlot InspectionLock or unlock persistent inspection at the active coordinate
EscapePlot InspectionDismiss locked inspection and release pinned tooltip
03 / Component API & Styling

Props Reference & Interactive Prop Explorer

Inspect every component property, customize semantic color roles live with instant visual feedback, and copy production-ready code with active prop configurations.

Colors & Appearance Configuration

Customize primary, reference, or annotation series colors. Defaults derive from Plotcn theme tokens.

Generated Usage Code (Live Props):
<PercentStreamArea
  data={data}
  xKey="date"
  seriesKey="value"
/>
Interactive Prop Preview Lab
curve"monotone" | "linear" | "step"

Curve interpolation algorithm shared across all stacked area boundaries.

Select value to preview live:
Active: curve="monotone"Default: "monotone"
fillOpacitynumber

Opacity of normalized area layers (0.1 to 1.0) ensuring layers remain distinguishable.

Select value to preview live:
Active: fillOpacity={0.75}Default: 0.75
gradientMode"none" | "vertical-fade"

Gradient fill treatment: "none" (solid translucent) or "vertical-fade" (restrained top-to-bottom fade).

Select value to preview live:
Active: gradientMode="none"Default: "none"
showGridboolean

Whether to render subtle horizontal Cartesian grid reference lines.

Select value to preview live:
Active: showGrid={true}Default: true
showLegendboolean

Whether to render the series identity legend.

Select value to preview live:
Active: showLegend={true}Default: true
interactiveLegendboolean

Whether legend items can be clicked/keyboard-activated to toggle layer visibility and re-normalize the visible whole.

Select value to preview live:
Active: interactiveLegend={true}Default: true
lockableTooltipboolean

Whether clicking or pressing Enter/Space pins the currently inspected X datum.

Select value to preview live:
Active: lockableTooltip={true}Default: true
missingValuePolicy"gap" | "zero"

Handling of missing observations: 'gap' marks composition incomplete; 'zero' substitutes 0.

Select value to preview live:
Active: missingValuePolicy="gap"Default: "gap"
All Properties (20)
Component properties
PropertyTypeDefaultRequiredDescription
dataReq
readonly TData[][]Yes

Readonly array of observation records. Caller data is never mutated or sorted in place.

xKeyReq
keyof TData & stringYes

Property name on data records for horizontal domain coordinates.

readonly PercentStreamSeries<TData>[][]Yes

Ordered array of additive series definitions. Geometry stacks from series[0] at bottom to series[n-1] at top.

number | string320No

Container height in pixels or standard CSS dimension strings.

"monotone" | "linear" | "step""monotone"No

Curve interpolation algorithm shared across all stacked area boundaries.

number0.75No

Opacity of normalized area layers (0.1 to 1.0) ensuring layers remain distinguishable.

"none" | "vertical-fade""none"No

Gradient fill treatment: "none" (solid translucent) or "vertical-fade" (restrained top-to-bottom fade).

string"var(--chart-selection)"No

Accent color for the locked crosshair and selection pin.

booleantrueNo

Whether to render subtle horizontal Cartesian grid reference lines.

booleantrueNo

Whether to render the horizontal category scale.

booleantrueNo

Whether to render the vertical percentage scale (0%–100%).

booleantrueNo

Whether to render the series identity legend.

booleantrueNo

Whether legend items can be clicked/keyboard-activated to toggle layer visibility and re-normalize the visible whole.

booleantrueNo

Whether clicking or pressing Enter/Space pins the currently inspected X datum.

"gap" | "zero""gap"No

Handling of missing observations: 'gap' marks composition incomplete; 'zero' substitutes 0.

"draw" | "fade" | "none""draw"No

Entry reveal animation mode.

(share: number) => string(s) => `${s.toFixed(1)}%`No

Custom formatter for normalized percentage shares in tooltips.

(value: number) => stringn.toLocaleString()No

Custom formatter for raw underlying quantities in tooltips and tables.

string"Percent Stream Area Chart"No

Accessible heading announced to screen readers.

stringundefinedNo

Accessible descriptive summary announced to screen readers.

04 / Cookbook & States

Component Variants & Edge States

Production cookbooks showcasing configuration variants alongside verified handling of loading, empty data, and network error states.

Traffic Mix Evolution

Tracking client platform share evolution over six months while underlying raw traffic scales 10x.

<PercentStreamArea
  data={trafficData}
  xKey="month"
  series={[
    { key: "web", label: "Web" },
    { key: "ios", label: "iOS" },
    { key: "android", label: "Android" },
  ]}
  showLegend
  interactiveLegend
  lockableTooltip
  valueFormatter={(v) => `${v.toLocaleString()} req/s`}
  showGrid
/>

Subscription Tier Mix

Monthly customer account mix across Free, Pro, and Enterprise tiers.

<PercentStreamArea
  data={tierData}
  xKey="month"
  series={[
    { key: "free", label: "Free Tier" },
    { key: "pro", label: "Pro Tier" },
    { key: "enterprise", label: "Enterprise Tier" },
  ]}
  showLegend
  interactiveLegend
  lockableTooltip
  valueFormatter={(v) => `${v.toLocaleString()} accounts`}
  showGrid
/>
Lifecycle & Exception States
01. Loading State

Skeletons indicate runtime fetch or pending data queries.

02. Empty Data State

Handles empty collections ([]) gracefully without crashing.

03. Error State

Graceful failure banner when data source or script fails.

05 / Responsive Lab

Container-Driven Breakpoints

Percent Stream Area preserves full 100% normalized layer composition and deterministic color mappings across all viewport widths without dropping series.

Desktop
>= 1024px

Full horizontal percentage ticks (0%, 25%, 50%, 75%, 100%), inline interactive legend, and spacious multi-column tooltip.

Tablet
640px - 1023px

Adaptive wrapped interactive legend, thinned domain ticks, and continuous 100% normalized geometry.

Mobile
< 640px

Compact percentage Y-axis, compact stacked tooltip, scrollable legend. Series are never silently dropped.

Tablet Preview (768px Container Constraint)
Mobile Preview (390px Container Constraint)
06 / Assistive Technology

Accessibility & Navigation Standards

Single keyboard tab stop on root figure with ArrowLeft, ArrowRight, Home, End, Enter, and Escape shortcuts. Separate standard button tab stops for interactive legend toggles.

Semantic Role & Landmark

Container mounts as region with explicit assistive label.

Color-Independent Legibility

Deterministic series order, distinct stroke boundaries, interactive fill swatches, and structured data table provide accessible non-color differentiation.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “Figure element announces chart title and 100% normalized composition summary. Live region announces inspected coordinate, visible shares, and raw quantities.

Reduced Motion Support

All entrance reveal animations and restacking transitions immediately bypass when prefers-reduced-motion is detected.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
ArrowRightInspect next chronological observation across visible series.
ArrowLeftInspect previous chronological observation across visible series.
HomeJump inspection directly to the first observation.
EndJump inspection directly to the final observation.
Enter / SpaceLock or unlock persistent inspection at the active coordinate.
EscapeRelease locked selection and dismiss active inspection tooltip.
TabMove focus to interactive series visibility controls in the legend.

Data Safety Checklist

  • ✓ Total Magnitude Deliberately Removed: Geometry strictly communicates relative shares; total volume changes are not visible in polygon height.
  • ✓ Input Immutability: Caller data arrays and series configuration objects are never mutated.
  • ✓ Missing Contributor Never Coerced to 0%: Under default gap policy, missing data breaks the stack honestly (null ≠ 0).
  • ✓ Zero Total Handled Safely: 0/00 / 0 avoids divide-by-zero, NaN%, or Infinity%.
  • ✓ Non-Negative Enforcement: Negative values trigger a descriptive error state rather than silent clamping.
  • ✓ Stable Series Identity: Canonical series index determines palette tokens; toggling series in the legend never reassigns colors.
  • ✓ Floating-Point Precision: Internal geometry calculations use full precision to reach the exact 100% boundary.
  • ✓ Responsive Integrity: Narrow viewports simplify labels and ticks; they never silently remove series to save space.
  • Stack Flow Area: Multi-series additive stacking for communicating both individual contributions and changing total magnitude.
  • Prism Area: Single-series magnitude visualization anchored to an explicit baseline.
  • Multi-Signal Line: Multi-series trend comparison without area filling or composition constraints.
07 / Source Anatomy

Internal Architecture & File Dependencies

Source-first ownership model. Inspect the exact component call tree, dependencies, and full implementation below.

Component Architecture Call Tree
PercentStreamArea(Root figure element with keyboard navigation and ARIA accessibility shell)
├──ChartContainer[Responsive container wrapper]

Handles container dimension measurement and CSS token scoping

├──AreaChart[Recharts Cartesian SVG coordinator]

Coordinates coordinate scales, 0%–100% Y-axis, Cartesian grid, and Area polygon rendering with stackId

└──PercentStreamLegend[Series identity & visibility controls]

Interactive button controls with rectangular area swatches and visible-series re-normalization

Involved Source Files & Registry Assets
registry/recharts/area-percent-stream.tsx
Complete Percent Stream Area component with 100% normalized composition, deterministic series identity, and re-normalization
registry/recharts/area-percent-stream.tsx
"use client"import * as React from "react"import {  ResponsiveContainer,  AreaChart,  Area,  XAxis,  YAxis,  Tooltip,  CartesianGrid,  ReferenceLine,} from "recharts"import { HugeiconsIcon } from "@hugeicons/react"import { LockKeyIcon, ViewIcon, ViewOffSlashIcon } from "@hugeicons/core-free-icons"import { useChartReducedMotion } from "../shared/use-chart-reduced-motion"import {  ChartLoadingState,  ChartEmptyState,  ChartErrorState,  ChartUnavailableState,} from "../shared/chart-state"import { ChartContainer } from "../shared/chart-container"import { cn } from "@/lib/utils"/* -------------------------------------------------------------------------- *//*  Type Definitions                                                          *//* -------------------------------------------------------------------------- */export type NumericKeyOf<TData> = [keyof TData] extends [never]  ? string  : {      [K in keyof TData]: TData[K] extends number | null | undefined ? K : never    }[keyof TData] extends never  ? string  : {      [K in keyof TData]: TData[K] extends number | null | undefined ? K : never    }[keyof TData] & string/** * Semantic descriptor for an individual contributing series in the normalized percent composition. */export interface PercentStreamSeries<TData extends Record<string, unknown> = Record<string, unknown>> {  /** Property key on observation records containing numeric additive metric */  key: NumericKeyOf<TData>  /** Human-readable display label for legend, tooltips, and screen readers */  label: string  /** Explicit stroke and fill color override. Defaults to deterministic palette tokens */  color?: string  /** Custom numeric metric formatter for raw tooltip values */  valueFormatter?: (value: number) => string}export interface ResolvedPercentStreamSeries<TData extends Record<string, unknown> = any> {  key: NumericKeyOf<TData>  label: string  color: string  valueFormatter?: (value: number) => string  originalIndex: number}export type ObservationCompositionState = "valid" | "incomplete" | "zero-total"export interface PercentStreamActiveDatum<  TData extends Record<string, unknown> = Record<string, unknown>,  XVal extends string | number = string | number> {  index: number  x: XVal  raw: TData  rawValues: Record<string, number | null>  shares: Record<string, number | null>  visibleRawTotal: number | null  state: ObservationCompositionState  isLocked: boolean}export interface NormalizedPercentStreamRow<TData = any> {  __x: string | number  __index: number  __raw: TData  __rawValues: Record<string, number | null>  __shares: Record<string, number | null>  __visibleRawTotal: number | null  __state: ObservationCompositionState  [key: string]: any}export interface PercentStreamAreaProps<  TData extends Record<string, unknown> = Record<string, unknown>,  XVal extends string | number = string | number> {  /** Readonly array of observation records. Caller data is never mutated. */  data: readonly TData[]  /** Key for horizontal domain coordinate (e.g. month, date, sprint, version). */  xKey: keyof TData & string  /** Ordered array of additive series. Stack order follows canonical array order (bottom -> top). */  series: readonly PercentStreamSeries<TData>[]  /** Container height in pixels or standard CSS dimension string. (default: 320) */  height?: number | string  /** Curve interpolation for area boundaries: "monotone" | "linear" | "step". (default: "monotone") */  curve?: "monotone" | "linear" | "step"  /** Overall fill opacity for normalized layers (0.0 to 1.0). (default: 0.75) */  fillOpacity?: number  /** Gradient fill mode: "none" (solid translucent) or "vertical-fade". (default: "none") */  gradientMode?: "none" | "vertical-fade"  /** Accent color for locked crosshair and selection markers. (default: "var(--chart-selection)") */  selectionColor?: string  /** Whether to render subtle horizontal Cartesian grid reference lines. (default: true) */  showGrid?: boolean  /** Whether to render the horizontal category scale. (default: true) */  showXAxis?: boolean  /** Whether to render the vertical percentage scale (0% - 100%). (default: true) */  showYAxis?: boolean  /** Whether to render the series identity legend. (default: true) */  showLegend?: boolean  /** Whether legend items can be clicked/keyboard-activated to toggle layer visibility. (default: true) */  interactiveLegend?: boolean  /** Default set of series keys that should be initially visible. (defaults to all configured series) */  defaultVisibleSeries?: readonly string[]  /** Callback fired whenever the set of visible series changes. */  onVisibleSeriesChange?: (visibleKeys: readonly string[]) => void  /** Whether clicking or pressing Enter/Space pins the currently inspected X datum. (default: true) */  lockableTooltip?: boolean  /** Default observation index to pin/lock on initial mount. (default: null) */  defaultLockedIndex?: number | null  /** Handling of missing observations: 'gap' marks composition incomplete, 'zero' treats as 0. (default: "gap") */  missingValuePolicy?: "gap" | "zero"  /** Animation mode: "draw" | "fade" | "none". (default: "draw") */  animation?: "draw" | "fade" | "none"  /** Motion toggle or configuration object. Respects prefers-reduced-motion. */  motion?: boolean | { duration?: number }  /** Custom formatter for normalized share percentages in tooltips. (default: `${share.toFixed(1)}%`) */  shareFormatter?: (share: number) => string  /** Custom formatter for raw metric values in tooltips. */  valueFormatter?: (value: number) => string  /** Custom formatter for X-axis coordinate labels. */  xFormatter?: (value: string | number) => string  /** Callback fired whenever the active inspected observation changes. */  onActiveDatumChange?: (datum: PercentStreamActiveDatum<TData, XVal> | null) => void  /** Optional heading announced to assistive technologies. */  title?: string  /** Optional descriptive summary announced to assistive technologies. */  description?: string  /** Optional CSS class name passed to the root figure element. */  className?: string  /** Display neutral skeleton loading state. */  loading?: boolean  /** Display actionable error banner. */  error?: Error | string | null  /** Display metric unavailability notice. */  unavailable?: boolean | string | null}/* -------------------------------------------------------------------------- *//*  Algorithmic & Normalization Helpers                                       *//* -------------------------------------------------------------------------- */export function isFiniteNumber(val: unknown): val is number {  return typeof val === "number" && Number.isFinite(val)}/** * Resolves canonical series definitions to concrete color tokens. * Crucial contract: Series index determines default palette token. * Hiding one series will never shift another series' assigned color token. */export function resolvePercentStreamSeries<TData extends Record<string, unknown>>(  series: readonly PercentStreamSeries<TData>[]): ResolvedPercentStreamSeries<TData>[] {  return series.map((s, index) => {    const paletteIndex = (index % 8) + 1    const defaultColor = `var(--chart-${paletteIndex})`    return {      key: s.key,      label: s.label || String(s.key),      color: s.color && s.color.trim() !== "" ? s.color : defaultColor,      valueFormatter: s.valueFormatter,      originalIndex: index,    }  })}export interface PercentStreamNormalizationResult<TData> {  rows: NormalizedPercentStreamRow<TData>[]  hasNegativeValues: boolean  negativeErrorDetails?: string}/** * Normalizes input observations for 100% stacked percent composition: * 1. Checks for negative values (violates additive composition contract; rejected in V1). * 2. Missing values: under "gap", remains null (composition incomplete). Under "zero", coerced to 0. * 3. Zero total (sum = 0): shares are null / 0, avoiding divide-by-zero or NaN. * 4. Normalizes visible series so they sum to exactly 100% of visible total (Model A). * 5. Never mutates caller array or objects. */export function normalizePercentStreamData<TData extends Record<string, unknown>>(  data: readonly TData[],  xKey: keyof TData & string,  resolvedSeries: readonly ResolvedPercentStreamSeries<TData>[],  visibleKeys: ReadonlySet<string>,  missingValuePolicy: "gap" | "zero" = "gap"): PercentStreamNormalizationResult<TData> {  if (!Array.isArray(data) || data.length === 0) {    return { rows: [], hasNegativeValues: false }  }  const rows: NormalizedPercentStreamRow<TData>[] = []  for (let i = 0; i < data.length; i++) {    const d = data[i]    const rawX = d[xKey]    const xVal = typeof rawX === "string" || typeof rawX === "number" ? rawX : String(rawX ?? "")    const rawValues: Record<string, number | null> = {}    const shares: Record<string, number | null> = {}    let isComplete = true    let visibleRawTotal = 0    // First pass: extract and validate individual series numbers    for (const s of resolvedSeries) {      const rawV = d[s.key]      if (typeof rawV === "number") {        if (!Number.isFinite(rawV)) {          rawValues[s.key] = null          if (visibleKeys.has(s.key)) isComplete = false        } else if (rawV < 0) {          return {            rows: [],            hasNegativeValues: true,            negativeErrorDetails: `Percent Stream Area requires non-negative raw contributions. Found negative value (${rawV}) for series "${s.label}" at "${xVal}".`,          }        } else {          rawValues[s.key] = rawV          if (visibleKeys.has(s.key)) {            visibleRawTotal += rawV          }        }      } else if (rawV === null || rawV === undefined) {        if (missingValuePolicy === "zero") {          rawValues[s.key] = 0        } else {          rawValues[s.key] = null          if (visibleKeys.has(s.key)) {            isComplete = false          }        }      } else {        // Unknown type / NaN / invalid        rawValues[s.key] = null        if (visibleKeys.has(s.key)) isComplete = false      }    }    // Determine state    let state: ObservationCompositionState = "valid"    if (!isComplete) {      state = "incomplete"    } else if (visibleRawTotal === 0) {      state = "zero-total"    }    // Second pass: compute normalized 0–100% shares safely    for (const s of resolvedSeries) {      const val = rawValues[s.key]      if (!visibleKeys.has(s.key)) {        shares[s.key] = null      } else if (state === "valid" && visibleRawTotal > 0 && val !== null) {        // Derive proportional share in 0–100 range        shares[s.key] = (val / visibleRawTotal) * 100      } else if (state === "zero-total") {        // Defined as zero height, distinct from missing        shares[s.key] = 0      } else {        // Incomplete / missing        shares[s.key] = null      }    }    const rowObj: NormalizedPercentStreamRow<TData> = {      __x: xVal,      __index: i,      __raw: d,      __rawValues: rawValues,      __shares: shares,      __visibleRawTotal: state === "incomplete" ? null : visibleRawTotal,      __state: state,    }    // Attach flattened keys for Recharts Area primitives    for (const s of resolvedSeries) {      rowObj[`__share_${s.key}`] = shares[s.key]    }    rows.push(rowObj)  }  return { rows, hasNegativeValues: false }}/* -------------------------------------------------------------------------- *//*  Custom Tooltip Component                                                  *//* -------------------------------------------------------------------------- */interface PercentStreamTooltipContentProps {  active?: boolean  activeDatum: PercentStreamActiveDatum | null  resolvedSeries: readonly ResolvedPercentStreamSeries[]  visibleKeys: ReadonlySet<string>  shareFormatter: (share: number) => string  valueFormatter: (value: number) => string  onUnlock?: () => void  isCompact?: boolean}function PercentStreamTooltipContent({  active,  activeDatum,  resolvedSeries,  visibleKeys,  shareFormatter,  valueFormatter,  onUnlock,  isCompact = false,}: PercentStreamTooltipContentProps) {  if (!active || !activeDatum) return null  const isLocked = activeDatum.isLocked  const state = activeDatum.state  return (    <div      role="region"      aria-label="Inspection Readout"      className={cn(        "plotcn-interactive-tooltip z-50 rounded-xl border bg-zinc-950/95 shadow-2xl backdrop-blur-md transition-all duration-150 animate-in fade-in zoom-in-95 select-none",        isCompact          ? "min-w-[130px] max-w-[200px] p-2 text-[10px]"          : "min-w-[220px] max-w-[300px] p-3.5 text-xs text-zinc-100",        isLocked          ? "border-amber-500/40 ring-2 ring-amber-500/20 shadow-amber-950/30"          : "border-white/[0.12] shadow-black/60"      )}    >      {/* Header */}      <div        className={cn(          "tooltip-header flex items-center justify-between border-b border-white/[0.08]",          isCompact ? "pb-1 mb-1.5" : "pb-2 mb-2.5"        )}      >        <div className="flex items-center gap-1.5 min-w-0">          <span            className={cn(              "font-mono font-semibold text-zinc-200 truncate",              isCompact ? "text-[10px]" : "text-xs"            )}          >            {String(activeDatum.x)}          </span>          {!isCompact && (            <span className="text-[10px] font-mono text-zinc-500 uppercase tracking-wider shrink-0">              · 100% Mix            </span>          )}        </div>        {isLocked && (          <button            type="button"            onClick={(e) => {              e.stopPropagation()              onUnlock?.()            }}            className={cn(              "flex items-center gap-1 rounded bg-amber-500/20 font-mono font-medium text-amber-300 hover:bg-amber-500/30 transition-colors focus:outline-none focus:ring-1 focus:ring-amber-400 shrink-0",              isCompact ? "px-1 py-0.2 text-[8px]" : "px-1.5 py-0.5 text-[10px]"            )}            title="Press Escape or click to unlock"          >            <HugeiconsIcon icon={LockKeyIcon} size={isCompact ? 9 : 11} />            <span>Pinned</span>          </button>        )}      </div>      {/* State Callouts */}      {state === "incomplete" && (        <div          className={cn(            "rounded-lg border border-amber-500/30 bg-amber-500/10 text-amber-300",            isCompact ? "p-1 mb-1 text-[9px]" : "p-2 mb-2.5 text-[11px]"          )}        >          <div className="font-semibold flex items-center gap-1">            <span className="size-1 rounded-full bg-amber-400" />            Incomplete          </div>          {!isCompact && (            <p className="text-zinc-400 text-[10px] leading-tight mt-0.5">              One or more visible series have missing observations at this coordinate.            </p>          )}        </div>      )}      {state === "zero-total" && (        <div          className={cn(            "rounded-lg border border-sky-500/30 bg-sky-500/10 text-sky-300",            isCompact ? "p-1 mb-1 text-[9px]" : "p-2 mb-2.5 text-[11px]"          )}        >          <div className="font-semibold flex items-center gap-1">            <span className="size-1 rounded-full bg-sky-400" />            Zero Total          </div>          {!isCompact && (            <p className="text-zinc-400 text-[10px] leading-tight mt-0.5">              All visible contributors recorded zero.            </p>          )}        </div>      )}      {/* Series Breakdown */}      <div className={isCompact ? "space-y-0.5" : "space-y-1.5"}>        {[...resolvedSeries]          .reverse()          .filter((s) => visibleKeys.has(s.key))          .map((s) => {            const rawVal = activeDatum.rawValues[s.key]            const shareVal = activeDatum.shares[s.key]            const formatter = s.valueFormatter || valueFormatter            return (              <div                key={s.key}                className={cn(                  "tooltip-row flex items-center justify-between gap-2",                  isCompact ? "text-[10px] py-0" : "text-xs py-0.5"                )}              >                <div className="flex items-center gap-1.5 min-w-0">                  <span                    className={cn(                      "rounded-xs shrink-0 border border-black/40",                      isCompact ? "size-2" : "size-2.5"                    )}                    style={{ backgroundColor: s.color }}                  />                  <span className="truncate text-zinc-300 font-medium">{s.label}</span>                </div>                <div className="flex items-center gap-1.5 shrink-0 font-mono text-right">                  <span className="font-semibold text-white">                    {shareVal !== null && Number.isFinite(shareVal)                      ? shareFormatter(shareVal)                      : "—"}                  </span>                  {!isCompact && (                    <span className="text-[10px] text-zinc-400">                      ({rawVal !== null && Number.isFinite(rawVal) ? formatter(rawVal) : "—"})                    </span>                  )}                </div>              </div>            )          })}      </div>      {/* Footer: Visible Raw Total */}      <div        className={cn(          "flex items-center justify-between border-t border-white/[0.08] font-mono",          isCompact ? "mt-1.5 pt-1 text-[10px]" : "mt-3 pt-2 text-[11px]"        )}      >        <span className="text-zinc-400">Total</span>        <span className="font-bold text-zinc-200">          {activeDatum.visibleRawTotal !== null && Number.isFinite(activeDatum.visibleRawTotal)            ? valueFormatter(activeDatum.visibleRawTotal)            : state === "zero-total"            ? "0"            : "Incomplete"}        </span>      </div>      {isLocked && !isCompact && (        <div className="mt-2 text-center text-[10px] text-zinc-500 font-mono">          Press <kbd className="px-1 rounded bg-zinc-800 text-zinc-400">Esc</kbd> to unlock        </div>      )}    </div>  )}/* -------------------------------------------------------------------------- *//*  Main Component: PercentStreamArea                                         *//* -------------------------------------------------------------------------- */export function PercentStreamArea<  TData extends Record<string, unknown> = Record<string, unknown>,  XVal extends string | number = string | number>({  data = [],  xKey,  series,  height = 320,  curve = "monotone",  fillOpacity = 0.75,  gradientMode = "none",  selectionColor = "var(--chart-selection)",  showGrid = true,  showXAxis = true,  showYAxis = true,  showLegend = true,  interactiveLegend = true,  defaultVisibleSeries,  onVisibleSeriesChange,  lockableTooltip = true,  defaultLockedIndex = null,  missingValuePolicy = "gap",  animation = "draw",  motion = true,  shareFormatter = (share: number) => `${share.toFixed(1)}%`,  valueFormatter = (value: number) => (isFiniteNumber(value) ? value.toLocaleString() : "—"),  xFormatter = (val: string | number) => String(val ?? ""),  onActiveDatumChange,  title = "Percent Stream Area Chart",  description,  className,  loading = false,  error = null,  unavailable = false,}: PercentStreamAreaProps<TData, XVal>) {  const isReducedMotion = useChartReducedMotion()  const motionEnabled = motion !== false && !isReducedMotion && animation !== "none"  // 1. Resolve Canonical Series Identities  const resolvedSeries = React.useMemo(() => {    return resolvePercentStreamSeries(series)  }, [series])  // 2. Interactive Visibility State  const [visibleKeys, setVisibleKeys] = React.useState<ReadonlySet<string>>(() => {    if (defaultVisibleSeries && defaultVisibleSeries.length > 0) {      return new Set(defaultVisibleSeries)    }    return new Set(resolvedSeries.map((s) => s.key))  })  // Synchronize if defaultVisibleSeries or series array changes  React.useEffect(() => {    if (defaultVisibleSeries && defaultVisibleSeries.length > 0) {      setVisibleKeys(new Set(defaultVisibleSeries))    } else {      setVisibleKeys(new Set(resolvedSeries.map((s) => s.key)))    }  }, [defaultVisibleSeries, resolvedSeries])  const toggleSeries = React.useCallback(    (key: string) => {      if (!interactiveLegend) return      setVisibleKeys((prev) => {        const next = new Set(prev)        if (next.has(key)) {          // Allow toggling off only if more than one series visible          if (next.size > 1) {            next.delete(key)          }        } else {          next.add(key)        }        const nextArr = Array.from(next)        onVisibleSeriesChange?.(nextArr)        return next      })    },    [interactiveLegend, onVisibleSeriesChange]  )  // 3. Normalization with Model A (Visible-Series Normalization)  const normalizationResult = React.useMemo(() => {    return normalizePercentStreamData(      data,      xKey,      resolvedSeries,      visibleKeys,      missingValuePolicy    )  }, [data, xKey, resolvedSeries, visibleKeys, missingValuePolicy])  const rows = normalizationResult.rows  // 4. Locked and Hovered Active Observation State  const [lockedIndex, setLockedIndex] = React.useState<number | null>(defaultLockedIndex)  const [hoverIndex, setHoverIndex] = React.useState<number | null>(null)  const activeIndex = lockedIndex !== null ? lockedIndex : hoverIndex  const activeDatum = React.useMemo<PercentStreamActiveDatum<TData, XVal> | null>(() => {    if (activeIndex === null || !rows[activeIndex]) return null    const row = rows[activeIndex]    return {      index: activeIndex,      x: row.__x as XVal,      raw: row.__raw,      rawValues: row.__rawValues,      shares: row.__shares,      visibleRawTotal: row.__visibleRawTotal,      state: row.__state,      isLocked: lockedIndex !== null,    }  }, [activeIndex, rows, lockedIndex])  React.useEffect(() => {    onActiveDatumChange?.(activeDatum)  }, [activeDatum, onActiveDatumChange])  // Keyboard navigation & lock management  const handleKeyDown = React.useCallback(    (e: React.KeyboardEvent) => {      if (!rows.length) return      const maxIdx = rows.length - 1      const current = activeIndex ?? 0      switch (e.key) {        case "ArrowRight":        case "ArrowDown": {          e.preventDefault()          const next = Math.min(maxIdx, current + 1)          if (lockedIndex !== null) setLockedIndex(next)          else setHoverIndex(next)          break        }        case "ArrowLeft":        case "ArrowUp": {          e.preventDefault()          const prev = Math.max(0, current - 1)          if (lockedIndex !== null) setLockedIndex(prev)          else setHoverIndex(prev)          break        }        case "Home": {          e.preventDefault()          if (lockedIndex !== null) setLockedIndex(0)          else setHoverIndex(0)          break        }        case "End": {          e.preventDefault()          if (lockedIndex !== null) setLockedIndex(maxIdx)          else setHoverIndex(maxIdx)          break        }        case "Enter":        case " ": {          if (!lockableTooltip) return          e.preventDefault()          if (lockedIndex !== null) {            setLockedIndex(null)          } else {            setLockedIndex(current)          }          break        }        case "Escape": {          if (lockedIndex !== null) {            e.preventDefault()            setLockedIndex(null)          }          break        }      }    },    [rows.length, activeIndex, lockedIndex, lockableTooltip]  )  // Chart Click toggles lock  const handleChartClick = React.useCallback(    (state: any) => {      if (!lockableTooltip) return      if (state && state.activeTooltipIndex !== undefined) {        const clickedIndex = Number(state.activeTooltipIndex)        if (lockedIndex === clickedIndex) {          setLockedIndex(null)        } else {          setLockedIndex(clickedIndex)        }      } else if (lockedIndex !== null) {        setLockedIndex(null)      }    },    [lockableTooltip, lockedIndex]  )  // SSR Safe Gradient ID prefix  const gradientIdPrefix = React.useId().replace(/:/g, "")  /* ------------------------------------------------------------------------ */  /*  Early States: Loading, Unavailable, Error, Empty                        */  /* ------------------------------------------------------------------------ */  if (loading) {    return (      <figure        role="region"        aria-label={title || "Percent stream loading state"}        className={cn("plotcn-percent-stream relative w-full overflow-hidden rounded-xl border border-white/10 bg-zinc-950 p-4", className)}        style={{ height, minHeight: typeof height === "number" ? height : 320 }}      >        <ChartLoadingState description="Loading percent stream composition..." />      </figure>    )  }  if (unavailable) {    return (      <figure        role="region"        aria-label={title || "Percent stream unavailable state"}        className={cn("plotcn-percent-stream relative w-full overflow-hidden rounded-xl border border-white/10 bg-zinc-950 p-4", className)}        style={{ height, minHeight: typeof height === "number" ? height : 320 }}      >        <ChartUnavailableState          title="Composition Unavailable"          description={typeof unavailable === "string" ? unavailable : "Composition data is currently unavailable."}        />      </figure>    )  }  if (error || normalizationResult.hasNegativeValues) {    const errorDescription =      normalizationResult.negativeErrorDetails ||      (error instanceof Error ? error.message : typeof error === "string" ? error : "An error occurred.")    return (      <figure        role="region"        aria-label={title || "Percent stream error state"}        className={cn("plotcn-percent-stream relative w-full overflow-hidden rounded-xl border border-rose-500/20 bg-zinc-950 p-4", className)}        style={{ height, minHeight: typeof height === "number" ? height : 320 }}      >        <ChartErrorState          title="Percent Stream Configuration Error"          description={errorDescription}        />      </figure>    )  }  if (!data || data.length === 0 || !series || series.length === 0) {    return (      <figure        role="region"        aria-label={title || "Percent stream empty state"}        className={cn("plotcn-percent-stream relative w-full overflow-hidden rounded-xl border border-white/10 bg-zinc-950 p-4", className)}        style={{ height, minHeight: typeof height === "number" ? height : 320 }}      >        <ChartEmptyState          title="No Composition Data"          description="Supply an array of records and at least one contributing series."        />      </figure>    )  }  // All series hidden recoverable state  if (visibleKeys.size === 0) {    return (      <figure        role="region"        aria-label={title}        className={cn(          "relative flex flex-col justify-between rounded-xl border border-white/[0.08] bg-zinc-950 p-4 text-zinc-200",          className        )}        style={{ minHeight: typeof height === "number" ? `${height}px` : height }}      >        <div className="flex flex-col items-center justify-center flex-1 py-12 text-center">          <div className="size-10 rounded-full bg-zinc-900 border border-white/[0.1] flex items-center justify-center mb-3 text-zinc-400">            <HugeiconsIcon icon={ViewOffSlashIcon} size={20} />          </div>          <h4 className="text-sm font-semibold text-white mb-1">All Series Hidden</h4>          <p className="text-xs text-zinc-400 max-w-sm mb-4">            No visible series remain. Click any series below to restore 100% normalized composition.          </p>        </div>        {/* Accessible Interactive Legend */}        {showLegend && (          <div className="pt-3 border-t border-white/[0.08] flex flex-wrap items-center justify-center gap-3 text-xs">            {resolvedSeries.map((s) => (              <button                key={s.key}                type="button"                onClick={() => toggleSeries(s.key)}                className="flex items-center gap-1.5 px-2.5 py-1 rounded-md border border-white/[0.1] bg-zinc-900 text-zinc-400 hover:text-white transition-colors"              >                <span className="size-2 rounded-xs" style={{ backgroundColor: s.color }} />                <span>{s.label}</span>              </button>            ))}          </div>        )}      </figure>    )  }  return (    <figure      role="region"      aria-label={title}      tabIndex={0}      onKeyDown={handleKeyDown}      className={cn(        "group relative flex flex-col justify-between rounded-xl border border-white/[0.08] bg-zinc-950/80 p-4 font-sans text-zinc-200 outline-none transition-all duration-150 focus-visible:ring-2 focus-visible:ring-sky-500/50",        className      )}      style={{        height,        minHeight: typeof height === "number" ? height : 320,      }}    >      {/* Screen Reader Live Narration */}      <div className="sr-only" aria-live="polite">        {description ||          `${title}. 100% normalized stacked area showing ${visibleKeys.size} visible series across ${rows.length} observations.`}        {activeDatum && (          activeDatum.state === "valid"            ? ` Active coordinate ${String(activeDatum.x)}. Visible series shares: ${resolvedSeries                .filter((s) => visibleKeys.has(s.key))                .map(                  (s) =>                    `${s.label}: ${                      activeDatum.shares[s.key] !== null                        ? shareFormatter(activeDatum.shares[s.key]!)                        : "unavailable"                    } (raw ${valueFormatter(activeDatum.rawValues[s.key] ?? 0)})`                )                .join(", ")}. Visible total: ${valueFormatter(activeDatum.visibleRawTotal ?? 0)}.`            : activeDatum.state === "zero-total"            ? ` Active coordinate ${String(activeDatum.x)}. Total is zero. Percentage composition is unavailable.`            : ` Active coordinate ${String(activeDatum.x)}. Composition unavailable due to missing observations.`        )}      </div>      {/* Optional Legend */}      {showLegend && (        <div          role="toolbar"          aria-label="Series visibility controls"          className="flex flex-wrap items-center justify-center gap-2 pb-2 text-xs select-none shrink-0"        >          {resolvedSeries.map((s) => {            const isVisible = visibleKeys.has(s.key)            return (              <button                key={s.key}                type="button"                disabled={!interactiveLegend}                onClick={() => toggleSeries(s.key)}                aria-pressed={isVisible}                className={cn(                  "flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-white/40 cursor-pointer",                  isVisible                    ? "border border-white/[0.12] bg-zinc-900/90 text-zinc-200 hover:bg-zinc-800"                    : "border border-white/[0.04] bg-zinc-950/40 text-zinc-500 line-through hover:text-zinc-400"                )}                title={                  interactiveLegend                    ? isVisible                      ? `Click to hide ${s.label} (re-normalizes remaining visible series)`                      : `Click to show ${s.label}`                    : s.label                }              >                <span                  className={cn(                    "size-2.5 rounded-xs shrink-0 transition-opacity",                    isVisible ? "opacity-100" : "opacity-35"                  )}                  style={{ backgroundColor: s.color }}                />                <span>{s.label}</span>              </button>            )          })}        </div>      )}      {/* SVG Canvas Container */}      <ChartContainer className="relative w-full flex-1 min-w-0 min-h-0 max-w-full overflow-hidden">        <ResponsiveContainer          width="100%"          height="100%"          minWidth={0}          minHeight={0}          initialDimension={{ width: 320, height: typeof height === "number" ? height : 320 }}        >          <AreaChart            data={rows}            onClick={handleChartClick}            onMouseMove={(state: any) => {              if (lockedIndex === null && state && state.activeTooltipIndex !== undefined) {                setHoverIndex(Number(state.activeTooltipIndex))              }            }}            onMouseLeave={() => {              if (lockedIndex === null) {                setHoverIndex(null)              }            }}            margin={{ top: 12, right: 16, left: -10, bottom: 4 }}          >            <defs>              {/* Optional vertical gradient fills per series */}              {resolvedSeries.map((s) => (                <linearGradient                  key={s.key}                  id={`${gradientIdPrefix}-grad-${s.key}`}                  x1="0"                  y1="0"                  x2="0"                  y2="1"                >                  <stop offset="0%" stopColor={s.color} stopOpacity={fillOpacity} />                  <stop                    offset="100%"                    stopColor={s.color}                    stopOpacity={gradientMode === "vertical-fade" ? fillOpacity * 0.4 : fillOpacity}                  />                </linearGradient>              ))}            </defs>            {showGrid && (              <CartesianGrid                strokeDasharray="3 3"                vertical={false}                stroke="rgba(255,255,255,0.06)"              />            )}            {showXAxis && (              <XAxis                dataKey="__x"                tickLine={false}                axisLine={{ stroke: "rgba(255,255,255,0.12)" }}                tick={{ fill: "#a1a1aa", fontSize: 11, fontFamily: "monospace" }}                tickFormatter={xFormatter}                dy={6}              />            )}            {showYAxis && (              <YAxis                domain={[0, 100]}                ticks={[0, 25, 50, 75, 100]}                tickFormatter={(v) => `${v}%`}                tickLine={false}                axisLine={{ stroke: "rgba(255,255,255,0.12)" }}                tick={{ fill: "#a1a1aa", fontSize: 11, fontFamily: "monospace" }}                width={44}              />            )}            {/* 100% Ceiling Reference Line */}            <ReferenceLine              y={100}              stroke="rgba(255,255,255,0.2)"              strokeDasharray="2 2"            />            {/* Neutral Inspection Crosshair */}            {activeIndex !== null && rows[activeIndex] && (              <ReferenceLine                x={rows[activeIndex].__x}                stroke={selectionColor || "var(--chart-selection)"}                strokeWidth={1.5}                strokeDasharray={lockedIndex !== null ? "none" : "3 3"}              />            )}            {/* Custom Interactive Tooltip */}            <Tooltip              isAnimationActive={false}              cursor={false}              content={                <PercentStreamTooltipContent                  activeDatum={activeDatum}                  resolvedSeries={resolvedSeries}                  visibleKeys={visibleKeys}                  shareFormatter={shareFormatter}                  valueFormatter={valueFormatter}                  onUnlock={() => setLockedIndex(null)}                  isCompact={typeof height === "number" ? height <= 260 : false}                />              }            />            {/* Stacked Area Primitives (stackId="plotcn-percent-stack") */}            {resolvedSeries.map((s) => {              const isVisible = visibleKeys.has(s.key)              if (!isVisible) return null              const fillUrl =                gradientMode === "vertical-fade"                  ? `url(#${gradientIdPrefix}-grad-${s.key})`                  : s.color              return (                <Area                  key={s.key}                  type={curve}                  dataKey={`__share_${s.key}`}                  name={s.label}                  stackId="plotcn-percent-stack"                  stroke={s.color}                  strokeWidth={1.5}                  strokeOpacity={0.9}                  fill={fillUrl}                  fillOpacity={gradientMode === "vertical-fade" ? 1 : fillOpacity}                  isAnimationActive={motionEnabled}                  animationDuration={650}                  animationEasing="ease-out"                />              )            })}          </AreaChart>        </ResponsiveContainer>      </ChartContainer>      {/* Structured Data Alternative for Screen Readers */}      <div className="sr-only">        <table>          <caption>{title || "Composition data table"}</caption>          <thead>            <tr>              <th scope="col">Coordinate</th>              {resolvedSeries.map((s) => (                <th key={s.key} scope="col">                  {s.label}                </th>              ))}              <th scope="col">Visible Total</th>              <th scope="col">State</th>            </tr>          </thead>          <tbody>            {rows.map((r, i) => (              <tr key={i}>                <td>{String(r.__x)}</td>                {resolvedSeries.map((s) => {                  const rawVal = r.__rawValues[s.key]                  const shareVal = r.__shares[s.key]                  return (                    <td key={s.key}>                      {shareVal !== null                        ? `${shareFormatter(shareVal)} (${rawVal !== null ? valueFormatter(rawVal) : "—"})`                        : "—"}                    </td>                  )                })}                <td>{r.__visibleRawTotal !== null ? valueFormatter(r.__visibleRawTotal) : "—"}</td>                <td>{r.__state}</td>              </tr>            ))}          </tbody>        </table>      </div>    </figure>  )}