v4.0.0
v4 is a ground-up re-architecture of the rendering engine. Charts now compile to
a serializable scene graph that is painted by a pluggable backend, rather than
mutating the DOM directly as they draw. SVG remains the default backend and the
rendered output is intended to match v3 — the public chart API (fluent setters,
config(), the chart classes) is unchanged. Most users upgrade with no code
changes. See MIGRATION.md for details.
Added
@d3plus/render— a new package providing the renderer abstraction. It
diffs and paints the scene graph and owns the SVG and Canvas backends.- Canvas backend. Every visualization accepts
.renderer("svg" | "canvas")
(default"svg"). The Canvas backend paints dense, high-shape-count charts more
efficiently and supports pointer hit-testing viaPath2D.
Example ↗ Viz.destroy()disconnects theResizeObserverand removes the body
touchstartlistener, preventing leaks when a chart is torn down. The React
wrapper calls it automatically on unmount.@d3plus/types— a unified package that re-exports every d3plus type from a
single import for typing config objects and parameters. React component types
are in a separate@d3plus/types/reactentry so non-React projects don't pull
in React.@d3plus/core/internal— an opt-in entry point exposing the v4 pipeline
(layout stages,ChartDefinitions, feature modules,runVizPipeline,
resolveSpec,installFluent, axis measurement, …) for parity tests and
advanced consumers building custom charts. The root@d3plus/coreentry stays
curated to the stable public API; theinternalsurface is not semver-stable.- Share-of-total percentages in Pie and Donut tooltips.
Example ↗ - Sankey link enter/exit animations (stroke-width grows from zero).
Example ↗ - Motion trails. Points that move between frames (Timeline play) sweep a
tapering "cone" from their previous position to the current one, fading from
the mark's color at the head to transparent at the tail. Each cone traces the
mark's swept silhouette — a circle capped with a rounded tail, a rect as the
convex hull of its corners (corner-to-corner off-axis). On by default for
scatterCircle/Rectmarks and Geomap points (opt out with
shapeConfig.Circle.trail: false); parity across the SVG and Canvas backends.
shapeConfig.Circle.trailPersistkeeps past moves visible too — a number of
step-segments, ortruefor a long fading snail-trail. Persistent trails
follow the timeline's direction (growing forward, retracting on scrub-back) —
a multi-period jump traces through the skipped periods rather than cutting a
straight line — and draw as a single shape so overlapping turns don't darken.
Setting
trailPersistautomatically switches the chart to a single-period timeline
(brushing: false) and fixed axes (axisPersist: true) — the two conditions a
persistent trail needs to stay coherent — so it works with the one option.
Circles ↗
· Squares ↗
· Persistent ↗ - Visual-regression, pipeline-parity, and v3↔v4 chart-compare test harnesses.
colorValidate(@d3plus/color) — validates a palette against the
checks that can be computed from color alone: OKLCH lightness band, chroma
floor, colorblind (Machado-2009 protan/deutan/tritan ΔE) separation, and WCAG
contrast vs the surface; plus an ordinal-ramp mode. The default categorical
palette is now gated against it.
Example ↗colorRamp(@d3plus/color) — builds an even single-hue light→dark ramp
in OKLab (holds the hue, so the pale end keeps its identity instead of drifting
to white). Continuous color scales now step through it.
Example ↗highlight(predicate)— a standing emphasis: the matching marks keep their
color while every other mark is de-emphasized to a neutral gray (highlight one
series, gray the rest). Unlikehover/activeit survives pointer movement.
Example ↗colorOrdinal(true)— treats a discrete color field as ordered, coloring
it with a single-hue light→dark ramp instead of nominal categorical hues.
Example ↗- OKLab/OKLCH conversions and a WCAG
contrastRatiohelper back the above. - Locale-aware
titleCase.titleCase(str, locale)accepts a locale code (or
aTitleCaseRulesobject) and normalizes case in both directions — it
lowercases ALL-CAPS "shouting" input and minor words, force-uppercases known
acronyms (with automatic plurals, e.g.tvs→TVs), and preserves genuine
mixed-case (McDonald,iOS). Per-language rule sets and a{style: "sentence"}
mode ship as the newtitleCaseLocaledictionary (and aTitleCaseRulestype),
exported from@d3plus/locales.
Example ↗ - Animated text font-size. Labels whose font-size changes between renders now
ease into the new size, position, and rotation (pivoted on the anchor-aware
visual center) instead of snapping — in both the SVG and Canvas backends. - Plot circles auto-layer by size. When a
sizeaccessor is set, scatter
circles paint largest-behind so smaller marks stay visible on top (override with
shapeConfig.Circle.sort).
Changed
- Scene-graph rendering pipeline. Charts are declarative
ChartDefinitions
fed through a pure draw pipeline (runVizPipeline) composed of stages and
opt-in feature modules (legend, color scale, timeline, zoom controls,
title/subtitle/total, back button). Drawing no longer mutates instance state
mid-pass. - Full, strict TypeScript. The chart pipeline is now
any-free; types are
generated bytscand shipped with every package. - Per-chart folder structure (
charts/<Chart>/{index,applyLayout,emit}.ts), with
instance state namespaced underschema/ctx. @d3plus/datagrouping now builds ond3-array(groups/rollups); the
deprecatedd3-collectiondependency has been removed.nest()/nestGroups()
remain exported.- Default color palette re-stepped for colorblind safety. The eight primary
categorical slots are new open-color steps chosen to sit inside the OKLCH
lightness band, clear the chroma floor, and stay distinct under protanopia and
deuteranopia (the slot order — the CVD-safety mechanism — is unchanged). Marks
shift hue slightly as a result.
Example ↗ - Continuous color scales default to a single blue hue (magnitude reads as
one hue getting darker) and diverging scales default to blue↔gray↔red (warm/cool
poles that stay distinct under CVD), replacing the previous multi-hue ramp and
red↔green diverging.on/off(green/red) still color boolean data.
Example ↗ colorContrastnow picks text color by WCAG contrast, not the YIQ
approximation — it returns whichever of the two text tokens has the higher
contrast ratio against the background (so e.g. bright greens/teals correctly get
dark text).
Example ↗.sort()/shapeConfig.sortdrives paint order. A sort comparator now
stamps a stable per-datum paint depth (z) rather than reordering the data
array, so mark layering no longer disturbs the data join, layout, or enter/exit
animations.- Timeline auto-play cadence follows the transition
duration. Each period
fully animates before the next advances;playButtonIntervalis the fallback
cadence only whendurationis0(v3 advanced on a fixed interval regardless
of the transition).
Fixed
- React charts tween between config/prop changes: the chart instance now persists
across updates (destroy()runs only on unmount) instead of tearing down and
re-entering from scratch on each render.
Documentation & website
The documentation site (Storybook, published at d3plus.org)
was substantially rebuilt:
- Every example now carries a prose description, and the "Show code" panel is
live — it rebuilds the<Chart config={…}/>snippet from the current control
values on each change instead of showing a frozen snapshot. - Storybook argTypes are generated from the charts themselves. The generator
instantiates each class and reads its runtimeinstallFluent/config()accessor
surface, enriched with types and descriptions mined from the typed config
interfaces — so the full configuration surface (width, domain, ticks, title, …)
now appears as interactive controls. - The sidebar was reorganized under a Guides root (Migration, Configuration,
Rendering, Data, Interactivity, Theming, Accessibility) ahead of the Core API. - Utility-function docs (
@d3plus/color,data,format,text) render as
side-by-side input → output blocks with a matching, drift-proof code snippet. - New example content: motion trails (circle / square / persistent), Canvas
rendering (BarChart, Geomap), interactivity (events, custom tooltip, download
button, RTL locale),highlight()and ordinal color, five color-scale types,
the CVD-color utilities (colorRamp,colorValidate), first-time example pages
for every shape and axis primitive, and function-call demos for the
color/data/format/text utilities.
Developer & tooling
- Testing. New real-Chromium visual-regression snapshots (a structural
fingerprint per chart, regenerated withUPDATE_SNAPSHOTS=1), jsdom
pipeline-parity snapshots, DOM-vs-scene render-parity checks, a full
@d3plus/renderunit suite, and dev harnesses (chart-comparefor
v3 / v4-SVG / v4-Canvas contact sheets,chart-screenshots, and
story-render-check). Playwright/Chromium is now a test dependency, and CI runs
abuild:typesdeclaration-emit typecheck across every package. - Docs generation was rebuilt for the v4 charts: README config tables and
Storybook argTypes are derived from eachChartDefinition's fields, the runtime
installFluentaccessors, and the typed config interfaces (not just JSDoc);
README source links pin tomainso regeneration no longer churns every
"Defined in" link. - Build & packaging. Every root entry file (
index,internal,react,
umd-entry) is transpiled to ESM;@d3plus/core's UMD/CDN global exposes the v4
pipeline via aumd-entry.tssuperset while the typed ESM entry stays curated;
the release script syncs all workspace versions so none ship pinned to a stale
version;@d3plus/typesships types-only (no UMD) with@d3plus/reactas an
optional peer so non-React projects don't pull in React. - Lint.
max-lines(500) andmax-lines-per-function(100) are now enforced
(data dictionaries exempt), which drove several oversized modules to be split. - Dev server live-reload is scoped per tab — editing one chart's dev page
reloads only the tabs viewing it, and each tab drops its SSE connection while
hidden, so many open dev pages no longer exhaust the browser's per-host
connection limit. - Contributor docs (
AGENTS.md,CONTRIBUTING.md) were rewritten for the v4
architecture and the TypeScript / TypeDoc / Storybook toolchain.
Breaking changes
- React
forceUpdateis now a top-level prop, not aconfigkey. Use
<Treemap forceUpdate />instead ofconfig={{forceUpdate: true}}. - Code that reached into a chart's intermediate DOM/d3-selection internals during
the draw pass, or subclassedVizand overrode private_drawinternals, may
need updating — drawing now flows through the scene-graph pipeline. The public
fluent/config()API is unchanged.
Known limitations
- The SVG backend remains the default; both backends paint shapes, gradients,
and texture/pattern fills. The Canvas backend is optimized for dense,
high-shape-count charts where paint performance matters.