004 / RECHARTS / LINE

Range Line

Recharts

Visualizes a central trend alongside a lower-to-upper interval envelope for forecasts, confidence intervals, and operational tolerance bands.

SPEC
#004
ENGINE
Recharts
FAMILY
Line
RENDERER
svg
STATUS
preview

Installation

PLOTCN/REGISTRY/LINE-RANGE/SOURCE
pnpm dlx shadcn@latest add @plotcn/line-range

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

Range Line visualizes a central trend alongside a continuous lower-to-upper interval envelope. It models three values per horizontal observation — a central observation, a lower boundary, and an upper boundary — over a shared, honest Cartesian scale.

Unlike generic multi-line charts that represent three disconnected lines, Range Line establishes an integrated visual hierarchy:

  • Central Signal Line: Rendered as a prominent solid stroke (2px) in the primary theme color (var(--chart-1)).
  • Interval Envelope Band: Rendered behind the central line as a filled area band between the lower and upper limits with configurable opacity (0.18 default).
  • Optional Boundary Strokes: Subtle dashed boundary lines (3 3) along the upper and lower limits of the envelope to clarify envelope edges.
  • Single Shared Y Scale: Evaluates the combined extent of all central values, lower limits, and upper limits to guarantee the interval envelope is never clipped.
  • Truthful Outlier Policy: If an actual observation falls outside the interval envelope, it is never clamped. Outliers remain visible outside the band.
TSX
import { RangeLine } from "@/components/charts/recharts/line-range"export function DemandForecastCard() {  return (    <RangeLine      data={forecastData}      xKey="month"      valueKey="forecast"      lowerKey="lower"      upperKey="upper"      label="Expected Demand"      rangeLabel="95% Confidence"      curve="monotone"      showRangeBoundary    />  )}

Best Suited For

Range Line is specifically engineered for:

  • Demand & Capacity Forecasting: Projected demand surrounded by widening confidence intervals over future planning horizons.
  • Operational SLA Envelopes: System latency, request duration, or throughput tracked against contracted minimum and maximum tolerance bands.
  • Process & Quality Control: Sensor telemetry and manufacturing metrics evaluated against upper and lower control limits (UCL / LCL).
  • Financial & Market Volatility: Asset price trends bounded by Bollinger Bands, implied volatility ranges, or price targets.

When to Avoid

  • Two Independent Competing Series: Use TwinlineCompare when comparing two distinct time periods (e.g. 2024 Actual vs 2023 Prior Year).
  • Unbounded Multiple Series: Use LineMultiple when plotting three or more independent metrics that do not represent an upper/lower interval.
  • Single Series Only: Use LineBasic or PulseLine when there is no uncertainty or tolerance range to display.

Installation

Install Range 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-RANGE/SOURCE
pnpm dlx shadcn@latest add @plotcn/line-range

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

Range Line accepts a readonly array of observations. Each record must contain a shared horizontal domain key and three numeric properties: central trend, lower boundary, and upper boundary.

TypeScript
export interface RangeLineDatum {  month: string       // Horizontal domain coordinate (e.g. "Jan", "2026-Q1")  forecast: number    // Central observation value (e.g. expected demand)  lower: number       // Lower interval limit (e.g. 5th percentile)  upper: number       // Upper interval limit (e.g. 95th percentile)}

Interval Integrity & Outlier Policy

Range Line enforces deterministic data safety across all observations:

  1. Mandatory Interval Validation (lower <= upper): For every observation, Range Line verifies that lower <= upper. If an inverted interval is encountered (e.g. lower = 200, upper = 100), the interval is treated as missing for that point to prevent rendering an inverted polygon. In development mode, a console warning is emitted.
  2. Unclamped Outlier Preservation: Range Line never assumes lower <= value <= upper. Real-world observations legitimately exceed tolerances or forecast bounds. Central points outside the band remain visible in their true position.
  3. Partial Missing Bounds: If only one boundary is present (e.g. lower = 100, upper = null), no valid interval exists. Range Line preserves the central line while leaving a clean gap in the envelope.
  4. Missing Central Value: If the central point is null but both lower and upper bounds are valid, the range band remains visible while the central line shows a gap.

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.
valueKeykeyof TData & string"value"Property name representing the central observation value.
lowerKeykeyof TData & string"lower"Property name representing the lower limit of the interval band.
upperKeykeyof TData & string"upper"Property name representing the upper limit of the interval band.
seriesRangeLineSeriesConfigundefinedOptional series descriptor combining keys, labels, and color overrides.
labelstring"Central"Human-readable label for the central line displayed in tooltip and legend.
rangeLabelstring"Range"Human-readable label for the interval envelope (e.g. "95% Confidence").
heightnumber | string340Container height in pixels or standard CSS dimension strings.
curve"monotone" | "linear" | "step""monotone"Geometric curve interpolation applied synchronously to both the line and band.
domain[number, number] | ["auto", "auto"]"auto"Explicit Y-axis bounds. Automatic mode calculates a unified scale spanning central and range limits.
missingValuePolicy"gap" | "connect""gap"Handling of null observations in the central line.

Visual & Appearance

Prop Type Default Description
PropTypeDefaultDescription
colorstring"var(--chart-1)"Stroke color for the central line.
rangeColorstringcolorFill color for the interval band. Defaults to the central line color.
rangeOpacitynumber0.18Fill opacity of the interval band (0 to 1).
showRangeBoundarybooleanfalseWhether to render subtle dashed boundary strokes along the upper and lower limits.
rangeBoundaryDashstring"3 3"Dash array for the boundary strokes if showRangeBoundary is enabled.
showGridbooleantrueRenders subtle horizontal dashed reference dividers (var(--chart-grid)).
showLegendbooleanfalseRenders legend distinguishing central line from range band.
showXAxisbooleantrueRenders horizontal domain tick labels.
showYAxisbooleantrueRenders vertical value tick labels on the shared scale.
showRangeWidthInTooltipbooleanfalseCalculates and displays the derived interval span (upper - lower) in the tooltip.
motionboolean | { duration: number }trueCoordinated reveal animation (350ms). Automatically disabled under prefers-reduced-motion.

Accessibility & States

Prop Type Default Description
PropTypeDefaultDescription
titlestring"Range Line Chart"Accessible name announced by screen readers for the <figure> region.
descriptionstringundefinedExtended contextual description for assistive technologies.
loadingbooleanfalseDisplays neutral loading state without fake data while preserving layout footprint.
errorError | string | nullnullActionable error banner with optional retry trigger.
unavailableboolean | string | nullfalseUnavailability notice (e.g. missing prediction interval model).
onRetry() => voidundefinedCallback invoked when user clicks the retry button in the error state.
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):
<RangeLine
  data={data}
  xKey="date"
  valueKey="value"
  lowerKey="lower"
  upperKey="upper"
/>
Interactive Prop Preview Lab
colorstring

Primary theme stroke color for the central line. Accepts CSS variables or color values.

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

Fill opacity applied to the range envelope band (0 to 1).

Select value to preview live:
Active: rangeOpacity={0.18}Default: 0.18
showRangeBoundaryboolean

Whether to render subtle dashed boundary strokes along the upper and lower limits of the band.

Select value to preview live:
Active: showRangeBoundary={false}Default: false
curve"monotone" | "linear" | "step"

Geometric curve interpolation applied synchronously to both the central line and the range band.

Select value to preview live:
Active: curve="monotone"Default: "monotone"
heightnumber | string

Container height in pixels or standard CSS dimension strings.

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

Whether to display the chart legend distinguishing central line from range band.

Select value to preview live:
Active: showLegend={false}Default: false
showGridboolean

Whether to render subtle horizontal background reference gridlines.

Select value to preview live:
Active: showGrid={true}Default: true
All Properties (26)
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"value"No

Field name for the central observation value plotted with dominant solid stroke.

Best for: Central signal series

keyof TData & string"lower"No

Field name for the lower limit of the range band.

Best for: Lower interval limit

keyof TData & string"upper"No

Field name for the upper limit of the range band.

Best for: Upper interval limit

RangeLineSeriesConfig<TData>undefinedNo

Optional range-aware series descriptor combining valueKey, lowerKey, upperKey, and semantic labels.

Best for: Encapsulated series config

string"Central"No

Human-readable label for the central series displayed in tooltips and legends.

Best for: Series identification

string"Range"No

Human-readable label for the interval envelope (e.g. 'Confidence Interval', 'Operating Range').

Best for: Interval identification

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

Primary theme stroke color for the central line. Accepts CSS variables or color values.

Best for: Primary brand theme

number0.18No

Fill opacity applied to the range envelope band (0 to 1).

Best for: Interval emphasis

booleanfalseNo

Whether to render subtle dashed boundary strokes along the upper and lower limits of the band.

Best for: Boundary clarity

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

Geometric curve interpolation applied synchronously to both the central line and the range band.

Best for: Interpolation style

number | string340No

Container height in pixels or standard CSS dimension strings.

Best for: Dashboard slot sizing

booleanfalseNo

Whether to display the chart legend distinguishing central line from range band.

Best for: Multi-series clarity

booleantrueNo

Whether to render subtle horizontal background reference gridlines.

Best for: Grid density control

booleanfalseNo

Whether to calculate and display the derived range span (upper - lower) in the tooltip.

Best for: Quantitative interval inspection

"gap" | "connect""gap"No

Handling of null observations in the central line. 'gap' preserves visual breaks; 'connect' bridges adjacent points.

Best for: Data safety & truthful representation

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

Explicit Y-axis numeric domain spanning both central and range values, or 'auto' unified scale calculation.

booleantrueNo

Whether to display the horizontal X-axis tick labels.

booleantrueNo

Whether to display the unified vertical Y-axis scale.

boolean | { duration?: number }trueNo

Synchronized reveal animation (350ms). Automatically disabled under prefers-reduced-motion.

string"Range Line Chart"No

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

stringundefinedNo

Long-form context describing what the trend and interval envelope communicate.

booleanfalseNo

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

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 missing interval model).

04 / Cookbook & States

Component Variants & Edge States

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

Demand Forecast with Confidence Band

Expected monthly product demand with a widening 95% statistical confidence envelope.

<RangeLine data={forecastData} xKey="month" valueKey="forecast" lowerKey="lower" upperKey="upper" label="Forecast" rangeLabel="95% Confidence" />

API Latency with Operating Envelope

Observed p95 latency curve plotted inside contractually acceptable SLA bounds with boundary strokes.

<RangeLine data={latencyData} xKey="time" valueKey="latency" lowerKey="slaMin" upperKey="slaMax" label="Observed" rangeLabel="SLA Envelope" showRangeBoundary color="#0ea5e9" />

Tiered Capacity Allocation Band

Scheduled infrastructure provision tiers with discrete step interpolation on both signal and limits.

<RangeLine data={capacityData} xKey="hour" valueKey="allocated" lowerKey="minCap" upperKey="maxCap" curve="step" label="Allocated" rangeLabel="Provisioned" color="#8b5cf6" />

Compact Metric Overview Widget

Condensed 200px dashboard tile with hidden axes, maintaining interval visibility in tight slots.

<RangeLine data={summaryData} xKey="week" valueKey="mrr" lowerKey="targetMin" upperKey="targetMax" showYAxis={false} height={200} />
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

Range Line automatically adapts to its parent container without clipping or layout shifts:

  • Desktop (1024px+): Spacious presentation, complete interval tooltip with exact lower and upper values, and optional range span calculation.
  • Tablet (640px – 1023px): Adaptive X-axis tick thinning to prevent collision, preserved band continuity, consolidated tooltip padding.
  • Mobile (< 640px): Compact padding, preserved start/end axis labels, full-width touch scrub interaction, and stacked tooltip items.
05 / Responsive Lab

Container-Driven Breakpoints

Range Line scales the central line and interval band synchronously across container widths. The unified Y-domain recalculates to comfortably enclose extreme bounds without clipping.

Desktop
>= 1024px

Full interval tooltip with exact lower and upper values, optional range span, and clear line-versus-band distinction.

Tablet
640px - 1023px

Thinned X-axis labels, preserved band continuity, compact margins, tooltip pinned within viewport boundaries.

Mobile
< 640px

Edge-to-edge scrub inspection, preserved interval visibility without opacity blowout, touch-first scrub interaction.

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

Accessibility & Keyboard Navigation

Range Line conforms to WCAG 2.1 AAA accessibility standards:

  1. Keyboard Operable: Pressing Tab focuses the chart region with a prominent var(--chart-focus) ring.
  2. Observation Navigation:
    • Navigates to the next time observation.
    • Navigates to the previous time observation.
    • Home Jumps to the first observation.
    • End Jumps to the final observation.
    • Esc Clears active point selection.
  3. Screen Reader Announcement: Announces central values and lower–upper interval limits without requiring manual SVG node traversal.
  4. Factual Figure Summary: An invisible <figcaption> provides an automated quantitative overview summarizing total observations, central range, and interval availability without marketing spin.
06 / Assistive Technology

Accessibility & Navigation Standards

Screen-reader figure region with quantitative interval summary (observation count, central range, and interval availability).

Semantic Role & Landmark

Container mounts as region with explicit assistive label.

Color-Independent Legibility

Central observation is a prominent solid line (2px) while the interval is an area band (fill with optional dashed boundary strokes), providing clear geometric differentiation independent of color.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “VoiceOver and NVDA announce central values and lower–upper limits without SVG node traversal.

Reduced Motion Support

Automatically suppresses stroke draw and area reveal animations when user requests reduced motion.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
TabFocus range chart region with prominent focus ring
ArrowLeftStep to previous observation and announce central + range values
ArrowRightStep to next observation and announce central + range values
HomeJump to first observation point
EndJump to latest observation point
EscapeClear active inspection state

Data Safety Guarantee

  1. Zero Fabricated Fallback Data: Range Line will never synthesize fake interval bands or fill missing data points with fabricated curves.
  2. Interval Boundary Check: Enforces lower <= upper before rendering any band polygon to prevent visual distortion.
  3. Shared Scale Integrity: The vertical scale is calculated across all central, lower, and upper values, guaranteeing that interval envelopes are never cut off by the container viewport.
  4. Unclamped Outliers: Preserves true observations when they exceed normal or expected limits rather than hiding anomalous behavior.
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
RangeLine(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-range.tsx
Primary RangeLine interval visualization component
components/charts/shared/chart-container.tsx
CSS variable token bridge and container wrapper
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-range.tsx
"use client"import * as React from "react"import {  ResponsiveContainer,  ComposedChart,  Area,  Line,  XAxis,  YAxis,  Tooltip,  CartesianGrid,  Legend,} 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 CurveType = "monotone" | "linear" | "step" | "natural"export interface RangeLineSeriesConfig<TData extends Record<string, unknown> = Record<string, unknown>> {  valueKey?: keyof TData & string  lowerKey?: keyof TData & string  upperKey?: keyof TData & string  label?: string  rangeLabel?: string  color?: string  rangeColor?: string  rangeOpacity?: number}export interface RangeLineProps<TData extends Record<string, unknown> = Record<string, unknown>> {  /**   * The array of structured 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, month, or ordered category).   */  xKey: keyof TData & string  /**   * Key for the central observation value (e.g. forecast, actual, or mean).   */  valueKey?: keyof TData & string  /**   * Key for the lower boundary of the interval band.   */  lowerKey?: keyof TData & string  /**   * Key for the upper boundary of the interval band.   */  upperKey?: keyof TData & string  /**   * Optional range-aware series descriptor combining keys and labels.   */  series?: RangeLineSeriesConfig<TData>  /**   * Display label for the central series (used in tooltips, legends, and accessibility).   * Default: "Central" or series.label   */  label?: string  /**   * Display label for the range envelope (used in tooltips, legends, and accessibility).   * Default: "Range" or series.rangeLabel   */  rangeLabel?: string  /**   * Primary stroke color for the central line. Accepts CSS variables or color values.   * Default: "var(--chart-1)"   */  color?: string  /**   * Fill color for the range envelope band. Accepts CSS variables or color values.   * Defaults to the primary color.   */  rangeColor?: string  /**   * Opacity applied to the range envelope band (0 to 1).   * Default: 0.18   */  rangeOpacity?: number  /**   * Whether to render subtle boundary strokes along the upper and lower limits of the band.   * Default: false   */  showRangeBoundary?: boolean  /**   * Dash array for the boundary strokes if showRangeBoundary is true.   * Default: "3 3"   */  rangeBoundaryDash?: string  /**   * Interpolation curve for both the central line and range envelope.   * Default: "monotone"   */  curve?: CurveType  /**   * Container height in pixels or standard CSS dimension strings.   * Default: 340   */  height?: number | string  /**   * Explicit Y-axis numeric domain spanning central and range values, or "auto".   * Default: "auto"   */  domain?: [number, number] | ["auto", "auto"]  /**   * Handling of null or undefined values in the central line.   * "gap" preserves visual break; "connect" bridges adjacent points.   * Default: "gap"   */  missingValuePolicy?: "gap" | "connect"  /**   * Formatter function for Y-axis and tooltip values.   */  valueFormatter?: (value: number) => string  /**   * Formatter function for X-axis tick labels.   */  xFormatter?: (value: string | number) => string  /**   * Whether to display horizontal reference gridlines.   * Default: true   */  showGrid?: boolean  /**   * Whether to display the chart legend.   * Default: false   */  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 the derived range width (upper - lower) in the tooltip.   * Default: false   */  showRangeWidthInTooltip?: boolean  /**   * Enable or disable entry and update transitions.   * Default: true   */  motion?: boolean | { duration?: number }  /**   * Accessible title announced by screen readers.   * Default: "Range Line 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 NormalizedRangeDatum {  __x: string | number  __value: number | null  __lower: number | null  __upper: number | null  __range: [number, number] | null  __raw: Record<string, unknown>}/** * Normalizes user data with strict interval safety: * 1. Checks that lower <= upper. If lower > upper, interval is treated as missing/invalid (no inverted polygon). * 2. Unclamped central values (observations may truthfully sit outside the range). * 3. Partially missing bounds create a gap in the band without fabricating data. * 4. Missing central value with valid bounds preserves the band with a gap in the line. * 5. Caller objects and arrays are NEVER mutated. */export function normalizeRangeLineData<TData extends Record<string, unknown>>(  data: readonly TData[],  xKey: keyof TData & string,  valueKey: string,  lowerKey: string,  upperKey: string): NormalizedRangeDatum[] {  if (!Array.isArray(data) || data.length === 0) return []  let hasWarnedInverted = false  return data.map((d) => {    const rawX = d[xKey]    const xVal = typeof rawX === "string" || typeof rawX === "number" ? rawX : String(rawX ?? "")    const rawV = d[valueKey]    const rawL = d[lowerKey]    const rawU = d[upperKey]    const val = isFiniteNumber(rawV) ? rawV : null    const low = isFiniteNumber(rawL) ? rawL : null    const up = isFiniteNumber(rawU) ? rawU : null    let safeLow = low    let safeUp = up    let rangeTuple: [number, number] | null = null    if (low !== null && up !== null) {      if (low <= up) {        rangeTuple = [low, up]      } else {        safeLow = null        safeUp = null        if (!hasWarnedInverted && process.env.NODE_ENV !== "production") {          console.warn(            `[plotcn] RangeLine: Invalid interval detected where lower (${low}) > upper (${up}). Treating range as missing for this observation.`          )          hasWarnedInverted = true        }      }    }    return {      __x: xVal,      __value: val,      __lower: safeLow,      __upper: safeUp,      __range: rangeTuple,      __raw: d,    }  })}/** * Calculates a single honest shared Y-domain spanning central observations, * lower limits, and upper limits to ensure the interval band never clips. */export function calculateRangeDomain(  normalized: readonly NormalizedRangeDatum[],  explicitDomain?: [number, number] | ["auto", "auto"] | "auto"): [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 validValues: number[] = []  for (const item of normalized) {    if (item.__value !== null) validValues.push(item.__value)    if (item.__lower !== null) validValues.push(item.__lower)    if (item.__upper !== null) validValues.push(item.__upper)  }  if (validValues.length === 0) {    return [0, 100]  }  const min = Math.min(...validValues)  const max = Math.max(...validValues)  if (min === max) {    return min === 0 ? [-10, 10] : [min - Math.abs(min) * 0.1, max + Math.abs(max) * 0.1]  }  const span = max - min  const pad = span * 0.06  return [Math.floor(min - pad), Math.ceil(max + pad)]}/* -------------------------------------------------------------------------- *//*  Synchronized Range Tooltip                                                *//* -------------------------------------------------------------------------- */interface RangeTooltipContentProps {  active?: boolean  payload?: readonly { dataKey?: string | number; value?: any; payload?: any; [key: string]: any }[]  label?: React.ReactNode  centralLabel?: string  rangeLabel?: string  primaryColor: string  rangeColor: string  valueFormatter?: (value: number) => string  showRangeWidth?: boolean}function RangeTooltipContent({  active,  payload,  label,  primaryColor,  rangeColor,  valueFormatter,  showRangeWidth = false,  centralLabel = "Central",  rangeLabel = "Range",}: RangeTooltipContentProps) {  if (!active || !payload || payload.length === 0) return null  const datum = payload[0]?.payload as NormalizedRangeDatum | undefined  if (!datum) return null  const fmt = valueFormatter ?? ((n: number) => n.toLocaleString())  const centralVal = datum.__value  const lowerVal = datum.__lower  const upperVal = datum.__upper  const rangeTuple = datum.__range  const hasCentral = centralVal !== null  const hasValidRange = rangeTuple !== null  let rangeWidth: number | null = null  if (hasValidRange && lowerVal !== null && upperVal !== null) {    rangeWidth = Math.abs(upperVal - lowerVal)  }  return (    <div className="z-50 min-w-[200px] 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 font-mono text-[11px] font-medium text-[var(--chart-tooltip-muted)]">        {datum.__x}      </div>      <div className="space-y-1.5">        {/* Central Observation Row */}        <div className="flex items-center justify-between gap-3">          <div className="flex items-center gap-1.5">            <span              className="h-2 w-2 rounded-full"              style={{ backgroundColor: primaryColor }}            />            <span className="font-medium text-[var(--chart-tooltip-foreground)]">              {centralLabel}            </span>          </div>          <span className="font-mono font-semibold text-[var(--chart-tooltip-foreground)]">            {hasCentral ? fmt(centralVal) : "—"}          </span>        </div>        {/* Range Band Row */}        <div className="flex items-center justify-between gap-3">          <div className="flex items-center gap-1.5">            <span              className="h-2 w-3 rounded-sm opacity-80"              style={{ backgroundColor: rangeColor }}            />            <span className="text-[var(--chart-tooltip-muted)]">              {rangeLabel}            </span>          </div>          <span className="font-mono text-[var(--chart-tooltip-muted)]">            {hasValidRange ? `${fmt(lowerVal!)} – ${fmt(upperVal!)}` : "Unavailable"}          </span>        </div>        {/* Optional Range Width */}        {showRangeWidth && rangeWidth !== null && (          <div className="mt-1.5 flex items-center justify-between border-t border-[var(--chart-tooltip-border)] pt-1.5 text-[11px]">            <span className="text-[var(--chart-tooltip-muted)]">Span</span>            <span className="font-mono text-[var(--chart-tooltip-foreground)]">              {fmt(rangeWidth)}            </span>          </div>        )}      </div>    </div>  )}/* -------------------------------------------------------------------------- *//*  Custom Range Legend                                                       *//* -------------------------------------------------------------------------- */interface RangeLegendContentProps {  label: string  rangeLabel: string  primaryColor: string  rangeColor: string}function RangeLegendContent({  label,  rangeLabel,  primaryColor,  rangeColor,}: RangeLegendContentProps) {  return (    <div className="flex items-center justify-center gap-6 pt-3 text-xs">      <div className="flex items-center gap-2">        <span          className="h-0.5 w-4 rounded-full"          style={{ backgroundColor: primaryColor }}        />        <span className="text-[var(--chart-foreground)] font-medium">{label}</span>      </div>      <div className="flex items-center gap-2">        <span          className="h-2.5 w-4 rounded-sm opacity-60"          style={{ backgroundColor: rangeColor }}        />        <span className="text-[var(--chart-muted)]">{rangeLabel}</span>      </div>    </div>  )}/* -------------------------------------------------------------------------- *//*  Component Implementation                                                  *//* -------------------------------------------------------------------------- */export function RangeLine<TData extends Record<string, unknown> = Record<string, unknown>>({  data = [],  xKey,  valueKey: propValueKey,  lowerKey: propLowerKey,  upperKey: propUpperKey,  series,  label: propLabel,  rangeLabel: propRangeLabel,  color: propColor,  rangeColor: propRangeColor,  rangeOpacity = 0.18,  showRangeBoundary = false,  rangeBoundaryDash = "3 3",  curve = "monotone",  height = 340,  domain,  missingValuePolicy = "gap",  valueFormatter,  xFormatter,  showGrid = true,  showLegend = false,  showXAxis = true,  showYAxis = true,  showRangeWidthInTooltip = false,  motion = true,  title = "Range Line Chart",  description,  loading = false,  error = null,  unavailable = false,  onRetry: _onRetry,  emptyContent,  errorContent,  loadingContent,  className,}: RangeLineProps<TData>) {  const reducedMotion = useChartReducedMotion()  const containerId = React.useId().replace(/[:]/g, "")  const titleId = `range-title-${containerId}`  const descId = `range-desc-${containerId}`  const summaryId = `range-summary-${containerId}`  const [, setActiveIndex] = React.useState<number | null>(null)  // Resolve keys and semantic descriptors (series object takes precedence if provided)  const valueKey = series?.valueKey ?? propValueKey ?? ("value" as keyof TData & string)  const lowerKey = series?.lowerKey ?? propLowerKey ?? ("lower" as keyof TData & string)  const upperKey = series?.upperKey ?? propUpperKey ?? ("upper" as keyof TData & string)  const centralLabel = series?.label ?? propLabel ?? "Central"  const rangeLabel = series?.rangeLabel ?? propRangeLabel ?? "Range"  const primaryColor = series?.color ?? propColor ?? "var(--chart-1, #10b981)"  const rangeColor = series?.rangeColor ?? propRangeColor ?? primaryColor  const activeOpacity = series?.rangeOpacity ?? rangeOpacity  // Normalized safe data and safe shared domain  const normalizedData = React.useMemo(    () => normalizeRangeLineData(data, xKey, valueKey, lowerKey, upperKey),    [data, xKey, valueKey, lowerKey, upperKey]  )  const safeDomain = React.useMemo(    () => calculateRangeDomain(normalizedData, domain),    [normalizedData, domain]  )  // Motion config  const isAnimated = motion !== false && !reducedMotion  const animationDuration =    typeof motion === "object" && motion?.duration !== undefined ? motion.duration * 1000 : 350  // Screen reader factual summary  const factualSummary = React.useMemo(() => {    if (normalizedData.length === 0) return "No range data observations recorded."    const centralValues = normalizedData      .map((d) => d.__value)      .filter((v): v is number => v !== null)    const validIntervalCount = normalizedData.filter((d) => d.__range !== null).length    const fmt = valueFormatter ?? ((n: number) => n.toLocaleString())    const cMin = centralValues.length > 0 ? fmt(Math.min(...centralValues)) : "none"    const cMax = centralValues.length > 0 ? fmt(Math.max(...centralValues)) : "none"    return `Time-series with range envelope visualizing ${normalizedData.length} observations. ${centralLabel} spans from ${cMin} to ${cMax}. Interval bounds are available for ${validIntervalCount} of ${normalizedData.length} observations.`  }, [normalizedData, centralLabel, 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 range data"          description={typeof error === "string" ? error : error?.message || "An unexpected error occurred while loading interval metrics."}        />      </div>    )  }  if (unavailable) {    return (      <div className={cn("w-full min-w-0 max-w-full overflow-hidden", className)} style={{ height }}>        <ChartUnavailableState          title="Range metrics unavailable"          description={typeof unavailable === "string" ? unavailable : "Interval envelopes 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 range visualization…"          description="Synchronizing trend and interval envelopes"        />      </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 range observations"          description="Observations will appear when central values and bounds are recorded."        />      </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 }}        >          <ComposedChart            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={                <RangeTooltipContent                  primaryColor={primaryColor}                  rangeColor={rangeColor}                  label={centralLabel}                  rangeLabel={rangeLabel}                  valueFormatter={valueFormatter}                  showRangeWidth={showRangeWidthInTooltip}                />              }              cursor={{                stroke: "var(--chart-crosshair)",                strokeDasharray: "3 3",                strokeWidth: 1.2,              }}            />            {showLegend && (              <Legend                content={                  <RangeLegendContent                    label={centralLabel}                    rangeLabel={rangeLabel}                    primaryColor={primaryColor}                    rangeColor={rangeColor}                  />                }              />            )}            {/* Range Envelope: Rendered first (behind central line) */}            <Area              type={curve}              dataKey="__range"              name={rangeLabel}              fill={rangeColor}              fillOpacity={activeOpacity}              stroke={showRangeBoundary ? rangeColor : "none"}              strokeOpacity={showRangeBoundary ? 0.6 : 0}              strokeDasharray={showRangeBoundary ? rangeBoundaryDash : undefined}              strokeWidth={showRangeBoundary ? 1.2 : 0}              isAnimationActive={isAnimated}              animationDuration={animationDuration}              activeDot={false}              dot={false}              connectNulls={false}            />            {/* Central Signal Line: Rendered over the range envelope */}            <Line              type={curve}              dataKey="__value"              name={centralLabel}              stroke={primaryColor}              strokeWidth={2}              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={missingValuePolicy === "connect"}              isAnimationActive={isAnimated}              animationDuration={animationDuration}            />          </ComposedChart>        </ResponsiveContainer>      </ChartContainer>    </figure>  )}