Skip to content

v4.0.0

Choose a tag to compare

@davelandry davelandry released this 07 Jul 20:38
· 35 commits to main since this release

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 via Path2D.
    Example ↗
  • Viz.destroy() disconnects the ResizeObserver and removes the body
    touchstart listener, 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/react entry 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/core entry stays
    curated to the stable public API; the internal surface 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
    scatter Circle/Rect marks and Geomap points (opt out with
    shapeConfig.Circle.trail: false); parity across the SVG and Canvas backends.
    shapeConfig.Circle.trailPersist keeps past moves visible too — a number of
    step-segments, or true for 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
    trailPersist automatically 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). Unlike hover/active it 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 contrastRatio helper back the above.
  • Locale-aware titleCase. titleCase(str, locale) accepts a locale code (or
    a TitleCaseRules object) and normalizes case in both directions — it
    lowercases ALL-CAPS "shouting" input and minor words, force-uppercases known
    acronyms (with automatic plurals, e.g. tvsTVs), and preserves genuine
    mixed-case (McDonald, iOS). Per-language rule sets and a {style: "sentence"}
    mode ship as the new titleCaseLocale dictionary (and a TitleCaseRules type),
    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 size accessor 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 by tsc and shipped with every package.
  • Per-chart folder structure (charts/<Chart>/{index,applyLayout,emit}.ts), with
    instance state namespaced under schema/ctx.
  • @d3plus/data grouping now builds on d3-array (groups/rollups); the
    deprecated d3-collection dependency 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 ↗
  • colorContrast now 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.sort drives 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; playButtonInterval is the fallback
    cadence only when duration is 0 (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 runtime installFluent/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 with UPDATE_SNAPSHOTS=1), jsdom
    pipeline-parity snapshots, DOM-vs-scene render-parity checks, a full
    @d3plus/render unit suite, and dev harnesses (chart-compare for
    v3 / v4-SVG / v4-Canvas contact sheets, chart-screenshots, and
    story-render-check). Playwright/Chromium is now a test dependency, and CI runs
    a build:types declaration-emit typecheck across every package.
  • Docs generation was rebuilt for the v4 charts: README config tables and
    Storybook argTypes are derived from each ChartDefinition's fields, the runtime
    installFluent accessors, and the typed config interfaces (not just JSDoc);
    README source links pin to main so 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 a umd-entry.ts superset while the typed ESM entry stays curated;
    the release script syncs all workspace versions so none ship pinned to a stale
    version; @d3plus/types ships types-only (no UMD) with @d3plus/react as an
    optional peer so non-React projects don't pull in React.
  • Lint. max-lines (500) and max-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 forceUpdate is now a top-level prop, not a config key. Use
    <Treemap forceUpdate /> instead of config={{forceUpdate: true}}.
  • Code that reached into a chart's intermediate DOM/d3-selection internals during
    the draw pass, or subclassed Viz and overrode private _draw internals, 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.