005 / RECHARTS / LINE

Step Signal

Recharts

Stepped visualization for discrete changes that remain in effect until the next observation, communicating quotas, pricing plans, and configuration states.

SPEC
#005
ENGINE
Recharts
FAMILY
Line
RENDERER
svg
STATUS
preview

Installation

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

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

Step Signal is a dedicated visualization for discrete changes that remain in effect until the next observation. It visualizes horizontal level segments with crisp 90° transitions, ensuring that time elapsed between recorded points communicates state persistence rather than gradual numerical movement.

A continuous trend line implies that values smoothly slope between two measurements (e.g. 100 → 101 → ... → 140). In contrast, Step Signal communicates:

SVG FLOW ANIMATIONState Persistence Architecture

Discrete Level Persistence & Boundary Transitions

Active Level
90° Boundary
Horizontal = Duration

Levels remain active and constant across time intervals. No artificial drift.

Vertical = Transition

Clean 90° boundary at the milestone. Zero time spent at intermediate states.

Truthful State Model

Engineered specifically for quotas, rate plans, and discrete system states.

The level remains active and constant until the boundary event occurs. That semantic distinction is the core reason Step Signal exists.

TSX
import { StepSignal } from "@/components/charts/recharts/line-step-signal"export function ApiRateLimitCard() {  return (    <StepSignal      data={quotaData}      xKey="period"      seriesKey="limit"      label="API Request Limit"      stepMode="after"      showGrid    />  )}

The Step Model

Standard Cartesian line charts interpolate continuous trajectories between coordinates (x0,y0)(x_0, y_0) and (x1,y1)(x_1, y_1). While appropriate for continuous measurements like temperature or sensor voltage, this interpolation is factually false for configured policies, pricing schedules, and discrete states.

In Step Signal, the coordinate space represents two distinct mathematical concepts:

  1. Horizontal Segments: Indicate duration. A horizontal line spanning from January to April at height $10,000$ signifies that the active policy or quota remained exactly $10,000$ throughout that entire interval.
  2. Vertical Segments: Indicate transitions. A vertical segment at April 15 marks the moment a transition took effect. No intermediate states exist; the system does not spend time at $12,500$ halfway through the transition.
INTERVAL & TRANSITION ANATOMYMathematical Coordinate Model

Interval Persistence vs Instantaneous Step Shift

Interval: 104 days · Shift: 0 days (Instant)
✓ Invariant:Between Jan 01 and Apr 15, the system never operated at 12,500.

Transition Semantics

Step Signal provides a semantic stepMode property ("after", "before", or "center") that defines exactly when an observation's value takes effect along the timeline:

After (stepMode="after", Default)

The value recorded at observation XX takes effect at that coordinate and remains in effect onward until the subsequent observation.

  • Mental Model: "Starting on April 15th, our new limit is 15,000."
  • Visual Path: Horizontal from x0x_0 to x1x_1 at level y0y_0, then a vertical step to y1y_1 at coordinate x1x_1.

Before (stepMode="before")

The value recorded at observation XX takes effect immediately prior to that coordinate.

  • Mental Model: "The target level is achieved by the milestone date."
  • Visual Path: Vertical step from y0y_0 to y1y_1 at coordinate x0x_0, followed by a horizontal segment to x1x_1 at level y1y_1.

Center (stepMode="center")

The transition between levels occurs halfway between adjacent observation points.

  • Mental Model: Transition occurs at the interval midpoint.
  • Visual Path: Horizontal to (x0+x1)/2(x_0 + x_1) / 2, vertical step to y1y_1, and horizontal continuation to x1x_1.

Best Suited For

Step Signal is engineered specifically for discrete numeric states:

  • API Rate Limits & Quotas: Request thresholds and concurrency limits that stay constant across billing windows.
  • Subscription Pricing Plans: Tier prices that remain fixed over contract terms before jumping to updated rates.
  • Infrastructure Capacity Allocation: Provisioned server instances, container replicas, or worker pool counts.
  • Feature Rollout Schedules: Deployment percentage gates (10%25%50%100%10\% \rightarrow 25\% \rightarrow 50\% \rightarrow 100\%) across verification phases.
  • Staffing & Budget Ceilings: Approved headcount limits or department budget caps by fiscal quarter.

When to Avoid

  • Continuously Measured Metrics: Use SignalLine or PulseLine for temperatures, revenue velocity, or network latency.
  • Multi-Series Comparisons: Use TwinlineCompare when benchmarking two competing time-series with continuous curves.
  • Interval & Confidence Envelopes: Use RangeLine when displaying uncertainty bands or tolerance ranges.
  • Non-Numeric Categorical States: Avoid forcing text states ("offline", "degraded", "healthy") onto a quantitative axis; use a state timeline component instead.

Installation

Install Step Signal directly into your project using the shadcn CLI. The component source and its minimal dependencies (recharts and shared Plotcn primitives) will be copied directly into your codebase under full ownership.

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

Checking public registry…

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

Copied as source into your project (requires recharts).

Data Contract

Step Signal accepts a readonly array of observations. Each record must contain a horizontal domain key and an active numeric level property.

TypeScript
export interface StepSignalDatum {  date: string       // Horizontal domain coordinate (e.g. "Jan 01", "2026-Q1")  limit: number      // Active numeric state level in effect}

State Persistence & Missing Value Policy

Handling missing data in discrete state charts requires deliberate domain decisions:

  1. Gap Policy (missingValuePolicy="gap", Default): If an observation's value is null or undefined, Step Signal treats the state as unknown. The stepped line breaks cleanly across the interval, honestly communicating that state was unrecorded.
  2. Carry Policy (missingValuePolicy="carry"): For configuration timelines where unrecorded periods inherit the prior valid level, setting missingValuePolicy="carry" propagates the last known finite level forward without creating artificial transitions.
  3. No Coercion to Zero: Unrecorded points are never coerced to 0. A missing limit does not imply a limit of zero.
  4. Out-of-Order Input Protection: Caller data is never mutated; observations are processed in caller-provided order.

Component Props

Property Type Default Required Description
PropertyTypeDefaultRequiredDescription
datareadonly TData[][]RequiredReadonly array of observation records. Caller data is never mutated.
xKeykeyof TData & stringRequiredProperty name for the horizontal X-axis domain coordinates.
seriesKeykeyof TData & string"limit"OptionalProperty name for the active numeric state level.
stepMode"after" | "before" | "center""after"OptionalTransition mode: "after" steps at point, "before" steps prior, "center" steps halfway.
missingValuePolicy"gap" | "carry""gap"OptionalHow missing observations are handled: "gap" breaks the path, "carry" persists the previous valid state.
heightnumber | string340OptionalContainer height in pixels or standard CSS dimension strings.
colorstring"var(--chart-1, #10b981)"OptionalPrimary theme stroke color for the stepped line.
showGridbooleantrueOptionalWhether to render subtle horizontal background reference gridlines.
showLegendbooleanfalseOptionalWhether to render a chart legend.
showXAxisbooleantrueOptionalWhether to render the horizontal category axis with tick thinning.
showYAxisbooleantrueOptionalWhether to render the vertical numeric scale.
showTransitionDeltabooleanfalseOptionalWhether to compute and display previous state and quantitative change in the tooltip.
referenceLinesStepReferenceLine[][]OptionalStatic horizontal reference lines representing quotas or SLA ceilings.
motionboolean | { duration?: number }trueOptionalEnables smooth entrance reveal while strictly maintaining 90° step geometry.
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):
<StepSignal
  data={data}
  xKey="date"
  seriesKey="limit"
/>
Interactive Prop Preview Lab
stepMode"after" | "before" | "center"

Semantic transition mode: 'after' takes effect at X and remains active onward; 'before' jumps immediately prior; 'center' transitions halfway.

Select value to preview live:
Active: stepMode="after"Default: "after"
missingValuePolicy"gap" | "carry"

Handling of null/undefined values: 'gap' breaks the line truthfully; 'carry' propagates the last known valid state.

Select value to preview live:
Active: missingValuePolicy="gap"Default: "gap"
heightnumber | string

Container height in pixels or standard CSS dimension strings.

Select value to preview live:
Active: height={340}Default: 340
colorstring

Primary theme stroke color for the stepped signal line.

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

Whether to render subtle horizontal background reference gridlines.

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

Whether to compute and display the prior state level and quantitative change in the tooltip on transitions.

Select value to preview live:
Active: showTransitionDelta={false}Default: false
All Properties (22)
Component properties
PropertyTypeDefaultRequiredDescription
dataReq
readonly TData[][]Yes

Readonly array of structured observation records. Caller data is never mutated.

Best for: Primary dataset

xKeyReq
keyof TData & stringYes

Property name for the horizontal X-axis domain (e.g. date, month, or sprint).

Best for: Domain coordinates

keyof TData & string"limit"No

Property name for the numeric state value plotted with stepped geometry.

Best for: Primary state level

"after" | "before" | "center""after"No

Semantic transition mode: 'after' takes effect at X and remains active onward; 'before' jumps immediately prior; 'center' transitions halfway.

Best for: Transition timing calibration

"gap" | "carry""gap"No

Handling of null/undefined values: 'gap' breaks the line truthfully; 'carry' propagates the last known valid state.

Best for: Missing state semantics

number | string340No

Container height in pixels or standard CSS dimension strings.

Best for: Dashboard slot sizing

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

Primary theme stroke color for the stepped signal line.

Best for: Thematic branding

booleantrueNo

Whether to render subtle horizontal background reference gridlines.

Best for: Level comparison

booleanfalseNo

Whether to compute and display the prior state level and quantitative change in the tooltip on transitions.

Best for: Transition inspection

[number, number] | ["auto", "auto"]"auto"No

Explicit Y-axis numeric domain, or auto calculated with safe padding.

booleantrueNo

Whether to display the horizontal category axis with tick thinning.

booleantrueNo

Whether to display the vertical numeric scale.

booleanfalseNo

Whether to display the chart legend. Single-series step signals keep this off by default.

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

Custom formatter for Y-axis ticks and tooltip state numbers.

(value: string | number) => stringStringNo

Custom formatter for X-axis tick labels.

StepReferenceLine[][]No

Array of static horizontal reference lines representing quotas or SLAs.

boolean | { duration?: number }trueNo

Controls entry reveal animations, respecting user reduced motion preferences.

string"Step Signal Chart"No

Accessible heading announced by screen readers.

stringundefinedNo

Long-form context describing what the discrete states represent.

booleanfalseNo

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

Error | string | nullnullNo

Renders an actionable error state banner with optional retry trigger.

boolean | string | nullfalseNo

Renders a metric unavailability notice (e.g. unconfigured plan or missing policy model).

04 / Cookbook & States

Component Variants & Edge States

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

API Request Quota Plan

Tiered request limits by quarter with stepAfter transitions ensuring limits remain constant throughout billing periods.

<StepSignal data={quotaData} xKey="period" seriesKey="limit" label="Request Limit" stepMode="after" />

SaaS Subscription Pricing History

Discrete pricing tier changes over 2 years with localized currency formatting and transition delta inspection.

<StepSignal data={pricingData} xKey="date" seriesKey="price" label="Monthly Subscription" color="#0ea5e9" showTransitionDelta valueFormatter={(v) => `$${v}/mo`} />

Worker Pool Allocation (Carry Policy)

Provisioned compute workers using carry policy across unobserved maintenance windows without dropping to zero.

<StepSignal data={workerData} xKey="hour" seriesKey="workers" label="Allocated Workers" missingValuePolicy="carry" color="#8b5cf6" />

Feature Flag Rollout Schedule

Staged feature rollout percentages across deployment stages with centered step transitions.

<StepSignal data={rolloutData} xKey="stage" seriesKey="rollout" label="Rollout %" stepMode="center" color="#f59e0b" valueFormatter={(v) => `${v}%`} />
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

Step Signal enforces container-driven responsive design:

  • Desktop (1024px\ge 1024\text{px}): Full transition inspection, formatted transition delta context, all horizontal X ticks, and room for reference threshold labels.
  • Tablet (640px1023px640\text{px} - 1023\text{px}): Intelligent X-axis label thinning via the Plotcn automatic tick manager, compact margins, and preserved step corner geometry.
  • Mobile (<640px< 640\text{px}): Touch-first scrub inspection, preserved state change points, zero diagonal slope simplification.
Core Rule: Responsive thinning may reduce non-essential axis tick labels, but never removes transition points or simplifies stepped geometry into a diagonal slope.
05 / Responsive Lab

Container-Driven Breakpoints

Step Signal preserves every discrete transition boundary across all container widths. Responsive thinning reduces non-essential tick labels without smoothing or simplifying the stepped geometry.

Desktop
>= 1024px

Full transition inspection, formatted transition delta context, all horizontal X ticks, and room for reference threshold badges.

Tablet
640px - 1023px

Thinned X-axis labels, preserved step corners, compact margins, tooltip pinned within viewport boundaries.

Mobile
< 640px

Edge-to-edge scrub inspection, preserved state change points, zero diagonal slope simplification, touch-first scrub interaction.

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

Accessibility & Screen Readers

Step Signal provides comprehensive accessibility for assistive technologies:

  • Semantic Role: The chart container renders as <figure role="region"> with proper aria-labelledby and aria-describedby attributes.
  • Factual Summary: Announces observation count, transition count, starting state, and ending state (e.g. "Discrete step visualization depicting 6 observations. Values remain constant between transitions and change at discrete boundaries. Initial level is 10,000, ending at 20,000. Recorded 3 distinct state transitions.").
  • Keyboard Navigation:
    • ArrowRight: Move focus to the next observation or state transition.
    • ArrowLeft: Move focus to the previous observation.
    • Home: Jump directly to the initial observation.
    • End: Jump directly to the latest observation.
    • Escape: Dismiss active inspection tooltip.
  • Color Independence: Stepped horizontal levels and vertical transitions communicate state changes geometrically, requiring zero color reliance.
  • Reduced Motion: Full support for prefers-reduced-motion: reduce; renders final stepped paths immediately without animation delay.
06 / Assistive Technology

Accessibility & Navigation Standards

Factual screen reader announcement reporting observation count, transition count, initial state value, and final state value.

Semantic Role & Landmark

Container mounts as region with explicit assistive label.

Color-Independent Legibility

Geometry alone communicates state persistence and transitions; zero color reliance for understanding level changes.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “Values remain level between observations and change at discrete transition points.

Reduced Motion Support

Animations disable automatically under prefers-reduced-motion; final stepped geometry renders instantaneously.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
ArrowRightAdvance to the next observation or transition point.
ArrowLeftNavigate to the previous observation or transition point.
HomeJump directly to the initial observation state.
EndJump directly to the latest observation state.
EscapeDismiss active tooltip inspection and reset focus.

Data Safety Guarantees

Step Signal upholds Plotcn's rigorous data safety standards:

  • ✓ No Missing-to-Zero Coercion: Missing values remain explicit gaps or carried states; never coerced to 0.
  • ✓ No Gradual Slope Interpolation: Diagonal curve types (monotone, linear) are intentionally omitted from public props.
  • ✓ Constant States Remain Constant: Datasets with identical values render a true horizontal level without domain collapse or flatlining.
  • ✓ Safe Domain Expansion: Single observations and constant levels automatically expand with sensible padding.
  • ✓ Non-Finite Filtering: NaN, Infinity, and -Infinity are safely treated as missing values, preventing malformed SVG paths.
  • ✓ Caller Data Immutability: Input data arrays and observation records are never mutated.
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
StepSignal(Root figure wrapper with keyboard navigation and ARIA accessibility shell)
├──ChartContainer[Responsive viewport host]

Manages container constraints and CSS variable token mapping

└──LineChart[Recharts Cartesian SVG coordinator]

Synchronizes horizontal X-axis, vertical Y-axis, grid, and stepped line paths

Involved Source Files & Registry Assets
registry/recharts/line-step-signal.tsx
Pure Recharts Step Signal component with data normalization, transition logic, and a11y shell
registry/recharts/line-step-signal.tsx
"use client"import * as React from "react"import {  ResponsiveContainer,  LineChart,  Line,  XAxis,  YAxis,  Tooltip,  CartesianGrid,  Legend,  ReferenceLine as RechartsReferenceLine,} from "recharts"import { useChartReducedMotion } from "../shared/use-chart-reduced-motion"import { ChartContainer } from "../shared/chart-container"import {  ChartLoadingState,  ChartEmptyState,  ChartErrorState,  ChartUnavailableState,} from "../shared/chart-state"import { cn } from "@/lib/utils"/* -------------------------------------------------------------------------- *//*  Type Definitions                                                          *//* -------------------------------------------------------------------------- */export type StepMode = "after" | "before" | "center"export interface StepSeriesConfig<TData extends Record<string, unknown> = Record<string, unknown>> {  key?: keyof TData & string  label?: string  valueFormatter?: (value: number) => string  color?: string}export interface StepReferenceLine {  value: number  label?: string  color?: string  strokeDasharray?: string}export interface StepSignalProps<TData extends Record<string, unknown> = Record<string, unknown>> {  /**   * The array of structured observation records to visualize.   * Accepts a readonly array and will not mutate caller data.   */  data: readonly TData[]  /**   * Key for the horizontal axis domain (e.g. date, month, or ordered category).   */  xKey: keyof TData & string  /**   * Direct key for the active numeric series level to plot.   * Fallback to series.key if not provided.   */  seriesKey?: keyof TData & string  /**   * Optional semantic series descriptor combining key, label, formatter, and color.   */  series?: StepSeriesConfig<TData>  /**   * Human-readable label for the stepped signal (used in tooltips, legends, and screen readers).   * Default: "Level" or series.label   */  label?: string  /**   * Primary stroke color for the stepped signal line. Accepts CSS variables or color values.   * Default: "var(--chart-1, #10b981)"   */  color?: string  /**   * Semantic step transition mode determining when a new value takes effect:   * - "after": New value takes effect at observation X and remains active onward (default, Recharts stepAfter).   * - "before": Value jumps immediately prior to observation X (Recharts stepBefore).   * - "center": Transition midpoint occurs between observations (Recharts step).   * Default: "after"   */  stepMode?: StepMode  /**   * Container height in pixels or standard CSS dimension strings.   * Default: 340   */  height?: number | string  /**   * Explicit Y-axis numeric domain, or "auto" calculation.   * Default: "auto"   */  domain?: [number, number] | ["auto", "auto"]  /**   * Handling of null or undefined values in the series:   * - "gap": Truthful break in the signal where state is unknown (default).   * - "carry": Persists the last known valid level across unrecorded intervals.   * Default: "gap"   */  missingValuePolicy?: "gap" | "carry"  /**   * Formatter function for Y-axis ticks and tooltip values.   */  valueFormatter?: (value: number) => string  /**   * Formatter function for X-axis tick labels.   */  xFormatter?: (value: string | number) => string  /**   * Whether to display subtle horizontal background reference gridlines.   * Default: true   */  showGrid?: boolean  /**   * Whether to display the chart legend.   * Default: false (single-series step signal focuses on clear title/series hierarchy)   */  showLegend?: boolean  /**   * Whether to display the horizontal category axis.   * Default: true   */  showXAxis?: boolean  /**   * Whether to display the vertical value axis.   * Default: true   */  showYAxis?: boolean  /**   * Whether to calculate and display transition delta (change from previous valid state) in the tooltip.   * Default: false   */  showTransitionDelta?: boolean  /**   * Optional reference thresholds (e.g. quota limits, contractual ceilings).   */  referenceLines?: readonly StepReferenceLine[]  /**   * Enable or disable entry and update transitions.   * Default: true   */  motion?: boolean | { duration?: number }  /**   * Accessible title announced by screen readers.   * Default: "Step Signal Chart"   */  title?: string  /**   * Optional long-form description for assistive technologies.   */  description?: string  /**   * Loading state indicator.   */  loading?: boolean  /**   * Error state indicator or Error instance.   */  error?: Error | string | null  /**   * Unavailable state indicator.   */  unavailable?: boolean | string  /**   * Callback invoked when the user clicks retry in the error state.   */  onRetry?: () => void  /**   * Custom content overrides for state placeholders.   */  emptyContent?: React.ReactNode  errorContent?: React.ReactNode  loadingContent?: React.ReactNode  /**   * Additional CSS classes applied to the root figure element.   */  className?: string}/* -------------------------------------------------------------------------- *//*  Pure Data Safety & Domain Algorithms                                      *//* -------------------------------------------------------------------------- */export function isFiniteNumber(val: unknown): val is number {  return typeof val === "number" && Number.isFinite(val)}export interface NormalizedStepDatum {  __x: string | number  __value: number | null  __previousValue: number | null  __delta: number | null  __isTransition: boolean  __isCarried: boolean  __raw: Record<string, unknown>}/** * Normalizes user observation records with strict step semantics: * 1. Missing values are handled according to policy: *    - "gap": null remains null (creates a clean break in the stepped signal). *    - "carry": persists the last known finite level forward. * 2. Detects exact change boundaries where current != previous valid state. * 3. Never mutates caller records or arrays. * 4. Filters or normalizes non-finite values (NaN, Infinity) safely to null. */export function normalizeStepSignalData<TData extends Record<string, unknown>>(  data: readonly TData[],  xKey: keyof TData & string,  seriesKey: string,  missingValuePolicy: "gap" | "carry" = "gap"): NormalizedStepDatum[] {  if (!Array.isArray(data) || data.length === 0) return []  let lastKnownValid: number | null = null  let previousValidState: number | null = null  return data.map((d) => {    const rawX = d[xKey]    const xVal = typeof rawX === "string" || typeof rawX === "number" ? rawX : String(rawX ?? "")    const rawV = d[seriesKey]    const isDirectFinite = isFiniteNumber(rawV)    let finalVal: number | null = null    let isCarried = false    if (isDirectFinite) {      finalVal = rawV      lastKnownValid = rawV    } else if (missingValuePolicy === "carry" && lastKnownValid !== null) {      finalVal = lastKnownValid      isCarried = true    } else {      finalVal = null      if (missingValuePolicy === "gap") {        // In gap policy, an unknown value breaks continuity; previous state does not immediately precede next entry        lastKnownValid = null      }    }    let isTransition = false    let delta: number | null = null    const prev = previousValidState    if (finalVal !== null) {      if (prev !== null && prev !== finalVal && !isCarried) {        isTransition = true        delta = finalVal - prev      }      previousValidState = finalVal    } else {      previousValidState = null    }    return {      __x: xVal,      __value: finalVal,      __previousValue: prev,      __delta: delta,      __isTransition: isTransition,      __isCarried: isCarried,      __raw: d,    }  })}/** * Calculates factual transition summary metrics for accessibility and telemetry: * - transitionCount: Count of distinct state changes (consecutive valid observations with unequal values). * - initialValue: First valid observation value. * - finalValue: Last valid observation value. */export function calculateStepTransitions(  normalized: readonly NormalizedStepDatum[]): {  transitionCount: number  initialValue: number | null  finalValue: number | null} {  const valid = normalized.filter((d) => d.__value !== null)  if (valid.length === 0) {    return { transitionCount: 0, initialValue: null, finalValue: null }  }  let transitions = 0  for (let i = 1; i < valid.length; i++) {    // Only count genuine state changes (unequal values where second point was not merely carried)    if (valid[i].__value !== valid[i - 1].__value && !valid[i].__isCarried) {      transitions++    }  }  return {    transitionCount: transitions,    initialValue: valid[0].__value,    finalValue: valid[valid.length - 1].__value,  }}/** * Calculates a safe Cartesian Y-domain covering all valid levels and reference thresholds. * Safely expands constant levels (e.g. all 100) and handles zero/negative numbers without collapsing. */export function calculateStepDomain(  normalized: readonly NormalizedStepDatum[],  explicitDomain?: [number, number] | ["auto", "auto"] | "auto",  referenceLines?: readonly StepReferenceLine[]): [number, number] {  if (    Array.isArray(explicitDomain) &&    typeof explicitDomain[0] === "number" &&    typeof explicitDomain[1] === "number" &&    Number.isFinite(explicitDomain[0]) &&    Number.isFinite(explicitDomain[1])  ) {    return explicitDomain  }  const values: number[] = []  for (const item of normalized) {    if (item.__value !== null) values.push(item.__value)  }  if (referenceLines && referenceLines.length > 0) {    for (const ref of referenceLines) {      if (isFiniteNumber(ref.value)) values.push(ref.value)    }  }  if (values.length === 0) {    return [0, 100]  }  const min = Math.min(...values)  const max = Math.max(...values)  // Single value or constant level: expand gracefully so the stepped line is visibly centered  if (min === max) {    if (min === 0) return [-10, 10]    const delta = Math.abs(min) * 0.15 || 10    return [Math.floor(min - delta), Math.ceil(max + delta)]  }  const span = max - min  const pad = span * 0.08  return [Math.floor(min - pad), Math.ceil(max + pad)]}/* -------------------------------------------------------------------------- *//*  Synchronized Step Tooltip                                                 *//* -------------------------------------------------------------------------- */interface StepTooltipContentProps {  active?: boolean  payload?: readonly { dataKey?: string | number; value?: any; payload?: any; [key: string]: any }[]  label?: React.ReactNode  primaryColor: string  seriesLabel: string  valueFormatter?: (value: number) => string  showTransitionDelta?: boolean}function StepTooltipContent({  active,  payload,  label: _label,  primaryColor,  seriesLabel,  valueFormatter,  showTransitionDelta = false,}: StepTooltipContentProps) {  if (!active || !payload || payload.length === 0) return null  const datum = payload[0]?.payload as NormalizedStepDatum | undefined  if (!datum) return null  const fmt = valueFormatter ?? ((n: number) => n.toLocaleString())  const val = datum.__value  const hasValue = val !== null  const isCarried = datum.__isCarried  const isTransition = datum.__isTransition  const delta = datum.__delta  const prev = datum.__previousValue  return (    <div className="z-50 min-w-[190px] rounded-lg border border-[var(--chart-tooltip-border)] bg-[var(--chart-tooltip-background)] p-2.5 text-xs shadow-md backdrop-blur-md">      <div className="mb-2 flex items-center justify-between gap-2 font-mono text-[11px] text-[var(--chart-tooltip-muted)]">        <span>{datum.__x}</span>        {isTransition && (          <span className="rounded bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-400 border border-emerald-500/20">            Transition          </span>        )}        {isCarried && (          <span className="rounded bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-400 border border-white/10">            Carried          </span>        )}      </div>      <div className="space-y-1.5">        {/* Active State Level */}        <div className="flex items-center justify-between gap-3">          <div className="flex items-center gap-1.5">            <span              className="h-2 w-2 rounded-sm"              style={{ backgroundColor: primaryColor }}            />            <span className="font-medium text-[var(--chart-tooltip-foreground)]">              {seriesLabel}            </span>          </div>          <span className="font-mono font-semibold text-[var(--chart-tooltip-foreground)]">            {hasValue ? fmt(val) : "—"}          </span>        </div>        {/* Optional Transition Delta Context */}        {showTransitionDelta && isTransition && delta !== null && prev !== null && (          <div className="mt-2 border-t border-[var(--chart-tooltip-border)] pt-1.5 text-[11px] space-y-1">            <div className="flex items-center justify-between text-[var(--chart-tooltip-muted)]">              <span>Prior State</span>              <span className="font-mono">{fmt(prev)}</span>            </div>            <div className="flex items-center justify-between font-mono font-medium text-[var(--chart-tooltip-foreground)]">              <span>Change</span>              <span className="text-zinc-200">                {delta > 0 ? `+${fmt(delta)}` : fmt(delta)}              </span>            </div>          </div>        )}      </div>    </div>  )}/* -------------------------------------------------------------------------- *//*  Custom Step Legend                                                        *//* -------------------------------------------------------------------------- */interface StepLegendContentProps {  label: string  primaryColor: string}function StepLegendContent({ label, primaryColor }: StepLegendContentProps) {  return (    <div className="flex items-center justify-center gap-6 pt-3 text-xs">      <div className="flex items-center gap-2">        <span          className="h-1 w-4 rounded-xs"          style={{ backgroundColor: primaryColor }}        />        <span className="text-[var(--chart-foreground)] font-medium">{label}</span>      </div>    </div>  )}/* -------------------------------------------------------------------------- *//*  Component Implementation                                                  *//* -------------------------------------------------------------------------- */export function StepSignal<TData extends Record<string, unknown> = Record<string, unknown>>({  data = [],  xKey,  seriesKey: propSeriesKey,  series,  label: propLabel,  color: propColor,  stepMode = "after",  height = 340,  domain,  missingValuePolicy = "gap",  valueFormatter,  xFormatter,  showGrid = true,  showLegend = false,  showXAxis = true,  showYAxis = true,  showTransitionDelta = false,  referenceLines = [],  motion = true,  title = "Step Signal Chart",  description,  loading = false,  error = null,  unavailable = false,  onRetry: _onRetry,  emptyContent,  errorContent,  loadingContent,  className,}: StepSignalProps<TData>) {  const reducedMotion = useChartReducedMotion()  const containerId = React.useId().replace(/[:]/g, "")  const titleId = `step-title-${containerId}`  const descId = `step-desc-${containerId}`  const summaryId = `step-summary-${containerId}`  const [, setActiveIndex] = React.useState<number | null>(null)  // Resolve series identity, label, and theme color  const seriesKey = series?.key ?? propSeriesKey ?? ("limit" as keyof TData & string)  const seriesLabel = series?.label ?? propLabel ?? "Level"  const primaryColor = series?.color ?? propColor ?? "var(--chart-1, #10b981)"  // Normalized safe observation data  const normalizedData = React.useMemo(    () => normalizeStepSignalData(data, xKey, seriesKey, missingValuePolicy),    [data, xKey, seriesKey, missingValuePolicy]  )  // Factual transition summary for screen readers  const transitionSummary = React.useMemo(    () => calculateStepTransitions(normalizedData),    [normalizedData]  )  // Safe calculated domain covering levels and reference thresholds  const safeDomain = React.useMemo(    () => calculateStepDomain(normalizedData, domain, referenceLines),    [normalizedData, domain, referenceLines]  )  // Map public stepMode to Recharts curve type  const rechartsCurveType = React.useMemo(() => {    switch (stepMode) {      case "before":        return "stepBefore"      case "center":        return "step"      case "after":      default:        return "stepAfter"    }  }, [stepMode])  // Animation config  const isAnimated = motion !== false && !reducedMotion  const animationDuration =    typeof motion === "object" && motion?.duration !== undefined ? motion.duration * 1000 : 350  // Screen reader factual accessibility summary  const factualSummary = React.useMemo(() => {    if (normalizedData.length === 0) return "No step signal observations recorded."    const fmt = valueFormatter ?? ((n: number) => n.toLocaleString())    const initStr = transitionSummary.initialValue !== null ? fmt(transitionSummary.initialValue) : "none"    const finalStr = transitionSummary.finalValue !== null ? fmt(transitionSummary.finalValue) : "none"    return `Discrete step visualization depicting ${normalizedData.length} observations. Values remain constant between transitions and change at discrete boundaries. Initial level is ${initStr}, ending at ${finalStr}. Recorded ${transitionSummary.transitionCount} distinct state transitions.`  }, [normalizedData, transitionSummary, valueFormatter])  if (error) {    if (errorContent) {      return (        <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>          {errorContent}        </div>      )    }    return (      <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>        <ChartErrorState          title="Unable to load step signal"          description={            typeof error === "string"              ? error              : error?.message || "An unexpected error occurred while loading state transitions."          }        />      </div>    )  }  if (unavailable) {    return (      <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>        <ChartUnavailableState          title="Step metrics unavailable"          description={            typeof unavailable === "string"              ? unavailable              : "State transition metrics are unavailable for this view."          }        />      </div>    )  }  if (loading) {    if (loadingContent) {      return (        <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>          {loadingContent}        </div>      )    }    return (      <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>        <ChartLoadingState          title="Loading step visualization…"          description="Synchronizing discrete state levels"        />      </div>    )  }  if (data.length === 0 || normalizedData.length === 0) {    if (emptyContent) {      return (        <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>          {emptyContent}        </div>      )    }    return (      <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>        <ChartEmptyState          title="No step signal data"          description="Provide ordered observations to visualize discrete changes."        />      </div>    )  }  // Keyboard navigation across observations  const handleKeyDown = (e: React.KeyboardEvent) => {    if (normalizedData.length === 0) return    if (e.key === "ArrowRight") {      e.preventDefault()      setActiveIndex((prev) => (prev === null ? 0 : Math.min(normalizedData.length - 1, prev + 1)))    } else if (e.key === "ArrowLeft") {      e.preventDefault()      setActiveIndex((prev) => (prev === null ? normalizedData.length - 1 : Math.max(0, prev - 1)))    } else if (e.key === "Home") {      e.preventDefault()      setActiveIndex(0)    } else if (e.key === "End") {      e.preventDefault()      setActiveIndex(normalizedData.length - 1)    } else if (e.key === "Escape") {      e.preventDefault()      setActiveIndex(null)    }  }  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 min-w-0 max-w-full outline-none focus-visible:ring-2 focus-visible:ring-[var(--chart-focus)] rounded-xl transition-all overflow-hidden",        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 min-w-0 max-w-full overflow-hidden">        <ResponsiveContainer          width="100%"          height="100%"          minWidth={0}          minHeight={0}          initialDimension={{ width: 320, height: typeof height === "number" ? height : 340 }}        >          <LineChart            data={normalizedData}            margin={{ top: 14, right: 16, left: showYAxis ? -16 : 10, bottom: showXAxis ? 6 : 6 }}          >            {showGrid && (              <CartesianGrid                strokeDasharray="3 3"                vertical={false}                stroke="var(--chart-grid)"              />            )}            <XAxis              hide={!showXAxis}              dataKey="__x"              tickLine={false}              axisLine={false}              tick={{ fontSize: 11, fill: "var(--chart-axis)" }}              tickFormatter={xFormatter}              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={                <StepTooltipContent                  primaryColor={primaryColor}                  seriesLabel={seriesLabel}                  valueFormatter={valueFormatter}                  showTransitionDelta={showTransitionDelta}                />              }              cursor={{                stroke: "var(--chart-crosshair)",                strokeDasharray: "3 3",                strokeWidth: 1.2,              }}            />            {showLegend && (              <Legend                content={                  <StepLegendContent                    label={seriesLabel}                    primaryColor={primaryColor}                  />                }              />            )}            {/* Optional Reference Lines (e.g. quotas, contract bounds) */}            {referenceLines.map((ref, idx) => (              <RechartsReferenceLine                key={`ref-${idx}-${ref.value}`}                y={ref.value}                stroke={ref.color ?? "var(--chart-reference, rgba(255, 255, 255, 0.25))"}                strokeDasharray={ref.strokeDasharray ?? "4 4"}                strokeWidth={1.2}                label={                  ref.label                    ? {                        value: ref.label,                        position: "insideTopRight",                        fill: "var(--chart-muted)",                        fontSize: 10,                        fontFamily: "monospace",                      }                    : undefined                }              />            ))}            {/* Stepped Signal Line: Strictly stepped curve, minimal dots */}            <Line              type={rechartsCurveType}              dataKey="__value"              name={seriesLabel}              stroke={primaryColor}              strokeWidth={2}              strokeLinecap="square"              strokeLinejoin="miter"              dot={                normalizedData.length === 1                  ? { r: 4, fill: primaryColor, stroke: "var(--chart-background)", strokeWidth: 1.5 }                  : false              }              activeDot={{                r: 4.5,                fill: primaryColor,                stroke: "var(--chart-background)",                strokeWidth: 2,              }}              connectNulls={false}              isAnimationActive={isAnimated}              animationDuration={animationDuration}            />          </LineChart>        </ResponsiveContainer>      </ChartContainer>    </figure>  )}