029 / D3.JS / LINE

D3 Animated Line Plot

D3.js

Direct SVG path rendering powered by D3 scales and stroke-dashoffset drawing animation using native SVG.

SPEC
#029
ENGINE
D3.js
FAMILY
Line
RENDERER
svg
STATUS
beta

Installation

PLOTCN/REGISTRY/D3-ANIMATED-LINE/SOURCE
pnpm dlx shadcn@latest add @plotcn/d3-animated-line

Checking public registry…

View local registry JSON
REGISTRY direct URL·ENGINE D3.js·FILES 1·DEPENDENCIES 5

Copied as source into your project (requires d3-scale, d3-shape, d3-array).

× 340Container width
Measuring preview...
D3 · SVG · 0 × 340pxMotion enabled · ResizeObserver
Component Specifications
BEST FOR
D3 Animated Line Plot visual analytics and line category telemetry.
DATA MODEL
Normalized line records matching canonical dataShape.
INTERACTION
Static presentation
RESPONSIVE
Container-aware ResizeObserver
ANIMATION
Interpolated transitions
RUNTIME
D3.js svg
01 / Component Usage

Basic & Interactive Integration

Import D3AnimatedLine directly into your React client component. Provide an array of observations conforming to the data shape.

page.tsx
import { D3AnimatedLine } from "@/components/charts/d3/d3-animated-line"

const data = [
  {
    "label": "Jan",
    "value": 186
  },
  {
    "label": "Feb",
    "value": 305
  },
  {
    "label": "Mar",
    "value": 237
  }
]

export default function ChartDemo() {
  return (
    <div className="w-full max-w-xl h-80">
      <D3AnimatedLine data={data} color="#10b981" />
    </div>
  )
}

Requires a client context ('use client') for DOM lifecycle and container measurement.

Expected ResultLive 220px
Container-aware width · 200px height
02 / Data Contract

Input Schema & Coordinates

Input data format for D3 Animated Line Plot. Records should provide required coordinates and metrics.

Chart data contract
FieldTypeRequiredMeaning
labelstringYesCategory label or horizontal axis timestamp
valuenumberYesMetric numerical magnitude
Null / Missing Value Policy:

Missing values are gracefully handled or skipped depending on interpolation rules.

Coordinate Ordering Policy:

Records should follow monotonic sorting for sequential coordinate plots.

Example Observation Payload:
[
  {
    "label": "Jan",
    "value": 186
  },
  {
    "label": "Feb",
    "value": 305
  },
  {
    "label": "Mar",
    "value": 237
  }
]
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):
<D3AnimatedLine
  data={data}
  xKey="date"
  seriesKey="value"
/>
Interactive Prop Preview Lab
colorstring

Primary theme stroke or fill color. Accepts CSS variables or color values.

Select value to preview live:#10b981
Active: color="#10b981"Default: "var(--chart-1, #10b981)"
All Properties (4)
Component properties
PropertyTypeDefaultRequiredDescription
dataReq
Record<string, any>[]Yes

Array of structured observations or datum objects to visualize.

Best for: Primary dataset

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

Primary theme stroke or fill color. Accepts CSS variables or color values.

Best for: Theming and visual branding

number | string320No

Container height in pixels or standard CSS dimension strings.

stringundefinedNo

Tailwind CSS classes or custom stylesheet class applied to container wrapper.

04 / Cookbook & States

Component Variants & Edge States

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

D3 Animated Line Plot (Default)

Standard presentation with Plotcn dark theme tokens.

<D3AnimatedLine data={points} color="#10b981" />
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

Scales gracefully according to container width, recalculating layout bounds.

Desktop
1100px+

Full horizontal scale and complete axis tick labeling.

Tablet
768px

Tighter margins with optimized coordinate grid spacing.

Mobile
390px

Compact labels and touch-friendly interaction.

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

Accessibility & Navigation Standards

Accessible visualization presenting D3 Animated Line Plot data with semantic contrast.

Semantic Role & Landmark

Container mounts as figure[role="region"] with explicit assistive label.

Color-Independent Legibility

Coordinate baselines and labels preserve data legibility regardless of color perception.

Screen Reader Summary

Embeds visually hidden summary (.sr-only) declaring: “D3 Animated Line Plot data visualization

Reduced Motion Support

Suppresses transitions when user prefers reduced motion.

Keyboard Interaction Model
Keyboard interaction model
KeyAction
TabFocus chart container and navigate through interactive regions.
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
D3AnimatedLine(Component Entry)
└──D3.js Engine Layer[svg]

Visualization calculations and rendering via D3.js.

Involved Source Files & Registry Assets
registry/d3/d3-animated-line.tsx
Primary component source implementation.
registry/d3/d3-animated-line.tsx
"use client"import * as React from "react"import { scaleLinear } from "d3-scale"import { line, curveMonotoneX, curveLinear, curveStep } from "d3-shape"import { max, min } from "d3-array"import { useChartReducedMotion } from "../shared/use-chart-reduced-motion"import { ChartEmptyState, ChartErrorState } from "../shared/chart-state"import { cn } from "@/lib/utils"export interface D3LineDatum {  x: number | string  y: number}export interface D3AnimatedLineProps {  data: D3LineDatum[]  width?: number  height?: number  color?: string  curve?: "linear" | "monotone" | "step"  grid?: "off" | "horizontal"  className?: string  /**   * Optional motion configuration or toggle.   * Section 10.5, 10.6.   */  motion?: boolean | { duration?: number }}/** * D3 Animated Line Chart * D3 computes continuous scale projections and monotone curves; React renders the SVG elements. * Features semantic initial draw animation and honors reduced-motion preferences. * Section 10.19, 10.20, 10.58. */export function D3AnimatedLine({  data,  width = 600,  height = 300,  color = "var(--chart-1)",  className,  curve = "monotone",  grid = "horizontal",  motion = true,}: D3AnimatedLineProps) {  const margin = { top: 20, right: 20, bottom: 30, left: 40 }  const innerWidth = width - margin.left - margin.right  const innerHeight = height - margin.top - margin.bottom  const pathRef = React.useRef<SVGPathElement>(null)  const [mounted, setMounted] = React.useState(false)  const [pathLength, setPathLength] = React.useState(0)  const reducedMotion = useChartReducedMotion()  React.useEffect(() => {    if (pathRef.current) {      setPathLength(pathRef.current.getTotalLength())    }    // Defer mount trigger to next frame for transition to run    const timer = setTimeout(() => setMounted(true), 16)    return () => clearTimeout(timer)  }, [data, width, height, curve])  const yValues = data.map((d) => d.y)  const minY = min(yValues) ?? 0  const maxY = max(yValues) ?? 100  const xScale = React.useMemo(() => {    return scaleLinear()      .domain([0, Math.max(data.length - 1, 1)])      .range([0, innerWidth])  }, [data.length, innerWidth])  const yScale = React.useMemo(() => {    return scaleLinear()      .domain([Math.min(0, minY), maxY * 1.1])      .range([innerHeight, 0])  }, [minY, maxY, innerHeight])  const linePath = React.useMemo(() => {    const generator = line<D3LineDatum>()      .x((_, i) => xScale(i))      .y((d) => yScale(d.y))      .curve({linear:curveLinear,monotone:curveMonotoneX,step:curveStep}[curve])    return generator(data) || ""  }, [data, xScale, yScale, curve])  const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null)  const svgRef = React.useRef<SVGSVGElement>(null)  if (!data.length) return <ChartEmptyState />  if (data.some((datum) => !Number.isFinite(datum.y))) {    return <ChartErrorState description="Values must be finite numbers. Missing values are not replaced with zero." />  }  const isAnimated = motion !== false && !reducedMotion  const duration = typeof motion === "object" && motion?.duration !== undefined ? motion.duration : 0.35  const pathStyle: React.CSSProperties =    isAnimated && pathLength > 0      ? {          strokeDasharray: pathLength,          strokeDashoffset: mounted ? 0 : pathLength,          transition: `stroke-dashoffset ${duration}s cubic-bezier(0.16, 1, 0.3, 1)`,        }      : {}  return (    <div className={cn("plotcn-chart relative w-full overflow-hidden rounded-xl border border-[var(--chart-border)] bg-[var(--chart-background)] p-4", className)}>      <svg        ref={svgRef}        viewBox={`0 0 ${width} ${height}`}        className="w-full h-auto overflow-visible select-none"        aria-label="D3 Animated Line Chart"        role="img"      >        <g transform={`translate(${margin.left},${margin.top})`}>          {/* Grid lines (decorative - Section 11.41) */}          <g aria-hidden="true">            {grid !== "off" && yScale.ticks(5).map((tick) => (              <line                key={tick}                x1={0}                x2={innerWidth}                y1={yScale(tick)}                y2={yScale(tick)}                stroke="var(--chart-grid)"                strokeDasharray="3 3"              />            ))}          </g>          {/* Curve */}          <path            ref={pathRef}            d={linePath}            fill="none"            stroke={color}            strokeWidth={2.5}            strokeLinecap="round"            style={pathStyle}          />          {/* Interactive Crosshair & Highlighted Point */}          {hoveredIndex !== null && data[hoveredIndex] && (            <g pointerEvents="none">              <line                x1={xScale(hoveredIndex)}                x2={xScale(hoveredIndex)}                y1={0}                y2={innerHeight}                stroke="var(--chart-crosshair)"                strokeDasharray="3 3"              />              <circle                cx={xScale(hoveredIndex)}                cy={yScale(data[hoveredIndex].y)}                r={6}                fill="var(--chart-background)"                stroke={color}                strokeWidth={2.5}              />            </g>          )}          {/* Coordinate points */}          {data.map((d, i) => (            <circle              key={i}              cx={xScale(i)}              cy={yScale(d.y)}              r={hoveredIndex === i ? 5.5 : 3.5}              fill="var(--chart-background)"              stroke={color}              strokeWidth={2}              className="transition-all duration-150"            />          ))}          {/* Transparent Overlay for Smooth Crosshair Hover Tracking */}          <rect            x={0}            y={0}            width={innerWidth}            height={innerHeight}            fill="transparent"            className="cursor-crosshair"            onMouseMove={(e) => {              const rect = e.currentTarget.getBoundingClientRect()              const relX = Math.max(0, Math.min(rect.width, e.clientX - rect.left))              const idx = Math.round((relX / rect.width) * (data.length - 1))              const clamped = Math.max(0, Math.min(data.length - 1, idx))              setHoveredIndex(clamped)            }}            onMouseLeave={() => setHoveredIndex(null)}          />        </g>      </svg>      {/* Floating Theme Tooltip */}      {hoveredIndex !== null && data[hoveredIndex] && (        <div          className="absolute pointer-events-none z-30 -translate-x-1/2 -translate-y-full transition-all duration-75"          style={{            left: `${((margin.left + xScale(hoveredIndex)) / width) * 100}%`,            top: `${((margin.top + yScale(data[hoveredIndex].y)) / height) * 100}%`,            marginTop: "-12px",          }}        >          <div className="plotcn-chart-tooltip">            <div className="mb-1.5 flex items-center justify-between gap-2 border-b border-[var(--chart-tooltip-border)] pb-1 font-mono text-[11px] text-[var(--chart-tooltip-muted)]">              <span>{String(data[hoveredIndex].x)}</span>              <span                className="size-2 rounded-full"                style={{ backgroundColor: color }}              />            </div>            <div className="flex items-center justify-between gap-3 font-mono">              <span className="text-[11px] text-[var(--chart-tooltip-muted)]">Value</span>              <span className="text-xs font-semibold tabular-nums text-[var(--chart-tooltip-foreground)]">                {data[hoveredIndex].y.toLocaleString()}              </span>            </div>          </div>        </div>      )}    </div>  )}