New placement="smart" mode that dynamically positions labels based on neighboring point values (peak, trough, rising, falling) to reduce overlapping.
feat(Chart, BrushState): Add band scale (categorical) support for transform pan/zoom and brush selection. Uses range-rescaling pattern to smoothly zoom and pan categorical bar charts. Automatically constrains panning to data boundaries and prevents zooming out past initial view. (#449)
feat(Chart): In projection mode, scaleExtent and translateExtent are now interpreted as relative values (like d3-zoom). scaleExtent: [0.5, 8] means 0.5x to 8x of the fitted projection scale. translateExtent is offset from the initial fitted position in pixels. (#449)
feat(Spline): Support function-valued stroke, fill, and opacity for per-segment styling (#449)
feat(Text): Add format prop and tween numeric value when motion is configured (#449)
Patch Changes
Support tickSpacing for band scales on Axis, thinning tick labels when the domain is larger than the available space. Automatically shows more tick labels when zoomed in on band scale transforms. (#449)
createMotion: Fast-path passthrough when no motion prop is provided, avoiding $state/$effect overhead per axis
createDataMotionMap: Short-circuit when motion is undefined, skipping parseMotionProp overhead
createKey: Only create fill/stroke key trackers in canvas layer (skipped for SVG/HTML)
registerComponent: Skip registerMark for empty MarkInfo (pixel-mode marks)
All primitives: Skip $effect for data motion tracking when no motion is configured
Rect/Image: Avoid per-axis parseMotionProp calls when motion is undefined
feat(Marker): Add square and square-stroke types (#805)
fix(GeoPath): Fix canvas tooltip by conditionally passing onclick to Path, preventing non-interactive overlays from capturing hit canvas events (#449)
fix(scaleBandInvert): Account for range offset in band scale inversion. Previously assumed range started at 0, causing incorrect pixel-to-category mapping when the scale range was transformed. (#449)
fix(TransformContext): Reactively sync processTranslate and disablePointer to TransformState when props change. Fixes inverted globe dragging when dynamically switching between flat and globe projections. (#449)
fix(Chart): Enable scroll zoom for globe projections by including scale: true in default transformApply for globes. (#449)
feat(Raster, Contour): support bounded geo raster overlays with projected interpolation (#449)
fix(LinearGradient, RadialGradient): Register as group instead of mark in canvas component tree so wrapped children (e.g. Arc, Path) are rendered (#449)
fix(ChartState): Don't create spurious implicit series when mark accessor matches chart's own axis accessor, fixing domain corruption for heatmap/Cell charts (#449)
breaking(BrushContext|TransformContext): Rename bind:brushContext / bind:transformContext to bind:state (#663)
Both BrushContext and TransformContext now use bind:state instead of their previous named bindings. Additionally, properties on ChartState have been renamed:
breaking: Rename render context APIs to layer context (#663)
getRenderContext() → getLayerContext()
setRenderContext() → setLayerContext()
supportedContexts prop → layers prop on components
Internal layout/ directory moved to layers/ (affects deep imports)
import { getRenderContext } from 'layerchart' import { getLayerContext } from 'layerchart'
breaking(Chart): Remove isVertical from ChartState, add valueAxis prop to Chart (#663)
ChartState.isVertical has been removed in favor of ChartState.valueAxis ('x' | 'y'), which explicitly defines which axis represents the value (dependent variable).
Simplified charts (BarChart, LineChart, AreaChart, ScatterChart) still accept the orientation prop as before — each chart maps it to the correct valueAxis internally. The <Chart> component itself now uses valueAxis directly, since orientation is ambiguous at that level (a "vertical" BarChart has valueAxis="y" while a "vertical" LineChart has valueAxis="x").
When accessing chart state:
if (chartContext.isVertical) { ... } if (chartContext.valueAxis === 'y') { ... }
When using <Chart> directly (not simplified charts):
The following standalone context functions have been removed in favor of the unified getChartContext() API:
getTooltipContext() / setTooltipContext() → use getChartContext().tooltip
getBrushContext() / setBrushContext() → use getChartContext().brushState
getTransformContext() / setTransformContext() → use getChartContext().transformState
import { getTooltipContext } from 'layerchart' const tooltip = getTooltipContext() import { getChartContext } from 'layerchart' const chart = getChartContext() // access via chart.tooltip
breaking(Arc|Pie|Calendar|GeoPath): Rename tooltipContext to simple tooltip (boolean), simplifying use case (#663)
Minor Changes
feat: Add BoxPlot component for box-and-whisker plots (#663)
New composite mark that renders whiskers, caps, IQR box, median line, and outlier dots. Supports both pre-computed statistics (min, q1, median, q3, max, outliers accessors) and automatic computation from raw values via the values prop. Orientation-aware via valueAxis context.
feat: Add statistical utility functions computeBoxStats() and kde() (#663)
computeBoxStats(values, k?) computes the five-number summary and outliers using the Tukey IQR method
kde(values, options?) computes kernel density estimation using the Epanechnikov kernel with Silverman's rule-of-thumb bandwidth
feat: Add Violin component for violin plots (#663)
New composite mark that renders a symmetric density curve (mirrored area) from raw data using kernel density estimation (Epanechnikov kernel). Supports pre-computed density data via density prop or automatic KDE from raw values via values prop. Optional box and median overlays. Configurable bandwidth, thresholds, and curve.
feat: Add geo projection support for primitives (Circle, Rect, etc) (#663)
feat(Highlight): Add r prop to scale highlight points using the chart's rScale. Supports r={true} to use the chart's r config or a custom accessor. (#663)
Add BrushState.move({ x?, y? }) for programmatic selection control (like d3's brush.move())
Add BrushState.selectAll() to select the full domain extent
Add BrushState.reset() to clear the selection
Add clickToReset prop (default true)
Add zoomOnBrush prop on Chart for simplified charts to opt into brush-to-zoom
Move domain clamping, edge adjustment, and range computation logic into BrushState class
Add BrushChartContext interface for easier testing
feat: Unified component tree for Canvas rendering with proper Group transform scoping. Fixes #662 (#663)
New registerComponentNode({ name, kind, canvasRender }) API replaces both registerCanvasComponent and the InsideCompositeMark boolean context with a single unified component tree.
Canvas rendering now walks the tree recursively with proper save()/restore() scoping, fixing Group transforms (translate, opacity) leaking to sibling components instead of only affecting children.
Composite marks (Area, Threshold, Hull, Labels, Grid) register as 'composite-mark' nodes, automatically preventing child marks from registering with the chart without manual _skipRegistration props.
Removed retainState and name from ComponentRender type — Group's transform scoping is handled by tree position, and component names live on the tree node.
feat: add downloadImage, downloadSvg, getChartImageBlob, and getChartSvgString utilities to export charts as PNG/JPEG/WebP images or SVG files (#663)
feat(SeriesState): Support passing selected as part of series declaration (Ex. <Chart series={...}>) (#663)
feat: Add data mode to primitive components (Circle, Ellipse, Group, Line, Polygon, Rect, Text) (#663)
Primitives now accept string or function accessors for positional props (e.g. x="date", y={d => d.value}) to automatically resolve values through chart scales and iterate over data. Components also accept an optional data prop to override chart context data.
Color properties (fill, stroke) can also be data-driven, resolving per-item through the chart's color scale (cScale). String values are disambiguated: data property names resolve through cScale, while literal CSS colors pass through unchanged.
feat: Mark registration for automatic domain calculation, accessor aggregation, and implicit series (#663)
Marks (Spline, Area, Points, Bars) now register their data, accessors, and colors with the Chart via registerMark().
Chart automatically aggregates y/x accessors from marks, removing the need to pass y={['apples', 'oranges']} when each mark specifies its own y. Works for both horizontal (valueAxis='y') and vertical (valueAxis='x') charts.
Per-mark data props are included in the chart's domain calculation automatically.
Implicit series are generated from mark registrations when no explicit series prop is provided, enabling tooltip and legend support without requiring series definitions.
feat: Add inertia (momentum) support for transform drag gestures (#663)
breaking(Chart): Rename tooltip prop to tooltipContext to better describe purpose and fix conflict with new tooltip snippet (#663)
feat: Add Chord layout and Ribbon primitive (#663)
breaking(TransformContext): Rename initialScrollMode to scrollMode and make it reactive (#663)
feat(TransformContext): Add scrollActivationKey option to require a modifier key (meta, alt, control, shift) for scroll/wheel zoom/pan, preventing accidental page scroll from triggering transforms (#663)
feat(Transform): Add zoom/pan constraints (scaleExtent, translateExtent, constrain, domainExtent), replace geo.applyTransform with transform.mode: 'projection' | 'rotate', and reactively sync initial transform values on projection changes (#663)
feat(Bar): Support fixed width and height props to override scale-derived dimensions, centering the bar within its band. Resolves #360 (#663)
feat: Auto-compute Bar/Bars mount animation initial values from chart scales (#663)
Bar now automatically derives initialY/initialHeight (vertical) or initialX/initialWidth (horizontal) from the chart's scale range when motion is configured, removing the need to hardcode pixel values.
Also improves valueAxis inference on ChartState — when not explicitly set, it is now derived from scale types (band scale on y → valueAxis: 'x', band scale on x → valueAxis: 'y').
feat(Chart): Add cartesian pan/zoom via transform={{ mode: 'domain' }} with single or both axis support. Resolves #366 (#663)
feat(Spline): Add motion support for mount animation from baseline (#663)
feat: Support continuous color scales via cScale prop without requiring cRange (#663)
Allow cScale (e.g. scaleSequential(interpolateTurbo)) to activate without cRange, enabling pre-configured sequential/diverging color scales
Guard createScale against undefined range to avoid breaking interpolator-based scales
Auto-detect numeric cDomain values and use extent instead of unique, producing correct [min, max] domains for continuous scales
Prefer cScale color over default series color in tooltip/highlight when a color scale is configured
feat(Labels): Support seriesKey in labels prop to filter which series renders labels. Resolves #633 (#663)
feat(Rect): New edge-based props (x0/x1/y0/y1) along with existing (x/y/width/height) and insets support (#663)
fix(Area): Default y0 baseline to chart's yBaseline when set (#663)
Area's y0 fallback now respects the chart's yBaseline prop (e.g. yBaseline={0} set by AreaChart) instead of always using min(yScale.domain()). This fixes areas filling to the bottom of the chart instead of to the baseline when data goes negative.
fix(Points|Labels): Correctly position when using x1 / y1 scales (issue #773) (#663)
refactor(Chart): Add debug prop and update settings context (#663)
fix: Default chart padding now applied when using ChartChildren layout (marks snippet) (#663)
When <Chart> is used with marks/grid/axis snippets (no explicit children snippet), default padding is now applied to match the axes that ChartChildren renders by default. When children is provided directly (e.g. Treemap, Pack), no default padding is applied since the user controls layout. This can still be overridden with explicit axis or padding props.
fix(Tooltip): Apply inverse transform for quadtree lookup when zoomed/panned (#663)