025 / RECHARTS / BAR & COLUMN

Variance Bars

Recharts

Actual-versus-plan delta bars centered on a truthful zero-variance baseline, with neutral signed semantics and auditable source values.

SPEC
#025
ENGINE
Recharts
FAMILY
Bar & Column
RENDERER
svg
STATUS
preview

Installation

PLOTCN/REGISTRY/BAR-VARIANCE/SOURCE
pnpm dlx shadcn@latest add @plotcn/bar-variance

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
Interactive Preview
Isolated Preview BoundaryViewport: DESKTOP

Overview

Variance Bars visualizes the categorical arithmetic difference between an observed measure and an explicit plan or reference value:

Variance=ActualPlan\text{Variance} = \text{Actual} - \text{Plan}

A positive bar indicates an actual value numerically above plan; a negative bar indicates an actual value numerically below plan; and zero represents exact parity with plan.

Terminology Clarification: In Plotcn, variance refers exclusively to categorical actual-versus-plan arithmetic delta. It does not mean statistical variance, sample variance, or dispersion around a mean.

Governing Principle

Variance Bars visualizes the arithmetic difference between an actual value and an explicit plan/reference value. Zero means actual equals plan, positive means actual is numerically above plan, and negative means actual is numerically below plan. Plotcn must never automatically translate those directions into good/bad, favorable/unfavorable, success/failure, profit/loss, or healthy/unhealthy.

Installation

PLOTCN/REGISTRY/BAR-VARIANCE/SOURCE
pnpm dlx shadcn@latest add @plotcn/bar-variance

Checking public registry…

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

Copied as source into your project (requires recharts).

Usage

TSX
import { VarianceBars } from "@/components/charts/recharts/bar-variance"const data = [  { segment: "Enterprise", actual: 124, plan: 110 },  { segment: "Mid-market", actual: 92, plan: 100 },  { segment: "SMB", actual: 74, plan: 74 },  { segment: "Public Sector", actual: 68, plan: 72 },  { segment: "Partners", actual: 81, plan: 75 },]export function BudgetVarianceExample() {  return (    <VarianceBars      data={data}      categoryKey="segment"      series={{        actualKey: "actual",        planKey: "plan",        label: "Revenue Variance",        valueFormatter: (v) => `$${v}M`,        varianceFormatter: (v) => `${v > 0 ? "+" : ""}$${v}M`,      }}    />  )}

Variance Model

Variance Bars derives one signed quantitative delta bar from two caller-supplied fields: actualKey and planKey.

Derivation Flow

Actual − Plan = Signed Variance

Flow diagram showing how consumer-supplied actual and plan values are subtracted to produce the derived signed variance bar, contrasting arithmetic delta with statistical variance.

Variance Derivation ModelInput Role 1Actual ValueInput Role 2Plan / ReferenceDerived Quantitative MarkSigned Variancevariance = actual − planCategorical Delta ≠ Statistical Mean DevZero Base+20−20

Actual vs Plan &rarr; Delta

Every observation resolves to one of three canonical factual positions:

Arithmetic Comparison

Three Canonical States: Above, Equal, Below Plan

Comparison diagram illustrating actual 120 vs plan 100 yielding positive 20, actual 100 vs plan 100 yielding zero, and actual 80 vs plan 100 yielding negative 20.

Actual vs Plan to Delta StatesStateActualPlanArithmeticVarianceBar GeometryAbove Plan120100120 − 100+20On Plan (Equal)100100100 − 1000Below Plan8010080 − 100−20
  1. Above Plan: Actual &gt; Plan &rArr; Variance &gt; 0. Bar extends upward (vertical) or rightward (horizontal).
  2. On Plan (Equal): Actual = Plan &rArr; Variance = 0. No quantitative bar is rendered; the baseline itself communicates equality.
  3. Below Plan: Actual &lt; Plan &rArr; Variance &lt; 0. Bar extends downward (vertical) or leftward (horizontal).

Zero-Variance Baseline

The zero line is a semantic equality reference, not optional background decoration.

Baseline Semantics

Zero Baseline as Equality Anchor

Diagram showing the prominent zero baseline as the dividing reference line between negative and positive variance, signifying actual equals plan.

Zero-Variance Baseline− Negative VarianceActual numerically below plan−45K0 Baseline+ Positive VarianceActual numerically above plan+45K
  • The zero line defaults to the structural --chart-axis token.
  • It remains visually stronger than ordinary grid lines but distinct from active focus rings.
  • Zero actual, zero plan, and zero variance are all legitimate, distinct states.

Positive, Zero, and Negative Geometry

Geometry Model

Symmetric Bar Extrusion Around Zero Reference

SVG demonstrating how positive bars extrude outward upward or rightward, negative bars extrude downward or leftward, and zero values produce no fake bar width.

Positive Zero Negative Geometry0+80Enterprise−50Mid-Market0SMB (No Bar)+25Public−80Partners
  • Positive bars extrude outward in the positive direction.
  • Negative bars extrude outward in the negative direction.
  • Categories with zero variance produce no artificial minimum width or height, preventing distorted visual arithmetic while remaining fully interactive via category hit bands.

Direction Is Not Favorability

A common design pitfall in financial dashboards is treating all positive variances as "good" (green) and negative variances as "bad" (red).

Governing Principle

Numerical Direction ≠ Business Favorability

Critical diagram contrasting revenue variance of plus 20 with operating cost variance of plus 20, demonstrating why Plotcn must never equate positive direction with good or negative direction with bad.

Direction Is Not FavorabilityExample A: Gross RevenueActual: $120M | Plan: $100MVariance: +$20MBusiness Meaning: Surplus / GrowthHigher revenue than budgeted may be favorable.Example B: Cloud Infrastructure CostActual: $120K | Plan: $100KVariance: +$20KBusiness Meaning: Budget Overrun / SpillHigher expenses than budgeted may be unfavorable.Plotcn strictly reports mathematical direction (“Above plan”), never automatic “Good” or “Bad”.
  • Revenue: Actual $120M vs Plan $100M &rarr; Variance +$20M (Surplus).
  • Infrastructure Cost: Actual $120K vs Plan $100K &rarr; Variance +$20K (Budget overrun).

Both have identical arithmetic direction (+20), but opposite operational implications. Plotcn strictly reports objective mathematical positions (Above plan, Below plan), leaving business interpretation to the consuming application.

Negative Input Arithmetic

Actual and plan values are not required to be non-negative. When dealing with signed inputs (such as debt, deficits, or sub-zero temperatures), variance direction is derived strictly by subtraction:

Signed Values

Negative Input Values Obey Arithmetic Delta

Mathematical diagram demonstrating that actual negative 80 minus plan negative 100 equals positive 20, proving that variance direction comes from subtraction and not raw input sign.

Negative Input Arithmetic0Plan (−100)Actual (−80)Δ = +20(−80) − (−100) = −80 + 100 = +20Even though Actual is negative, it is numerically above Plan.
MATH
Actual = -80, \quad Plan = -100 \implies (-80) - (-100) = +20

Even though the observed actual value is negative, it is numerically above the reference plan, yielding a positive variance bar.

Pairwise Validity & Missing Data

A valid variance requires both actual and plan to be finite numbers (Number.isFinite).

Data Safety

Pairwise Validity: Missing Pair Yields Unavailable

Two-panel diagram showing that missing actual with valid plan, or valid actual with missing plan, produces an unavailable variance rather than coercing missing data to zero.

Missing Pair SemanticsCase 1: Actual MissingActual: null | Plan: 100Variance: Unavailable× Never infer 0 − 100 = −100. No fake bar.Case 2: Plan MissingActual: 80 | Plan: nullVariance: Unavailable× Never infer 80 − 0 = +80. No fake bar.
  • Missing Actual: If actual is null and plan is 100, variance is unavailable. Never infer 0 &minus; 100 = &minus;100.
  • Missing Plan: If actual is 80 and plan is null, variance is unavailable. Never infer 80 &minus; 0 = +80.
  • Non-Finite Values: NaN, Infinity, and -Infinity are rejected as unavailable and never entered into SVG coordinates.

Symmetric Domain Policy (Policy B)

To guarantee that equal absolute deviations (e.g. +40 and −40) receive equal visual bar lengths, Plotcn applies Policy B:

Domain Resolution (Policy B)

Symmetric Zero-Centered Domain Preserves Equal Visual Magnitude

Comparison contrasting an asymmetric data domain with a symmetric zero-centered domain [-80, +80], proving that equal absolute values like plus 40 and minus 40 receive equal visual bar lengths.

Mixed Sign Symmetric Domain0 Center−80+80−40 (175px)+40 (175px)Equal Absolute Lengths (1:1)When both positive and negative variances exist, domain expands to ±max(|min|, |max|).
  • Mixed Signs: Symmetric domain [-extent, +extent] centered at zero, where extent = max(|min|, |max|).
  • Positive Only: [0, safeMax] (preserves zero without wasting the negative half of the canvas).
  • Negative Only: [safeMin, 0] (preserves zero without wasting the positive half of the canvas).
  • All Zero: Safe expansion [-1, 1] centered at zero.

Interaction & Category Band Hit Region

Zero-variance categories, tiny deviations, and rows with missing data must remain easily inspectable without distorting bar geometry.

Interaction Geometry

Category Band Hit Targets Keep Zero/Tiny Bars Accessible

Interaction diagram showing how the entire categorical band acts as the hit target, allowing zero or tiny variance bars to remain inspectable on touch devices and desktop pointers.

Category Band Hit RegionEnterprise (+35K)SMB (0 Variance)Active Hit Band (48px)Partners (−1K)
  • Pointer and touch hit testing are bound to the full category band (36–48px).
  • Tiny bars do not require sub-pixel pointer accuracy.
  • Vertical page scrolling is preserved on mobile devices (touch-action: pan-y).

Layout: Vertical vs Horizontal

Both orientations are first-class, specialized compositions:

Layout Specialization

Vertical vs Horizontal Orientation

Side-by-side comparison illustrating vertical orientation best for standard metrics vs horizontal orientation best for long category labels and compact scorecards.

Vertical vs Horizontal CompositionVertical (Default)Best for standard labels & dashboard tilesHorizontalBest for long department names & mobile scorecardsEngineeringInfrastructureGlobal SalesCustomer Ops
  • Vertical (orientation="vertical", default): Ideal for short category labels (quarters, regions) in standard dashboard layouts. Traversal uses ArrowLeft and ArrowRight.
  • Horizontal (orientation="horizontal"): Ideal for long department names, ledger accounts, and narrow mobile viewports. Traversal uses ArrowUp and ArrowDown.

Rendering Architecture

Internal Pipeline

VarianceBars Rendering & Accessibility Pipeline

Complete rendering architecture flowchart tracing consumer data through pair validation, arithmetic subtraction, symmetric domain resolution, Recharts SVG rendering, and offscreen accessible tables.

Variance Rendering ArchitectureConsumer Data (TData[])Pairwise ValidationisFinite(actual) & isFinite(plan)Derived Varianceactual − planPolicy B Domain±maxAbsRecharts SVG LayerBar + Cell Coloring + Zero ReferenceLineContainer-Clamped TooltipActual, Plan, Signed Variance, PositionOffscreen Accessible TableKeyboard Traversal + Screen Reader Summary

Props Reference

Property Type Default Description
PropertyTypeDefaultDescription
datareadonly TData[]RequiredSource dataset. Array order is strictly caller-preserved.
categoryKeykeyof TData & stringRequiredProperty key representing the discrete category label.
seriesVarianceBarSeries<TData>RequiredSemantic series contract declaring actualKey and planKey.
orientation"vertical" | "horizontal""vertical"Axis arrangement for categories and quantitative variance bars.
heightnumber | string340Chart container height in pixels or CSS dimension.
domain[number, number] | "auto""auto"Quantitative scale bounds. Must include zero.
positiveColorstring"var(--chart-1)"Fill color for bars where actual is numerically above plan.
negativeColorstring"var(--chart-2)"Fill color for bars where actual is numerically below plan.
zeroColorstring"var(--chart-axis)"Stroke color for the zero baseline reference rule.
selectionColorstring"var(--chart-selection)"Stroke emphasis for the currently focused category bar.
showGridbooleantrueWhether to render subtle background Cartesian gridlines.
showZeroLinebooleantrueWhether to render the prominent zero reference line.
showLegendbooleanfalseWhether to render the structural directional legend.
valueLabel"none" | "variance" | "auto""none"Permanent outward numeric variance label policy.
tooltipMode"variance" | "full""full"Tooltip detail level ("full" audits actual, plan, and variance).
maxBarSizenumber36Maximum bar thickness in pixels.
motionboolean | { duration?: number }trueAnimation toggle (honors prefers-reduced-motion).
loadingbooleanfalseWhether to render the skeleton loading 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):
<VarianceBars
  data={data}
  categoryKey="segment"
  series={{
    actualKey: "actual",
    planKey: "plan",
    label: "Revenue Variance",
  }}
/>
Interactive Prop Preview Lab
orientation"vertical" | "horizontal"

Arrangement of categorical and quantitative axes.

Select value to preview live:
Active: orientation="vertical"Default: "vertical"
showGridboolean

Whether to render subtle background Cartesian grid lines.

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

Whether to render the prominent zero reference baseline.

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

Whether to display the directional legend.

Select value to preview live:
Active: showLegend={false}Default: false
valueLabel"none" | "variance" | "auto"

Policy for rendering inline numeric variance labels.

Select value to preview live:
Active: valueLabel="none"Default: "none"
tooltipMode"variance" | "full"

Tooltip depth ("full" displays actual, plan, and variance).

Select value to preview live:
Active: tooltipMode="full"Default: "full"
All Properties (16)
Component properties
PropertyTypeDefaultRequiredDescription
dataReq
readonly TData[][]Yes

Array of categorical data records. Order is strictly preserved.

keyof TData & stringYes

Key on data records representing the discrete category label.

VarianceBarSeries<TData>Yes

Series definition specifying actualKey, planKey, label, and optional formatters.

"vertical" | "horizontal""vertical"No

Arrangement of categorical and quantitative axes.

number | string340No

Chart container height in pixels or CSS dimension string.

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

Quantitative domain bounds. Follows Policy B symmetry for mixed signs.

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

Fill color for bars where actual is numerically above plan.

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

Fill color for bars where actual is numerically below plan.

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

Stroke color for the zero baseline reference line.

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

Stroke emphasis color for the currently focused category bar.

booleantrueNo

Whether to render subtle background Cartesian grid lines.

booleantrueNo

Whether to render the prominent zero reference baseline.

booleanfalseNo

Whether to display the directional legend.

"none" | "variance" | "auto""none"No

Policy for rendering inline numeric variance labels.

"variance" | "full""full"No

Tooltip depth ("full" displays actual, plan, and variance).

boolean | { duration?: number }trueNo

Animation toggle honoring reduced-motion preferences.

04 / Cookbook & States

Component Variants & Edge States

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

Revenue vs Plan by Segment

Standard business scorecard comparing segment revenues against financial budget plans.

<VarianceBars
  data={[
    { segment: "Enterprise", actual: 124, plan: 110 },
    { segment: "Mid-market", actual: 92, plan: 100 },
    { segment: "SMB", actual: 74, plan: 74 },
    { segment: "Public sector", actual: 68, plan: 72 },
    { segment: "Partners", actual: 81, plan: 75 },
  ]}
  categoryKey="segment"
  series={{
    actualKey: "actual",
    planKey: "plan",
    label: "Revenue Variance",
    valueFormatter: (v) => `$${v}M`,
    varianceFormatter: (v) => `${v > 0 ? "+" : ""}$${v}M`,
  }}
/>

Operating Cost Variance (Horizontal)

Compact horizontal scorecard for operating budget variance with long department labels.

<VarianceBars
  data={[
    { dept: "Engineering & Cloud", actual: 145, plan: 120 },
    { dept: "Product Design", actual: 48, plan: 50 },
    { dept: "Customer Support", actual: 82, plan: 82 },
    { dept: "Global Marketing", actual: 95, plan: 110 },
    { dept: "Legal & Compliance", actual: 34, plan: 30 },
  ]}
  categoryKey="dept"
  orientation="horizontal"
  series={{
    actualKey: "actual",
    planKey: "plan",
    label: "Operating Expenses",
    valueFormatter: (v) => `$${v}K`,
    varianceFormatter: (v) => `${v > 0 ? "+" : ""}$${v}K`,
  }}
/>
Lifecycle & Exception States
01. Loading State

Skeletons indicate runtime fetch or pending data queries.

02. Empty Data State

Handles empty collections ([]) gracefully without crashing.

03. Error State

Graceful failure banner when data source or script fails.

05 / Responsive Lab

Container-Driven Breakpoints

VarianceBars uses container-driven geometry via ResizeObserver and SVG viewbox scaling. Zero baseline and equal absolute bar lengths are preserved at every width.

Mobile Compact
< 440px

Category labels truncate gracefully or reflow into horizontal scorecard rows; tooltips clamp within container width with compact typography.

Tablet / Split
440px – 768px

Standard vertical column composition with reduced tick density on the quantitative scale and full category band touch hit testing.

Desktop Expanded
> 768px

Full analytical layout displaying quantitative gridlines, outward value labels, and rich actual/plan/delta tooltip inspection.

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

Accessibility & Navigation Standards

Semantic figure region with single tab stop, orientation-specific arrow-key traversal, Home/End navigation, polite ARIA announcements, and an offscreen structured HTML table disclosing actuals, plans, and variances.

Semantic Role & Landmark

Container mounts as region with explicit assistive label.

Color-Independent Legibility

Direction is encoded geometrically by extrusion relative to the center zero baseline (above/right for positive, below/left for negative). Color serves purely as supplementary visual identity.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “Announces category name, observed actual value, reference plan, derived signed variance, and factual position relative to plan.

Reduced Motion Support

All initial entrance animations are bypassed immediately when prefers-reduced-motion is detected.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
ArrowLeft / ArrowRightTraverse categories in vertical orientation
ArrowUp / ArrowDownTraverse categories in horizontal orientation
HomeJump focus to the first category
EndJump focus to the last category
EscapeClear active category inspection
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
VarianceBars(Semantic figure and variance coordinator)
└──ChartContainer[Container query wrapper]

Provides responsive sizing and token styling

Involved Source Files & Registry Assets
registry/recharts/bar-variance.tsx
Complete VarianceBars component with actual-vs-plan arithmetic derivation, Policy B symmetric domain, and accessible table.
registry/recharts/bar-variance.tsx
"use client"import * as React from "react"import {  ResponsiveContainer,  BarChart,  Bar,  Cell,  XAxis,  YAxis,  CartesianGrid,  Tooltip,  ReferenceLine,  LabelList,} from "recharts"import { cn } from "@/lib/utils"import { ChartContainer } from "@/registry/shared/chart-container"import { ChartEmptyState, ChartLoadingState } from "@/registry/shared/chart-state"/* -------------------------------------------------------------------------- *//*  Types & Contracts                                                         *//* -------------------------------------------------------------------------- */export type NumericKeyOf<TData> = [keyof TData] extends [never]  ? string  : {      [K in keyof TData]: TData[K] extends number | null | undefined ? K : never    }[keyof TData] extends never  ? string  : {      [K in keyof TData]: TData[K] extends number | null | undefined ? K : never    }[keyof TData] & stringexport type VarianceOrientation = "vertical" | "horizontal"export type VarianceValueLabel = "none" | "variance" | "auto"export type VariancePosition = "above" | "below" | "equal" | "unavailable"export interface VarianceBarSeries<TData extends Record<string, unknown> = Record<string, unknown>> {  /** Property on data record representing the observed actual measure */  actualKey: NumericKeyOf<TData>  /** Property on data record representing the reference plan/target */  planKey: NumericKeyOf<TData>  /** Semantic label for the series (e.g. "Revenue variance", "Operating budget") */  label: string  /** Human-readable label for the actual value (default: "Actual") */  actualLabel?: string  /** Human-readable label for the plan value (default: "Plan") */  planLabel?: string  /** Human-readable label for the derived delta (default: "Variance") */  varianceLabel?: string  /** Custom formatter for the source actual and plan values */  valueFormatter?: (value: number) => string  /** Custom formatter for the derived signed variance */  varianceFormatter?: (value: number) => string}export interface PreparedVarianceDatum<TData> {  __source: TData  __index: number  __category: string | number  __actual: number | null  __plan: number | null  __variance: number | null  __direction: "positive" | "negative" | "zero" | "unavailable"  __position: VariancePosition  __plotcnVariance: number | null  [key: string]: unknown}export interface VarianceBarsProps<TData extends Record<string, unknown> = Record<string, unknown>> {  /** Array of categorical data records. Order is strictly caller-preserved. */  data: readonly TData[]  /** Key on data records representing the discrete category label */  categoryKey: keyof TData & string  /** Strong series definition for actual and plan measures */  series: VarianceBarSeries<TData>  /**   * Bar orientation:   * - "vertical": Categories on horizontal X-axis, bars extend vertically from baseline (default).   * - "horizontal": Categories on vertical Y-axis, bars extend horizontally from baseline.   */  orientation?: VarianceOrientation  /** Container height in pixels or CSS string (default: 340) */  height?: number | string  /** Quantitative domain policy or explicit bounds (must include zero) */  domain?: [number, number] | "auto"  /** Fill color for positive variance bars (actual > plan, default: var(--chart-1)) */  positiveColor?: string  /** Fill color for negative variance bars (actual < plan, default: var(--chart-2)) */  negativeColor?: string  /** Stroke color for the zero baseline reference line (default: var(--chart-axis)) */  zeroColor?: string  /** Emphasis color for the active/inspected category (default: var(--chart-selection)) */  selectionColor?: string  /** Whether to render subtle quantitative grid lines (default: true) */  showGrid?: boolean  /** Whether to render the prominent zero reference baseline (default: true) */  showZeroLine?: boolean  /** Whether to display a directional legend (default: false) */  showLegend?: boolean  /** Permanent value label display policy (default: "none") */  valueLabel?: VarianceValueLabel  /** Tooltip depth: "full" shows actual, plan, and variance; "variance" shows delta only (default: "full") */  tooltipMode?: "variance" | "full"  /** Maximum bar thickness in pixels (default: 36) */  maxBarSize?: number  /** Motion configuration (honors prefers-reduced-motion) */  motion?: boolean | { duration?: number }  /** Additional CSS class names */  className?: string  /** Semantic chart title for accessibility */  title?: string  /** Analytical description for screen readers */  description?: string  /** Whether the chart is currently loading data */  loading?: boolean}/* -------------------------------------------------------------------------- *//*  Mathematical & Domain Helpers                                             *//* -------------------------------------------------------------------------- */export function isFiniteNumber(val: unknown): val is number {  return typeof val === "number" && Number.isFinite(val) && !Number.isNaN(val)}/** * Computes arithmetic variance as `actual - plan`. * Returns null if either value is non-finite or missing. */export function computeVariance(  actual: number | null | undefined,  plan: number | null | undefined): number | null {  if (!isFiniteNumber(actual) || !isFiniteNumber(plan)) {    return null  }  return actual - plan}/** * Classifies variance direction into neutral factual terms. */export function classifyVarianceDirection(  variance: number | null): "positive" | "negative" | "zero" | "unavailable" {  if (variance === null || !Number.isFinite(variance)) {    return "unavailable"  }  if (variance > 0) return "positive"  if (variance < 0) return "negative"  return "zero"}export function classifyVariancePosition(  variance: number | null): VariancePosition {  if (variance === null || !Number.isFinite(variance)) {    return "unavailable"  }  if (variance > 0) return "above"  if (variance < 0) return "below"  return "equal"}/** * Resolves quantitative domain following Policy B: * - Mixed signs: Symmetric around zero [-extent, +extent] * - Positive only: [0, safeMax] * - Negative only: [safeMin, 0] * - All zero: [-1, 1] */export function resolveVarianceDomain(  validVariances: readonly number[],  explicitDomain?: [number, number] | "auto"): [number, number] {  if (explicitDomain && explicitDomain !== "auto" && Array.isArray(explicitDomain)) {    const [dMin, dMax] = explicitDomain    if (isFiniteNumber(dMin) && isFiniteNumber(dMax)) {      // Must include 0      return [Math.min(0, dMin), Math.max(0, dMax)]    }  }  if (validVariances.length === 0) {    return [-10, 10]  }  let min = 0  let max = 0  for (const v of validVariances) {    if (v < min) min = v    if (v > max) max = v  }  // All zero  if (min === 0 && max === 0) {    return [-1, 1]  }  // Mixed signs -> Symmetric domain  if (min < 0 && max > 0) {    const extent = Math.max(Math.abs(min), Math.abs(max))    const padded = Math.ceil(extent * 1.08)    return [-padded, padded]  }  // Negative only  if (max <= 0) {    const paddedMin = Math.floor(min * 1.08)    return [paddedMin, 0]  }  // Positive only  const paddedMax = Math.ceil(max * 1.08)  return [0, paddedMax]}/** * Formats a signed variance number with explicit +/- and unicode minus. */export function defaultFormatVariance(v: number): string {  if (v === 0) return "0"  if (v > 0) return `+${v.toLocaleString()}`  return `\u2212${Math.abs(v).toLocaleString()}`}/* -------------------------------------------------------------------------- *//*  Main Component                                                            *//* -------------------------------------------------------------------------- */export function VarianceBars<TData extends Record<string, unknown> = Record<string, unknown>>({  data,  categoryKey,  series,  orientation = "vertical",  height = 340,  domain = "auto",  positiveColor = "var(--chart-1, #3b82f6)",  negativeColor = "var(--chart-2, #f97316)",  zeroColor = "var(--chart-axis, #71717a)",  selectionColor = "var(--chart-selection, #eab308)",  showGrid = true,  showZeroLine = true,  showLegend = false,  valueLabel = "none",  tooltipMode = "full",  maxBarSize = 36,  motion = true,  className,  title = "Variance Analysis",  description = "Categorical variance chart displaying actual-versus-plan deviations around an explicit zero baseline.",  loading = false,}: VarianceBarsProps<TData>) {  const isHorizontal = orientation === "horizontal"  // 1. Prepare data immutably  const preparedData = React.useMemo<PreparedVarianceDatum<TData>[]>(() => {    if (!Array.isArray(data)) return []    return data.map((item, idx) => {      const rawActual = item[series.actualKey]      const rawPlan = item[series.planKey]      const actual = isFiniteNumber(rawActual) ? rawActual : null      const plan = isFiniteNumber(rawPlan) ? rawPlan : null      const variance = computeVariance(actual, plan)      const direction = classifyVarianceDirection(variance)      const position = classifyVariancePosition(variance)      const categoryVal = item[categoryKey]      const category = categoryVal != null ? String(categoryVal) : `Item ${idx + 1}`      return {        ...item,        __source: item,        __index: idx,        __category: category,        __actual: actual,        __plan: plan,        __variance: variance,        __direction: direction,        __position: position,        __plotcnVariance: variance,      }    })  }, [data, categoryKey, series.actualKey, series.planKey])  // 2. Extract valid finite variances and compute domain  const validVariances = React.useMemo(() => {    return preparedData      .map((d) => d.__variance)      .filter((v): v is number => isFiniteNumber(v))  }, [preparedData])  const computedDomain = React.useMemo(() => {    return resolveVarianceDomain(validVariances, domain)  }, [validVariances, domain])  // 3. Active inspection state  const [activeIndex, setActiveIndex] = React.useState<number | null>(null)  const containerRef = React.useRef<HTMLDivElement>(null)  // Keyboard navigation  const handleKeyDown = React.useCallback(    (e: React.KeyboardEvent) => {      if (preparedData.length === 0) return      if (isHorizontal) {        if (e.key === "ArrowDown") {          e.preventDefault()          setActiveIndex((prev) => (prev === null || prev >= preparedData.length - 1 ? 0 : prev + 1))        } else if (e.key === "ArrowUp") {          e.preventDefault()          setActiveIndex((prev) => (prev === null || prev <= 0 ? preparedData.length - 1 : prev - 1))        } else if (e.key === "Home") {          e.preventDefault()          setActiveIndex(0)        } else if (e.key === "End") {          e.preventDefault()          setActiveIndex(preparedData.length - 1)        }      } else {        if (e.key === "ArrowRight") {          e.preventDefault()          setActiveIndex((prev) => (prev === null || prev >= preparedData.length - 1 ? 0 : prev + 1))        } else if (e.key === "ArrowLeft") {          e.preventDefault()          setActiveIndex((prev) => (prev === null || prev <= 0 ? preparedData.length - 1 : prev - 1))        } else if (e.key === "Home") {          e.preventDefault()          setActiveIndex(0)        } else if (e.key === "End") {          e.preventDefault()          setActiveIndex(preparedData.length - 1)        }      }    },    [isHorizontal, preparedData.length]  )  // Color resolver per datum  const getBarFill = React.useCallback(    (datum: PreparedVarianceDatum<TData>, isFocused: boolean) => {      if (isFocused && selectionColor) {        return selectionColor      }      if (datum.__direction === "positive") return positiveColor      if (datum.__direction === "negative") return negativeColor      return zeroColor    },    [positiveColor, negativeColor, zeroColor, selectionColor]  )  // Formatters  const valFmt = series.valueFormatter || ((v: number) => v.toLocaleString())  const varFmt = series.varianceFormatter || defaultFormatVariance  const actualLabelText = series.actualLabel || "Actual"  const planLabelText = series.planLabel || "Plan"  const varianceLabelText = series.varianceLabel || "Variance"  // Summary counts for accessibility  const summaryCounts = React.useMemo(() => {    let above = 0    let below = 0    let onPlan = 0    let unavail = 0    for (const d of preparedData) {      if (d.__position === "above") above++      else if (d.__position === "below") below++      else if (d.__position === "equal") onPlan++      else unavail++    }    return { above, below, onPlan, unavail, total: preparedData.length }  }, [preparedData])  if (loading) {    return <ChartLoadingState style={{ height }} className={className} />  }  if (preparedData.length === 0) {    return <ChartEmptyState style={{ height }} title="No variance data available" className={className} />  }  const chartMargin = isHorizontal    ? { top: 16, right: 32, bottom: 24, left: 16 }    : { top: 24, right: 20, bottom: 32, left: 20 }  return (    <figure      ref={containerRef}      role="region"      aria-label={title}      tabIndex={0}      onKeyDown={handleKeyDown}      className={cn(        "plotcn-chart plotcn-variance-chart relative flex flex-col w-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 select-none",        className      )}      style={{        ["--chart-1" as string]: positiveColor,        ["--chart-2" as string]: negativeColor,      }}    >      <div className="sr-only">        <h3>{title}</h3>        <p>{description}</p>        <p>          Showing {summaryCounts.total} categories. {summaryCounts.above} above plan, {summaryCounts.below} below plan,{" "}          {summaryCounts.onPlan} on plan          {summaryCounts.unavail > 0 ? `, ${summaryCounts.unavail} unavailable` : ""}.          Use arrow keys ({isHorizontal ? "Up and Down" : "Left and Right"}) to inspect categories.        </p>      </div>      {/* Structural Directional Legend */}      {showLegend && (        <div          role="region"          aria-label="Directional Legend"          className="flex flex-wrap items-center justify-end gap-5 px-3 py-2 text-xs text-muted-foreground border-b border-border/50 mb-2"        >          <div className="flex items-center gap-1.5">            <span              className="inline-block w-3 h-3 rounded-xs shrink-0"              style={{ backgroundColor: positiveColor }}              aria-hidden="true"            />            <span className="font-medium text-foreground">              Above {planLabelText} (+{varianceLabelText})            </span>          </div>          <div className="flex items-center gap-1.5">            <span              className="inline-block w-3 h-3 rounded-xs shrink-0"              style={{ backgroundColor: negativeColor }}              aria-hidden="true"            />            <span className="font-medium text-foreground">              Below {planLabelText} (&minus;{varianceLabelText})            </span>          </div>          <div className="flex items-center gap-1.5">            <span              className="inline-block w-3 h-0.5 shrink-0"              style={{ backgroundColor: zeroColor }}              aria-hidden="true"            />            <span className="font-medium text-foreground">Equal (Zero variance)</span>          </div>        </div>      )}      {/* Main Visualization Canvas */}      <ChartContainer        className="w-full relative"        style={{ height: typeof height === "number" ? `${height}px` : height }}      >        <ResponsiveContainer          width="100%"          height="100%"          initialDimension={{            width: 320,            height: typeof height === "number" ? height : 340,          }}        >          <BarChart            data={preparedData}            layout={isHorizontal ? "vertical" : "horizontal"}            margin={chartMargin}            onMouseMove={(state) => {              if (state && state.activeTooltipIndex !== undefined) {                const idx = Number(state.activeTooltipIndex)                if (!Number.isNaN(idx)) {                  setActiveIndex(idx)                }              }            }}            onMouseLeave={() => setActiveIndex(null)}          >            {showGrid && (              <CartesianGrid                strokeDasharray="3 3"                className="stroke-border/40"                horizontal={!isHorizontal}                vertical={isHorizontal}              />            )}            {isHorizontal ? (              <>                <XAxis                  type="number"                  domain={computedDomain}                  tickLine={false}                  axisLine={{ stroke: "var(--border)", strokeWidth: 1 }}                  tick={{ fill: "var(--muted-foreground)", fontSize: 11 }}                  tickFormatter={varFmt}                />                <YAxis                  type="category"                  dataKey="__category"                  tickLine={false}                  axisLine={false}                  width={100}                  tick={{ fill: "var(--foreground)", fontSize: 12, fontWeight: 500 }}                />                {showZeroLine && (                  <ReferenceLine                    x={0}                    stroke={zeroColor}                    strokeWidth={2}                    className="plotcn-variance-zero-baseline"                  />                )}              </>            ) : (              <>                <XAxis                  type="category"                  dataKey="__category"                  tickLine={false}                  axisLine={{ stroke: "var(--border)", strokeWidth: 1 }}                  tick={{ fill: "var(--foreground)", fontSize: 12, fontWeight: 500 }}                />                <YAxis                  type="number"                  domain={computedDomain}                  tickLine={false}                  axisLine={false}                  tick={{ fill: "var(--muted-foreground)", fontSize: 11 }}                  tickFormatter={varFmt}                  width={55}                />                {showZeroLine && (                  <ReferenceLine                    y={0}                    stroke={zeroColor}                    strokeWidth={2}                    className="plotcn-variance-zero-baseline"                  />                )}              </>            )}            <Tooltip              isAnimationActive={false}              cursor={{                fill: "var(--accent)",                opacity: 0.15,              }}              allowEscapeViewBox={{ x: false, y: false }}              content={({ active, payload }) => {                if (!active || !payload || !payload.length) return null                const datum = payload[0].payload as PreparedVarianceDatum<TData>                if (!datum) return null                const hasActual = datum.__actual !== null                const hasPlan = datum.__plan !== null                const hasVariance = datum.__variance !== null                return (                  <div                    role="tooltip"                    className="rounded-lg border border-border/70 bg-popover/95 backdrop-blur-md px-3.5 py-2.5 shadow-xl text-xs space-y-2 pointer-events-none min-w-[190px] max-w-[calc(100cqw-16px)]"                  >                    <div className="font-semibold text-foreground text-sm border-b border-border/50 pb-1.5 truncate">                      {datum.__category}                    </div>                    <div className="space-y-1.5">                      {tooltipMode === "full" && (                        <>                          <div className="flex items-center justify-between gap-4 text-muted-foreground">                            <span>{actualLabelText}</span>                            <span className="font-medium text-foreground font-mono">                              {hasActual ? valFmt(datum.__actual!) : "Unavailable"}                            </span>                          </div>                          <div className="flex items-center justify-between gap-4 text-muted-foreground">                            <span>{planLabelText}</span>                            <span className="font-medium text-foreground font-mono">                              {hasPlan ? valFmt(datum.__plan!) : "Unavailable"}                            </span>                          </div>                        </>                      )}                      <div className="flex items-center justify-between gap-4 pt-1 border-t border-border/40 font-semibold">                        <span className="text-foreground">{varianceLabelText}</span>                        <span                          className={cn(                            "font-mono font-bold",                            datum.__direction === "positive"                              ? "text-blue-500 dark:text-blue-400"                              : datum.__direction === "negative"                              ? "text-orange-500 dark:text-orange-400"                              : "text-muted-foreground"                          )}                        >                          {hasVariance ? varFmt(datum.__variance!) : "Unavailable"}                        </span>                      </div>                      <div className="flex items-center justify-between gap-4 pt-0.5 text-[11px] text-muted-foreground/80">                        <span>Position</span>                        <span className="capitalize text-foreground font-medium">                          {datum.__position === "above" && `Above ${planLabelText.toLowerCase()}`}                          {datum.__position === "below" && `Below ${planLabelText.toLowerCase()}`}                          {datum.__position === "equal" && `On ${planLabelText.toLowerCase()}`}                          {datum.__position === "unavailable" && "Unavailable"}                        </span>                      </div>                    </div>                  </div>                )              }}            />            <Bar              dataKey="__plotcnVariance"              fill={positiveColor}              maxBarSize={maxBarSize}              isAnimationActive={Boolean(motion)}              animationDuration={typeof motion === "object" && motion.duration ? motion.duration : 600}              radius={isHorizontal ? [0, 4, 4, 0] : [4, 4, 0, 0]}            >              {preparedData.map((entry, idx) => {                const isFocused = activeIndex === idx                return (                  <Cell                    key={`variance-cell-${entry.__category}-${idx}`}                    fill={getBarFill(entry, isFocused)}                    stroke={isFocused ? selectionColor : "none"}                    strokeWidth={isFocused ? 2 : 0}                    className="transition-colors duration-150"                  />                )              })}              {valueLabel !== "none" && (                <LabelList                  dataKey="__plotcnVariance"                  position={isHorizontal ? "right" : "top"}                  formatter={(val: unknown) => (isFiniteNumber(val) ? varFmt(val) : "")}                  className="fill-foreground font-mono text-[11px]"                />              )}            </Bar>          </BarChart>        </ResponsiveContainer>      </ChartContainer>      {/* Accessible Structured Data Alternative Table */}      <div className="sr-only">        <table>          <caption>            {title} — Structured actual versus plan variance data          </caption>          <thead>            <tr>              <th scope="col">Category</th>              <th scope="col">{actualLabelText}</th>              <th scope="col">{planLabelText}</th>              <th scope="col">{varianceLabelText}</th>              <th scope="col">Position</th>            </tr>          </thead>          <tbody>            {preparedData.map((d, idx) => (              <tr key={`a11y-row-${idx}`}>                <th scope="row">{d.__category}</th>                <td>{d.__actual !== null ? valFmt(d.__actual) : "Unavailable"}</td>                <td>{d.__plan !== null ? valFmt(d.__plan) : "Unavailable"}</td>                <td>{d.__variance !== null ? varFmt(d.__variance) : "Unavailable"}</td>                <td>                  {d.__position === "above" && `Above ${planLabelText.toLowerCase()}`}                  {d.__position === "below" && `Below ${planLabelText.toLowerCase()}`}                  {d.__position === "equal" && `On ${planLabelText.toLowerCase()}`}                  {d.__position === "unavailable" && "Unavailable"}                </td>              </tr>            ))}          </tbody>        </table>      </div>    </figure>  )}