001 / RECHARTS / LINE

Signal Line

Recharts

Focused single-series time-series visualization with restrained active-point emphasis, accessible keyboard exploration, and Plotcn semantic tokens.

SPEC
#001
ENGINE
Recharts
FAMILY
Line
RENDERER
svg
STATUS
preview

Installation

PLOTCN/REGISTRY/LINE-SIGNAL/SOURCE
pnpm dlx shadcn@latest add @plotcn/line-signal

Checking public registry…

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

Copied as source into your project (requires recharts).

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

Overview

Signal Line is Plotcn's reference single-series Cartesian visualization for continuous time-series and ordered metrics. Built on native Recharts primitives and styled with Plotcn's semantic color system, it delivers an uncluttered, high-density analytical surface.

Unlike standard charting defaults, Signal Line eliminates unnecessary marker clutter, replaces noisy grids with quiet horizontal reference rules, and emphasizes data through a crisp active-point dot and vertical nearest-X crosshair.

TSX
import { SignalLine } from "@/components/charts/recharts/line-signal"export function MetricCard() {  return (    <SignalLine      data={telemetryData}      xKey="timestamp"      seriesKey="latency"      color="var(--chart-1)"    />  )}

Best Suited For

Signal Line is specifically calibrated for:

  • Application Performance: Request latency, error rates, and throughput over time.
  • Operational & Infrastructure Telemetry: CPU load, memory pressure, active worker threads, and queue depth.
  • Product Growth & Usage: Daily active users (DAU), conversion rates, checkout velocity, and subscription changes.
  • Financial & Revenue Trends: Monthly recurring revenue (MRR), cash flow runs, and gross merchandise volume (GMV).

When to Avoid

  • Comparing Multiple Series: Use MultiSeriesLine or StackedArea when comparing 3+ independent series simultaneously.
  • Part-to-Whole Ratios: Use Donut or Treemap when the primary question is categorical share rather than temporal progression.
  • Network Topologies: Use D3ForceNetwork for non-Cartesian node and edge graphs.

Installation

Install Signal Line directly into your project via the shadcn CLI. The component source and its required dependencies (recharts and shared Plotcn primitives) will be copied directly into your repository under complete source ownership.

PLOTCN/REGISTRY/LINE-SIGNAL/SOURCE
pnpm dlx shadcn@latest add @plotcn/line-signal

Checking public registry…

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

Copied as source into your project (requires recharts).

Data Contract

Signal Line accepts a readonly array of objects. Each record must contain a horizontal domain key (time, date, or ordered category) and a numeric metric value.

TypeScript
export interface TelemetryPoint {  date: string      // ISO date string, formatted timestamp, or ordinal label  latency: number   // Numeric observation in milliseconds}

Truthful Missing Values

When an observation is missing or disconnected in production, Signal Line respects the data's truth:

Policy Prop Configuration Visual Behavior
PolicyProp ConfigurationVisual Behavior
Gap (Default)missingValuePolicy="gap"Produces an explicit break in the stroke. Missing values are never silently coerced to zero.
ConnectmissingValuePolicy="connect"Line draws across the gap to bridge adjacent valid observations.

Component Props

Core Configuration

Prop Type Default Description
PropTypeDefaultDescription
datareadonly TData[][]Readonly array of observation records. The caller's array is never mutated.
xKeykeyof TData & stringrequiredProperty name representing the horizontal X axis coordinate.
seriesKeykeyof TData & string"value"Property name representing the numeric metric value.
heightnumber | string320Container height in pixels or standard CSS string (e.g. 100%, 380px).
curve"monotone" | "linear" | "step""monotone"Line interpolation. Use monotone for smooth trends, linear for raw points, step for discrete state changes.
domain[number, number] | ["auto", "auto"]"auto"Explicit Y-axis bounds. Single-value datasets automatically expand around the value to prevent zero-height scales.
missingValuePolicy"gap" | "connect""gap"Handling of null or undefined observations.

Visual & Appearance

Prop Type Default Description
PropTypeDefaultDescription
colorstring"var(--chart-1)"Primary stroke color. Supports CSS custom properties, hex, and RGB tokens.
showGridbooleantrueRenders subtle dashed horizontal reference dividers (var(--chart-grid)).
showXAxisbooleantrueRenders horizontal domain tick labels.
showYAxisbooleantrueRenders vertical metric values.
showLegendbooleanfalseToggles series legend. Disabled by default for single-series clarity.
motionboolean | { duration: number }trueLine reveal animation (350ms). Automatically disabled under prefers-reduced-motion.

Accessibility & States

Prop Type Default Description
PropTypeDefaultDescription
titlestring"Signal Line"Accessible name announced by screen readers for the <figure> region.
descriptionstringundefinedExtended description providing context on metric behavior and trends.
loadingbooleanfalseDisplays neutral loading skeleton while preserving chart footprint.
errorError | string | nullnullDisplays actionable error state banner with optional retry trigger.
unavailableboolean | string | nullfalseDisplays metric unavailability notice (e.g. tier restrictions or retention limits).
onRetry() => voidundefinedCallback invoked when user clicks the error retry button.
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):
<SignalLine
  data={data}
  xKey="date"
  seriesKey="value"
/>
Interactive Prop Preview Lab
curve"monotone" | "linear" | "step"

Interpolation strategy between points. Monotone is ideal for smooth rates; step for discrete state shifts.

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

Primary line stroke color. Accepts any CSS variable token or hex string.

Select value to preview live:var(--chart-1, #10b981)
Active: color="var(--chart-1, #10b981)"Default: "var(--chart-1, #10b981)"
heightnumber | string

Container height in pixels or standard CSS dimension strings.

Select value to preview live:
Active: height={320}Default: 320
showGridboolean

Whether to render subtle horizontal background reference gridlines.

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

Handling of null or undefined observations. 'gap' preserves visual breaks; 'connect' bridges adjacent points.

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

Readonly array of observation records. Will not be mutated by the component.

Best for: Primary dataset

xKeyReq
keyof TData & stringYes

Field name for the horizontal axis domain (e.g. date, timestamp, or step).

Best for: Domain mapping

keyof TData & string"value"No

Direct field name for the numeric value to plot.

Best for: Single series lookup

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

Interpolation strategy between points. Monotone is ideal for smooth rates; step for discrete state shifts.

Best for: Visual curve style

string"var(--chart-1, #10b981)"No

Primary line stroke color. Accepts any CSS variable token or hex string.

Best for: Theming & brand identity

number | string320No

Container height in pixels or standard CSS dimension strings.

Best for: Dashboard slot sizing

booleantrueNo

Whether to render subtle horizontal background reference gridlines.

Best for: Grid density control

"gap" | "connect""gap"No

Handling of null or undefined observations. 'gap' preserves visual breaks; 'connect' bridges adjacent points.

Best for: Data safety & truthful representation

"auto" | "all" | "preserve-start" | "preserve-end" | "preserve-both""auto"No

X-axis tick density calculation to prevent label collision on narrow containers.

booleantrueNo

Whether to display the horizontal axis tick labels.

booleantrueNo

Whether to display the vertical axis metric values.

booleanfalseNo

Whether to display a chart legend. Hidden by default for focused single-series clarity.

boolean | { duration?: number }trueNo

Initial line draw animation. Automatically disabled when prefers-reduced-motion is active.

string"Signal Line"No

Accessible name announced to screen-readers for the chart region.

stringundefinedNo

Long-form context describing what the metric trend communicates.

booleanfalseNo

Renders a neutral loading skeleton preserving container footprint without fake data.

Error | string | nullnullNo

Renders an actionable error state banner with optional retry trigger.

boolean | string | nullfalseNo

Renders a metric unavailability notice (e.g. permission restriction or retention limit).

04 / Cookbook & States

Component Variants & Edge States

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

Daily Server Latency

Standard single-series monitoring metric with monotone smoothing and active crosshair.

<SignalLine data={latencyData} xKey="time" seriesKey="ms" color="var(--chart-1)" />

Truthful Data Gap

Missing telemetry observations rendered with explicit visual gaps instead of coercing to zero.

<SignalLine data={downtimeData} xKey="hour" seriesKey="requests" missingValuePolicy="gap" />

Concurrency Limit Changes

Discrete step curve illustrating instantaneous state transitions.

<SignalLine data={capacityData} xKey="date" seriesKey="slots" curve="step" color="#0ea5e9" />

Compact Card Trend

Condensed 180px trend view without Y-axis clutter for dashboard overview tiles.

<SignalLine data={metricData} xKey="day" seriesKey="value" showYAxis={false} height={180} />
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.

Responsive Behavior

Signal Line automatically adapts to its parent container via an internal ResizeObserver:

  • Desktop (1024px+): Spacious margins, full tick intervals, and hover tooltip inspector.
  • Tablet (640px – 1023px): Adaptive X-axis tick thinning to prevent label collision, compact margin offsets.
  • Mobile (< 640px): Aggressive tick reduction (preserving start/end endpoints), full touch-scrub hit area, and edge-to-edge container expansion (w-full px-4).
05 / Responsive Lab

Container-Driven Breakpoints

Signal Line leverages container ResizeObserver measurements to dynamically reduce X-axis tick frequency, preserving legible typography down to 320px containers.

Desktop
>= 1024px

Full tick density, spacious margins, detailed tooltips

Tablet
640px - 1023px

Thinned X-axis labels, preserved line continuity, compact margins

Mobile
< 640px

Aggressive tick thinning, touch scrub target, negative margin compensation

Tablet Preview (768px Container Constraint)
Mobile Preview (390px Container Constraint)

Accessibility & Keyboard Navigation

Signal Line is fully operable without a mouse:

  1. Focusable Region: Pressing Tab focuses the chart container with a prominent focus ring (var(--chart-focus)).
  2. Arrow Key Navigation:
    • Selects the next data observation.
    • Selects the previous data observation.
    • Home Jumps directly to the first observation.
    • End Jumps to the final observation.
    • Esc Clears active selection.
  3. Screen Reader Announcement: A live region announces the selected index and value without requiring pointer interaction.
  4. Factual Figure Summary: An invisible <figcaption> provides an automated quantitative overview (total observation count, net change from start to finish, and min/max extremes).
06 / Assistive Technology

Accessibility & Navigation Standards

Screen-reader figure region with programmatic title and automated factual metric summary (observation count, net delta, min/max bounds).

Semantic Role & Landmark

Container mounts as region with explicit assistive label.

Color-Independent Legibility

Active point renders an enlarged concentric marker; tooltips provide explicit alphanumeric readout.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “VoiceOver and NVDA announce the chart region and summary without traversing dozens of raw SVG nodes.

Reduced Motion Support

Automatically suppresses stroke draw animation when user requests reduced motion.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
TabFocus chart interaction surface with visible focus ring
ArrowLeftSelect previous data observation
ArrowRightSelect next data observation
HomeJump to first data point
EndJump to last data point
EscapeClear active selection

Data Safety Guarantee

  1. Zero Fabricated Fallback Data: Signal Line will never generate fake metrics when data is empty or missing.
  2. Finite Number Enforcement: Non-finite values (NaN, Infinity, -Infinity) are cleanly caught before calculation, preventing broken d="M NaN..." SVG paths.
  3. Safe Scale Expansion: If every datum contains the exact same value (e.g. [42, 42, 42]), the Y-domain deterministically expands ([37, 47]) rather than collapsing into a zero-height division error.
  4. Distinct State Precedence: error, unavailable, loading, and empty are mutually exclusive and strictly prioritized.
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
SignalLine(Root figure wrapper)
└──ChartContainer[CSS token bridge]

Scoped CSS variables for axes, grid, and crosshairs without global pollution.

Involved Source Files & Registry Assets
components/charts/recharts/line-signal.tsx
Primary SignalLine React component implementation
components/charts/shared/chart-container.tsx
CSS variable token bridge and container wrapper
components/charts/shared/chart-tooltip.tsx
Tabular numerical inspection card
components/charts/shared/chart-state.tsx
Loading, empty, and error fallback states
components/charts/shared/use-chart-reduced-motion.ts
prefers-reduced-motion media query hook
registry/recharts/line-signal.tsx
"use client"import * as React from "react"import {  ResponsiveContainer,  LineChart,  Line,  XAxis,  YAxis,  Tooltip,  CartesianGrid,  Legend,} from "recharts"import { useChartReducedMotion } from "../shared/use-chart-reduced-motion"import { ChartLegend } from "../shared/chart-legend"import { ChartContainer } from "../shared/chart-container"import { ChartTooltip } from "../shared/chart-tooltip"import {  ChartLoadingState,  ChartEmptyState,  ChartErrorState,  ChartUnavailableState,} from "../shared/chart-state"import { cn } from "@/lib/utils"/* -------------------------------------------------------------------------- *//*  Type Definitions                                                          *//* -------------------------------------------------------------------------- */export interface SignalSeriesConfig<TData extends Record<string, unknown> = Record<string, unknown>> {  key: keyof TData & string  label?: string  valueFormatter?: (value: number) => string}export interface SignalLineProps<TData extends Record<string, unknown> = Record<string, unknown>> {  /**   * The array of data observations to visualize.   * Accepts a readonly array and will not mutate caller data.   */  data: readonly TData[]  /**   * Key for the horizontal axis domain (e.g. date, timestamp, or ordered category).   */  xKey: keyof TData & string  /**   * Direct key for the numeric series to plot.   * Mutually exclusive or fallback to series.key.   */  seriesKey?: keyof TData & string  /**   * Optional semantic series descriptor containing key, label, and formatter.   */  series?: SignalSeriesConfig<TData>  /**   * Height of the chart container in pixels or standard CSS string.   * Default: 320   */  height?: number | string  /**   * Curve interpolation for the signal line.   * Default: "monotone"   */  curve?: "monotone" | "linear" | "step"  /**   * Explicit Y-axis numeric domain, or "auto" calculation.   */  domain?: [number, number] | ["auto", "auto"]  /**   * Tick thinning policy for the horizontal X axis.   * Default: "auto"   */  tickStrategy?: "auto" | "all" | "preserve-start" | "preserve-end" | "preserve-both"  /**   * Whether to display subtle horizontal background gridlines.   * Default: true   */  showGrid?: boolean  /**   * Whether to display a chart legend.   * Default: false (Signal Line is focused single-series by default).   */  showLegend?: boolean  /**   * Whether to display the horizontal X axis.   * Default: true   */  showXAxis?: boolean  /**   * Whether to display the vertical Y axis.   * Default: true   */  showYAxis?: boolean  /**   * Primary color for the signal line. Defaults to Plotcn semantic token.   * Default: "var(--chart-1)"   */  color?: string  /**   * How missing observations (null or undefined values) are rendered.   * "gap": line breaks at the missing point (truthful representation).   * "connect": line connects adjacent valid points across the gap.   * Default: "gap"   */  missingValuePolicy?: "gap" | "connect"  /**   * Motion configuration or boolean toggle.   * Respects prefers-reduced-motion automatically.   * Default: true   */  motion?: boolean | { duration?: number }  /**   * Accessible title for screen-reader regions and figures.   * Default: "Signal Line"   */  title?: string  /**   * Accessible description of the chart trend and domain.   */  description?: string  /**   * Loading state flag. Renders neutral skeleton preserving layout footprint.   */  loading?: boolean  /**   * Error state or message. Renders actionable error state.   */  error?: Error | string | null  /**   * Metric unavailability flag (e.g. lack of permissions or historical retention limit).   */  unavailable?: boolean | string | null  /**   * Optional custom retry callback for error states.   */  onRetry?: () => void  /**   * Custom empty state content fallback.   */  emptyContent?: React.ReactNode  /**   * Custom error state content fallback.   */  errorContent?: React.ReactNode  /**   * Custom loading state content fallback.   */  loadingContent?: React.ReactNode  /**   * Additional CSS classes applied to the root container.   */  className?: string}/* -------------------------------------------------------------------------- *//*  Pure Data Safety & Domain Algorithms (Self-Contained for Registry)         *//* -------------------------------------------------------------------------- */function isFiniteNumber(val: unknown): val is number {  return typeof val === "number" && Number.isFinite(val)}/** * Calculates a safe numeric Y-domain preventing zero-height scales and NaN attributes. */function calculateSafeDomain(  data: readonly Record<string, unknown>[],  valueKey: string,  explicitDomain?: [number, number] | ["auto", "auto"]): [number, number] | ["auto", "auto"] {  if (explicitDomain) {    return explicitDomain  }  const validValues: number[] = []  for (const item of data) {    const v = item[valueKey]    if (isFiniteNumber(v)) {      validValues.push(v)    }  }  if (validValues.length === 0) {    return [0, 100]  }  const min = Math.min(...validValues)  const max = Math.max(...validValues)  // Single-value domain expansion (Section 19: e.g. all values are 42)  if (min === max) {    if (min === 0) {      return [-1, 1]    }    if (min > 0) {      return [Math.floor(min * 0.9), Math.ceil(min * 1.1)]    }    return [Math.floor(min * 1.1), Math.ceil(min * 0.9)]  }  // Padding extent by 5%  const span = max - min  const pad = span * 0.05  return [min >= 0 ? Math.max(0, Math.floor(min - pad)) : Math.floor(min - pad), Math.ceil(max + pad)]}/** * Normalizes dataset without mutating caller data: * - Drops invalid non-finite values (NaN, Infinity) * - Converts missing points to null for truthful gap rendering */function normalizeSignalData<TData extends Record<string, unknown>>(  data: readonly TData[],  xKey: string,  valueKey: string,  missingPolicy: "gap" | "connect"): Record<string, unknown>[] {  const normalized: Record<string, unknown>[] = []  for (let i = 0; i < data.length; i++) {    const raw = data[i]    const rawVal = raw[valueKey]    const xVal = raw[xKey] ?? `Point ${i + 1}`    let cleanVal: number | null = null    if (isFiniteNumber(rawVal)) {      cleanVal = rawVal    } else if (rawVal === null || rawVal === undefined) {      cleanVal = null    } else {      // Non-finite strings or objects that don't parse to finite numbers      cleanVal = null    }    normalized.push({      ...raw,      [xKey]: xVal,      [valueKey]: cleanVal,    })  }  return normalized}/* -------------------------------------------------------------------------- *//*  Component Implementation                                                  *//* -------------------------------------------------------------------------- */export function SignalLine<TData extends Record<string, unknown> = Record<string, unknown>>({  data = [],  xKey,  seriesKey,  series,  height = 320,  curve = "monotone",  domain,  tickStrategy = "auto",  showGrid = true,  showLegend = false,  showXAxis = true,  showYAxis = true,  color = "var(--chart-1)",  missingValuePolicy = "gap",  motion = true,  title = "Signal Line",  description,  loading = false,  error = null,  unavailable = false,  onRetry,  emptyContent,  errorContent,  loadingContent,  className,}: SignalLineProps<TData>) {  const reducedMotion = useChartReducedMotion()  const activeSeriesKey = series?.key || seriesKey || ("value" as keyof TData & string)  const activeSeriesLabel = series?.label || activeSeriesKey  const valueFormatter = series?.valueFormatter  const containerId = React.useId().replace(/[:]/g, "")  const titleId = `signal-title-${containerId}`  const descId = `signal-desc-${containerId}`  const summaryId = `signal-summary-${containerId}`  const [activeIndex, setActiveIndex] = React.useState<number | null>(null)  // Normalized safe data  const safeData = normalizeSignalData(data, xKey, activeSeriesKey, missingValuePolicy)  const safeDomain = calculateSafeDomain(safeData, activeSeriesKey, domain)  // Factual screen-reader summary (Section 37: factual statements only, no business conclusions)  const validValues = safeData    .map((d) => d[activeSeriesKey])    .filter((v): v is number => isFiniteNumber(v))  const factualSummary = React.useMemo(() => {    if (validValues.length === 0) return "No valid numeric observations recorded."    const count = validValues.length    const min = Math.min(...validValues)    const max = Math.max(...validValues)    const start = validValues[0]    const end = validValues[validValues.length - 1]    const delta = end - start    const direction = delta > 0 ? "rose" : delta < 0 ? "declined" : "remained unchanged"    const fmt = valueFormatter ?? ((n: number) => n.toLocaleString())    const startStr = fmt(start)    const endStr = fmt(end)    const minStr = fmt(min)    const maxStr = fmt(max)    return `Visualizing ${count} observations. The signal ${direction} from ${startStr} to ${endStr}, reaching a minimum of ${minStr} and maximum of ${maxStr}.`  }, [validValues, valueFormatter])  // Motion config  const isAnimated = motion !== false && !reducedMotion  const animationDuration =    typeof motion === "object" && motion?.duration !== undefined ? motion.duration * 1000 : 350  // Tick interval calculation based on strategy  const tickInterval = React.useMemo(() => {    if (tickStrategy === "all") return 0    if (tickStrategy === "preserve-start") return "preserveStart"    if (tickStrategy === "preserve-end") return "preserveEnd"    if (tickStrategy === "preserve-both") return "preserveStartEnd"    // "auto"    if (safeData.length > 30) return Math.ceil(safeData.length / 8)    if (safeData.length > 15) return Math.ceil(safeData.length / 6)    return "preserveStartEnd"  }, [tickStrategy, safeData.length])  if (error) {    if (errorContent) return <div className={cn("w-full", className)} style={{ height }}>{errorContent}</div>    return <div className={cn("w-full", className)} style={{ height }}><ChartErrorState title="Unable to load signal" description={typeof error === "string" ? error : error.message} onRetry={onRetry} /></div>  }  if (unavailable) {    return <div className={cn("w-full", className)} style={{ height }}><ChartUnavailableState title="Signal unavailable" description={typeof unavailable === "string" ? unavailable : undefined} /></div>  }  if (loading) {    if (loadingContent) return <div className={cn("w-full", className)} style={{ height }}>{loadingContent}</div>    return <div className={cn("w-full", className)} style={{ height }}><ChartLoadingState title="Loading signal visualization..." description="Preparing time-series metrics and calculating axes" /></div>  }  if (data.length === 0) {    if (emptyContent) return <div className={cn("w-full", className)} style={{ height }}>{emptyContent}</div>    return <div className={cn("w-full", className)} style={{ height }}><ChartEmptyState title="No signal observations" description="Observations will appear when metrics are recorded for this timeline." /></div>  }  // Keyboard navigation across observations (Section 33, 34)  const handleKeyDown = (e: React.KeyboardEvent) => {    if (safeData.length === 0) return    if (e.key === "ArrowRight") {      e.preventDefault()      setActiveIndex((prev) => (prev === null ? 0 : Math.min(safeData.length - 1, prev + 1)))    } else if (e.key === "ArrowLeft") {      e.preventDefault()      setActiveIndex((prev) => (prev === null ? safeData.length - 1 : Math.max(0, prev - 1)))    } else if (e.key === "Home") {      e.preventDefault()      setActiveIndex(0)    } else if (e.key === "End") {      e.preventDefault()      setActiveIndex(safeData.length - 1)    } else if (e.key === "Escape") {      e.preventDefault()      setActiveIndex(null)    }  }  const defaultFormatter = (val: number | string) => {    if (typeof val === "number" && isFiniteNumber(val)) {      return valueFormatter ? valueFormatter(val) : val.toLocaleString()    }    return String(val ?? "—")  }  return (    <figure      role="region"      aria-labelledby={titleId}      aria-describedby={description ? descId : summaryId}      tabIndex={0}      onKeyDown={handleKeyDown}      onBlur={() => setActiveIndex(null)}      className={cn(        "group relative flex flex-col w-full outline-none focus-visible:ring-2 focus-visible:ring-[var(--chart-focus)] rounded-xl transition-all",        className      )}      style={{ height }}    >      <figcaption className="sr-only">        <h3 id={titleId}>{title}</h3>        {description && <p id={descId}>{description}</p>}        <p id={summaryId}>{factualSummary}</p>      </figcaption>      <ChartContainer className="w-full h-full">        <ResponsiveContainer          width="100%"          height="100%"          minWidth={0}          minHeight={0}          initialDimension={{ width: 320, height: typeof height === "number" ? height : 320 }}        >          <LineChart            data={safeData}            margin={{ top: 12, right: 14, left: showYAxis ? -16 : 10, bottom: showXAxis ? 4 : 4 }}          >            {showGrid && (              <CartesianGrid                strokeDasharray="3 3"                vertical={false}                stroke="var(--chart-grid)"              />            )}            <XAxis              hide={!showXAxis}              dataKey={xKey as any}              tickLine={false}              axisLine={false}              interval={tickInterval}              tick={{ fontSize: 11, fill: "var(--chart-axis)" }}              dy={6}            />            <YAxis              hide={!showYAxis}              domain={safeDomain as any}              tickLine={false}              axisLine={false}              tick={{ fontSize: 11, fill: "var(--chart-axis)" }}              tickFormatter={valueFormatter ? (v) => valueFormatter(Number(v)) : undefined}              dx={-4}            />            <Tooltip              content={                <ChartTooltip                  indicator="line"                  formatter={(val, name) => defaultFormatter(val)}                  labelFormatter={(label) => label}                  compact={typeof height === "number" ? height <= 260 : false}                />              }              cursor={{                stroke: "var(--chart-crosshair)",                strokeDasharray: "3 3",                strokeWidth: 1.2,              }}            />            {showLegend && <Legend content={<ChartLegend/>} />}            <Line              type={curve}              dataKey={activeSeriesKey}              name={activeSeriesLabel}              stroke={color}              strokeWidth={2}              dot={                safeData.length === 1                  ? { r: 4.5, fill: color, stroke: "var(--chart-background)", strokeWidth: 2 }                  : false              }              activeDot={{                r: 4.5,                fill: color,                stroke: "var(--chart-background)",                strokeWidth: 2,              }}              connectNulls={missingValuePolicy === "connect"}              isAnimationActive={isAnimated}              animationDuration={animationDuration}              animationEasing="ease-out"            />          </LineChart>        </ResponsiveContainer>      </ChartContainer>      {/* Visual Indicator for Keyboard Inspection */}      <div className="sr-only" aria-live="polite">        {activeIndex !== null && safeData[activeIndex] && (          <span>            Observation {activeIndex + 1} of {safeData.length}: {String(safeData[activeIndex][xKey])} is{" "}            {String(safeData[activeIndex][activeSeriesKey] ?? "missing")}          </span>        )}      </div>    </figure>  )}