Changelog
What changed for users of active-charts, per version.
@workspace/active-charts 6.0.0Status unknowncontent differs from the recorded release 6.0.0baseline ce081dc3
The version line at the top of this page is read from the package and the release register of Foundation. A recorded release counts only for exactly the content it recorded: when the package changed after it, this site does not call that version released. The section Unreleased lists what changed since, including the changes made since Foundation maintains the package.
Notable changes to @workspace/. Format based on
Keep a Changelog; versioning per
Semantic Versioning.
This file is the authoritative release history for @workspace/
from v1.0.0 onward (the externally-publishable baseline).
A note on version-numbering#
The visible version-history may look non-monotonic at first glance. The
package was internally iterated through 1.0.0 → … → 9.1.0 during
pre-publication development, then reset to 1.0.0 on 2026-05-18 to mark
the externally-publishable baseline with an API-stability promise. The
internal 1. history is intentionally not preserved in this file —
those versions never shipped externally and the reset was the cleanest way
to start the public SemVer-contract from a meaningful 1.0.0. Git tags
from the pre-reset period still exist for archeological purposes; consumers
should treat the post-2026-05-18 1. line as the only authoritative
version-sequence.
[Unreleased]#
Maintained in Foundation (since 2026-10-21)#
Dependencies:
@workspace/replacesactive- helpers @workspace/. The export-font, delimited-text and menu helpers (active- utils ./,export- fonts ./,serialization ./) now come frommenu- items @workspace/, the one package that owns them. Same functions, same behaviour; the public API is unchanged. A host that receives the charts receivesactive- helpers active-with it.helpers Browser suite without the platform appearance: a Playwright project
standaloneruns the suite withhost: 'none'(the package defaults only), for a repository that has no platform stylesheet to read by path. The default project and its screenshot baselines are unchanged.Documentation: references to documents of the source repository are text, marked
(source repository: …), instead of links that resolve nowhere here.
Changed — BREAKING for imports (platform ADR-114)#
Iso,Date String toand the zone brand come fromIso Date String @workspace/, which owns them since ADR-114; the package no longer depends onactive- format @workspace/, and the managed package set of a charts consumer no longer contains it. The props are unchanged; a consumer imports the brand constructors from active-format.active- types
Changed — BREAKING for the managed package set (ADR-124)#
Formatting comes from
@workspace/, not from theactive- format formattersentry of a translation package. The manifest declares@workspace/instead ofactive- format @workspace/; the package imports nothing from a translation package any more. No prop, export or behaviour of active-charts changed: its presentation type is still theactive- i18n Valueunion, now owned byPresentation @workspace/.active- format A consumer that formats next to active-charts with the same functions imports them from
@workspace/.active- format
[6.0.0] - 2026-10-18#
Changed — canonical transfer form (2026-10-18, platform ADR-112)#
The manifest is the transferable manifest (RPS-D7): no workspace package in
dev(what the package's own development takes from Datyx, such as the lint preset, is host tooling in the reusable package register); the declaration packages of what the source imports sit in the field of their module (Dependencies @types/andreact @types/as optional peers beside thereact- dom reactpeers; the@types/packages as dependencies beside their modules). Nothing changes for a Datyx consumer.d3- * tsconfig.is self-contained: it no longer extendsjson @workspace/, with the same compiler options, because a bundler in a consumer reads it for every source file it compiles.typescript- config E2e harness: the host stylesheet it serves is the Datyx one, read from the host repository (active-ui's kit followed by
@workspace/, fonts fromactive- config/ platform- appearance. css packages/); it stops with an error when they are missing, outside Datyx. Declared as host tooling. Rendered output is unchanged.active- config/ assets/ fonts/ v1
Changed — hierarchy node identity is a fixed-length structural key (ET-4B, ADR-122, CH-56, 2026-10-12)#
Treemap, sunburst and org-chart node keys are no longer the
.-joined path of segments from the root butkplus a 14-character base-36 hash of the parent's key and the node's own segment (the hostid, else the sibling position). The same place gives the same key across a data replacement; a hashed key that meets another node's falls back to that node's full path (detected per build, deterministic). Stored key characters are now 15 per node instead of growing with depth × id length (a 1,000-level chain: 15,015 instead of 2,471,098). Drill path, focus retention, React keys and DOM ids keep their behaviour; the DOM ids behindaria-change form. No public API change.activedescendant Retained focus no longer reads the shape of a key. An org chart's focus kept across a collapse falls back to its nearest ancestor through the previous keyboard tree's parent links.
The org chart's provisional depth boundary (ADR-119) is unchanged; its development diagnostic no longer gives the old key growth as its reason.
Changed — timeline label placement is indexed (ET-4B B6, 2026-10-12)#
placereads only the blocks and markers near a candidate through an x-index instead of scanning all of them per event. The placement is identical (proved against the previous function on seeded layouts); 8,000 events place about 8× faster. Dense timelines still grow faster than linearly with the events per pixel column.Timeline Labels
Added — ET-4 benchmark harness (ET-4B, ADR-123, 2026-10-12)#
benchmark:pe4(the minified production bundle, the only profile with verdicts),benchmark:pe3,benchmark:simandbenchmark:selftest: a typed workload registry with deterministic generators, the sample policy (p95 only at n ≥ 20, three runs, nearest rank), the reproducibility block (source,Hash bundle, machine, power, load,Hash quiet), CDP memory and long-task metrics, dashboards, soak and export resource runs, andresults.plusjson summary.per run. Test and benchmark code only.md CH-58: the selection/hover ratio of
t8-is a diagnostic; two count guards (selection e2e/) and the absolute PE4 select budget replace it (QUALITY-BUDGETS § 28).et4b- ch58- count- guards. spec. ts
Added — hierarchy drill and return announced (ET-3B2, ADR-121, 2026-10-11)#
A treemap or sunburst drill and return are announced (CH-67). With
drilldown, a drill says "Drilled into {name}" and a return "Returned to {name}" in the chart's live region, once per drill or return that actually happens, from the keyboard and the pointer alike; nothing on the first render, for Backspace at the top level, Enter on a leaf or a data update. The drill state commits a record of each accepted move with the level it produced, so a render that never commits says nothing.Three new host keys:
charts.,accessibility. drilled Into charts.,accessibility. returned To charts.. A host that does not provide them fails its coverage proof (ADR-028): migration note. No other public API change.accessibility. unnamed Group
Fixed — pre-qualification (ET-3B, 2026-10-11)#
No keyboard item model without room (CH-63). A heatmap, radar, timeline or radial bar below its room draws no items, and now has no item model either: one image with its name, its description (with the no-room sentence) and its table; not a tab stop, no arrow stepping or position announcement, and no
aria-naming an element that is not drawn. With room the keyboard returns with no focused position. The radial bar's existing guard did not hold (activedescendant undefined && …fell back to the hook default);usenow names no descendant while its model is off, also in the commit that turns it off.Kbd Base A treemap leaf without a value keeps its keyboard element (CH-70). The keyboard reaches it and reads "(no value)"; its
aria-now names an element (0 × 0, nothing painted), as the sunburst's empty arc already did.activedescendant The unavailable sentence under forced colours (CH-69). The state's text takes
Canvas, as every chart text does (ADR-108).Text No public API change. Docs: ACCESSIBILITY § 2 (the sankey switches layers with Enter, CH-68), § 5 (no room), and the ET-3 qualification protocol in BROWSER-AT-CHECKLIST and EVIDENCE (ADR-120).
Fixed — bounded safety correction (ET-2B2, ADR-119, 2026-10-10)#
Org chart depth (CH-59, ET2-OD-5, ADR-119). The org chart's data-table walk was still recursive: a valid chain of about 2,500 levels threw a
Range. It is iterative now, and the org chart has a PROVISIONAL depth boundary of its own, 139 levels below the root, derived from the growth of its card identity keys (one segment per ancestor, CH-56): a deeper organisation, collapsed branches included, shows the workload-unavailable state with aError warndiagnostic in every environment and offers no CSV.No host-sized argument spread (CH-60). A collection whose length follows the data (heatmap categories, Sankey columns and a hub's neighbours, pie-like rows, sparkline points, radar values, histogram values, timeline lanes, legend entries, a series' hit-test points, linked hover rows, axis tick lines, bar rects of one category, compound series) was spread into
Math./max Math./min push, which throws past the engine's argument limit (about 125,000). They go throughmax/Of min(Of utils/) or a loop, with identical results; a sparse heatmap with 140,000 distinct rows and columns now draws. A source guard (math. ts et2b2-) reviews every remaining spread.argument- spread- guard. test. ts
Added — failure safety and resource boundaries (ET-2B, ADR-115 to ADR-118, 2026-10-10)#
Copy outcome (IR-004, ADR-116). Copy value, Copy values and Copy as table are disabled with a reason (
charts.) when the browser cannot copy at the moment the menu opens (no clipboard API, an insecure context); otherwise the outcome is announced in the chart's live region (context Menu. copy Unavailable charts.,context Menu. copied charts.). A failed copy reaches the host through the newcontext Menu. copy Failed contextor, without it, anMenu. on Copy Error(error: Chart Copy Error) errordiagnostic in every environment; never both, never with the copied text. New public exports:Chart,Copy Error Chart,Copy Error Code Chart. No toast, noCopy Action execfallback.Command Workload-unavailable state (ADR-115). The unavailable placeholder says why:
data-(chart- unavailable="input" charts.) orunavailable "workload"(newcharts.). A treemap or sunburst deeper than the PROVISIONAL boundary of 139 levels below its root (derived: the largest depth whose chain writes at most 10,000 path segments) shows the workload state with aunavailable Workload warndiagnostic in every environment.Sparse heatmap table (IR-005, ADR-117). The data table is the matrix while it is no larger than the list of present cells, else that list (row, column, value) with a summary (
charts.,data Table. row charts.,data Table. column charts.).data Table. cells Listed
Fixed — failure safety and resource boundaries (ET-2B, 2026-10-10)#
Sankey (IR-006). The cycle check walks an explicit stack: a deep acyclic chain (100,000 nodes) validates instead of overflowing the call stack. Same cycle and duplicate-id diagnostics.
Heatmap (IR-005). No structure grows with rows × columns: the keyboard reads present cells through a lookup and the overlay is one node, the focused position's (it still walks every position and reads an absent one "(no value)").
Hierarchies (CH-48). Every walk of a host tree is iterative (table, CSV, node keys, colour measure, selection index, keyboard map, the org chart's walks and ancestor climbs; the org chart's table walk followed in ET-2B2, CH-59): depth alone never throws.
Hierarchy keyboard (CH-55). One sibling list per parent instead of a copy per node: a flat tree of 10,000 leaves holds 10,000 slots, not 100 million.
Item keys (CH-49, ADR-118). One reader for every family:
Na, an infinity, an invalidN Dateand any non-key type name no item (asnull); a boolean is its text everywhere, so a boolean bar is now selectable.Unknown series type (CH-44). A runtime series type without a plugin shows the unavailable state with one development diagnostic instead of throwing in the accessible name.
Heatmap
order(CH-51). Linear matching through sets; a missing category named twice is counted once in the diagnostic.<Chart>in its unavailable state offers no CSV: the handle rejects withEXPORT_and the menu lists no CSV item.NO_ DATA Tests (CH-54). The oracle import guard also reads
import., fails closed on a loader handed on as a value or by bracket access, and compares module paths case-insensitively.meta. glob
Fixed — hierarchy data is a prepared snapshot (ET-1B3, ADR-114, 2026-10-10)#
Hierarchy (CH-52, ADR-114). A treemap or sunburst that laid out again on its own (a new size, a drill) after a host changed a fallback parent's
value, or achildrenarray, in place under the SAME root drew the live tree while every reader stated the preparation (Summary 30drawn as 90,Sector 40drawn as 80). One preparation now owns the measures AND the structure they were resolved over: the layout, node keys, drill path, table, keyboard, CSV, selection index and colour measure follow the prepared children, and the layout weights come from the preparation. The contract (API § 3.3): hierarchy data is a snapshot; when it changes, pass a new root object (structural sharing is fine). Under the same root the chart stays on what it prepared, consistent on every surface. Still one pass, O(nodes); no new public type.The ET-1 oracle import guards share one reader that also sees
vi./import Actual vi./import Mock vi., template-literal loads and type arguments, compares whole path segments, and fails closed on a load whose module it cannot read (CH-53).mock
Fixed — a measure per preparation, zoom options at the commit (ET-1B2, 2026-10-10)#
Hierarchy (CH-50, ADR-112). A host that changed a node in place and passed a new root got geometry from the new value and every reader (table, tooltip, keyboard, announcement, Copy value, CSV, second label, share) and the strict mismatch rule from the old one: the resolved measure was cached per node for the life of the page. Each treemap and sunburst now resolves its
dataonce per data object, and the layout, the check and every reader read that one resolution; nothing is kept across roots or charts, and hierarchy input is not required to be immutable. Still one pass, O(nodes).Zoom (CH-47). Drag-select and touch pinch/pan read their options (axis direction and x map,
min, whether zoom is enabled, plot size and margins) from the last committed render, never from a host render that never commits: a held transition that reversed the axis no longer made the visible chart zoom the mirrored rows, and one that raisedVisible Items minno longer refused a valid drag. A committed change still applies to the next gesture.Visible Items The ET-1 oracle import guard now compares the module a specifier names (extension,
/,index ./and../spellings), not its text.
Fixed — one measure, one identity, committed state (ADR-112, ADR-113, ET-1B, 2026-10-10)#
Hierarchy (IR-001, ADR-112). Treemap and sunburst size, label, share, read, copy and export every node by ONE resolved measure: a leaf's value, a parent's derived sum, a summary's own value only where nothing below it is valued. A parent value beside valued children used to add to the area (150 drawn for a group of 100 over 30 + 20, read as 50, exported as 100); a matching value is now silent and a contradicting one makes the chart unavailable with a diagnostic. The hierarchy CSV
valueis the resolved measure; both families divide a share by the shown root. The table walk is O(nodes) instead of O(nodes · depth).Heatmap (IR-002, ADR-113). A coordinate is its item key (ADR-106):
1and"1"are two columns, a timestamp and itsDateone. The cells, keyboard, selection, table, hit test and Copy value share that identity. Two rows on one cell make the chart unavailable (selection on or off); a row with anullcoordinate is left out with a diagnostic.Heatmapwidens toAxis Config. order readonly Chart.Item Key[] Controlled selection (IR-003, ADR-105 restored). The selection a handler reads is published at the commit (
use), not in render: a suspended host transition no longer changes the next request.Insertion Effect An independent semantic oracle suite (
et1b-) and its import guard check geometry and every reader against literal expectations.oracle- *
Documentation — the chart-family authoring guide (ADR-111, CF-1, 2026-10-09)#
docs/is the one authoring guide for a new chart family: whether it is a family at all, its archetype, the shared routes it must use, the internal family register, the UI Lab page, the evidence layers and a Definition of Done, with a table of what the repository checks. API § 7 ("Authoring a New Chart") is now a pointer to it; its oldADDING- A- CHART- FAMILY. md forwardsketch is gone.Ref API § 3.2 records why
Radarhas no data labels and why its props extendChart Baserather thanChart Props Without Categorical.Chart Props No runtime and no public-API change.
Fixed — residual shared runtime truth (ADR-108, CF-1C3b, 2026-10-09)#
Print keeps the chart as drawn. The print stylesheet no longer forces a grey text colour, a light-grey grid or a white page: they broke the label language (an on-mark label printed at 3.9:1 in light and 1.7:1 in dark). A chart prints on its own mode's background with its colours exact.
Forced colours: labels on a mark. Chart text keeps
Canvason aText Canvashalo, so a label on a pie, funnel, treemap or sunburst mark that keeps its colour stays readable under any system scheme.A deliberate press on
<Chart>and the Cartesian wrappers. A pinch and the click after a long press that opened the menu no longer select or activate; the Cartesian overlay now uses the press rule of every family.Radial scales show what they step. Radar rings and radial-bar ticks never repeat a label (small values, or money in whole units, step coarser).
Diagnostics per input or chart. A development diagnostic is once per condition and input object, or per chart where it concerns a value; a second chart is no longer silenced by the first for the session.
Changed — BREAKING (what a family accepts, it does, ADR-107, CF-1C3, 2026-10-09)#
No replay switch where there is no replay. The menu of
<Chart>and the Cartesian wrappers takesfeatures.,copy zoom,seriesandexport;features.was accepted and ignored and is now a type error.replay LinearandGauge Bulletno longer takeChart animation.: they never replayed.replay Gaugekeeps both.Chart
Fixed — archetype and family truth (ADR-107, CF-1C3, 2026-10-09)#
Stagger on the profile. Funnel segments and the treemap and sunburst depths stagger on the chosen profile's step and cap, not on
calm.onon time. A staggered entrance (funnel, treemap, sunburst) reportsAnimation Complete loadonce, when its last mark has ended, counted from the first frame it is drawn in: the treemap no longer reports up to 300 ms late, the sunburst's morph no longer early.No room, no broken geometry. Below its room the timeline, sankey, heatmap, radar, linear gauge and bullet draw no marks and say so in a caption and in their description; the name, the table and a scalar's readout stay. Normal sizes are unchanged.
The organisation chart. The zoom presets are a real menu (focus moves in and back, arrows, Home, End, Escape, Tab; presets as checked radio items), the toolbar stays at full strength while focused and stops its hover transitions without motion; its shadows are the chrome tokens. Shift+F10 opens the focused card's menu on every card. A finger pans the view; a cancelled gesture ends the pan.
Items keep their element. A reordered slice, stage, sector, ring, radar series, treemap or sunburst node, sankey node or flow, or org card keeps its DOM element: React keys follow identity.
Radial scales. Radar rings and radial-bar ticks never step finer than the values' semantic quantum (a count at whole numbers).
<Chart>and<Chart.Defaults> <Chart>takes the provider'stooltipandaccessibilityunder its own, as every wrapper does.Copy value on a hierarchy node writes the value its tooltip and table write (the sum of its valued children, else its own).
Internal: the timeline's CSV rows live with the other families'; the treemap's leaf shading reads its sibling position in O(1); latent literal axis colours and series easing are tokens.
Changed — BREAKING (public types say what the package does, ADR-106, CF-1C2, 2026-10-09)#
A category is its value. A Cartesian category axis follows
Chart: the numberItem Key 1and the text"1"are two categories with two bands, two marks, two table rows and two selectable items, even when both read "1"; before, the band axis merged them by text. ADatecategory is its time. A reference value on a category axis (cross line, annotation,tick, a linked range) is given in the type the data has. The tickValues formatterstill receives the category's text.<Chart>is generic and takesevents.<Chart<TDatum>>(inferred fromdata): its context menu hands the host a typedCartesianinstead ofPoint<TDatum> never, andeventsbundles the chart and selection events, the individual prop winning.Seriesnames onlyType <Chart>series. It isSeries, the thirteen Cartesian series; the standalone families and the scalars are gone from it.Options['type'] No internal shape in a public type.
Histogramloses the internalSeries Options _;precomputed Bins Heatmapis the new exportedChart Props['label'] Heatmap.Label Options One Cartesian prop contract. Every Cartesian wrapper derives its props from
Cartesian, which now also carries theChart Props eventsbundle with the selection events. No wrapper gains or loses a prop.
Fixed — shared truth and touch (CF-1C1, 2026-10-09)#
A tap's echo is no hover. On every Cartesian chart the compatibility mouse events a browser fires after a tap no longer turn the tap's sticky tooltip into a hover that never ends: the card stays, then leaves on its touch timer, and a real mouse afterwards hovers as before. A tap with pinning pins and announces once.
A sliding finger names the mark under it. On the eleven standalone families a slide now carries the tooltip, attention and current item to the mark under the finger, as the pointer does; before, the card kept naming the mark the slide started on.
CSV and copy keep every column. Raw rows without declared columns (the categorical families, the heatmap) export every field any row carries, in first-seen order, instead of the first row's fields only. Explicit
columnsstill win.Missing is never blank or 0. A Cartesian data-table cell without a host value reads "(no value)", as the tooltip does. A treemap or sunburst node without a value reads "(no value)" in the tooltip and the announcement instead of 0, and a group is announced with the sum of the values below it, as the table reads it. A real 0 stays 0.
Repeated sibling ids. Treemap, sunburst and organisation-chart siblings that repeat an
idstay two nodes (keyboard, attention, drill path) instead of one silently overwriting the other, and are reported once in development.Category identity follows the selection key. A categorical legend keeps a number or a
Dateapart from a text that spells it (1and"1") in any row order, and names aDateby its time. The legend entry id of a number orDatecategory changes form (it was its text); entry ids stay opaque, so store them as reported.
Added (CF-1C1, 2026-10-09)#
Single: the item carries no value; the default card writes "(no value)" andTooltip Data. value Missing yis no reading.Value
Changed — BREAKING (bounded host composition, ADR-105, AF-B, 2026-10-09)#
Headless legend.
useand theChart Legend() legendprop let a host render its own legend from the chart's resolved discrete legend (Link Chart: identity, target, label, semantic key as painted, visibility, available actions) and act through the chart's own legend route (Legend Entry activate,show), which owns the state, the guards and the announcement. The five standalone legend families now announce and report every legend change, isolate and Show all included.All Identity-based legend events.
onreplacesLegend Change on(no alias): one event per applied change from every route, naming the entry and target by identity, every hidden target and the input.Legend Toggle Chartis removed; a menu's legend target carries theLegend Item Ref Chart. The export legend types areLegend Entry ChartandExport Legend Item Chart.Export Legend Options Every export returns its file. The handle exports and the standalone functions resolve with
Chart(Export Result format,filename,blob,download,Started warnings);exportis asynchronous.Chart As Csv download: falseproduces the same file, captured once and at rest, without a download.onreceives that object, also for menu exports. Failures stayExport Complete(result) Chart; an inherit-mode PNG whose web font could not be embedded warnsExport Error font-.fallback A controlled change is a request.
requestedis always present onChartandSelection Change Org. WithCollapse Change selectedorItems collapsedthe host prop is the truth: a request is computed from it, and drawn, listed and announced only once the host passes it back.Ids One input vocabulary. Selection, activation, org collapse, the legend and the context menu report
Chart:Interaction Source 'pointer'(mouse, pen),'touch'(finger) or'keyboard'; a menu action reports the input that opened the menu (Orgno longer saysCollapse Change 'menu').Serialisable zoom easing.
zoom.is aanimation. easing Chartname (defaultMotion Easing 'calm', the same curve) instead of a function;zoom.is bounded to 0 to 3,000 ms.animation. duration Per-format Export menu.
contexttakesMenu. features. export true,falseor{png?, svg?, csv?, pdf?}; a format the chart cannot write is never offered and an empty submenu is not shown. The default is unchanged.Public type cleanup.
Chart(without the internalAnnouncement Category 'immediate') andChartname types that public signatures referenced;Tooltip Context contextcarries noMenu any, and the thirteen Cartesian wrappers type the menu element asCartesian.Point<TDatum>
Changed — BREAKING (motion, ADR-104, 2026-10-08)#
animationis a profile and six classes.{enabled, profile, entrance, update, exit, tooltip, attention, replay}; the entrance fields (duration,easing,type,stagger,layers) moved underentrance.AnimationandUpdate Options Exitare removed;Phase Options Chart,Motion Profile Chart,Motion Easing Entrance,Motion Options Update,Motion Options Exit,Motion Options Interaction,Motion Options ReplayandMotion Options Seriesare new. A series owns only itsEntrance Motion Options entrance(withdelay) and itsexit.Profiles
calm(default) andresponsive. Calm: entrance 400 ms (stagger 30 ≤ 200), update 300, exit 200, tooltip 130, attention 140 (release 250), replay 1,000; responsive is shorter in every class.Data updates move by default.
update: falseswitches it off.Named curves only.
easingis'calm' | 'linear' | 'ease-; no free CSS curve, no callback. The default is the calm curve, also for the scalar count-up; the default gauge sweep no longer overshoots.in' | 'ease- out' | 'ease- in- out' Standalone, scalar and graph families take
profile, and where they have themtooltip,attention(standalone, sankey, org chart) andreplay(the gauge). The sankey and the organisation chart now takeanimation.animation: falsealso stops a discrete zoom animation and the grouped slot movement, as reduced motion already did.
Added (motion, ADR-104, 2026-10-08)#
A calmer interaction. The tooltip fades in and out on the tooltip duration and travels between the marks it describes (a change of side, a card following the pointer or a finger, and a new card are placed directly); a return while it fades reverses instead of blinking. The crosshair and the cues stay immediate and now leave together with the tooltip. The context steps back on the attention duration and returns more slowly when the pointer leaves. The heatmap's colour-key marker travels to a new value.
Grouped series reflow. When a grouped bar, range bar, box plot or dumbbell series is hidden or shown, the others move to their new slots on the update timing, after the exit if one is configured; the pointer and the labels follow the drawn glyph.
A calmer gauge replay on the replay timing: back for about a third, up for the rest, on an ease-in-out.
Fixed (motion, 2026-10-08)#
animation.animates every data change. An isolated change snapped and only a second change inside the same window moved. Every change after the first rows now travels from what is drawn, and a change mid-update continues from there.update A horizontal bar moves its length in an update; a bar or histogram moves its whole rectangle; waterfall, candlestick and OHLC updates move, every part of a glyph together.
Line and area updates follow the configured curve (they used a fixed one).
A change of orientation is drawn at once, never as a morph between a vertical and a horizontal layout.
No neighbour flashes into its final slot when a grouped series is hidden, and range bar, box plot and dumbbell keep their slots through an exit, as grouped bars do.
The tooltip's first frame carries its content. Firefox could show one frame of a target without its card.
A pointer that moves within one item of a standalone family renders nothing; a travelling tooltip renders no family component either.
A plain
<Chart>followsChart.Defaults. animation An export never captures a motion mid-way: every scripted motion (an update, a reflow, a count-up or replay, a discrete zoom) declares its end and the export waits for it, within the chart's motion budget plus one second (at least four).
Invalid durations fall back to the profile with a development diagnostic; tooltip and attention are held at 600 ms, geometry at 3,000 ms.
A two-finger gesture on
<Chart>is no hover: a pinch or pan no longer drives the tooltip and the attention from its first finger.A tap on a radial bar ring shows its card, and a sliding finger carries it, as on the other standalone families.
Fixed (keyboard menu target, grouped bar exit, hierarchy attention, 2026-10-07)#
Shift+F10 and the ContextMenu key open the menu on the keyboard's current item on
Pie(and the donut),Chart Funnel,Chart Radial,Bar Chart Nightingale,Chart Radar,Chart Treemap,Chart Sunburst,Chart Sankey(node or link) andChart Timeline, with or without selection and activation props. Before, without those props, they opened the chart's menu. Enter, Space, selection and tab stops are unchanged; the radar opens on the same vertex for Shift+F10 as for Enter.Chart A grouped bar series that leaves through its exit animation keeps its column. Hiding one of several unstacked bar series no longer fades the leaving bars across the whole category band, and the remaining bars hold their columns until the exit ends, then take their final columns. Rest geometry and an instant hide are unchanged.
Treemap and sunburst address a tile or arc by its node identity, the same identity the keyboard, selection and the menu use. Two nodes with the same label are always two nodes; a value change, resize or reorder keeps the hovered or focused node current; drilling in or out clears the previous view's hover and keyboard highlight instead of marking another node.
Added (chart-family capabilities: histogram bins, funnel context, timeline time, bar label placements, sparkline domain, ADR-103, 2026-10-07)#
Histogram
bin: every bin the given width in x units, on multiples of it; the last bin includes its upper bound. One bin rule:Width bin,Count binorWidth bins(Histogram); an untyped call with several usesBin Rule Options bins, thenbin, thenWidth bin, and says so in development. A width that is not a finite number above 0, or that would make more than 1,000 bins, falls back to the default count with a diagnostic.Count Histogram
area(count and sum,Proportional Histogram): unequal bins are drawn per the narrowest bin's width, so a bar's area is its amount; equal bins draw exactly as before. Labels, the tooltip value, the announcement and the table keep the amount; the tooltip adds a "Per {width}" row (host keyAmount Options charts.). Unequaltooltip. bin Rate binswithout it are reported in development.Funnel formatter context:
datareceivesLabels. formatter(value, context?) Funnel(Label Formatter Context datum,series,Id series, the sourceName index,share,Of First shareover the visible stages).Of Previous Funneltypes the funnel'sData Label Options data.Labels Timeline
timewithAxis Timeline(Event. time Timeline,Time Axis Timeline): events placed by machine time, equal times on one x, markers never moved by labels; keyboard, table and announcement in time order, equal times in the host's order; the CSV keeps the host's order and addsTime time. Ordinal placement stays the default.Candlestick
body(default 1,Stroke Width 0allowed) onCandlestickand the candlestick series; the colour staysChart up./stroke down., the wick keepsstroke wick; forced colours keep a border on a borderless rising body.Stroke Width Bar label positions
'inside-andend' 'inside-(base' Bar,Label Options Bar): inside the bar against its value end or its base, by sign and axis direction, a stack segment at its own ends. Bar only; the waterfall keepsLabel Position 'inside' | 'outside'.Bar: a series' own labels, replacing the chart'sChart Series Config. label labelfor that series as a whole object.Sparkline
domain: an exact two-sided value domain for line, area and bar; equal values draw at equal heights across sparklines that share it; values beyond it are drawn to the edge and keep their number.
Changed (chart-family capabilities, ADR-103)#
A bar
Sparklinereads from zero. Its automatic domain now always reaches zero (padded only away from it); before, all-positive bars stood on the bottom of a padded min–max domain, so their lengths misstated the values (T9-D9). Line and area sparklines are unchanged. With adomain, a zero outside it is cut at the edge and the domain is never widened.HistogramandSeries Options Timelineare type aliases (they carry the new unions); anChart Props interface … extendsof either becomes atype … &. SettingbinandCount binstogether is now a type error (binsalready won at runtime).
Added (selection and activation on the standalone families, ADR-102, 2026-10-06)#
Selection and activation on nine standalone families:
selection,selected,Items default,Selected Items onandSelection Change on(also inItem Activate events) onPie,Chart Funnel,Chart Radial,Bar Chart Nightingale,Chart Radar,Chart Heatmap,Chart Treemap,Chart SunburstandChart Sankey, with the meaning they have onChart <Chart>(ADR-042): a click or tap selects, then activates; Space selects, Enter activates (Shift+Enter on a sankey, whose Enter still moves between nodes and links); controlled or uncontrolled;singleormultiple; an item that is not drawn is inactive, not removed. Without the props nothing changes.The items: a slice, stage, bar or sector (an item of the chart's one series, keyed by
categoryorKey selection.); a radar vertex (series × variable, reached on the keyboard with PageUp / PageDown); a heatmap cell with a row; a treemap or sunburst leaf by itsitem Key id(a parent keeps drilling); a sankey node by itsidand a link by itsidor its source and target.Link, optional: a link's identity for selection, activation and attention. Parallel links without ids share one identity, select together and are reported once in development.Datum. id Types:
Chart,Series Item Ref Chart,Cell Item Ref Chart,Node Item Ref Chart,Link Item Ref Chart,Series Item Chart,Cell Item Chart,Node Item Chart.Link Item selectedin the menu context now counts the selected drawn items of the standalone families.Item Count
Changed (selection and activation, ADR-102)#
ChartandItem Ref Chartare a union of four forms (an item of a series, a cell, a node, a link). The series form is unchanged; code that readsItem seriesorId keyfrom aChartit did not narrow now narrows (Item Ref 'series, orId' in ref kind). See the migration notes.
Fixed (selection and activation, ADR-102)#
A sankey link's attention was named by its nodes' labels: two nodes with one label shared the attention of their links. It now reads the link's
id, else its source and target node ids (T8-X1).Categoricalon pie, funnel, radial bar and nightingale was the position among the drawn slices; it is now the row's index inSlice. index data, as documented (T8-X5).Docs: a selected item carries a caret, not an outline (T8-X2); ADR-042's export sentence is superseded (T8-X3).
Added (compound grouping and orientation, ADR-101, 2026-10-06)#
Several series on the four compound wrappers:
seriesonRange,Bar Chart Range,Area Chart BoxandPlot Chart Dumbbell(Chart Range,Bar Chart Series Config Range,Area Chart Series Config Box,Plot Chart Series Config Dumbbell), each from the single-series keys or fromChart Series Config series, never both (the type refuses the mix; a caller outside the type system is told once in development).Automatic grouping: several visible range bars, boxes or dumbbells of one family, orientation and axis stand side by side in their category, in series order; a hidden one gives its slot back and the rest re-centre. No option. Families never group with each other. Range areas overlay in series order, each at its own opacity.
Horizontal box plot:
horizontalonBoxandPlot Series Options Box(noPlot Chart navigatororannotationswhen horizontal, as a horizontal range bar); the same five statistics, roles and words.Several dumbbells: one legend entry per series with a paired key of its two end colours, on screen and in an export; every end is read with its series first ("Plan: 2024"). One dumbbell series is unchanged.
Fixed (compound grouping and orientation, ADR-101)#
A box on a reversed value axis ran from Q3 away from Q1; it now spans the two quartiles (T4-X1).
Two range bars or boxes in one category drew in one slot, the earlier hidden and not reachable by pointer (T4-X5).
Two dumbbell series were announced, tooltipped and tabled by their end names only (T4-X4).
Endpoint labels of range bars, range areas and dumbbells could overlap another series' labels; they now join the plot's one collision decision.
Docs: the reason given for "dumbbell single-series only" (T4-X2) and the box plot's
widthbase (the band, not the step; T4-X3).Ratio
Added (scalar scales, targets and gauge replay, ADR-100, 2026-10-06)#
A tick scale on
Gauge,Chart LinearandGauge Bullet:Chart scale(Scalar):Scale Option truefor ticks at round values for the scale's length,{ticks: [...]}for exactly these values. The ends are always labelled; a tick label gives way to an end or the target, its tick staying; labels use the readout's notation. Ticks are in no name, table or CSV.One target on all three:
target(Scalar) is now onTarget Scalar, soChart Props Gaugehas one too (a mark across the band, above the needle or the bar), and takesChart {value, label}to name it ("Budget 1,500"). Beyond the scale its mark stops at the nearer end, every text keeps the true number.GaugeChart indicator:'needle'(default) or'bar', a narrower arc inside the band fromstartto the value; zones, target and readout stay.Angle GaugeChart replay(default off): a hidden click or tap at the pivot and, where the host's menu keeps it (context), Replay animation re-run the entrance to the current value. Ignored while the gauge moves; nothing moves under reduced motion; no value, callback, name or export changes.Menu. features. replay Forced colours: the scalars' value marks and targets in
Canvas, scale ticks inText Gray.Text Host keys
charts.,context Menu. replay Animation charts.,context Menu. replay Reduced Motion charts.,context Menu. replay Animation Off charts.,scalar. named Target charts.,scalar. above Scale charts.,scalar. below Scale charts.,accessibility. target Plain charts.,accessibility. named Target charts.,accessibility. named Target Plain charts.,accessibility. scalar Value Above charts..accessibility. scalar Value Below
Changed (scalar scales and targets, ADR-100)#
targetonLinearandGauge Props Bulletmoved toChart Props Scalarand widened fromChart Props numbertoScalar(Target number | {value, label?}). Code that passes a number is unchanged; code that reads the prop as a number needs a narrowing (MIGRATION-NOTES).
Fixed (scalar scales and targets, ADR-100)#
A target of 0 was missing from a scalar's accessible name (T7-X1).
A target that is not a finite number was drawn at NaN coordinates and stated as "NaN"; it is now neither drawn nor stated, and reported (T7-X2).
A value beyond the scale read "(100%)" or "(0%)" in the accessible name; it now reads "above the range" or "below the range" without a share (T7-X3).
"% of target" was stated for a negative target or a value of the other sign; it is stated only where it means something (T7-X4).
The documentation said a scalar has no context menu; it has one that exports (T7-X5).
Added (hierarchy and flow, ADR-099, 2026-10-06)#
One label policy on
TreemapandChart Sunburst:Chart label(Hierarchy):Label Options secondary('share','value'in the chart'svalue,Semantics 'none'; treemap default'share', sunburst'none') andlevels(the levels that draw text, counted from the shown root). Visual only; every reader keeps every name and value.Sankey:
node(Alignment 'justify'default,'source','sink','center'),link(Color 'source'default,'target','gradient', also in SVG, PNG and PDF) andnode(Order 'auto'default,'input').Org chart:
collapsiblewithcollapsed(controlled),Ids default(uncontrolled) andCollapsed Ids on(Collapsed Change Org); a count badge, Enter and Space, and the menu items Collapse, Expand and Expand all below;Collapse Change card(Fields Org, at most threeCard Field metafields per card, default the title).Host keys
charts.,context Menu. collapse charts.,context Menu. expand charts.,context Menu. expand All Below charts.,org. collapsed State charts.,org. expanded State charts..org. collapsed View
Fixed (hierarchy and flow, ADR-099)#
A treemap with groups started 23 px below the top of its plot: the shown root reserved a header band but draws no header (CH-34). Only a group that draws a header reserves one now; the tiles use the space.
A treemap with three or more levels reserved an empty header band on every nested group and drew no header there; every internal node now draws its header (N3). A nested group also had no drawn element behind its keyboard focus.
A drilled treemap or sunburst kept drawing the old tree after the host replaced it; the drill path is now held by node identity (N2).
The sankey keyboard grouped nodes by their distance from the sources instead of the column they are drawn in, and stepped them in input order; it now walks the drawn columns top to bottom (N1). The data table keeps the order of the links.
Added (quantitative scales, ADR-098, 2026-10-05)#
One quantitative colour scale
Quantitative(Color Scale Linearwith its ownColor Scale colors,Stops/Color Scale Color,Stop Classes/Color Scale Color) andClass Color, shared by the heatmap, the treemap and the sunburst.Legend Options Colour by measure on
TreemapandChart Sunburst:Chart color(Measure Color:Measure Options label,scale,value,Semantics legend) and the node fieldHierarchy; a colour key with the measure's name; the measure in the tooltip, a table column, the announcement, the description and the CSV (Node. color Value color).Value Bubble size:
sizeandDomain sizeon scatter series andName Scatter; the opt-in size keyChart sizeonLegend ScatterandChart Chart; a size column in the data table.Options Heatmap axes:
xandAxis y(Axis Heatmap:Axis Config title,formatter,temporal,Semantic order,reversed,wrap,auto).Rotate Host keys
charts.,scale. no Value charts.,scale. measure Value charts.,scale. value With Measure charts.,scale. size charts.,scale. size Clamped Below charts.; tooltip fieldsscale. size Clamped Above color,Measure size,Name size.Missing
Changed (quantitative scales, ADR-098) — BREAKING#
Heatmap,Color Scale Options Heatmap,Linear Color Scale Options Heatmap,Stops Color Scale Options Heatmap,Classes Color Scale Options Heatmap,Color Stop HeatmapandColor Class Gradientare replaced by the neutral names (no alias);Legend Options Heatmapis no longer exported.Series Options Heatmapis removed: the colours are the linear scale'sChart. color Range color.Scale. colors Heatmapis renamedChart. gradient Legend color.Legend The heatmap's row gutter and column band are measured (no fixed 80 px): long labels wrap, turn or are cut with their full name, never overflow. With a
name, the colour key carries it as a caption.A bubble's size domain is the full data, not the zoomed, reduced or grouped window; a bubble without a finite size is drawn at its marker size (it was drawn at the middle radius).
Fixed (quantitative scales, ADR-098)#
A bubble kept changing its radius while the reader zoomed or panned.
A
Naor infinite size drew an invalid circle radius.N The bubble size never reached the data table.
Under forced colours the heatmap's gradient kept its authored hue while every cell collapsed to
Canvas.<Chart>'s screen-reader table re-rendered every row on every pointer move (30 moves over 10,000 points: 11.9 s → 0.47 s in the development bundle).
Added (waterfall subtotals, ADR-097, 2026-10-05)#
Floating subtotals on the waterfall: a row of kind
'subtotal'runs from the balance at the previous subtotal or total to the current balance and states the net movement since then; a'total'stands on zero at any position. One role field,kind(Key Waterfall:Step Kind 'change','subtotal','total'), onWaterfallandChart Waterfall.Series Options The label formatter context carries
stepon a waterfall label.Kind The data table names each waterfall bar's kind in its own column (host key
charts.); CSV and copy-as-table carry the rawaccessibility. step Kind kindcolumn.Key Development diagnostics for an unknown kind, a summary row whose own value differs from the derived amount, and a non-finite change.
Changed (waterfall kinds, ADR-097) — BREAKING#
totalandKey totalare removed (no alias); useIndices kind.Key Singleis replaced byTooltip Data. waterfall. is Total kind; itsvalueand the tooltip'syare the amount the bar draws.Value A change without a value draws no bar (it drew a zero-height bar).
Fixed (waterfall, ADR-097)#
The tooltip, the keyboard announcement and the table no longer disagree on a summary's word, and the word no longer depends on whether the row is the last of the zoom window.
The crosshair guide of a negative total stands at its balance, not on the zero edge, where its readout already was.
A
Naor infinite change no longer removes every later bar.N A summary row is a pointer and keyboard stop whatever its own value field holds (hit points stand at each bar's balance), and Copy value copies the drawn amount.
On a reversed value axis every waterfall bar is drawn at its values; it was shifted down by its own height.
Forced colours paint a subtotal
Gray, like a total.Text
Added (Cartesian overlays: error bars, soft bounds, zones, ADR-096, 2026-10-05)#
Error bars on line, scatter and bar (
error,Bars: {low Key, high Key} Error; scatter alsoBar Options x/Low Key x,High Key Scatter), onError Bar Options <Chart>and theLine,Chart ScatterandChart Barseries configs. Absolute bound fields; a stem and caps in neutral foreground ink; an end the domain or a log axis cannot show runs open to the plot edge. The bounds join the automatic domain and are read in every tooltip mode, the data table, the keyboard announcement, CSV and copy-as-table. Custom renderers receiveChart error/Bounds x(Error Bounds Tooltip).Error Bounds Soft axis bounds (
soft/Min softon numeric axes): the automatic domain reaches at least to them, never narrowing the data.Max Zones on line and area (
zones: {axis?, ranges},Series,Zones Zone,Range Line,Zone Range Zone): the paint split by ranges of y or x where the drawn path crosses a boundary; an area's fill split by region; a named zone in tooltip (Boundary zone), table and announcement.Host keys
charts.,error Bar. range charts.,error Bar. lower Only charts.,error Bar. upper Only charts.,error Bar. x Value charts..tooltip. zone One internal domain contribution model (
Domain).Contribution
Changed (Cartesian overlays, ADR-096)#
Breaking: the Bar's
error,Bars. stroke errorandBars. stroke Width errorare removed (no alias). See the migration notes.Bars. cap Length A bar's error bar draws in neutral foreground ink instead of the series colour (its lower half was invisible inside its own bar, EB-3).
A series with error bars is never reduced and blocks time grouping (the bar's bounds were summed, EB-4).
The
Barwrapper no longer injects a tooltipChart value; the bounds come from the tooltip data on every route, and a host formatter writes all three numbers instead of silencing the range (EB-2).Formatter
Fixed (Cartesian overlays, ADR-096)#
CSV and copy-as-table carry the error-bound columns (EB-1).
One bound a log axis cannot show no longer erases the valid side (EB-5).
An outside bar label and a point label sit beyond their whisker (EB-6, confirmed in Firefox).
Fixed (radial sweep entrance in every engine, ADR-095 amendment, 2026-10-05)#
Fixed (sweep entrance, Firefox): the radial sweep of
Radial,Bar Chart Gauge,Chart Nightingale,Chart SunburstandChart Radarturned about a point beside the pivot in Firefox (up to 95 px on a half dial; the rings and roses appeared cut, the dial skewed). The reveal is now an SVG mask in the swept group's own coordinates, about the pivot each family passes explicitly, and measures the same in Chromium and Firefox: on the pivot, from the resolved start, clockwise, the domain's share at every moment of the duration.Chart Changed (internal): the CSS
conic-mask, its registered propertiesgradient --ac-/sweep- angle --ac-and the inlinesweep- open --ac-/sweep- from --ac-/sweep- share --ac-/sweep- duration --ac-are gone (undocumented, read by no consumer); the transient frame circle is replaced by a transient mask. When the reveal ends the mask and the group's mask reference are removed, so a chart at rest carries no animation helper; an export taken mid-sweep leaves both out. Reduced motion draws no mask, as before.sweep- easing No public API changed. Resting geometry and every visual baseline are unchanged; the seven
visual--donut-baselines updated in T2B (callout leaders on the drawn slice's middle) are reviewed and accepted.legend- *
Changed (one radial angle convention, ADR-095, 2026-10-05) — BREAKING#
One radial clock: degrees, clockwise from 12 o'clock, a domain as
start + end. Default charts draw exactly as before. Migration:
migration/.
Breaking (
Radial):Bar Chart startcounts from 12 o'clock (was 3 o'clock; defaultAngle 0, was-90, same rings).maxis removed;Angle end(defaultAngle start) is where the tracks end. No alias.Angle + 360 Breaking (
Pie,Chart Nightingale):Chart padis degrees (was radians); the defaults are exactly the former quantities.Angle Changed (
Gauge): a reversed, empty or full-circle pair draws the default dial and reports a diagnostic (it drew a malformed or empty arc); aChart startalone keeps the 270° dial.Angle Added:
PieandChart. start Angle Nightingale(defaultChart. start Angle 0; whole chart).Added (diagnostics): an invalid radial angle (not finite, a sweep of 0° or less, more than one turn) is reported once through the host sink (ADR-076); the family default is drawn, and no
Nareaches a path.N Fixed (sweep entrance): a partial domain is revealed over the whole duration (it finished at the domain's share of it) and the mask is centred on the pivot (it was centred on the swept group's box, up to 95 px off on a half dial). The Gauge now uses the shared radial entrance.
Fixed (Pie): one slice-angle model for the slices, sector labels, callouts, keyboard cue and menu point and the tooltip anchor. The callouts and anchors used a second walk that shared the pad differently from the drawn slices; their leaders now leave the drawn slice's middle exactly (they sat off it by up to half the pad times the slice count: 0.92° on a five-slice allocation with the default pad).
Removed (internal): the unexported
core/(shapes/ pie. tsx Pie), whose only reader now draws from the slice model.
Added (per-datum paint on more families, ADR-094, 2026-10-05)#
Additive public API (ADR-029). Without a resolver every chart draws what it drew.
Added (
painton six more families): the BC4 per-datum paint resolver (Datum, fill only) on the lollipop series (the head; the stick follows it unlessPaint Resolver stickis set), the range-bar series (the interval; contextStroke valueis the high end), the dumbbell series (each end separately), and onFunnel,Chart RadialandBar Chart Nightingale(the item is its legend category, so its key, the radial name-column key and the export legend show the paint; a funnel key shows the stage as drawn). Tooltip swatch, export, label contrast and forced colours follow, as on bar and pie.Chart Added (
Datum): the part of a mark painted part by part; a dumbbell's resolver is asked once per end withPaint Context. role?: Chart Value Role role: 'low'or'high'and that end's value. The connector keeps the series stroke.Changed (funnel legend): a hidden stage's key keeps its own (painted or palette) colour instead of falling back to the palette.
Fixed (truth corrections, ADR-093 amendment, 2026-10-05)#
Fixed (CH-28): under forced colours a rising candle is hollow and a falling one filled, with solid wicks (the rules pointed at test ids that no longer existed), and range bars take the bar series' system colours, their legend keys too.
Fixed (CH-29): the crosshair's value readout on a stacked bar or area segment names the stack's sum through the segment, and on a waterfall step its running balance: what the axis reads where the guide stands. On a reversed value axis the tooltip and crosshair anchor stands at the bar's value end (it stood at its start).
Fixed (CH-32): sankey node and link DOM ids encode the host id (spaces, punctuation, any script) injectively, so every
aria-resolves; the host id itself is unchanged.activedescendant
Fixed (truth and defect corrections, ADR-093, 2026-10-05)#
Corrections of existing promises; one additive field (Sparkline.).
No new capability, no family feature.
Fixed (CH-7, zoom follows the scale): drag selection, wheel, pan, pinch and the navigator read pixels through the x scale: a reversed axis zooms the part under the pointer (it zoomed the mirrored part), a drag on an uneven continuous axis selects exactly the rows inside it, and the navigator follows
reversed. Anxydrag picks the band shown on a log or reversed y axis. Changed meaning (fix): the y fractions ofonandZoom Change setare positions on the plot from its bottom edge, read through each y scale; on a linear axis that is not reversed nothing changes, on a log or reversed axis they now name the band shown. The x fractions stay row fractions of the full data.Zoom Fixed (CH-8): a pinned tooltip stays on the host row it was pinned on across zoom, pan, reduction, grouping and rerender (it showed the row at the same window position); it clears under the existing policy.
Fixed (CH-12): a lone explicit
minat or above every value (ormaxat or below every value) no longer turns the axis around: it is ignored with a diagnostic and the axis spans the data. A non-finiteminormaxshows the unavailable state with a diagnostic (W12).Fixed (radar): a missing or non-finite value is a gap (no vertex, edge or fill), never 0; the table, the announcement and the keyboard readout say "(no value)". Without
min/maxthe scale follows the visible data (with zero, round ring steps) instead of a fixed 0 to 10 that clamped real values silently; beyond an explicit bound a value keeps its true number, with≥/≤on the scale and in the description (W-CF-3). New host keyscharts.andscale. axis Clamped Below charts..scale. axis Clamped Above Fixed (CH-20):
Radarno longer takes a fixed 30 px ofChart heightfor a legend above or below; the legend adds its measured rows, as on the other categorical families.Fixed (shared tooltip): a row without a value in
<Chart>'s shared tooltip reads "(no value)" instead of a blank.Fixed (CH-24): without
stack, a stack beside an unstacked bar takes its own column instead of filling the band under the bar (the column rule of ADR-091 now holds without a declared group).Group Fixed (CH-25): a stack total or an area value that would print on a bar it does not belong to is hidden; its value stays in the tooltip, table and announcement.
Fixed (CH-26): under forced colours bars, histogram bins and waterfall steps take their system colours (the rules targeted
rect; the marks are paths), and a bar's legend key takes its bar's colour.Fixed (CH-27, Walt's decision): under forced colours a classed heatmap's legend swatches collapse with the cells, a missing cell keeps no edge, and the class stays a name in the legend, tooltip, table and announcement, with the current cell's class marked in
Highlight.Fixed (LAB-AT4): each crosshair guide passes through what its readout names: a horizontal series reads its value on x and its category on y (they were swapped); a compound glyph's value guide crosses the high end, the median or the close its readout names; a histogram bin reads as its interval.
Fixed (CH-10): a scalar's accessible name reads its range when
minis not 0 ("0 in the range −50 to 50", new host keycharts.), never "0 of 50"; a missing pie, funnel, nightingale or radial-bar value reads "(no value)" in the announcement, value cell and share cell, never 0; two treemap, sunburst or org-chart siblings with one label are two reachable nodes with their own DOM id (the key is the hostaccessibility. scalar Value In Range idor the sibling position, not the label); an empty or blankaccessibility.is no name on any family,label <Chart>included, so no chart carries an empty name. Changed (DOM ids): hierarchy item ids now encode the node key (e.g.…-n0.), not the label.n2 Fixed (CH-11): treemap, sunburst and org-chart paths isolate each label, join with
→(ltr) or←(rtl) and open with a direction mark, in the tooltip and the table; the treemap breadcrumb separator points to the inline end under rtl (it was mirrored twice).Added (
Sparkline.):accessibility accessibility={{label}}(Pick<Accessibility) makes a sparkline one image with that name; without a name it is decorative and hidden from assistive technology (Options, 'label'> aria-).hidden Proven (QR-2): OHLC, box plot, range area and range bar name and toggle their series in the legend, and the exported legend draws the screen's key and name (browser, Chromium and Firefox).
Added (per-datum paint and quantitative colour, ADR-092, 2026-10-05)#
Additive public API (ADR-029). Without the new forms and resolvers every chart draws what it drew.
Added (
Heatmapforms): the type is now the union ofColor Scale Options Heatmap(the former shape, unchanged),Linear Color Scale Options Heatmap(Stops Color Scale Options type: 'stops',Heatmap: colours at values, the first and last stop are the ends) andColor Stop[] Heatmap(Classes Color Scale Options type: 'classes',Heatmap: namedColor Class[] [from, to)ranges with a class legend; the class is named in the tooltip, the data table, the announcement and the description). A value in no class is drawn neutral and named; a missing value stays empty. Malformed stops and classes, overlaps, values in no class and an unusedcolorare reported.Range Added (per-datum paint):
painton bar and scatter series (andBar,Chart Series Config Scatter),Chart Series Config marker.on line and area series, andpaint paintonPie: a bounded resolver (Chart Datum,Paint Resolver Datum,Paint Context Datum,Fill Datum) that returns paint only. The mark, its inside-label contrast, its tooltip swatch and its export follow the result; forced colours override it; a series legend keeps the series colour (a pie key follows its slice). Other returned fields, a non-colour and a throwing resolver are reported and fall back to the series paint.Paint Added (tooltip):
Single, the class row of the default tooltip.Tooltip Data. color Class Translations:
charts.,scale. classes class,Range class,From class,Below outside,valueandIn Class charts., in every available language.tooltip. color Class
Fixed (label collision per plot, ADR-091 amendment, 2026-10-05)#
Fixed (NEW-01): the value labels of several area series and the plot's stack totals take one collision decision: a label no longer prints over a label of another series or over a total. The totals come first, then the areas in series order; a value that gives way stays in the tooltip, the data table, the announcement and the CSV, and an export draws the same kept labels. No API change.
Docs: API.md § 3.1 no longer says area, range, dumbbell and lollipop wrappers take no label; the § 10.8 example compiles again.
Added and fixed (Cartesian labels and stacks, ADR-091, 2026-10-04)#
Additive public API (ADR-029). Without the new options every chart draws what it drew.
Fixed (CH-23): the export legend draws a line key with the series' dash pattern (explicit
lineor the automatic second encoding), at the painted key's length; SVG, PNG and PDF.Dash Legend.Export Item. dash Added (
stack): on bar and area series (Group <Chart>;Bar,Chart Series Config Area). Implies stacking. Bar groups stand side by side in the category, each with its own baseline, percentage base, tooltip total and stack total; area groups stack from their own baselines.Chart Series Config Added (
stack,Total Stack,Total Options Stack): one total per stack and category on bar series (Total Formatter Context <Chart>;Bar): the net total of the visible members, beyond the end its sign points to. None on a percentage stack. The data table gains a total column and the keyboard announcement names the total.Chart Added (label roles):
labelon area and lollipop series and wrappers (Point);Label Options label: Endpoint(Label Options endpoints: 'both' | 'low' | 'high') on range bar, range area and dumbbell series and wrappers.Labelnames the end. Off by default.Formatter Context. role Added (
wrap):AxisandConfig. wrap Axison a category axis: names wrap into at most that many lines before they rotate or are cut; the full name stays theOptions. label. wrap <title>. Default 1. On a vertical category axis it caps the existing gutter wrap.Time,Axis Config Numberand the upright value axis config do not take it.Axis Config Changed (only with the new options): an automatic value domain keeps room for a stack total or an outside value label at the end it points to, so the label at the domain's extreme is drawn rather than hidden.
Added and changed (marker shapes and legend reading order, ADR-090, 2026-10-04)#
Additive public API (ADR-029) and three changed defaults that change visual baselines where they apply.
Added (
Marker): one bounded marker vocabulary,Shape 'circle' | 'square' | 'diamond' | 'triangle' | 'triangle-, exported from the root.down' Markeron line and area markers (Options. shape <Chart>and the wrappers);marker.on scatter and bubble points (shape Scatterseries andChart <Chart>scatter series);markeronShape Lollipop(chart and per series),Chart Dumbbelland theirChart <Chart>series;marker.onshape Radarseries. Every shape has the area of the circle of the same size. The legend key, the tooltip key, the emphasised node, the keyboard cue, every export and forced colours follow it.Chart Changed (
Timeline): takes the whole vocabulary instead ofChart. marker 'circle' | 'diamond'. An explicit'diamond'is now the equal-area diamond of the vocabulary: about a quarter wider than the inscribed diamond it was, and the timeline layout reserves its reach.Changed (default, automatic shapes): a point series whose colour does not keep it apart (from the seventh default palette slot, or where a host palette repeats; the trigger of the dash pattern, ADR-038) draws a marker shape. A chart of six or fewer distinctly coloured series is unchanged. An explicit shape always wins.
Added (
legend.):align 'start' | 'center' | 'end'along the side, logical underrtl; the default is the old alignment (centerabove or below,startbeside). The export legend follows.Changed (default, stack reading): the legend and the shared tooltip of a stacked bar or area list the stack's members in the order the eye meets their segments (the top segment of a vertical stack first). No option. A chart without a stack keeps the series order. Legend entries now carry
data-(the series they toggle) besidelegend- series data-(their position).legend- item Changed (default, narrow side legend): a legend at
leftorrightstands below the chart where beside it the chart would keep less than 240 px, and returns when the room returns. The export carries it where it is drawn. A side column is as wide as its widest entry; its names no longer wrap (closes CH-16).Fixed: an export handle kept from an earlier render exported the composition of that render; every handle now runs the latest export work, as the context menu already did.
Changed (default label text and pie tooltip rows follow value semantics, ADR-089, 2026-10-04)#
Two default texts now follow the value semantics. No new API.
Changed (data labels): a bar, waterfall, line, scatter or histogram label without
formatterorformatwrites the series' value semantics (its own, else its y axis'), in the chart's presentation, as the axis, tooltip and table already did. Without a declaration it stays the plain number; a percentage stack keeps its share labels. Placement and collision are unchanged.Changed (tooltip): the pie tooltip always states the value and the share on two rows; the share is a percentage (
61.7%) with or withoutvalue, never a bare number.Semantics tooltip.writes the value row of the pie and funnel tooltips and no longer reaches their share row. CSV and Copy value stay raw.value Formatter
Added (value semantics and wrapper configuration, ADR-089, 2026-10-04)#
Value meaning on every numeric family and wrapper parity with <Chart>.
Additive: no member changes meaning and no consumer migrates. See
DECISIONS. ADR-089 and API. § 3.1,
§ 10.6.
Added (value semantics):
value(Semantics Chart, the Cartesian type) onValue Semantics Pie,Chart Funnel,Chart Nightingale,Chart Radial,Bar Chart Radar,Chart Heatmap,Chart Treemap,Chart Sunburst,Chart SankeyandChart Scalar(Chart Props Gauge,Chart Linear,Gauge Bullet). Labels, scales, readouts, tooltips, data tables and announcements write a value in it; a role's ownChart formatterorformatandtooltip.still win; a share stays a percentage; CSV and Copy value stay raw. Without it every text is as before.value Formatter Added (wrappers):
label(Point) onLabel Options Line,Chart ScatterandChart Histogram;visibleandshowon the series configs ofIn Legend Line,Chart Area,Chart Bar,Chart ScatterandChart Lollipop;Chart annotationson every Cartesian wrapper, onBar,Chart Dumbbell,Chart LollipopandChart Rangeonly while upright.Bar Chart Changed (types, compatible):
horizontalofBarandChart Dumbbellmoved into an orientation union, as onChart LollipopandChart Range; every existing call, a computedBar Chart booleanincluded, still compiles.Changed (tooltip): with
valuethe pie tooltip states the value and the share on two rows; without it, unchanged.Semantics
Changed (unified context menu, ADR-087, ADR-088, 2026-10-04)#
One context menu across the Active product family (CM1–CM6). Breaking type
changes, with a migration note in
migration/. See
DECISIONS. ADR-087.
Changed (renderer): every chart except the Sparkline renders its menu with the Base UI menu the Active Grid uses (
@base-is a new dependency). The package's own popup,ui/ react use,Standalone Context Menu useandContext Menu Items useare gone.Long Press Changed (types):
ContextandMenu Item Contextare removed. New:Menu Options Chart,Context Menu Options Chart(the Grid vocabulary:Context Menu Item label,action,icon,variant,disabled,disabled,Reason shortcut,keep,Open visible,separator,Before children,'separator'),Chart,Context Menu Builder Chart,Context Menu Context Chart,Context Menu Target Chart,Context Menu Features Chart.Legend Item Ref itemsis a builder that receives one frozen context per opening.Added: host items first; a legend entry is a target (Hide or Show, Show only this, Show all); Copy value(s) on an element and Copy as table on the chart; disabled items with a reason;
featuresswitches the chart's own groups off;trigger: 'button'adds a visible menu button; the scalars have an export-only menu; every family announces the opening (closes CH-5); items are at least 44 px high under a coarse pointer.Removed: Zoom here and Pan here (Reset zoom stays); the translation keys
charts.andcontext Menu. zoom Here charts..context Menu. pan Here Added (translation keys):
copy,Values hide,show,show,Only show,All label,label,For not,Zoomed all,Shown last,Visible nounderValue charts..context Menu Added (token): the host role
--radius, from which the menu derives its corner radius.Fixed (PDF, ADR-088): a PDF export draws the whole chart: the export clone always carries a
view, so a wide chart is scaled into the page instead of clipped on the right.Box
Changed (handle contract, ADR-086, 2026-10-04)#
Changed (type, A4):
setexists only onZoom Chart, the handle ofHandle <Chart>and the Cartesian wrappers. Every other family (pie, heatmap, the categorical, hierarchical, graph and scalar families, timeline, sparkline) exposes the new baseChart(exports and element access), and its runtime handle no longer carries a no-opExport Handle set. AZoom Chartref still fits those charts. Supersedes ADR-085 point 4.Handle Docs: the audit report under
reviews/(deleted 2026-10-07) is Prettier-formatted (whitespace only).
Fixed and narrowed (labels, export routes and public options, ADR-085, 2026-10-03)#
The one correction tranche after the independent audit
reviews/ (deleted 2026-10-07).
A public option works on its family or leaves its type (W10, ADR-082); no new
capability. Breaking type changes, each with a migration note in
migration/. See
DECISIONS. ADR-085.
Fixed (value truth, NEW-01, NEW-02):
Gauge,Chart LinearandGauge Bulletrest on the semantic value in every animation mode. With count-up the readout ended on the value clamped to the scale (Chart 150on 0–100 read100) and rounded to two decimals (1.234read1.23); it now ends onvalueitself, as withanimation: false, reduced motion and zero duration. The geometry still clamps to the scale; readout and accessible name agree.Fixed (CH-18):
LinearandGauge Bulletcount up fromChart minon the packageease-, as the gauge needle (ADR-084): no wait at the start on a scale that does not start at 0.out Fixed (export, LAB-AT12, LAB-AT18, CH-17): the context menu runs the handle's composed exports. A menu PNG, SVG or PDF carries the same legend on the same side as a handle export, on
<Chart>and on every standalone family; the standalone menu writes its PNG atcontextand offers CSV. AMenu. png Scale <Chart>side legend is exported beside the plot. The exported legend uses the screen's entries: a dumbbell's two endpoints, a tile for a shape series, the value key as the name wherenameis absent.Fixed (legend, LAB-AT24):
Funnel,Chart Radar,Chart RadialandBar Chart Nightingaledraw aChart left/rightlegend, isolate an entry on a double click and export their legend.Fixed (AT8): a waterfall, candlestick or OHLC series has a neutral legend key instead of a palette colour that matched no mark.
Fixed (tooltip, LAB-AT6): split cards honour
tooltip.andstyle tooltip.; a shared or split row is read at the addressed x (a scatter series without a point there is not listed); a chart of only ranges, box plots or bins shows its single card instead of an empty one.value Formatter Fixed (crosshair, LAB-AT4): the y readout uses the series value semantics, the notation of the tooltip, instead of two fixed decimals.
Fixed (histogram, LAB-AT11): a selected bin stays selected (the selection was pruned unless a row lay exactly on the bin's bound); an explicit
y/Axis. min maxis honoured and a negative sum or mean reaches below zero; a sum or mean withoutydraws counts and says so.Key Fixed (motion, LAB-AT10):
animation.reaches bar and histogram updates (the JavaScript transition ignored it); a range area enters withupdate. easing fadeby default (its declareddraw-had no band renderer).x Fixed (time axis, LAB-AT14): a
parentrow groups by the unit it names: one "2025" under a year of daily ticks, not one per month.Level Fixed (marks, LAB-AT15): an inside label longer than its bar or waterfall step along the value direction moves outside (or hides) under
fallback: 'auto'; a box plot outlier on the axis extreme is drawn whole.Fixed (radial bar, LAB-AT25, CH-19): the sweep entrance starts where the rings start (it began a quarter turn early); a value past
maxfills its track and keeps its true number; the name, key and value columns stand from the first frame instead of being revealed by the sweep.Fixed (navigation, LAB-AT20, LAB-AT22): the org chart's wheel zooms without also scrolling the page, and only while its zoom controls show; an image or PDF of a partial org view carries its note in the caption. The timeline pans only as far as its content, and keyboard focus brings its event into view.
Fixed (CH-10): closing a context menu returns focus to the chart; the scalars' descriptions no longer promise arrow-key navigation.
Fixed (LAB-AT19, LAB-AT23): a sankey node's
valuesizes the node (it was overwritten by the flow sum);Sparklinedelivers a replacedon,Export Complete onorExport Error on(its memo comparator kept the first); its bare-Ready <svg>root andgetreturningContainer Element() nullstay, now documented on the handle.Changed (LAB-AT7): with several
ys, theAxis Id yofAxis Line,Chart AreaandChart Scatterconfigures the first axis only; a further axis is a plain number axis on the opposite side (title, range and formatter were copied to every axis).Chart Changed (LAB-AT16):
Charton a chart without a data-range zoom reports the no-op through the chart diagnostics.Handle. set Zoom Removed from types (no reader):
Crosshair; per-seriesOptions. snap descriptionandtooltip;label.on line, scatter and histogram series;position tooltip.other thanmode 'single'on range area, range bar, box plot and histogram;y/Axis. auto Rotate parenton an upright value axis; lollipopLevel series[].;y Axis Id colorson waterfall, candlestick and OHLC;navigatoron dumbbell, waterfall and a horizontal lollipop or range bar;animation.on dumbbell, lollipop, box plot, range bar and range area;update draw-for a range area; range-areax data; histogramGrouping. approximation selection., a category or time x axis and a sum or mean withoutitem Key y; the standalone tooltip'sKey mode,snap,Distance pin(andvalueon org chart and timeline) and animation'sFormatter stagger,update,layers,exit;accessibility.on the standalone families;announcements Node;Datum. group showon a bar sparkline.Last Point Docs (LAB-AT13, LAB-AT21): contract text that contradicted the runtime (animation defaults 400 ms and 30 ms, waterfall corner radius, dumbbell and lollipop connector paint, rising candle
close ≥ open, MACD without a histogram, radialstart, linear gaugeAngle sweep, scalarcolors, standalonename, hierarchymeta).
Fixed (radial bar scale beside its columns, gauge needle motion, ADR-084, 2026-10-03)#
Two visual corrections found on the chart reference before the audit. No API
change. See DECISIONS. ADR-084 and
QUALITY- § 7.
Fixed:
Radialno longer drops the scale label that falls in its name or value column's band (the 30 at nine o'clock of the reference allocation, which left a bare tick beside the first key). A column starts past the scale label its rows would meet, plus 6 px; a side without a column keeps its labels inside the frame. The rings stay centred in the room that remains.Bar Chart Fixed:
Gauge's count-up starts atChart min, not at 0. On a scale that does not start at 0 the needle stood still for up to 40 % of the entrance while the readout counted through values off the scale, then swung late and fast. The needle and the readout move together from the first frame on the package's CSSease-(was a steeper cubic) within the unchanged 400 ms; an update moves on from the resting angle; reduced motion draws the final state at once.out Tests:
e2e/(new, scenariosreference- corrections. spec. ts ref/); baselinesvq5-andrest- radial- * fv0-updated for the radial bar only.capture- *
Changed and fixed (legend in a pie export, legend height by side, ADR-083, 2026-10-03)#
Closes ROADMAP LAB-AT3. See API. § 1, § 3.1, § 5, § 10 and
migration/.
Fixed: a
Pieimage export (PNG, SVG, PDF) carries its legend on the side it is drawn: the same entries and order, each slice's drawn colour, a hidden slice dimmed, every entry also when the painted legend is folded, no preview.Chart includenow applies to the pie.Legend Added:
LegendtakesExport Options. position 'left'and'right': one column beside the chart, mirrored underrtl.topandbottomare unchanged;<Chart>still exports its legend under the plot.Changed: the default of
Legenddepends on the side: 25 % of the chart height above or below the chart (unchanged), the chart height beside it (was 25 %). An explicitOptions. max Height maxwins on every side.Height Fixed: the default applies on
Pie,Chart Funnel,Chart Nightingale,Chart RadarandChart Radial, which had no default cap; an explicitBar Chart maxalready worked there.Height
Changed and fixed (API truth on PieChart and HeatmapChart, ADR-082, 2026-10-03)#
Found by the chart reference in UI Lab (ADR-081, LAB2). See
API. § 3.1, § 3.2 and
migration/.
Narrowed (types only, no runtime change): the
tooltipofPieandChart Heatmapno longer acceptsChart mode,snaporDistance pin; theiranimationno longer acceptsstagger,update,layersorexit, and the pie's no longer acceptstype. Neither family ever read them.Fixed:
Piedraws its legend on theChart leftandright, in one row with the pie, which takes the width the legend leaves; it drew no legend at all there before. Top and bottom are unchanged.Fixed: a double click on a pie legend entry isolates its slice, as
Legendpromises; the pie did not pass it on before.Options. isolate
Fixed (gallery findings, ADR-080, 2026-10-03)#
No public type changes. See API. § 2 (Automatic value
domains) and § 10.4.
Pie and donut callouts stay inside the chart. The centred pie now gives each side room for the wider callout column; a long name on one side (for example "Government bonds" beside "Equity" at 438 px) no longer runs past the chart edge. Charts with callouts of unequal width draw their pie a few pixels smaller at narrow widths.
A box plot's outliers are inside its value domain.
outliersnow spans the automatic domain, so an outlier beyond the whiskers is drawn instead of being clipped away while the tooltip names it.Key
Fixed (label contrast, radial rows and heatmap scale side, ADR-079, 2026-10-02)#
No public type changes. See API. § 1 (Text on marks), § 10
and § 10.5.
Label contrast on real paint (N1). Inside labels and label-safe surfaces now measure the colour the browser paints in the chart's scope: tokens inside
color-or other functions, alias chains of any depth, and translucent paint composed over the chart's background. A colour the browser cannot resolve is unknown paint and no longer counts as readable: the label goes outside or is hidden. Affects bar, waterfall, pie and donut sector labels, funnel, treemap, sunburst and heatmap labels.mix() Label background (N2). A bar or waterfall label background is a surface only through its
fill; a border-only background no longer lets an unreadable inside label through.Heatmap scale at extreme magnitudes (N3). Positions and the repaired centre stay finite for every finite domain (
-1e308 … 1e308).Radial Bar name rows (N4, revised 2026-10-03). Name and value rows never overprint: closer than a text line they are thinned at one regular step, outermost first, as Highcharts and AG Charts thin a crowded category axis; the rings never change for it. The value column is left out before any ring is. Each drawn name carries a small key in its ring's colour.
Radial Bar pointer. Moving across the rings no longer returns the chart to rest in the gaps between them: each ring owns its band to the middle of the gaps (an unpainted hit band).
Tooltip beside grouped bars. The card is placed beside the category's group, so neighbouring bars keep one side; it changes side only where the group's side cannot hold it.
Radial Bar colour keys stand in one straight column between the names and the rings.
Radial Bar tooltip under the pointer stands beside the pointer, towards the centre first, never over the addressed ring, and keeps its side from ring to ring; it no longer jumps to the quadrant of each ring's arc.
Heatmap tooltip keeps its side from cell to cell while that side fits, instead of taking the first side that fits each cell. In a heatmap too short for the card to stay above or below its cells through a vertical move, the card stands beside the cell first (right, then left), so moving down a column no longer flips it across the pointer.
gradient(N5). Now honoured: a vertical scale beside the matrix, which gives up the width; exported in place.Legend. position: 'right' 'bottom'(default) unchanged.Tests: the four W12 browser cases assert the host diagnostics sink (ADR-076) instead of console text.
Added (configuration: bar width, label fallback, pie roles, heatmap scale, ADR-078, 2026-10-02)#
Additive public options, decided by Walt (W-CF-1 … W-CF-3); no existing
member changes meaning. See API. § 10.2–10.5.
Bar sizing.
width(share of the category step, 0.1 … 1) andRatio max(CSS px;Width Infinityallowed) onBar,Series Options Waterfall,Series Options Rangeand theBar Series Options Bar,Chart WaterfallandChart Rangeprops (Bar Chart Bar). Bars shrink, never overflow; the bars of one category are sized as one centred group; the first declaring bar series decides for the group, and a disagreement is reported once.Sizing Options Label fallback.
Label(defaultOptions. fallback: 'auto' | 'hide' 'auto'): withpositionas the preferred place,'auto'keeps the family's alternative and'hide'keeps the preferred place only.Formatter context.
LabelreceivesOptions. formatter(value, context?) {datum, series(Id, series Name} Label) on bar, waterfall, line and scatter labels. One-argument formatters are unchanged.Formatter Context Pie and donut roles.
data(the callout) takesLabels name,valueandminbesideAngle percentage(Pie); newCallout Label Options sectorputs one line inside each slice (Labels Pie:Sector Label Options percentageby default,value,name,formatter,format,min). A sector label shows where it fits and reads, else it is hidden; with sector labels a slice is drawn on its label surface and the legend swatch follows (W-FV-3).Angle Heatmap colour scale.
colorand a three-colourScale: {type: 'sequential' | 'diverging', min, max, mid} colorfor a diverging scale (Range Heatmap). Values beyond a fixed end take its colour, keep their true value everywhere, and the scale readsColor Scale Options ≤/≥; the chart's description says so (fourcharts.keys in all five languages). An invalid scale is reported once and drawn safely.scale.* Heatmap recede.
HeatmapChart highlight={{recede: true}}lets the other cells step back to Active's near context step while a cell is addressed (W-CF-2, an amendment of W-FV-4; default off, no strength, never under forced colours or in an export).
Changed (configuration, ADR-078, 2026-10-02)#
A waterfall label honours
position:insidedraws it on its step where it reads; the default stays above the step.Stacked segment labels are inside. A stacked bar with labels and no
positiondrew each label beyond its segment, on the next segment; it is now inside where it reads, and an explicit'outside'is reported once.A bar label inside an unreadable bar that would leave the plot outside is hidden instead of clipped; a range bar on an axis with under 4 px per category draws one pixel instead of nothing; a waterfall step is never narrower than one pixel.
Changed — BREAKING (public contract and missing data, ADR-077, 2026-10-02)#
Public types narrowed to what the charts draw (Walt, W-QC-1 … W-QC-3). No
consumer in the repository used a removed member; see
migration/.
<Chart>series are Cartesian.Series(the type ofOptions Chart) is the thirteen series typesOptions. series <Chart>renders;PieandSeries Options Heatmapare no longer members (they typechecked and then drew nothing). UseSeries Options PieandChart Heatmap.Chart Markers.
Markeris removed (every marker was a circle). A scatter or bubble series'Options. shape markerisPick<Marker(Options, 'size'> enabled,visible,fillandstrokewere ignored there; the point's paint is the seriesfillandstroke).Scalar readout.
dataonLabels Gauge,Chart LinearandGauge BulletisChart Pick<Data: the readout is always drawn (FV4), soLabel Options, 'formatter' | 'format' | 'background'> false,enabled,font,Size colorandpercentageare gone.FunnelandChart Radialno longer acceptBar Chart dataorLabels. font Size data, which they ignored.Labels. color
Fixed (public contract and missing data, ADR-077, 2026-10-02)#
Heatmap: a missing value is not 0. A cell whose value is
null,undefined,Naor infinite is drawn empty (no colour, no label), shows no tooltip, reads "(no value)" to the keyboard and in the data table, and never enters the colour scale's extent (aN Naused to give every cell aN Naposition).N 0is a value.OrgChart: two people with the same name are two cards. Pointer, keyboard, tooltip anchor and reporting lines address a card by its id path; hovering the second of two equal names at one depth made both current and anchored the tooltip to the first.
Line and scatter labels honour
label.through the shared number resolver (format .25with'.0%'is25%); measurement and drawing read the same text.Horizontal bar outside labels sit beyond the value end: left of a negative bar, and on the correct side on a
reversedvalue axis (also vertical); the edge check that moves a label inside reads that side.Stale JSDoc and API prose: marker
visibledefaults to'hover',highlightdefaults to the far context step (0.64 light, 0.70 dark), the heatmapStyle. dim Opacity 'auto'label colour is one colour, a selected item carries a caret beside it (no outline).
Added (diagnostics family parity, ADR-076, 2026-10-02)#
Diagnostics go to a host sink.
<Charttakes aDefaults diagnostics> Chart(Diagnostic Sink {level: 'debug' | 'warn' | 'error', source, message, data?}, a caught error underdata.); nearest provider wins, a provider without one inherits. Without one,error warnanderrorgo to the console anddebugis dropped. New public typesChart,Diagnostic Chart,Diagnostic Level Chart; the same contract as active-grid'sDiagnostic Sink Grid.Defaults diagnostics
Changed (diagnostics family parity, ADR-076, 2026-10-02)#
Development diagnostics are reported outside production only and once per ground; four producers that ran in production (data validation, format strings, axis formatter, unreadable ticks) no longer do, and none repeats on a re-render or recompute.
A contract problem the chart handles itself is
warn, no longerconsole.; only a failed context-menu export withouterror onisExport Error error('Export failed',data: {error, context: format}), in every environment.Messages name structure and never host data: invalid sankey, hierarchy and scalar input report positions, depths and the violated rule instead of node ids, labels and values; a repeated category reports the field and the rows; a series is named by its identity, not its display name. The
[active-prefix is replaced by the source (charts] [Active,Charts] [Active, …).Charts. Bar Chart] Empty data no longer produces a validation warning.
Changed (attention on Cartesian layout, intervals and financial charts, ADR-075, 2026-10-02)#
No public API change. Bars and their kin speak the calm attention grammar (class A, W-FV-2, ADR-075): bar, grouped, stacked, mixed-sign and horizontal bars, histogram, range bar, waterfall, box plot, candlestick, OHLC and the bars of a combination. The hovered or focused item holds its colour; the other series at its category (a grouped neighbour, the other segments of its stack) step back a little (light 0.82, dark 0.85); everything else steps back to one moderate tier (0.64 / 0.70) instead of the old 0.35 / 0.30. A shared tooltip holds its whole category. Value labels never change on hover.
One label colour. A value label asked inside a bar uses the chart's foreground and stays inside only where that reads (4.5:1); otherwise it stands outside the bar (a stack segment drops it). No bar picks black or white text of its own any more.
The tooltip sits beside the bar or its category, never over it, its value label or its category label.
Richer tooltips: a histogram bin as an interval with its boundary convention and its count, sum or mean; a range bar with its length; a waterfall step with starting balance, change and resulting balance, and a total named as a subtotal or the total; a box plot with its outliers; a stacked segment with its stack's total; a shared tooltip over two value axes grouped per axis. In a shared tooltip each row now shows its own series' key shape, and only the hovered series' row is emphasised.
Keyboard and pointer cues: a histogram bin is named by its interval on the axis; candles and OHLC bars get a time crosshair that leaves the glyph clear (quiet for the pointer, the focus colour for the keyboard), their rising and falling colours never change; a box plot's outliers each get a marker and a value tick under the keyboard.
Waterfall bridges are solid structure (1.5 px, the muted foreground), also into a subtotal or total, and never step back with a step.
Horizontal bars wrap long category names in a gutter of at most 40 % of the chart's width before cutting them.
Compact axis numbers are a trade, not a floor (N6): only the axes that need it switch, and a wider chart never draws a narrower plot.
A zero-length range bar is drawn as one pixel at its value.
Changed (attention on Cartesian marks and connections, ADR-074, 2026-10-01)#
No public API change. Lines, areas and points speak the calm attention grammar (W-FV-2, ADR-074). In single tooltip mode, and while a legend entry previews a series, the other series of a line, area, range area, scatter, bubble, dumbbell or lollipop chart step back to one moderate tier (light 0.64, dark 0.70) instead of nearly disappearing; a series' own points never step back; a shared or split tooltip dims nothing. Bar and other shape series keep their deeper step until FV7.
A point keeps its size. The hovered or focused scatter point, bubble, dumbbell end or lollipop head is drawn again on top at full strength and at exactly its own radius, with no halo; the bubble's tooltip sits beside the circle. A line's 1 px emphasis no longer animates.
One opacity per scatter point: a point is drawn at its
fillonce (it was that opacity twice, 0.49 for the default 0.7).Opacity Stacked areas are flat bands by default (
gradientdefaults tofalsefor a stacked area, at the gradient's strongest opacity, 0.4); an unstacked area keeps its gradient.Dumbbell connectors read as data: the muted foreground at 0.8 (3.2:1 light, 5.2:1 dark), 2 px, unless the host sets
connector.Stroke Several lollipop series stand side by side within a category instead of on top of each other.
A range area is marked at both ends of its band under the pointer and the keyboard, never at its middle.
A bubble at the edge of its domain stays inside the chart: the layout keeps the largest marker's radius free on every side (the domain is unchanged).
Changed (attention and labels on trees, flows and timelines, ADR-073, 2026-10-01)#
No public API change. Trees, flows and events speak the same calm attention grammar and label language (W-FV-2, W-FV-3, ADR-073). Treemap and sunburst: every tile, arc and header label is the mode's foreground colour in light and in dark (no black-and-white patchwork per branch); tiles, arcs and header strips are drawn just far enough towards the background for it, one factor per branch colour, so the branch stays recognisable. A sibling of the addressed item steps back a little, an unrelated branch more (light 0.82 and 0.66, dark 0.85 and 0.70); the sankey keeps one-hop adjacency and steps the rest back to the far tier.
Names wrap before they are cut. Treemap tiles, sankey node labels and org cards take a name over at most two lines, breaking only where a line may break, and cut it once at the end of its last line; a sunburst label is a straight line measured to fit inside its arc (one or two lines), never by the arc's length. Every sankey node keeps a label, also a very small one; the labels of a column make room for each other.
Org chart. Cards rest on the card surface with a name, then the role; the reporting lines are visible in both modes and restored to full strength around the hovered or focused card, which takes a subtle tint measured so its texts keep 4.5:1. A tree that cannot fit at its readable scale starts on its root and says so ("Partly shown: drag or zoom to see the rest") in a strip under the plot, beside the zoom controls, and in its description. The tooltip names the person, the role and whom they report to, with no number.
Timeline. Labels and dates sit in collision-free lanes above or below their marker, the first and last moved inward so they never leave the frame; a crowded lane cuts a label to the room it gets. The current event no longer grows or turns bold: its connector is drawn solid. The keyboard is located by a mark beside the event's label. The tooltip shows the date, the event and the host's description, with no index number.
Two translation keys in every language:
charts.,org. partial View charts..org. reports To
Changed (heatmap and scalars at rest and under attention, ADR-072, 2026-10-01)#
No public API change. The heatmap is quantitative colour, so nothing in it changes under attention (W-FV-4, ADR-072): no cell brightens or recedes, the addressed one included; quiet marks in the row and column header gutters locate the pointer's cell, focus-colour marks the keyboard's, and the marker on the colour scale its value. The source that acted last owns the one painted cell (a key press after a hover now names the keyboard). The pointer tooltip sits above or below the cell, leaving its row and column headers visible.
The heatmap's numeric colour scale is shown by default (W-EC-3), inside the chart and its export, with its low end, middle and high end in the cells' notation;
gradienthides it. Heatmap numbers use the shared chart notation (whole numbers withoutLegend={false} .00).Gauge fits its swept arc (N2): radius and centre follow from the actual
start/Angle endsweep, the end labels and the readout; below a useful size it keeps the value and says it has no room.Angle Linear gauge and bullet name both domain ends and the target at rest (W-EC-5); the value has its own row and is never drawn where the maximum would be. Gauge, linear gauge and bullet draw every number in the value's notation (N11).
Scalar keyboard focus shows a tick at the indicator (bullet: the measure's end) and a mark beside the readout; never an outline.
Two translation keys in every language:
charts.,scalar. target charts..scalar. insufficient Space
Changed (attention and labels on radial and categorical charts, ADR-071, 2026-10-01)#
No public API change. One calm attention grammar and one label language for the radial and categorical families (W-FV-2, W-FV-3, ADR-071). Pie, donut, nightingale, radial bar and funnel: the addressed part keeps its own colour, size and place (the pie and donut slice no longer moves out) and every other part steps back to one moderate tier (light 0.64, dark 0.70, derived so both modes step back equally far; dark steps back less than before). Radar: a vertex no longer grows; under the pointer the other series step back to that tier, under the keyboard nothing steps back and the tooltip lists every series at that dimension; the keyboard is located by a mark beside the dimension's label. No text changes colour, weight or size on hover or focus.
Nightingale, radial bar and funnel rest at full colour (they rested at 80–85 % opacity) and take their colours from the ten-colour palette (they repeated after five, so categories six and up change colour); colours follow each category, so hiding one recolours no other.
Radial bar draws every category or says it cannot. Rings are fitted to the radius and the number of categories, thinning from
bar(now the preferred thickness) to a floor; where not every ring fits, the chart shows "Too small to show all N items" (translated) and states it in its description instead of leaving rings out. An angular scale is drawn at rest outside the rings, with its origin labelled.Thickness Radar ring values are drawn at rest beside the first spoke, and the web is centred with slightly more room.
Funnel labels in one colour. Inside labels are the mode's foreground on a stage drawn towards the background just as far as needed for 4.5:1 (the legend swatch shows the stage as drawn); where a colour would need more than half, the label goes outside. Percentages are shares of the first stage, which a caption under the stages names, and the tooltip adds a row with that share.
Nightingale fits its frame. The rose is sized from its measured labels; on a narrow chart the labels are shortened, then hidden, before the rose shrinks below half the frame. Values use the shared notation in pie, nightingale, radial bar and funnel.
Tooltips sit beside the addressed slice, wedge, ring, vertex or stage, away from the centre, its label and a donut's centre metric, wherever a side fits.
New translation keys
charts.,insufficient Space charts.andfunnel. percent Of charts.(every available language).funnel. share Of
Changed (cues instead of rings, ADR-070, 2026-10-01)#
No public API change. Cues instead of rings (W-FV-1, ADR-070). In normal light and dark rendering no family draws a border, outline, ring, contour, halo or bracket around a data mark any more, for pointer hover, keyboard focus or persistent selection. Keyboard focus is located by a short mark in the focus colour (3:1 or more against the background) that points at the item or sits beside its identity: in
<Chart>on the category axis (a stacked segment also at its level, an interval at both ends, on the value axis; a sample with a solid marker, a quiet guide and an axis cue, a scatter point on both axes); a radial or side tick just outside a slice, wedge, radial bar end, spoke, sunburst arc, funnel stage or timeline event; a mark beside a treemap tile's label or group header, under a sankey node's label (a flow: under both endpoint labels), before an org card's name. The heatmap draws nothing around the current cell and changes nothing else: only that cell turns a touch brighter in its own hue, the colour scale marks its value, and the keyboard adds a cue in each header gutter; no text changes. Values shown in the cells (opt-in) now take one label colour, the foreground, instead of black or white per cell, and the cell scale is drawn towards the background as far as that colour needs for 4.5:1 on every cell (the colour legend shows the same range). A selected<Chart>item carries a filled caret beside it instead of an outline or ring. The current org card is tinted on its surface instead of edged in its colour. Under forced colours every cue isHighlightand the current mark keeps aHighlightedge; the chart root keeps its control focus frame. The root attributedata-is nowac- locator data-(package-private).ac- cue
Changed (branch context in hierarchy and flow, ADR-069, 2026-10-01)#
No public API change. Treemap, sunburst and sankey: hierarchy and flow attention (ADR-069). Pointer hover draws no foreground edge on a tile, arc or node any more; the addressed item keeps full strength while its context follows the branch: a sibling steps back a little, an unrelated branch more, ancestors and descendants stay. The sunburst no longer dims by ring depth. A treemap leaf directly under the root draws no group background or header strip (the false band in a receded root leaf is gone). A keyboard-focused sankey flow keeps its colour and width and gets a link locator at both ends of its band; a thin sunburst arc gets a short radial locator. The tooltip of these three families sits beside the addressed tile, arc, node or flow when a side fits.
Changed (attention core, ADR-068, 2026-10-01)#
No public API change. Chart entry and datum focus are two stages: while the keyboard is on the chart itself, its 2 px focus ring is the indicator; once a datum carries the keyboard locator, the chart ring becomes one quiet 1 px line, so the datum locator is the single dominant ring (ADR-068). Pointer and keyboard are two sources of attention in every standalone family: a pointer that leaves the plot hands the attention back to the keyboard's item instead of clearing it.
Internal: a shared attention core, a closed vocabulary of relation roles, context policies and locator kinds, family sections in
interaction.without added specificity, new locator kinds in the transient layer and an anchor option for the tooltip positioner. New transient root and item attributes (css data-,ac- origin data-,ac- target- kind data-,ac- locator data-) never reach an export.ac- relation
Added (family visual evidence, ADR-067, 2026-10-01)#
No runtime change and no public API change. The family visual target is now measured in the browser (
e2e/, ADR-067): encoding, isolation, relation, focus, tooltip-anchor and context-at-rest probes over every family, with a ledger of known gaps owned by the phases FV1–FV8, and a change-detecting capture matrix. The closeout ledger is re-keyed to the same phases. Source comments that cited the deleted visual designs now cite ADRs, ARCHITECTURE and QUALITY-BUDGETS (comment-only).visual- family- target. spec. ts
Changed (tooltip containment and keyboard focus shape, ADR-066, 2026-10-01)#
A tooltip is as wide as its content needs, up to four fifths of the chart: names wrap only at that cap, and every value stays inside the card's padding. The cap now resolves against the chart (it resolved against the tooltip itself, so a value could stand outside its card). A split tooltip keeps its fixed column and wraps a long name in it instead of cutting it, inside the chart.
A tooltip that fits the chart stays inside it: when neither side of the pointer has room, it shifts inside the chart instead of leaving it.
One keyboard ring for every family, without an outline: the host's ring colour drawn a quarter towards the foreground, so the ring itself keeps 3:1 against the background in both modes. The Cartesian ring loses its foreground edge; the standalone families draw it on the focused shape (a sankey node's rect, an org-chart card, a timeline marker), never on a group, so a focused label is no longer drawn thick or blurred. No glow.
Fixed (tooltip containment and keyboard focus shape, ADR-066, 2026-10-01)#
A standalone chart's tooltip stayed hidden until the pointer moved again, and never showed for the first keyboard-focused item.
Under forced colours, a focused pie, funnel or nightingale item lost its ring to the system background; it now takes
Highlight.An export of a chart with keyboard focus carried the focused item (
data-) and the root'sfocused aria-; an export shows the chart at rest.activedescendant
Changed (interaction states across every family, ADR-065, 2026-10-01)#
A narrow axis degrades in one order: a plain number axis (auto-formatted, no declared form or
kind: 'number') first turns compact ("48 bn") when its labels would squeeze the plot below its minimum; only then does a title give way; a time axis whose scale knows no coarser set thins by stride rather than overlap its labels; and a category label is cut only while it stays recognisable (four graphemes, or its whole first word: "Jan …"), otherwise the axis shows fewer whole labels. An outer date label of a truncating axis takes its shorter form ("1 Jul") before it is cut.A tooltip is at most four fifths of the chart's width; a long name wraps, a value never does.
Exports and prints show the chart at rest, selection included: selection marks are interaction state and no longer reach an SVG, PNG, PDF or print (W-VQ-6). The hover emphasis of an org-chart card or a timeline event comes from the stylesheet and is never exported.
Legend spacing in ems of the legend font (identical at the default size).
Changed (standalone families on the shared state model, ADR-065, 2026-10-01)#
Standalone families on the shared interaction state. Donut and pie (the current slice moves out, its siblings step back), funnel, nightingale and radial bar (current item full strength, siblings step back), radar (series emphasis, a growing vertex, the other series inactive), treemap (a foreground edge, the other leaves step back), sunburst (the arcs of the current ring step back) and sankey (a node keeps its links and their far ends, a link its two endpoints; the rest of the flow steps back). No standalone renderer paints a dim or an offset of its own.
Legend preview on the donut, pie, funnel, nightingale and radial bar (the item) and the radar (the series), by pointer and keyboard focus.
Heatmap hover and focus: an outline (a background halo and a foreground edge) over the current cell; no other cell changes colour. The keyboard ring sits on the drawn cell.
Radar keyboard ring on the data vertex (it sat on the tip of the axis); vertices are drawn above every polygon, so a vertex inside a later series' polygon can be hovered. The axis labels are measured before the radius is fixed.
Treemap and sunburst tints are opaque tints of the group colour, tuned per colour mode: a dark surface keeps their hue instead of turning muddy.
Sankey labels: sources outside on the left, sinks outside on the right, middle columns in the gap; the side columns are measured from the labels.
labelnow defaults to the measured width (it was 90 px).Padding Timeline lane labels are measured and no longer cut at the chart edge.
Entrance animations release their final state (fill mode
backwards), so state paint applies after the entrance.
Fixed (interaction-state coverage, ADR-065, 2026-10-01)#
Shared and split tooltips on bars: the hovered category now stands out in every series (its bars full strength, the other categories step back); before, a grouped, stacked or mixed-sign bar chart with a shared tooltip showed no hover at all. No series is dimmed.
Histogram: the keyboard reaches every bin, and the tooltip shows the bin's value (it showed the bin's start).
Changed (Cartesian geometry standards, 2026-09-30)#
Bars round their value end by default: 3 px (
cornerstill sets a series' own, 0 rounds nothing), clamped to a quarter of the bar's width, on the value end only — the top of a positive column, the bottom of a negative one, the inline end of a horizontal bar. In a stack only the outermost segment of each sign rounds. Negative bars used to round their base.Radius Bars are at most 64 px wide. A chart with few categories draws deliberate columns instead of slabs; grouped bars narrow as a group and stay together and centred. The hit targets, focus ring and selection outline follow the drawn bar.
Crisp edges. Bar and waterfall edges sit on whole pixels (stack segments meet without a hairline); a candle's 1 px wick sits on a half pixel with an odd body centred on it, and candle and OHLC strokes render crisp.
The waterfall joins the bar family: the same value-end radius, width cap and crisp edges; its connectors run edge to edge.
Stroke hierarchy: a box plot's whiskers and caps are one step lighter than its box, the median stays the strongest; a lollipop stick is drawn one tier below its marker (stroke opacity 0.55 unless the series sets its own stick colour).
Changed (tooltip, typography and chrome, 2026-09-30)#
One surface family. The tooltip (every mode), the context menu and the crosshair value chips share the popover surface, a 1 px border and a per-mode elevation from the package stylesheet: a soft shadow in light, a deeper one in dark. The context menu lost its blur; the crosshair chips are no longer foreground-filled pills. A tooltip
style.still wins.box Shadow Tooltip hierarchy. Values are drawn one weight step above names (500 and 400), with tabular numerals at the inline end; in shared mode the row of the hovered series names it in the value weight. The key of a row has the shape of the legend key for its series type (a bar a rounded square, a line a stroke with its dash, a marker a circle); a candle or OHLC bar keys its up or down colour and a waterfall step its increase, decrease or total colour.
Value-end anchor. A bar, histogram bin, waterfall step, range bar, box, candle or OHLC bar opens its tooltip (and its crosshair) at its value end instead of its middle.
No blink across bar gaps. A pointer hover outlives a miss by 100 ms; the keyboard still ends its hover at once.
Typography tiers. Data labels are drawn in the value weight (500); the empty-state text is the metadata tier (11 px, muted). No size floor moves.
Grid. Grid lines run perpendicular to the value axis only: a category axis draws no grid by default, and a time axis only for a line, area or range-area series. The grid is a little stronger and tuned per mode, and a zero line one step stronger marks zero when the domain crosses it.
gridstill decides where it is set.Line. enabled The legend and tooltip keys of range area (a stroke) and of OHLC, box plot, range bar and candlestick (a square) now follow their series family.
Changed (interaction state model, ADR-064, 2026-09-30)#
One interaction-state model for every Cartesian family (ADR-064). The chart computes the state once (
rest,emphasis,recede,inactive,focus) and writes it asdata-attributes; the package stylesheet paints it. No series renderer applies a dim opacity of its own any more.* Bars and other shapes answer a hover with their own geometry. The hovered bar, histogram bin, waterfall step, range bar, box, candle or OHLC bar keeps full strength and its siblings recede; other series go inactive. The dot that used to sit in the middle of a bar is gone.
Markers get an emphasised node: the hovered scatter, bubble, dumbbell or lollipop mark is drawn again on top, slightly larger, with a background halo; a lone marker series recedes behind it.
A shared or split tooltip dims no series: every series the tooltip lists gets a node at the hovered row.
Keyboard focus has its own ring around the current bar body or node: the
--ringcolour between a background halo and a foreground edge (3:1 or more against the background), shown whether or not anything dims and with the tooltip off.Legend preview: hovering or focusing a legend item of
<Chart>emphasises its series and fades the others. It never toggles and never changesaria-; click and double-click are unchanged.pressed The recede and inactive strengths are tuned per colour mode from contrast floors (2:1 and 1.4:1 for every categorical colour on background and card; QUALITY-BUDGETS § 12).
highlightstill sets a series' own inactive strength.Style. dim Opacity
Fixed (interaction state model, ADR-064, 2026-09-30)#
Exports taken while hovering, focusing or previewing a legend item carried the transient state (dimmed series, crosshairs, the hover dot). An export now shows the chart at rest, byte for byte; print does the same.
The zoom animation of
<Chart>ignoredprefers-.reduced- motion The tooltip fade ignored reduced motion wherever the tooltip was mounted outside a chart scope; the fade is now a stylesheet rule the reduced-motion rule always reaches.
A hover no longer re-resolves or re-measures the axis ticks (CH-13), and moving within one item re-renders nothing.
The forced-colours rule for the hover dot targeted a circle inside the dot and never applied; the node now takes the system highlight colour.
Changed (axis layout, ADR-063, 2026-09-30)#
Axes are laid out from measured extents (ADR-063). One internal function stacks tick, gap, measured labels, the parent row and — only when it is drawn — the title, and both the margin reservation and the axis placement read it. An axis title now keeps one gap (0.75 em of the tick font) outside its widest label: it moves in for short labels and out for wide currency or percentage labels, on left and right axes alike. Visible effect: margins and title positions shift, an unrotated x axis takes about 24 px instead of a fixed 40 px, and screenshots of Cartesian charts differ.
Rotated x labels reserve the row they draw. The resolved rotation feeds the bottom (or top) extent; a resolved 0° no longer reserves the old 88 px. A truncating axis caps a rotated row at 70 px and cuts longer labels there (full text in
<title>); rotated labels are no longer cut to the tick spacing. An axis withlabel.reserves its full label length.truncate: false A title that is not drawn reserves nothing, on every side (the vertical axes previously kept 16 px under
hide). When the measured axes would squeeze the plot below 120 × 80 px, the titles of that dimension are left out before any label.Axis Titles First and last glyphs on an ordinal-time axis (candlestick, OHLC, box) keep the same gap to the plot edge as two neighbours: the axis pads 0.7 step instead of 0.5 when it carries glyphs.
Fixed (axis layout, ADR-063, 2026-09-30)#
Axis titles overlapping or crowding tick labels (for example
−12,5 %,€ 1.234.567or a dual NAV axis), including underdir="rtl".Dumbbell and lollipop markers at the value extremes were cut by the plot clip; they are now drawn whole (the domain is unchanged).
The forced-colours and print stylesheets targeted grid-line test ids that no longer existed, so grid lines kept their screen colours in Windows High Contrast and in print.
Fixed (re-convergence, 2026-09-30)#
A
timex axis honoursminandmax. The domain was built from the data alone; explicit bounds (epoch milliseconds) now set it, as on a number axis.A chart without rows rejects
exportwithAs Csv EXPORT_.NO_ DATA <Chart>withdata={[]}and a standalone family with fixed columns (the timeline) wrote a header-only file and reported success.onfires once per toggle. It ran inside a state updater, which React may run twice (always under StrictMode).Legend Toggle Sunburst ring-one labels are fitted in their painted weight (600). They were measured at the inherited weight and could overflow their arc.
Categorical item names use the formatted value (pie, funnel, nightingale, radial bar, radar), like the announcement already did.
A grouped bucket of ISO-string x values stays selected. The selection existence check read only
Dateand number x values, so an uncontrolled selection dropped the bucket at once.Indicators treat
NaandN Infinityas missing, likenull; one non-finite source value no longer hides every later EMA and MACD value.Periodwindows clamp to the end of a shorter month: 31 March minus one month starts on 29 February, not 2 March.Selector
Changed (packaged-artifact proof, ADR-061)#
date-andfns @date-are declared dependencies. The inlined formatter closure imports them; an install outside the workspace reads only this manifest. No runtime change.fns/ tz Documentation. README Quick Start passes a presentation and translator, the export paragraph describes
Chartinstead of the removed base64Export Fonts font, and API § 4 states that a host withoutjspdf/svg2pdf.declares them external to its bundler.js
Documentation (export guarantee tiers, ADR-060)#
Export guarantees.
docs/states the tiers L0 to L3b-print per format and script, the route to take, the error kinds, a host font example and the host L3b recipes. No runtime change.EXPORT- GUARANTEES. md
Added (export font sets in SVG and PNG, ADR-059)#
Export fonts in SVG and PNG. The same
Chartas PDF, with the same precedence (the call'sExport Fonts fonts, elseChart, else none;Defaults. export Fonts 'inherit'skips the defaults), onexport/As Png exportof the handle, onAs Svg Png/Export Options Svg, and in the context menu's PNG and SVG export. With a set, every text names the host's families in order before its live font stack.Export Options Chart(defaultExport Fonts. embed In Svg false): the SVG also carries every face as an@font-data URL. A PNG with a set always embeds every face, so the browser falls back per glyph across the host's families before any system font (CH-6, for the explicit set).face SVG and PNG embed TrueType, OpenType CFF, WOFF and WOFF2 as given; a font collection or unknown bytes reject with
EXPORT_. Font errors of an SVG or PNG export carryFONT_ FORMAT_ UNSUPPORTED format: 'svg'or'png'.
Without a set, SVG and PNG export exactly as before (the SVG is byte-identical to the earlier capture). PDF is unchanged.
Changed — BREAKING (export font model and PDF, ADR-058)#
One export-font model.
Chartand the PDF optionPdf Font font: {family, normal, bold?}(base64) are removed. PDF export takesfonts: Chart: families in fallback order, each with faces whoseExport Fonts sourceis anArray, aBuffer Uint8or a lazy host loader, plusArray weightandstyle.exportalso acceptsAs Pdf fonts: 'inherit'. Migratefont: {family, normal: base64}tofonts: {families: [{family, faces: [{source: bytes}]}]}.PDF refuses what it cannot draw. Without export fonts, text outside WinAnsi rejects with
EXPORT_(it used to draw broken). With or without fonts, complex and right-to-left scripts, combining marks, ZWJ/ZWNJ and variation selectors, emoji and supplementary-plane characters reject withFONT_ REQUIRED EXPORT_; text no family covers rejects withTEXT_ UNSUPPORTED EXPORT_. No file is written.GLYPH_ COVERAGE The standard font is Helvetica. Without export fonts the PDF drew in Times since FAM-V2-T6 (svg2pdf.js did not find the family
Helvetica); it now draws in Helvetica, as documented.
Added (export font model and PDF, ADR-058)#
Chart: the export fonts of every chart below, used by the handle's and the context menu's PDF export. An explicitDefaults. export Fonts fontswins;'inherit'skips the defaults for one call.Per-element font choice in PDF. Each text element draws in the first family that covers all of it (coverage read with jsPDF's own parser); bold, italic and bold italic faces are optional and fall back deterministically, never synthesised. Faces load before the capture moment; a loader runs once per face object and a failed load is retried by the next export.
Chart(Export Error. code Chart) and its metadata (Export Error Code format,family,detected,code,Point reason,script,normalizable,families);missingstays. Never the exported text.Module Types
Chart,Export Fonts Chart,Export Font Family Chart,Export Font Face Chart,Export Font Source Chart.Export Error Options
Added (text measurement and RTL chrome, ADR-057)#
RTL chrome. Under
dir="rtl"the legend flows from the right and its arrow keys follow the reading direction; the tooltip prefers the inline-end side of the pointer; the context menu opens toward the inline end, its submenu opens left with ArrowLeft and closes with ArrowRight; exported legends and report text follow the same direction. Axes, data order, the navigator and the plot arrow keys are unchanged.Direction-aware SVG anchors. Geometric labels (ticks, annotations, slice, bar, funnel and sankey labels) keep their physical side in rtl; tile, header, breadcrumb and gauge titles start at the inline start. A reference-line label without a position sits at the inline end.
Measured, grapheme-safe text. Mounted charts measure labels in their painted font (weight, style, direction, letter spacing, kerning) and again after a font load; truncation never splits a grapheme cluster; export report text wraps CJK and never breaks at NBSP or NNBSP.
Changed (text measurement and RTL chrome, ADR-057)#
A treemap tile shows its share whole or not at all (no truncated number).
Added (direction and bidi, ADR-056)#
dirprop on every chart. One UI direction: the explicitdir, else the page's direction (the nearestdirattribute, followed when it changes), elseltr; never the locale. The context menu under<body>, the tooltip and the screen-reader table carry it. It never reverses an axis or reorders data.Isolated text. Series names in the tooltip and legend and labels in the screen-reader table are
dir="auto"and isolated; formatted values are one isolated run; SVG text isunicode-(new partialbidi: isolate styles/). A right-to-left series name in the legend's isolation announcement is isolated with FSI/PDI.direction. css Export keeps direction semantics.
directionandunicode-are materialised into SVG, PNG and PDF exports; anbidi ltrchart with Latin text exports byte-identically. CSV carries no bidi controls.Formatted values read in their locale's direction (P6 refinement): tooltip and screen-reader table values carry their presentation locale's script direction where it differs from the chart's.
Changed (portable runtime, ADR-054)#
jspdfandsvg2pdf.arejs optional: rendering never loads them, and an installation without them failsDependencies export(and the menu's PDF export) with aChart As Pdf ChartwhoseExport Error missingnames the module, instead of the bundler's resolution error.Module Chartis public (root export), so a host can recognise a failed export inExport Error on.Export Error The CSV serializers come from
@workspace/, andactive- utils/ serialization IsofromDate String @workspace/, instead of the server-bearingactive- types/ primitives/ iso- date /barrel and thehelpers active-root.types
Fixed (reporting and export, ADR-053)#
Each defect below was found by the T6 browser evidence and has a witness that failed on the old code.
An export across a data change shows one moment. The legend of an image export was built when the export was called and the drawing was cloned after the chart had settled; a host that changed its series in between got the new lines under the old legend. The handle now reads the legend at the capture moment, from the latest commit (
<Chart>and the standalone families alike).Legend and report text are visible in a PDF under
oklch()tokens. The legend swatches and the report title, subtitle and caption kept the host token'soklch()value, which svg2pdf cannot read: the swatches were missing and the text was drawn invisibly. Their colours are now written asrgb()/rgba()like the rest of the chart.A PDF draws only in the font it embeds. Text of weight 600 (the report title) fell back to Times, unembedded and Latin-1 only, also with a host font: svg2pdf asked for a style named after the weight. Numeric weights now become
boldornormal, and withoutfont.bold text uses the regular embedded file.bold
Added (reporting and export, ADR-053)#
legendonItems export,Chart As Png exportandChart As Svg exportaccepts a function, called at the capture moment. An array is taken as given. Additive.Chart As Pdf
Fixed (accessibility and touch, ADR-052)#
No public API changed. Each defect below was found by the T5 browser evidence and has a witness that failed on the old code.
A reference-line label is no longer hidden by the marks (PH6-01). The lines and bands of
crossstay between the grid and the series; their labels are now drawn above the series with a halo in the chart background (Lines Canvasunder forced colours). At 390 and 320 px the "105" of the dual-axis chart was covered by the last bar.Every way of opening the context menu is announced. Shift+F10, the context-menu key and a long press now announce "Context menu opened", as a right click already did.
The keyboard reader hears menu, zoom, legend and pin announcements. The chart's live region showed the keyboard cursor text before any other announcement, and that text stays set while a reader navigates, so a keyboard zoom was never heard. The most recent announcement now speaks, and a cleared one no longer brings the older cursor text back.
A host menu item gets the item the menu was opened on. Opened from the keyboard, the element is the focused item; from a right click or a long press, the item under the pointer; on empty plot area,
nullas before. Opening moves focus into the menu, which cleared the hover the element was read from, so a keyboard-opened host item receivednulland the built-in "Copy value" was disabled.The keyboard focus follows onto a submenu trigger ("Export ▸"), as on a plain item.
A long-press menu stays open when the finger lifts. A browser whose own long press is longer than the chart's (500 ms) read the lift as a tap and closed the menu with the click that followed; the press now cancels its own click.
No console error on every touch move over a Cartesian chart. The hit layer called
preventin a passive listener, which the browser ignored with an intervention error; the page scrolled regardless, and still does.Default() The navigator brush follows a finger. The navigator strip declares
touch-, so a sideways drag moves the brush instead of being taken for a page pan; a vertical swipe still scrolls the page.action: pan- y
Added (host-integration contract, ADR-051)#
english: the package's English translator, complete by type, for standalone hosts, fixtures and examples that pass it on purpose. Nothing installs it implicitly; a chart without a translator still throws.Chart Translator Chartis an alias of the active-i18nPresentation Value(active-i18n D23); the type is unchanged. No breaking change.Presentation
Fixed (stylesheet activation and scoped text measurement, ADR-050)#
A stylesheet that stops or starts applying refreshes the chart's decisions. A change of
media,disabled,rel,type,hrefortitleon a<style>or stylesheet<link>in<head>, and a stylesheet<link>that fails to load, now move the style generation: on-fill label colours follow, and an export taken during such a change starts again instead of writing the previous view. What still needsinvalidateis listed in API.md § 13 (CSSOM edits includingChart Styles() sheet.anddisabled <style>., adopted sheets, stylesheets outsidedisabled <head>, media conditions other than colour scheme and forced colours).Labels are measured in the font they are painted in. Tick labels, data-label collision and export report text resolve
var(--font-, an inherited family and the weight in the chart's own element instead of the page body. Two sibling charts under different font variables now lay out the same labels differently and correctly (rotation, truncation, thinning, edge fit); a change ofsans) --font-to a font that is already loaded measures again. Before mount a chart estimates its labels and reads no style, so server markup and hydration agree. A bold report title wraps where its bold width says.sans No public API changed.
Changed — BREAKING (collision-safe series identity, ADR-049)#
A series that declares an
idis namedid:<id>in selection. TheseriesofId ChartandItem Ref Chart(inItem selected,Items default,Selected Items onandSelection Change on) is now the series identity itself:Item Activate id:<id>for a declaredid, else<type>:<key>(unchanged, e.g.line:price), elseposition:<index>. The namespaces are disjoint, so no two series of a chart share aseries. TheId id:prefix used to be stripped, so anidspelled like another series' identity (id: 'line:a'next to a line overa,id: 'position:1',id: 'line:a#2') produced oneseriesfor two series: a click on one reported the other, and a stored selection marked both or neither.Id Migration. Where a host writes a
seriesfor a series with anId id, prefix it:{seriesbecomesId: 'revenue', …} {series. Selections stored fromId: 'id:revenue', …} onmust be discarded or migrated the same way. Derived identities are unchanged. No alias is provided.Selection Change Repeats are defined, not only reported. A repeated identity keeps its order-based
#nsuffix and is documented as such; the development report now names that outcome.
Fixed (series-scoped display truth, ADR-048)#
A percentage stack no longer changes what other series show. A percentage-stack member used to write its share into the displayed row shared by every series, keyed by field. A raw series over the same field then showed the share, a second percentage stack over that field overwrote the first stack's shares, and a hidden member emptied the field for every series. Each member now displays its share from rows of its own; geometry, domain, stack baselines, hit geometry, tooltips, the accessible table and announcements read those, and every other series keeps its own value. CSV, copy-as-table and the navigator stay raw, as before.
A zoomed waterfall keeps its running total beside a percentage stack. The waterfall used to carry the running total of the steps before a zoom window only while the displayed rows were the zoomed array itself, so any percentage stack in the chart restarted it at zero. The positions are now placed on the displayed rows by the zoom window and the origin of each prepared row.
No public API changed.
Changed (theming: the package owns only --ac- *, ADR-047)#
The internal animation custom properties moved out of the host's namespace. The fourteen properties the charts write inline for their entrance and update animations are now
--ac-,fade- * --ac-,morph- * --ac-,segment- * --ac-andsweep- * --ac-(they werewipe- * --chart-,fade- duration --chart-and their siblings). The publicsweep- angle --chart-names belong to the host's token contract. They were never documented and no workspace consumer read them; a host stylesheet that did must use the new names. Timing and behaviour are unchanged.* Documentation. The theming design, the architecture and competitive review and the two older library comparisons are non-canonical research under
docs/(deleted 2026-10-07); the obsolete stacked-area analysis is removed. Canonical claims that no longer matched the source are corrected (research/ Chartexports return promises,Handle on, noExport Error Chart, noOptions. background d3-, no visual fields oninterpolate <Chart, the fixedDefaults> sqrtx-axis domain, the PDF font).
Added (theming: a host palette is the host's, ADR-046)#
Guards for the package's brand-neutral identity. The unit suite pins the chart values of
styles/in both modes and the thresholds the browser suite asserts. The platform host now sets its own chart palette (active-ui ADR-030) and charts pick it up through the token contract; the package defaults, the public API and the rendering are unchanged, so a page without host tokens shows the same palette as before.defaults. css
Removed (theming: no visual theme API, ADR-045) — breaking#
themeandChart. Charts andTheme Override Charttake noOptions themeprop and the type is no longer exported. A per-chart or per-region change of a token is plain CSS on the chart'sclassor an ancestor; charts follow it without a remount (ADR-044).Name The visual fields of
<Chart.Defaults> <Chartno longer takesDefaults> themeorcolors, and a chart no longer inherits a palette from it. Setcolorson the chart that needs its own categorical palette; leave it out to use the host palette.--bullet-. The token, its package default and its role are gone. The bullet's value bar is drawn in the on-fill token that contrasts most with the bands and track beneath it: dark over pastel bands, as before; light over a dark track, where it used to be dark on dark.bar
Changed (theming, ADR-045)#
The chart wrapper is
data-(waschart- scope data-) and carries no inline style. A scoped host override set on it or on an ancestor reaches the chart.chart- theme- scope colorsis documented as what it is: the categorical palette of one chart, replacing the host palette in the colour assignment and nothing else.
Fixed (theming, ADR-045)#
The pie and donut legend colours every key like its slice. Without
colors, the legend used a five-slot palette, so the sixth slice onwards showed a legend colour that differed from its slice; it now follows the same ten slots (orcolors) as the slices.
Added (theming: live-element resolution, invalidation and export consistency, ADR-044)#
invalidate. A value-free notification for a style change no observer can see (a rule inserted through the CSSOM, an adopted stylesheet): every chart re-reads its tokens in its own scope. It sets no theme and carries no value. A class, an inline custom property or a stylesheet element needs no call.Chart Styles()
Changed (theming, ADR-044)#
Colour decisions resolve in the chart itself. The on-fill colour of a label (bar, heatmap, treemap, sunburst, funnel) is decided on the tokens of the live chart element, so a scoped host override and the per-chart
theme(removed in T3) count; it no longer reads the page body. The decision is taken after mount, before the first paint; server render and hydration read no computed style for a colour.Runtime switching without a remount. Charts re-decide on a colour-mode, accent, token, scoped-override or stylesheet change: a class,
data-or custom-property change on any ancestor of the chart, a stylesheet added, removed, rewritten or loaded, andtheme prefers-/color- scheme forced-. One shared observation serves every chart and is released with the last one; a colour change recomputes no geometry.colors Heatmap cells and gradient legend are one scale. A cell paints its colour as a CSS mix of the two ends of the scale, the same references the legend uses, so both follow a token change by themselves. Behaviour: a cell's
fillandpoint.in its hover event are now a CSS expression (color color-or a token reference) instead of anmix(in srgb, …) rgb()string.Exports are one moment of the chart. SVG, PNG and PDF run one capture transaction: they wait for a still chart and loaded fonts, clone and read every colour and the background in one step, and start again (at most three times, then
Chart) when the page styles changed meanwhile. Every colour an element declares is resolved in that element's own scope and written asExport Error rgb()/rgba(), which a PDF can read. Behaviour: SVG files carryrgb()instead of the host's notation (oklch()).Behaviour — the PDF page takes the chart's background.
exportandChart As Pdf exportdefault to the chart's own background, as PNG already did, so a dark view exports as a dark page. PassAs Pdf backgroundfor another page colour; a light report from a dark view is an export in the light colour mode.Color
Removed (theming, ADR-044)#
The unused dependency
d3-(the heatmap no longer interpolates in JavaScript).interpolate
Changed (theming: CSS token contract and package defaults, ADR-043)#
Charts read the public tokens directly. Categorical and data roles come from
--chart-…1 --chart-,10 --chart-/positive -negative/-caution,--chart-,sequential- * --chart-,diverging- * --chart-(and the transitionalon- fill- * --bullet-); surfaces, text and controls from the host'sbar --background,--foreground,--card,--muted,--muted-,foreground --border,--ring,--popover,--accent,--primary(and their-foregroundpairs). No chart reads a Tailwind--color-alias any more, and the scattered literal fallbacks are gone.* Complete package defaults. Every token has a package default for light and for dark (
.on the document root or an ancestor), so a chart renders fully without any host stylesheet. A token the host sets always wins, whatever the stylesheet order: the package never declares a public token.dark One package stylesheet, loaded by the entry. Token defaults, focus, motion, forced colours and print are one stylesheet that the package entry imports once as a static CSS side effect; components no longer import stylesheets one by one. A host needs no utility-class scan of the package source and no import.
theme(Chart) writes the public tokens it overrides (Theme Override --chart-,N --chart-,positive --foreground,--card, …) on the chart wrapper, so it keeps working against the new reads.theme,colorsand the<Chartvisual fields are otherwise unchanged.Defaults> Structural colours follow the host on the element. The focus ring, for example, now reads
--ringwhere a scoped theme sets it, instead of an alias resolved on the document root.
Fixed (theming, consumer-visible, ADR-043)#
The heatmap's cells follow the sequential colour role. The cells used a fixed light-to-dark ramp while the gradient legend used
--chart-; in dark mode the two disagreed, and a host value never reached the cells. The cells now use the same resolved range as the legend.sequential- min/-max
Added (selection, linked views and navigator, ADR-041, ADR-042)#
See ADR-041 and ADR-042 and the migration notes.
Item selection and activation.
<Chart>and the thirteen Cartesian wrappers takeselection(mode: 'single' | 'multiple',item),Key selected(controlled) orItems default(uncontrolled),Selected Items onandSelection Change(selection, change) on, also insideItem Activate(item, source) events;Chart. An item isOptions. selection Chart: the series identity and the value of the item key, never an array index; a grouped bucket carries its interval. New public typesItem Ref {series Id, key, interval?} Chart,Item Chart,Item Key Chart,Item Ref Chart,Selection Change Chart,Selection Events Chart,Selection Options Chart,Selection Props Chart.Selection State Selection for every reader. A click selects and activates; Space selects and Enter activates the focused item; Shift+F10 and the ContextMenu key open the context menu. A selected item is outlined (in
Highlightunder forced colours), read "…, selected" in the screen-reader table and in the focus announcement, and each change is announced. A hidden, zoomed-out or regrouped item stays selected but inactive; an uncontrolled selection drops removed rows withsource: 'update'.Host words. Keys
charts.,accessibility. selected State item,Selected item,Deselected selection,Hint activationin the key contract and in every platform language.Hint
Changed (selection, linked views and navigator, ADR-042)#
Behaviour — linked views link by value. A
<Syncshares the x value under the pointer or keyboard focus and the zoomed x range as values, and each member shows them over its own rows: two charts with other rows, sizes or series now show the same dates. Members that read x differently (another axis kind or temporal semantic) no longer follow each other, the y range is no longer linked, a member that unmounts withdraws what it shared, and ranges that arrive faster than a chart renders no longer echo between two charts.Group> Behaviour — the navigator draws compound series. Candlestick and OHLC as a low–high band with the close, range area and range bar as a band, a box plot as a min–max band with the median; hidden series are left out; gaps stay gaps (a missing value was drawn as 0); long series keep every spike (extrema per pixel column). Date rows on an
ordinal-axis are placed by date (they were all drawn at the left edge).time
Changed (Level 1 budgets and evidence, ADR-039, ADR-040)#
See ADR-039 and ADR-040 and the migration notes.
Breaking — percentage stacks take raw values (W9).
stacked(Percentage Bar, and new onChart AreaandChart Area) draws each value as its share of the visible members of its stack; hiding a series restacks to 100 %, and a hidden member has no share. Mixed signs keep their direction (ADR-020). Axis, tooltip, table, announcement and segment labels read percentage points; CSV stays raw.Series Options stackandTotal stackare removed.Total Tolerance Breaking — honest options (W10).
Chartis removed. Scalar, categorical, hierarchical, graph, heatmap and timeline props no longer accept the props they cannot act on (Options. background colors,tooltip,context,Menu on,Zoom Change on,Legend Toggle animation, per family; also insideevents). An area'sfillnow acts in gradient mode too (the top of the gradient; default 0.4).Opacity Breaking — exports return promises (W11).
export,As Png export,As Svg exportandAs Csv exportresolve when the file is handed over and reject with an error when it could not be produced;As Pdf exportis asynchronous. NewChart As Svg onevent; context-menu export failures are reported instead of swallowed. Image exports wait until the chart is still. NewExport Error report: {title, subtitle, caption}export option (Chart) and PDFExport Report fontoption (Chart); PNG embeds the host's same-origin web font. Standalone families export their own rows as CSV instead of doing nothing.Pdf Font Breaking — invalid input shows the unavailable state (W12). A sankey cycle or unknown node, an empty or inverted scalar range, a negative or cyclic hierarchy and an inverted explicit axis domain render the translated
charts.state with a development diagnostic instead of throwing (sankey) or drawing a degenerate chart.unavailable Two y axes. One binding (
axis.) for every stage; an unknownid ?? axis. position yfalls back to the default axis everywhere, with a diagnostic.Axis Id Crossbinds a reference line to an axis. Fixed: the right y axis stood on the left edge of the plot, its labels inside the plot.Line Options. y Axis Id Fixed: a bottom or top axis took the unit of vertical series (an age axis under a percentage area read "30%").
Fixed (PH5-01): the outer labels of a truncating category or ordinal-time axis stay inside the chart.
Accent themes (W18): the active-ui accent themes no longer replace the measured categorical palette.
Evidence: performance budgets for the accepted workloads (
benchmark:browserfails on a breach), detached-DOM and heap measurement in the cleanup suite, and type-checked documentation examples.
Changed (readable presentation, ADR-037, ADR-038)#
Charts stay readable and distinguishable as width shrinks, series multiply and translations lengthen (W7, W8, ADR-037, ADR-038). No prop changed; the defaults did, and every dashboard looks different.
Palette (W7). The default categorical palette is hue-distinct: blue, orange, teal, pink, violet, green, sky, ochre, crimson, slate, with its own lighter values in dark mode. Every colour reaches 3:1 against page and card; the first six stay distinct under simulated protanopia, deuteranopia and tritanopia.
colorsand per-series colours still win.Colour roles. Tokens at the owner (
@workspace/active- ui themes.):css --chart-replacessequential- min/-max --chart-(the heatmap is its one reader; a host that set the old names sets the new ones), plus the newheatmap- min/-max --chart-anddiverging- * --chart-.on- fill- dark/-light Text on marks (QR-4). Inside bar labels, funnel segments, sunburst arcs, treemap tiles and headers and heatmap cells take the on-fill colour that reaches 4.5:1 against what they sit on; a label that no on-fill colour makes readable moves outside a free-standing bar or is left out. The funnel and heatmap luminance heuristics are gone; the funnel's value line is no longer drawn at 80 % opacity.
Second encoding. A line series from the seventh palette slot on, or whose colour repeats past the palette, draws a dash pattern that follows its identity, in the plot and in the legend key.
linewins.Dash Text floors (W8). The layout scale runs from 1.0 at 600 px to 0.9 at 320 px (it was 0.75 up to 500 px), and responsive text never goes below 10 px (ticks, labels) or 12 px (legend). Fixed 9 px texts (treemap values, sunburst outer arcs, timeline dates, small heatmap cells, pie and nightingale values) are 10 px. A text that does not fit is left out, not shrunk.
Tick measurement. The canvas measured tick labels in its default
10px sans-, because it ignores a font stack withserif var(); it now measures the host font, and measures again once fonts have loaded. Axes show fewer ticks where labels used to touch, and vertical axes are a few pixels wider.Dense categories (PH3-01). A category axis with less than 4 px per category no longer rounds or pads its bands, and a bar is at least 1 px wide: a 1,000-category chart on a 1,120 px plot draws every bar instead of none.
Plot edges (PH3-03, PH4-01). A candlestick, OHLC, box plot, bar, range bar or waterfall on a continuous x axis without an explicit domain gets half a glyph of room at both ends; the first and last glyph are whole and hoverable in every engine.
Legend (AC-B1). A legend that does not fit its height folds behind a "Show N more" / "Show fewer" button (
aria-) instead of scrolling without a cue. New host keysexpanded charts.andlegend. show More charts., in every language oflegend. show Fewer @workspace/.locales Organisation chart. The initial fit never shrinks node text below 10 px; a tree that does not fit starts on its root, and keyboard focus pans to the node it reaches.
Motion (W8). Default entrance 400 ms (was 600, standalone families up to 800), stagger 30 ms per series (was 80) capped at 200 ms; funnel segments and hierarchy levels use the same pace. Explicit
animationsettings still win; reduced motion still turns motion off.Screen-reader table (PH1-03). The Cartesian data table sits in a clipping container and no longer widens a narrow page.
Forced colours. The first six line series and their legend keys get system colours with distinct patterns.
Changed (complete information paths, ADR-035, ADR-036)#
What a chart draws can be read without sight or pointer (W5, W6, ADR-035, ADR-036).
Compound glyphs are one item (W5). Candlestick, OHLC, box plot, range bar and range area now have hover targets and keyboard stops: one per glyph, covering its whole value extent. Range bar and box plot had none by an earlier decision, which W5 reverses. The default tooltip lists every value role with its host word (
charts.) instead of the lettersrole.* O H L C, the keyboard announcement states every role, and the data table has one column per role instead of an empty cell.Single(new public typeTooltip Data. roles Chart) carries the roles to a custom tooltip renderer.Value Role Standalone families have a data table (W6). Pie, donut, funnel, radar, nightingale, radial bar, heatmap, treemap, sunburst, sankey, org chart, timeline, gauge, linear gauge and bullet render a screen-reader table with their own structure (category and share, matrix, path, source and target, parent, track, value with range, target and zones). It follows
accessibility.and cannot widen a narrow page. Gauge, linear gauge and bullet now render a fragment (the SVG plus the table).data Table Waterfall. The table lists each step and its running total, and the announcement states both. A zoomed waterfall keeps the running total of the steps before the window; before, it restarted at zero (PH3-02).
Instants keep their time in the tooltip, crosshair, table and announcement (
datetimepreset); before, rows of one day read the same (PH1-02). A calendar date stays a day.Standalone announcements (pie, donut, funnel, nightingale, radial bar, radar, heatmap, treemap, sunburst, sankey) read the value in the chart's notation (
12,400), as the table does, instead of the raw number (12400).Legend announcements use the name the legend item shows; an unnamed compound series was announced with an empty name.
New host keys (every language, ADR-028):
charts.,role. open high,low,close,min,q1,median,q3,max;charts.,accessibility. series Role role;Value charts.,data Table. category value,share,series,name,parent,path,source,target,date,track,measure,zone,range,detail.Accessibility statement.
docs/is an evidence-scoped, AA-oriented support statement without a certification claim; the supported AT pairs are NVDA with Chrome or Edge and VoiceOver with Safari. No human AT run is recorded yet.accessibility/ ACCESSIBILITY. md
Changed (truth-preserving data preparation, ADR-033, ADR-034)#
Reduction and grouping may make a chart faster; they no longer change what the data means (ADR-033, ADR-034).
Hidden series leave the automatic domain (W17). Hiding a line or a non-stacked bar now rescales the axis to the series still drawn, as hiding a stacked series already did. An explicit
minormaxis unchanged.Reduction replaces LTTB. Above
maxrows (default 500,Points 0turns it off), lines and areas keep, per pixel column, every series' first, last, lowest and highest row, every stacked-area top edge and both sides of every gap, bucketed on the actual x value (number, time, log, sqrt, or the rank on an ordinal-time axis). Every displayed row is a source row. Before, LTTB sampled by row position on the first series only and could drop a null gap, a secondary series' spike, categories, scatter points, waterfall steps and the rows of a compound series next to a line.Families that are never reduced (W3). Bars (category or time x), scatter, histogram, waterfall, candlestick, OHLC, ranges, box plots, lollipops and dumbbells keep every row; next to a reduced line, every row they draw is kept. A chart with a waterfall is not reduced at all, so its running totals are computed on every step. A category x axis is never reduced. Charts below the density of their plot (at most four rows per series per pixel column) draw every row, as before; the long price series shape at desktop width now lists all its days in the data table instead of 500.
maxonPoints LineandChart Area. The threshold is reachable from the two wrappers whose families reduce.Chart Grouping by role. Candlestick and OHLC group as first open, highest high, lowest low and last close; range-area and range-bar as lowest low and highest high; a bar's error bounds and a scatter's size follow the value's method. Every field is written under the series' own key (the
'ohlc'and'range'methods no longer write hard-codedopen/high/low/closefields or a[low, high]tuple). On a single-valued series,'ohlc'and'range'fall back to the type's default with a development diagnostic.One grouping policy per chart. A chart groups every series or none: a series with
data(orGrouping: false {enabled: false}), or a waterfall, histogram, box plot, lollipop, dumbbell or horizontal series, keeps the whole chart ungrouped, with a development diagnostic. Before, an opt-out on a later series was ignored and that series was averaged.Groupis now the number of source rows in the bucket's interval (it counted the first series' values);Meta. count Groupdescribes the first series with a value in the bucket.Meta. raw Raw and displayed data (W4). The screen-reader table and keyboard announcements describe the displayed rows: a reduced table is described as showing N of M rows, a grouped table reads each x cell as its interval and adds a column with the number of source rows, and a grouped point's announcement names both. The CSV export (handle and context menu) writes the chart's full raw data; copy-as-table now writes the raw rows of the visible range instead of the displayed ones.
New host keys (every language, ADR-028):
charts.,accessibility. reduced Data grouped,Data interval,sourceandCount grouped.Point
Changed (resolved series model, ADR-031, ADR-032)#
Every stage of a Cartesian chart now reads one description of each series (ADR-031, ADR-032). Visible corrections a consumer notices without changing any code:
Series identity (W1). A series is identified by its
id, or else by its type and primary value key. A series the host adds is drawn; a user's legend hide stays with its series when the host adds, removes or reorders series; a series the host removes and adds back is visible again; colours and exit animations follow the series, not its position. Two series with the same identity are reported in development.visible: falsenow means "starts hidden" consistently: the series is not drawn, stacked, hit or counted until the user shows it in the legend (before, it was drawn but not interactive). Categorical legends (pie, funnel, nightingale, radial bar, radar) follow the category (or series) the same way.Hidden stacked series (W2). A hidden series no longer lifts the stack: the visible series restack onto zero and the automatic domain follows the visible stack.
Bars start at zero (W16). The automatic value domain of bar, histogram, lollipop and waterfall marks includes zero (not on a log axis); an explicit
minormaxstill wins.Stacked domains. A line on the same axis as a stack contributes its own extent instead of being added to the stack sum; bar and area stacks are separate stacks in the domain, as they already were in the geometry; horizontal stacked bars get a stacked x domain.
Orientation and scale. A horizontal range bar and points on a
sqrtx axis get a numeric domain and are drawn inside the plot.Category position. Points, lines, areas, candlestick and OHLC glyphs, hit points, annotations, vertical reference lines and the synchronised crosshair sit at the centre of their category, where the tick label is; bars are centred in their column (the gap between grouped bars is split over both sides).
Axis binding. A series bound to a second y axis (
y) is drawn on that axis's scale; line, area, scatter, candlestick, OHLC, box-plot, histogram, waterfall and range renderers projected on the default axis before.Axis Id Hit geometry. After a legend toggle, grouped-bar hover targets are the drawn bars; a hidden series has no hover target.
Removed (breaking)#
The root entry publishes the chart contract only (ADR-029). Removed from
@workspace/: the renderer building blocks (active- charts Svg, theRenderer *Seriescomponents), axis and grid-line components, overlays (Annotations,Chart,Context Menu Cross,Lines Crosshairs,Chart,Navigator Brush,Overlay Zoom,Selection Overlay Sync,Crosshair Gradient,Legend Accessible,Data Table Voronoi,Overlay Chart,Tooltip Content Tooltip,Fade Wrapper Chart),Legend Responsive, all internal hooks (Container use,Layout use,Prepared Data use,Scales use,Voronoi use,Chart Tooltip use,Legend use,Chart Defaults use,Chart Handle use, the touch, zoom, animation and keyboard hooks,Period Selector use,Sync State use), scale, layout and data helpers (Sync Publish create,Chart Scale compute,Layout lttb,validate,Chart Data applywith itsMissing Data Strategy Missingtype (no chart option used it), the grouping internals,Data Strategy compute,Histogram Bins compute, …), the CSV building helpers (Waterfall Positions build,Csv String format,Csv Value derive),Columns From Options append,Legend To Svg resolve,Css Variables ACTIVE_,CHARTS_ THEME resolve,Color normalize, the animation, touch and tooltip constants, and the utilitiesData Label Option get,Series Color resolve,Collisions estimate,Label Rect mean,median,local. No consumer used them. Migration: none for the documented API; a host that composed these parts usesPoint <Chart options>instead.Formatter re-exports removed.
format,Number format,Currency format,Date format,Date Time format,Fraction As Percent formatand their option types come from their owner,Percentage Points @workspace/.active- i18n/ formatters Georemoved. It had no implementation; a geographic chart will bring its own props type.Chart Props
Added#
A browser benchmark (
pnpm --filter @workspace/, no retries) with the Phase 3 baseline inactive- charts benchmark:browser docs/.PERFORMANCE. md idon every series type (candlestick, OHLC, box plot, waterfall, range area, range bar, dumbbell, lollipop) and on the wrapper series configurations (Line,Chart Area,Chart Bar,Chart Lollipop,Chart Scatter,Chart Radar). Optional; the derived identity applies without it.Chart Root exports for types a consumer needs to name:
Axis,Formatter Axis,Tick Value Category,Axis Tick Value Number,Axis Tick Value Time,Axis Tick Value Category,Axis Config Number,Axis Config Time,Axis Config Accessibilityand its three option types,Options Keyboard,Announcement Mode Animation,Type Exit,Animation Type Exit,Phase Options Choreography,Layer Period.
Fixed (consumer-visible)#
CSV and clipboard export are safe for spreadsheets (ADR-030). Text fields, and the header row, go through the delimited-text boundary of
@workspace/: always quoted, a carriage return or tab no longer breaks a row, and text a spreadsheet would execute as a formula (active- utils =…,@…,-1+1) is neutralised. Numbers, booleans, instants and declared calendar days stay unquoted machine literals. "Copy as table" uses tab-separated quoting instead of the CSV rules. Derived CSV columns now include the price keys of candlestick/OHLC, the low/high keys of range series and the box-plot keys (these exports contained only the x column).Data grouping keeps every series: a grouped multi-series chart aggregated only the first y key, so the other series rendered empty.
Histogram bins: rows without a finite x value are skipped instead of counted as 0, explicit
binsranges are honoured as bin boundaries, and the default bin name formats its bounds in the chart's presentation instead of raw numbers.One
Infinityvalue no longer empties a numeric axis; non-finite values are left out of the domain.Technical indicators: two indicators of the same kind and period on different source keys no longer overwrite each other (the key gains the source key when it differs from the default); a period that is not an integer ≥ 1 yields
nullinstead ofNa.N minis a hard floor: when it exceedsPixel Interval target, ticks now stay at leastPixel Interval minapart instead of the floor being lowered to the target.Pixel Interval Timers of the tooltip auto-dismiss, the legend click debounce and the long-press no longer fire callbacks after unmount.
onfires after a data-update animation; it never fired, and the update state never ended.Animation Complete({type: 'update'}) Gauge,Chart LinearandGauge BulletwithChart min === maxrender an empty range instead ofNageometry.N Sankeywith only zero-valued links renders like an empty chart instead ofChart Napaths;N SankeyandChart OrgfireChart ononce on mount.Animation Complete({type: 'load'}) Heatmapcontext-menu cell lookup no longer confuses cells whose x or y value containsChart |.A cancelled touch no longer opens the context menu through the long-press.
A legend at the bottom, left or right now reports a double-click isolate through
onand announces toggles and isolates to screen readers, as a top legend already did.Legend Toggle
Changed (breaking)#
A chart no longer speaks English and no longer reads the translator sideways from active-i18n (ADR-028, 2026-09-18). The translator comes from the
tprop or from<Chart, and without either every public entry point throws a configuration error: every wrapper,Defaults t> <Chart>,Period,Selector Accessible,Data Table useand the default fallback ofKeyboard Navigation Chart. All hardcoded texts (about seventy, most of them screen-reader text and keyboard announcements, plus two Dutch tooltip labels) go through theError Boundary Chartkey contract.Translation Key Removed:
useandChart Translations fallback. New exported types:Translate Chart,Translation Key Chart.Translation Variables Chartnow takes a contract key; any host translator with aTranslate Fn stringkey remains assignable.generateandAria Label(options, t) applytake the translator as an argument.Indicators(data, indicators, t, source Key?) A combination chart's accessible name uses full type names (
bar chart/instead ofline chart bar/).line chart A time axis declares
temporal, and without that declaration the chart fails (ADR-027, 2026-09-17). Required in the type on everySemantic: 'calendar Date' | 'instant' 'time'and'ordinal-axis, including the default x-axis oftime' Line,Chart Area,Chart Range,Area Chart CandlestickandChart Ohlc(whereChart xis now required), and at runtime viaAxis <Chart>, including the implicit ordinal-time axis of a<Chart>without axes. The string shorthandsxandAxis="ordinal- time" xare gone.Axis="time" usetakes the semantic as its first argument.Chart Axis Formatters(). date(semantic, preset?) Timeline:Chart Timelineis display text and is shown verbatim in the tooltip, the announcement and theEvent. date aria-, as on the marker label;label useno longer has aTimeline Kbd localeoption.
Changed#
A chart's presentation comes from its
localeprop or from<Chart, and otherwise the chart fails visibly (ADR-026, 2026-09-16). TheDefaults locale> 'en'fallback of<Chart>, of the chart-local context and ofuse/Chart Axis Formatters usehas been removed; without an owner the entry point throws a configuration error. A nestedKeyboard Navigation <Chartnow refines the enclosing provider instead of replacing it.Defaults> Charthas been removed from the public API (zero consumers).Locale Override The accessibility documentation no longer claims WCAG 2.1 AAA or a verified screen-reader matrix (2026-09-25, Enterprise Parity Programme Phase 1). Neither was backed by a recorded run, and known gaps contradict them: compound series show empty data-table values, the standalone families have no data table, and OHLC, range and box-plot series have no keyboard stepping.
accessibility/now states what each family provides and at which evidence level; the replacement claim is an open decision (W6). No runtime behaviour changed.ACCESSIBILITY. md Documentation now states the known limits of automatic reduction and data grouping (ACHARTS-01, ACHARTS-06): default LTTB above 500 rows can drop a null gap, another series' extreme, categories, scatter points, waterfall steps and compound rows; compound series are not grouped. Stale README, API, architecture and recipe examples were repaired to the current contract (temporal x-axis declaration,
height, tooltip renderer parameters, formatter presets). No runtime behaviour changed.
Fixed#
A calendar date no longer shows the previous day on any surface (ADR-027). Under a zone west of UTC,
2026-rendered as01- 01 31 Dec 2025on the axis, and the tooltip, crosshair, parent row, screen-reader table and keyboard announcement each showed their own reading (a raw ISO string in the table, a parsed string in the tooltip). All surfaces now read the value through the declared semantic; the CSV export writes acalendarcolumn asDate YYYY-.MM- DD The parent row groups ticks on the day its label shows, no longer on the runtime's local fields (ADR-027).
The internal elements of the standalone wrappers follow the app's presentation (ADR-026). Tooltip and series labels of, among others, Heatmap, Pie and Funnel read a chart-local context that was never set and showed English notation, even under
<Chart.Defaults locale> The chart context-menu labels use the infinitive and sentence case (context-menu standardisation, 2026-08-31). In Dutch,
Kopieer waardebecameWaarde kopiëren,Kopieer als tabelbecameAls tabel kopiëren,Zoom naar hierandPan naar hierbecameHierheen zoomenandHierheen pannen,Reset zoombecameZoom resetten,ExportbecameExporteren, andExporteer als PNG/becameSVG/ CSV/ PDF Exporteren als …. In English the UI Title Case disappeared (Pin to Top->Pin to top). Only the TEXT changed: the same keys, the same actions, the same interaction. The norm lives indocs/section "A context-menu item names the action" ("Een contextmenu-item noemt de handeling");standards/ PAGE_ STANDARDS. md lint-guards its mechanically checkable half.context- menu- label. mjs
[5.0.0] - 2026-08-22#
Axis ticks are now resolved once, by one engine, and every consumer of a
tick reads that result: the axis labels, the grid lines, the axis margin and the
export. The immediate defect was a value axis over [0, 22] rendering
twenty-three crammed labels in 130 pixels; the cause was that four layers each
computed their own answer, and that integer discarded the density budget
the renderer had already calculated. See
ADR-024
and ADR-025,
and migration/ for the
upgrade.
Removed#
Axis/Config. integer Ticks Axis. It generated twenty-plus ticks and filtered the non-integers out afterwards, which cannot control density by construction, and it silently overrode a caller'sOptions. integer Ticks tick. Replaced by deriving the smallest meaningful step fromCount value— the declaration the library already has for what a value means (WS-13 / PP-045).Semantics {kind: 'count'}means whole numbers because that is what a count is; no second flag says it again.Axis/Config. grid Line. num Ticks Axis. Grid lines had their own density, which is how twelve grid lines ended up under twenty-three labels. Grid lines now always follow the resolved ticks of their axis. A deliberate minor/major grid contract does not exist today; when it is wanted it will be designed as one.Options. grid Line. num Ticks Axispropsnum,Ticks tick,Values tick,Format integer,Ticks rotation,auto;Rotate XAxispropwidth;XAxis/YAxispropnum;Ticks XGrid/Lines YGridpropLines num. These components no longer choose anything; they receive a resolvedTicks ticksobject and draw it.
Added#
core/— the tick engine.ticks/ resolveis the single authority. Interval-first: pick a nice step fromTicks {1, 2, 2.5, 5} × 10ⁿ, then generate, then measure, then coarsen if the labels do not fit. Public helpers:choose,Nice Step enumerate,Nice Steps generate,Ticks For Step nice,Domain For Step resolve,Tick Budget semantic,Tick Quantum has,Duplicate Adjacent Labels get.Tick Pixel Position Axis. A fixed step in domain units. Stronger thanConfig. tick Interval tick; an unreadable choice warns in development but is still honoured, because an explicit statement is not silently discarded.Count Axis(default 72 vertical / 100 horizontal) andConfig. target Pixel Interval Axis(default 32 / 40). The target drives how open the axis looks; the floor is a readability guarantee that no automatic resolution and noConfig. min Pixel Interval tickmay cross. The old singleCount minplayed both roles and was a reasonable floor but a hopeless target.Tick Spacing = 24 Calendar-aware ticks for
ordinal-axes. Ticks land on the first observation of each week, month, quarter or year rather than on every n-th data point, so a 30- or 90-day window reads as a calendar.time
Changed#
ticknow works. It is a target: the pixel floor and the semantic quantum remain constraints, but nothing overrides it behind the caller's back. The precedenceCount tickis documented and covered by tests.Values > tick Interval > tick Count > automatic Axis margins are measured, not estimated. The left/right reservation now comes from
measureover the actual resolved labels. The previousText Width 8 chars × 7px + 16estimate reserved 82px for labels needing 30 — over ten percent of the plot width on every count chart in the repository.Category axes keep their labels longer. A category label is an identity, so rotation and truncation now come before thinning. Time-series axes are unchanged in that respect: there a label is a position, and thinning costs nothing.
Category thinning uses a uniform stride anchored on the LAST value, with strides forming a doubling chain so a coarser step is always a strict subset of a finer one. Labels therefore disappear on resize without the remaining ones shifting. The previous
sampleproduced irregular gaps (indices 0, 2, 3, 5, 6 for seven days over five labels).Evenly The legend no longer takes plot space (ADR-025). It is rendered as HTML outside the
Responsiveand is now the sole owner of its own height. Every chart with a legend gains ~30px of plot area, and two same-height charts side by side align again when only one has a legend.Container
Fixed#
A value axis can no longer render two adjacent ticks with the same label.
hasinspects the real formatter output, so it also covers compact notation, consumer closures and every locale.Duplicate Adjacent Labels Grid lines coincide with their labels. They previously used
scale.(d3's default of ten) while the axis used its own count.ticks(undefined) Axisno longer re-formats labels the resolver already produced. A second formatting pass could diverge, because the boundary-aware date formatters read the tick index and the full tick list.
Also released here#
This release also cuts the work that had been accumulating under
[Unreleased] since 4.1.0 — the WS-02 formatter re-exports, the WS-13
Chart declaration and the theme-token rename. Two of those
carried their own ### Breaking notes and were waiting for a major to land in;
their entries follow below, unchanged.
Why major#
Public props were removed (integer, grid) and the
internal axis components changed shape. Independently of that, ADR-025 changes
the rendered height of the plot area for every chart carrying a legend, which is
a visible change on 72 call-sites. Both are contract changes, not fix size.
Added#
Chart: a series declares what its values mean, once (WS-13, PP-045, 2026-08-17). Until now only the axis was served: the app passed a closure, and a closure is behaviour you can call but cannot derive anything from. The tooltip and the screen-reader table fell back to one generic shape heuristic (Value Semantics Number.), so a chart with a currency axis showedis Integer(v) ? 'integer' : 'decimal- 2' € 1.235.000to a sighted reader and said1.234.567,89to a screen-reader user. That is not a matter of taste; it is a materially different quantity.The declaration is generic and owner/domain-blind:
number,count,money(with an explicit currency),price(currency plus a declared price precision) andpercent(with its stored unit explicit). Unit and precision are two independent properties —money EUR + 4 decimals,fraction-andas- percent + 3 decimals price in EUR + its own precisionmust all remain expressible, so a chart never derives that currency means two decimals.It lives on
Axis/Options Axis(the usual place — one axis normally carries one unit) and onConfig Series(the exception, for a chart with two units on one axis); the series wins. Axis, tooltip, split tooltip, screen-reader table and keyboard announcement all read the same declaration. Without a declaration nothing changes — the pre-WS-13 fallback stands, so the repair reaches exactly as far as the declaration does. CSV/data export is untouched by construction: it never took a presentation input (PP-013).Options Base
Changed#
Screen-reader output of custom accessible columns now follows the resolved presentation (WS-11 / CR-A11, 2026-08-16).
SeriesreceivedColumn. formatter (value, row)and therefore knew the value but not its notation. Two bar modes wrote past the shared formatters as a result: stacked percentages emitted`${v}%`and error bars concatenated raw numbers. A reader under a comma-decimal presentation heard12.5%while the visible axis showed12,5%.The signature is widened additively to
(value, row, context), wherecontext: Accessiblecarries the chart's own presentation input — nothing else. Single- and two-argument formatters remain valid, because a function with fewer parameters is assignable in TypeScript.Formatter Context Consequences for the bar plug-in, both with the source unit stated explicitly rather than inferred from magnitude:
stacked percentages go through
format— percentage points by definition (the stack sums toPercentage Points(v, 'decimal- 1', …) stack), never a fraction. The preset mirrors the tooltip rather than the axis: a table cell shows one value, andTotal integerwould round 12.5 to 13 in the only output a non-sighted reader has;error-bar value, low and high all go through
format— the same rule the default cell and the visible tooltip already used.Number(v, integer|decimal- 2, …)
Visible output moved with it: the default error-bar tooltip formatter of
Barformatted its value but concatenated raw bounds. It now uses the same rule, so visible and assistive output cannot diverge.Chart Nothing here changes the package boundary. The context carries a generic presentation and no administration, tenant or profile concept; machine CSV is untouched and still takes no presentation at all.
Machine CSV no longer infers date-only semantics from a clock value (CORR-09, 2026-08-15).
formattreated any UTC-midnightCsv Value Dateor ISO string as a calendar day and truncated it, so a genuine instant of2026-left the export as01- 19 T00:00:00.000 Z 2026-— time and type gone, and unrecoverable. Midnight is a clock reading, not a meaning.01- 19 Semantics are now declared or lossless: the new
Csv(Value Semantic 'date' | 'date) can be supplied per column key throughTime' CsvonExport Options. column Semantics build/Csv String export; without a declaration the full value is kept. No locale, no presentation context — the machine boundary stays presentation-immune (PP-013).Chart As Csv Migration: a column that really is a calendar day must now say so. An undeclared
Dateexports as a full ISO instant, which re-reads as the same value; the previous output re-read as a different type.
Added#
localeaccepts a presentation-context object in addition to a language code. The new public typeChart(Presentation string | (Date) applies to thePresentation Context & Number Presentation Context) localeprop of every chart,Chartand all internal pass-through points; the value goes unchanged to the central active-i18n formatters. A bare language code remains fully valid ("notation follows the language") — existing consumers do not change. The package stays tenant-blind: no knowledge of administrations, profiles or settings; the consumer composes the context in the app layer.Defaults. locale use/Chart Axis Formatters(presentation?) create: shared axis-formatter factories (Chart Axis Formatters number,currency,fraction,As Percent percentage,Points date) that resolve their presentation fromChart— the replacement for the hand-written axis closures in the apps.Defaults
Changed#
Screen-reader text follows the chart's presentation (screen-reader text is presentation). The sr-only data table no longer formats numbers with the browser locale and no longer formats dates as ISO strings, but uses the central formatters with the chart presentation; the keyboard announcement of Cartesian charts formats the y-value (previously the raw JS number); the scalar aria-labels of gauge, linear-gauge and bullet format their numbers and percentages centrally. The English prose words in those labels ("of", "zone", "target") are deliberately left untouched — prose translation is a separate i18n follow-up.
Breaking#
The formatter re-exports follow the preset API of active-i18n.
formatno longer exists; the barrel andPercent utils/re-exportformat. ts formatandFraction As Percent format(source unit in the name) plus the typesPercentage Points Number/Format Number/Preset Percent.Preset formattakes a closed preset instead of a freeNumber Intl.bag.Number Format Options
Changed#
Numeric
type: 'number'axes without their own formatter orformatstring get a locale-aware default formatter. Until this change such an axis fell back to d3'stickwith US punctuation, so anFormat() nlreader saw1,234.5on the axis next to a tooltip showing1.234,5. Log axes deliberately keep the d3 behaviour (label suppression of non-significant ticks).The internal share, legend, ring and zoom labels of pie, nightingale, radial-bar, treemap, funnel, radar, heatmap, org-chart and the stacked-percentage defaults of bar format through the central presentation layer with the chart locale, instead of
to/Fixed ${v}%with a fixed dot notation. The tooltip, crosshair and split-tooltip defaults have been moved to the same presets.
Breaking (theme)#
Chart/Theme Override. success ./warning .are renameddestructive positive/negative/caution, and they now write--color-/chart- positive -negative/-cautioninstead of--color-/success --color-/warning --color-.destructive Zero consumers in the monorepo: all twenty
theme={{…}}call sites (nineteen in tests, one in an admin demo) set onlyforeground,cardormuted. No compat shim, in line with CLAUDE.md Critical Rule 12.Foreground
Changed#
This package no longer reads semantic UI colours.
--color-,success --color-,warning --color-andinfo --color-belong todestructive @workspace/. There they carry a text contrast contract (4.5:1) that does not apply to a chart mark; as long as both roles shared one value, an accessibility retuning on the UI side silently shifted candlestick, OHLC, waterfall and sparkline — and vice versa. No type binds a colour string to a role, so this passed typecheck, lint and the full test suite green.active- ui Migrated:
candlestick-,series ohlc-,series waterfall-,series sparkline,cross-, the theme object and two JSDoc locations. The values of the new tokens equal the old ones, so no chart changed colour. See the addendum to ADR-007 and active-ui ADR-013.lines core/reads its defaults fromoverlays/ cross- lines. tsx ACTIVE_instead of from its own literals. Found along the way: the two sources had diverged — the theme object saidCHARTS_ THEME. cross Line band.whilefill: var(--color- muted) var(--color-was rendered, and the field nobody read was the one the documentation described. The theme object now carries the values that were actually shown (warning) 4,4dashes, opacity0.1), so there is no visual change.ACTIVE_are renamedCHARTS_ THEME. colors. success/ warning/ destructive positive/negative/caution. These fields had zero readers inside the package, as did the wholecrosssub-object until the change above.Line
Fixed#
Documentation drift in
ARCHITECTURE.§5.1. The code sample ofmd ACTIVE_diverged from the source in four places: the bareCHARTS_ THEME var(--chart-form instead ofN) var(--color-, five series colours instead of ten, achart- N) statuskey that does not exist, andaxisfields (tick,Stroke label,Color title,Color grid) the theme object never had. Replaced by an excerpt from the source.Stroke
[4.1.0] - 2026-06-01#
Stacked area charts now stack at the geometry level. Previously <Area only scaled the y-axis to the cumulative total while every area was
still drawn from the plot floor (overlapping). Each stacked area now sits on a
per-point baseline — positive segments stack upward from zero, negative segments
downward — consistent with stacked-bar mixed-sign semantics (ADR-020). Stroke,
markers, path-morph animation and the tooltip hit-test all follow the stacked
coordinate. See ADR-023.
Added#
Stacked-area geometry (incl. mixed-sign).
<Area(and the low-levelChart stacked> areaseries withstacked: true) renders true stacked bands with a per-pointy0baseline. Mixed-sign data stacks positives up / negatives down from the zero-line. Non-stacked area is unchanged (still anchored at the plot floor). The stacked-baseline source is shared with the renderer and the hit-test, so the tooltip lands on the stacked band rather than the raw value.
Why minor (not major)#
No public API or prop shape changed and all non-stacked area rendering plus all
bar/percentage stacking is byte-identical (regression anchors: 2150 tests green,
unchanged assertions). The bump above patch (4.1.0, minor) reflects
newly-defined, consumer-visible behaviour for stacked area (geometry + the
docs claim that area "stacks" is now true — O-2 closed). Additive, not breaking.
[4.0.0] - 2026-05-31#
PNG/SVG/PDF export now reproduces the chart's live colours and fonts: a
per-chart theme override and var(--x, fallback) syntax (in colour and
font-) are resolved correctly instead of being lost (AC-A4). See
ADR-022.
BREAKING CHANGES#
resolve→Css Variables(svg Clone) resolve. The exported helper now requires a second argument: the live, in-DOM element to resolve CSS variables against (the chart'sCss Variables(svg Clone, scope Element) <svg>, which sits inside its theme scope). This is the only correct source — the detached clone cannot resolve its own scoped variables. All in-tree callers (the PNG/SVG/PDF export routes) pass the originalsvg. The major bump is on contract grounds: a public export's signature changed. Consistent with the v3.0.0 precedent (removed publicElement create), the major is justified by the public-API change itself — not by the size of the change or the (zero) number of external callers. See migration note below.Defined Accessor
Fixed#
Export honours the per-chart
themeoverride. CSS variables are now resolved against the chart's own scope element (get) instead ofComputed Style(svg Element) document.. Because custom properties inherit, a scopeddocument Element --color-(set by* theme={...}) wins, so the export shows the same colours as the live chart. The PNG background colour is resolved the same way.var(--name, fallback)is honoured. The previous regex mis-parsed the name when a fallback was present (var(--color-capturedforeground, #1f2937) color-as the name and left the reference literal). A balanced-paren parser now splits name/fallback correctly and resolves the fallback recursively (foreground, #1f2937 var(--a, var(--b, #fff))).font-var() is resolved too. Axis labels usefamily font-; an unresolvedfamily: var(--font- sans, …) var()there is invalid in a standalone SVG and made text fall back to the default font.font-is now part of the resolved attributes, so the export is fullyfamily var()-free.
Migration#
If you call
resolvedirectly, pass the chart's liveCss Variables <svg>element as the second argument:resolve. There is no compatibility default — the scope element is required (an optional default-to-Css Variables(clone, svg Element) documentwould silently reproduce the bug). Seemigration/.MIGRATION- NOTES. md The high-level export API (
Chart,Handle. export As Png/ export As Svg/ export As Pdf <Chart>'s export) is unchanged — no consumer action needed there.
Notes#
All other behaviour is unchanged. The live-chart rendering and the server-side
pdf-route (which screenshots the live DOM and never used this resolver) were already correct.worker
[3.2.0] - 2026-05-31#
Axis tick labels and cartesian data labels now use accurate canvas-backed text
measurement: long custom-formatted axis labels no longer overlap, the
first/last tick label no longer clips at the plot edge (AC-V5), and data-label
collision hiding is based on real label widths instead of a 0.6×font
estimate (AC-B3). See
ADR-021.
Fixed#
Axis tick-label overlap (AC-V5a). On number/log/time x-axes with
autooff, the tick count came from aRotate width/density heuristic that ignored label width, so long custom-formatted labels overlapped. Tick labels are now thinned by measured width: overlapping labels are dropped, the two domain endpoints are always kept, and the thinning is anchor-aware (a start/end-anchored edge label reserves its true horizontal extent). Short, well-spaced labels and truncating axes (category/ordinal-time) are unchanged.70 First/last tick-label edge-clip (AC-V5d). The outermost tick labels were
middle-anchored and overflowed the plot edge by half their width. The leftmost tick is nowstart-anchored and the rightmostend-anchored (by pixel position, so reversed axes work). Gated to non-truncating axes, so category/ordinal-time labels keep theirmiddleanchor.Data-label collision accuracy (AC-B3).
estimate(bar, line, scatter, histogram, waterfall data labels) now measures label width with the shared canvas measurer instead ofLabel Rect 0.6×font, so labels are hidden/shown based on their real rendered width.Size
Changed#
New internal
utils/— one canvas-backedmeasure- text. ts measure(with a character-count SSR fallback) now serves both the axis label layout and the data-label collision.Text Width axis.'s former private measurer was promoted into it; no public API changed.tsx
Notes#
Minor (not major): no public API/prop shape changed, and the axis/label behaviour only changes where labels actually overlapped or clipped before. Positive-path layouts (short labels, truncating axes, pie/gauge) are unchanged. The bump above patch reflects the consumer-visible layout improvement.
Deliberately narrow: the
0.58-ratioutils/(pie/gauge/funnel/nightingale/radial-bar/bullet/linear-gauge) and thetext- measurement. ts legend-canvas measurer were left untouched. Broad measurement consolidation is tracked separately.export. ts
[3.1.0] - 2026-05-31#
Mixed-sign / negative values in stacked bar charts now render correctly:
positive segments stack up from zero, negative segments stack down from zero
(AC-A1). Adds an absolute-sum percentage-normalisation (P1) so
stacked works for mixed-sign data. See
ADR-020.
Added#
Mixed-sign
stacked(P1 / absolute-sum). When aPercentage stackedstack contains a negative value, each segment renders atPercentage |value| /(keeping its sign for direction) and the value-axis is pinned to a symmetricΣ|value| × stack Total [-stack. All-positiveTotal, stack Total] stackeddata is unchanged — it still uses the consumer-pre-normalised contract (the row-sum gap stays by-design). General auto-normalisation of all-positive raw values remains future work (AC-B2), which will reuse this helper rather than add a second implementation.Percentage
Fixed#
Stacked bar domain is mixed-sign correct.
compute's stacked branch previously pinnedNumeric Domain minto0and used per-row total sums, so negative segments fell outside the domain. It now derivesmaxfrom the per-row positive-sum andminfrom the per-row negative-sum (zero-line always in-domain). An all-negative stack now clampsmaxto0and renders downward instead of blank/clipped.Stacked bar baseline accumulates by sign.
computenow keeps independent positive (up) and negative (down) running totals, so a negative segment stacks below zero on top of preceding negatives instead of being painted above the zero-line. Positive-only stacks are byte-identical to before.Stack Baseline Map Negative-bar data-labels. An
outsidedata-label on a negative bar now hangs below the bar's value-end (with bottom-edge auto-flip toinside) instead of floating above the zero-line detached from the bar. Positive bars are unchanged.
Notes#
This is a minor (not major) release: no public API/prop shape changed and all positive-only stacked rendering is pixel-identical. The bump above patch reflects newly-defined, consumer-visible behaviour for mixed-sign stacks.
Stacked area is out of scope here and still renders overlapping from the plot floor (the domain fix corrects its axis extent only). Real area-stacking is tracked as a new feature in
ROADMAP.§4.md
[3.0.0] - 2026-05-30#
Adds a non-finite-projection guard so a value ≤ 0 on a log axis renders as a
gap instead of a broken Na path. Closes the shared root-cause behind AC-V1
(log-axis NaN coordinates) and AC-V3b (NaN path-tweens). See
ADR-019.
BREAKING CHANGES#
createremoved from the public export surface. Its sole role — theDefined Accessor definedaccessor for gap rendering — is superseded by the projection-awareispredicate returned fromDefined create(Series Accessors {x). This is a breaking change to the public contract (hence the major bump on the v1.0.0 stability promise), even though no consumer imported it.Accessor, y Accessor, is Defined} Internal:
prepare's second parameter is now theMissing Data ispredicate (Defined (d) => boolean) instead ofy. Not part of the public export surface.Key: string
Fixed#
Non-finite projections no longer leak
Na/N ±Infinitycoordinates.callnow returnsScale undefinedfor a non-finite scale result (e.g. a value≤ 0on ascale), honouring the already-documented "undefined-on-rejection" contract of the projection helpers. Previously theLog Napassed theN typeof === 'number'check and the?? 0fallback did not catch it, producing a broken line/area path and silentNapath-tweens.N Every renderer now treats an unprojectable point as a gap/skip, not as pixel 0. Path series (line, area, range-area) break the path; point/rect/ segment series (scatter, box-plot, bar error-bars, range-bar, dumbbell, lollipop, histogram, waterfall) and the overlays (annotations, cross-lines) omit the element. The hit-test (Voronoi) already skipped unprojectable points via its
== nullguard and is fixed by the central guard alone.
Added#
Dev-only diagnostic. A deduped
console.(per axis-type + value) fires in development when a finite numeric value projects to a non-finite result (e.g.warn ≤ 0on a log axis), making the otherwise-silent gap diagnosable. Production rendering is unchanged.
Migration#
If you imported
create, switch to theDefined Accessor ispredicate fromDefined create. (No datyx or geneax consumer imported it; this is contract-hygiene, not a code migration.)Series Accessors(x Key, y Key, project X, project Y) No runtime config change is required. Charts on a log axis that previously rendered a broken path through a
≤ 0value now show a clean gap there.
[2.0.0] - 2026-05-29#
First major since the 2026-05-18 baseline reset. Makes the x-axis tick
formatter value type honest per wrapper, so the date-on-category trap is a
compile-time error instead of a silent runtime invalid-date label.
BREAKING CHANGES#
Axis-type-honest formatter typing. Each Cartesian wrapper now narrows the tick
formattervalue to its default scale:stringfor category-default wrappers (Bar,Chart Waterfall,Chart Box,Plot Chart Range,Bar Chart Dumbbell,Chart Lollipop),Chart Date | stringfor time-default wrappers (Line,Chart Area,Chart Range,Area Chart Candlestick,Chart Ohlc), andChart numberfor number-default wrappers (Scatter,Chart Histogram). Consequence:formaton a (default) bar chart no longer compiles, becauseDate(v as Date, ...) visstring. Fix by pre-formatting a label and using the category axis, or by passing an explicit timetype. SeeMIGRATION-.NOTES. md Dumbbell/Lollipop
xis now optional. Both default toAxis categoryat the type level, resolving the prior "explicitxrequired" runtime dissonance.Axis The internal
Cartesian/XKey Discriminator Cartesian/Time Like Axis Cartesianhelpers are replaced byCategory Axis Cartesian/XKey Category Default Cartesian/XKey Time Default Cartesian(and a narrowedXKey Number Default Cartesian). These were not part of the public export surface.XKey Time Only
Added#
Dev-only runtime guard: a single
console.per dataset when aerror Datevalue reaches a category (scale) x-axis, catching type bypasses (Band as/any/ untyped data). Production rendering unchanged.New per-axis-type config types
Category,Axis Config Time,Axis Config Number.Axis Config
Notes#
The narrowing is uniform per wrapper, not per branch: opting into a non-default scale via an explicit
typedoes not re-narrow the formatter value. This is a deliberate trade-off to keep(v) => ...inferable without an explicittype(per-branch narrowing makes TypeScript fall back toimplicit any). Functionally safe:formataccepts both strings and dates. SeeDate DECISIONS..md
[1.8.1] — 2026-05-29#
Documentation#
QR-4 — date-on-category-axis guidance. Documents the silent invalid-date trap (a
DatexKey on acategory/scaleaxis is stringified to a non-ISO string, so aBand formatformatter rendersDate(v as Date, …) common.as the literal tick label). No source/type change — the runtime behaviour is unchanged; this release makes the behaviour discoverable.placeholder. invalid Date API.§9.1 — new Default x-axis per chart table (bar-class charts default tomd category), a formattervalueruntime type per axis type table, and a warning block with the two correct patterns.New recipe page
recipes/— monthly bars, daily lines, range-comparison snippets.DATE- AXES. md DECISIONS.— QR-4 (e) recorded as a deliberate non-implementation: a per-axis-type strictmd formatter-narrowing onAxiswould be a breaking change to the public x-axis discriminator (type-level time-like default vs. runtime category default on bar-class wrappers). Continues the v1.8.0 decision to keep the wideConfig Axisdefault.Tick Value
[1.8.0] — 2026-05-26#
Added#
Typed axis-formatter signature (QR-4).
AxisandConfig. formatter Axisnow acceptOptions. label. formatter (value: Axisinstead ofTick Value, ...) => string (value: unknown, ...) => string.Axisis the unionTick Value Date | string | number— an honest representation of what the runtime can produce across all scale types. Newly exported:Axis— base union of all possible tick-value runtime types.Tick Value Time(Axis Tick Value Date | string) — for'time'/'ordinal-.time' Number(Axis Tick Value number) — for'number'/'log'/'sqrt'.Category(Axis Tick Value string) — for'category'.Axis— generic formatter-signature alias.Formatter<V extends Axis Tick Value = Axis Tick Value>
Consumers can opt into stricter typing by using
Axisetc. when declaring their formatter. The default (without a type argument) stays permissive for all existing call sites.Formatter<Time Axis Tick Value> Background: during the datyx-v3 FormSection migration two consumer-side bugs surfaced (
Number(v)on an ISO string from ordinal-time,(v as Date).on a string). The oldget UTCMonth() unknownsignature gave no hint about what consumers could expect at runtime. Full rationale inDONE.§2026-05-26.md
Notes#
Non-breaking for TypeScript consumers:
unknownis contravariant-assignable toAxis. Concrete narrowing casts such asTick Value v as Datekeep working (Date ∈ union).Internal axis-rendering call sites (
x-,axis. tsx y-) were adjusted with explicitaxis. tsx as Axiscasts. The runtime flow is identical — the cast tells TypeScript that the d3-axis callback payload fits within the typed signature.Tick Value
[1.7.5] — 2026-05-22#
Performance#
TR-9 —
Chartreferential stability. Before this change,Geometry Context <Chart>'susearoundMemo buildproduced a newChart Geometry Chartinstance on every render whenever one of its deps (Geometry options,x,Scale y,Scales layout) was referentially unstable — which, under real-world consumer-sidedatainstability, cascades throughuse. Consequence: allScales useconsumers re-rendered, andChart Geometry() React.on the 13 series renderers +memo <Svgwas effectively inactive. Now the geometry bundle is reused through a structural-equality wrapper as soon as its contents are identical to the previous instance — the provider value stays reference-equal, memoized consumers do not re-render. A stability test (S1: unstable upstream + identical contents) proves N → 1 renders per unrelated parent toggle.Renderer>
Added (internal)#
useinStable Chart Geometry src/. A wrapper aroundhooks/ use- chart- geometry/ use- chart- geometry. tsx buildthat compares the geometry bundle field by field with the previous instance and reuses the previous instance when the contents are equal.Chart Geometry <Chart>consumes it instead of a directuse.Memo(build Chart Geometry, [...]) buildstays pure — the stabilisation lives in the wrapper layer. Not exported fromChart Geometry src/(ADR-009 narrow discipline respected).index. ts Type-aware scale-contents compare.
iscomparesScale Content Equal scale.+domain() scale.element-wise. A naiverange() Object.compare would fail under upstream instability becauseis(a. scale, b. scale) useproduces new d3-scale instances on every data-ref change. Element comparison viaScales ishandles theScale Element Equal scaleedge case (Date instances compared viaTime get); linear/log/sqrt/band/point/ordinal work withTime() Object.on number/string elements.is Safety rail —
STABILISE_. For pathologically large axis sets (>8 y-axes) the compare-cost-versus-re-render-cost trade-off no longer holds; the wrapper then falls back to a plain rebuild without compare. No current chart type reaches this threshold; the rail is a safety valve for future callers.MAX_ AXES = 8
Changed (internal)#
<Chart>usesuseinstead of an inlineStable Chart Geometry use. One-line replacement plus import update.Memo(build Chart Geometry, [options, x Scale, y Scales, layout]) Stability test (
src/) — 3 new tests that verify the invariant:hooks/ use- chart- geometry/ use- chart- geometry- stability. test. tsx S1 — unrelated parent re-render with identical-content inputs → 1 render in total (no re-renders through a memoized consumer).
S3 (regression guard) — an actual data change → the consumer does re-render (correct behaviour, no stale-data regression).
S1 variant — with a memoized upstream the fix also stays stable (no regression versus the previous useMemo chain).
ADR-009 supplemented (not superseded) with the new invariant: "provider-value contents-equal ⇒ instance-equal" — see
DECISIONS.§ ADR-009.md
Notes — what surfaced during execution#
Analysis gap: the TR-9 analysis document (removed after completion) §3.1 noted "scale → compare via Object.is, stable as long as useScales is stable upstream". During execution it turned out that S1 (consumer-side
datainstability) cascades precisely throughuseinto scale-ref instability — that was the source of the S1-red-after-fix finding. Walt's stop-moment discipline (report, do not force) led to option (i) — extendingScales iswith a scale-contents compare — without scope creep beyond TR-9.Axis Context Equal The stability test also uncovered a test-design bug:
Consumerwas initially not wrapped inSpy React., so the measuring instrument itself re-rendered on parent renders regardless of context. Withmemo memoaroundConsumerthe test replicates the production pattern exactly (all 13 production series renderers areSpy memo'd) and measures what we want to measure.Compare cost in practice: for typical charts (1-2 y-axes) ~15-25 primitive
Object.ops + 4-8 element compares per scale. Negligible compared with the re-render work it saves.is
Verification#
pnpm --filter @workspace/— 0 errorsactive- charts typecheck pnpm --filter @workspace/— 2109/2109 green (was 2106; +3 stability tests)active- charts test pnpm --filter @workspace/— 18 warnings (same baseline; motivatedactive- charts lint eslint-arounddisable react- hooks/ refs previousaccess inside useMemo per the React docs)Ref. current pnpm typecheck(monorepo) — 38/38 tasks greengrep "✅" docs/— 0 hitsROADMAP. md
This fully closes Phase 3 (Performance) of the Traycer v2 follow-up. ROADMAP §1 (Traycer v2 follow-up) is empty after this release.
[1.7.4] — 2026-05-22#
Performance#
TR-8 —
computeout of the tooltip hot path. PreviouslyWaterfall Positions getcalledTooltip Extras computeon every hover event on a Waterfall chart; that function recomputes the cumulative-position array in O(n) over all steps. With fast mouse movement over large waterfalls (>100 steps) this recomputed on every mousemove frame — wasted work, because the positions are identical between hover events. Now the positions are pre-computed once per chart render inWaterfall Positions(all Data, ...) <Chart>via auseoverMemo [options.;series, prepared Data] getdoes an O(1) array lookup. Pure performance refactor — tooltip content pixel-identical.Tooltip Extras
Changed (internal)#
getsignature: the 3rd parameter changes fromTooltip Extras alltoData?: readonly Chart Datum[] waterfall. The caller passes pre-computed positions for the active series instead of raw data. Per §7.14 no dual API is kept (no fallback compute path).Positions?: readonly Waterfall Position[] useexportsChart Tooltip build+ theWaterfall Positions By Series Waterfalltype.Positions By Series <Chart>consumes the helper in ause; the result is aMemo Map<seriespassed as the 6th optional argument toIndex, Waterfall Position[]> handleand as the 5th optional argument toShow Tooltip handle.Pin Tooltip builddoes the per-series lookup viaTooltip Data point..series Index Scope boundary held. Only the tooltip call site in
series-moves; the other two production callers oftooltip- extras. ts compute(Waterfall Positions hooks/for domain computation,use- scales/ use- scales. ts core/for hit-testing) are untouched. Both run per render, not per mousemove — no hot-path problem. The parked "series- plugins/ waterfall- plugin. tsx compute3-call-site duplication" observation inWaterfall Positions DECISIONS.has been updated: no longer "3 call sites with a tooltip hot-path concern", but "2 call sites, both per render, no performance bottleneck".md
Test suite#
2 waterfall tests in
series-migrated to the new signature — the caller now builds the pre-computedtooltip- extras. test. ts computearray and passes it toWaterfall Positions(...) get. Assertions identical in substance (same value/cumulative/isTotal expectations). Test name "allData-context" → "pre-computed positions" for readability.Tooltip Extras 2106/2106 green on identical assertions; no test added or removed.
Verification#
pnpm --filter @workspace/— 0 errorsactive- charts typecheck pnpm --filter @workspace/— 2106/2106 greenactive- charts test pnpm --filter @workspace/— 18 warnings (same baseline)active- charts lint pnpm typecheck(monorepo) — 38/38 tasks green — no external consumer affectedgrep "✅" docs/— 0 hitsROADMAP. md
Phase 3 of the Traycer v2 follow-up is now partly closed (TR-8 done; TR-9 Chart Map reference-equality remains open — requires an analysis phase).
[1.7.3] — 2026-05-22#
Fixed (consumer-visible behavior)#
The tooltip stays inside the chart card and follows the cursor — three coupled fixes. v1.7.1's cursor-follow contract (ADR-016) let the tooltip visually exceed the chart area in order to stay close to the cursor; the viewport clamp caught the viewport edge. Visual verification revealed three problems that had to be addressed together:
Bug A — Cartesian margin-offset blind spot in the projection.
usecomputedTooltip Position projfor the viewport clamp. For Cartesian charts (chart.tsx viaVp X = container Rect. left + clamped X + x Off Tooltip),Positioner + marginis added to the rendered position after the hook. The projection missed that translation → the viewport clamp underestimated the actual tooltip position byLeft marginpixels → tooltip overflow on cards at the viewport edge. Present in limited form before v1.7.1 (the same blind spot in theLeft parentversion), masked by the bounds flip; fully visible after v1.7.1.Element Bug B — no flip at the card boundary. v1.7.1's "always follow the cursor, the viewport is the only boundary" rule lets the tooltip exceed the chart card. Walt's requirement: the tooltip must NEVER fall outside the chart card. v1.7.3 introduces the card-boundary flip: when the tooltip would exceed the container bounds on its current side AND the other side does fit, it flips to the other side of the cursor (stays with the cursor + inside the card). Conceptually a different mechanism from the bounds flip removed in ADR-016: that one triggered at the plot boundary (smaller than the card) and pulled the tooltip away even where there was room; this one triggers only at the card boundary and flips to follow the cursor on the other side, not away from it.
Bug C — first-hover measurement on an empty div. The
<Tooltipbetween the TooltipPositioner div and the content delays children mounting by one render (FadeWrapper'sFade Wrapper> mountedstate startsfalseand flips via useEffect). The hook's firstgettherefore read 0×0;Bounding Client Rect setwas called anyway → the tooltip gotMeasured(true) visibility:visibleat the default offset before the card/viewport clamp could fire. Subsequent re-measures (on mousemove) did read the real size and corrected it — hence "first hover wrong, subsequent correct". v1.7.3 skips zero-rect measurements and uses aResizeon the tooltip div to re-measure when the size transitions from 0 to real.Observer
Priority rule for a tight card: when the tooltip fits inside the card on neither side (tooltip larger than the card), cursor-follow wins over card bounds — the tooltip stays with the cursor and may stick out slightly beyond the card. The viewport boundary is the absolute hard limit that is never exceeded. Three levels: cursor → card (may be given up) → viewport (never given up).
See ADR-017 for the three coupled decisions and the cross-link to ADR-016 (why the plot-boundary flip had to go then, and why the card-boundary flip is added now — a different mechanism, a different trigger, preserving cursor-follow where possible).
Changed (internal)#
useoptions: newTooltip Position origin+X?: number originfields (defaultY?: number 0). Cartesian'sTooltip(inPositioner chart.) passestsx originso that the hook's card and viewport projection accounts for the post-hook coordinate-system translation. 11 standalone call sites unchanged (default 0 suffices).X: margin Left, origin Y: margin Top Test suite — 2 v1.7.1 tests removed, not reshaped (§7.14 applied consistently: tests that assert removed functionality are deleted, not bent):
"does not flip when the cursor is near the right edge of bounds"— asserted v1.7.1's "always follow the cursor up to the viewport boundary", replaced by v1.7.3's card-boundary flip."does not flip when the cursor is near the bottom edge of bounds"— same reason, Y-axis variant.
6 new tests added: card flip on X, card flip on Y, cursor wins on a tight card, originX/Y in the projection (Cartesian scenario), ResizeObserver re-measure (Bug C reproduction), plus the viewport hard-limit test kept with an adjusted rationale. ResizeObserver is mocked in tests (jsdom does not have it).
[1.7.2] — 2026-05-22#
Changed (internal)#
TR-2 —
useconsumed by GaugeChart + TimelineChart. Both charts held the same inlineAnimation Complete Timer use+Effect setblock that v1.6.2 already extracted intoTimeout usefor PieChart + HeatmapChart. Drift-fix per Traycer v2 §11 R-2: the shared hook is now consumed by 4 standalone wrappers. Behaviour identical (same setTimeout duration, same cleanup, same callback-invocation underAnimation Complete Timer !enabled).TR-4 —
normalizeparameter removed. The unusedParent Level Option(_ axis Type) _parameter onaxis Type: Axis Type | undefined normalizewas retained per a JSDoc comment "for API stability and future axis-specific defaults" — but no concrete axis-specific default was planned. Removed per Traycer v2 §11 R-4. Three call-sites (Parent Level Option x-,axis. tsx compute-×2,layout. ts use-) adapted in the same change;auto- axis- formatters. ts Axisimport dropped fromType utils/(no longer referenced after the parameter removal).normalize- options. ts TR-5 — internal-only symbols dropped from public barrel. Removed from
src/andindex. ts src/:types/ index. ts ANIMATION_,TYPE_ WHITELIST EXIT_,ANIMATION_ TYPES resolve,Animation Type For Series call,Scale pack,Bar Positions unpack. All six are still available as direct imports from their source files (Bar Positions types/,animation- types. ts types/,scales. ts hooks/) for in-package consumers; only the barrel surface is reduced. Per Traycer v2 §11 R-6: these are internal implementation details (per-series animation constraints, D3-scale polymorphism escape-hatch, animation-transition packing) that external consumers should not depend on. Two in-package callsites (use- data- transition/ use- data- transition. ts svg-,renderer. tsx series-) migrated to direct imports.accessors. test. ts
Removed#
TR-1 —
src/dead folder deleted. The stub file (hooks/ use- dark- mode/ export {};+ a comment saying "No consumers any more. This file + theuse-folder can go") had been left behind since v1.6.2'sdark- mode/ useremoval. No consumers; physically removed per Traycer v2 §11 R-1.Dark Mode
Documentation#
TR-3 — README de-Geneax-genericised.
"Enterprise chart library for the Geneax platform."→"Enterprise chart library."; the package-structure section'sthemes/→# Geneax chart theme themes/(post-MP-9 rename, drift). Per Traycer v2 §11 R-3.# ACTIVE_ CHARTS_ THEME TR-6 —
core/added to README package-structure overview. The T2+T9 plug-in system was a major architectural feature (13 Cartesian plug-ins +series- plugins/ _helpers + registry) but absent from the README's structure table since its introduction. Per Traycer v2 §11 R-7.shared TR-7 verified, no action. Traycer v2 §11 R-7 flagged
colors-prop JSDoc-parity on Scalar charts — re-verification showed LinearGauge (lines 50–55) and BulletChart (lines 54–57) already carry the equivalent JSDoc note that GaugeChart (lines 50–55) carries. Audit drift; no change needed.
Notes#
This release closes Phase 2 (Discipline-Slip Detection) of the post-maturity roadmap from docs/ (removed after completion). All six hygiene items + the audit-drift verification were bundled in one session per the original effort estimate (~1.5 hours). Test-suite 2103/2103 green on identical assertions, no test adapted. Lint baseline unchanged (18 warnings, well within the 100-cap). Monorepo-wide typecheck 38/38 tasks green — no consumer in apps/ reaches into the removed barrel symbols. Phase 3 performance items (TR-8 memoize compute in tooltip path, TR-9 Chart Map reference-equality) remain open on the ROADMAP.
[1.7.1] — 2026-05-22#
Fixed (consumer-visible behavior)#
The tooltip now consistently follows the cursor on all 24 chart types. Before v1.7.1 (ADR-014 default #2: "edge-clamp left/top: always on") the tooltip flipped to the other side of the cursor as soon as its projected position would fall outside the chart bounds. On edge-to-edge chart types (treemap, funnel, sunburst, nightingale, radial-bar, sankey, heatmap), where the cursor is regularly close to the bounds, that constantly broke the cursor-follow agreement — treemap drill-down was visually always broken because of it, funnel intermittently. On Cartesian types (bar/line/area/scatter/etc.) the same happened only rarely because the axis margins keep the cursor away from the bounds edge: the same rule violation, just triggered less often. The bounds flip + bounds edge clamp has been removed; the tooltip now follows one rule — follow the cursor, the viewport clamp keeps it on screen. See ADR-016.
The viewport clamp now uses the correct positioning ancestor as its anchor. Before v1.7.1 the QR-7 viewport clamp read
el.— which resolved to theparent Element. get Bounding Client Rect() <Tooltipdiv (a 0×0 box that sits below the chart SVG in document flow), not to the actual positioned ancestor. The resulting viewport projection was offset by roughly the chart height from the actual tooltip position; viewport overflow was wrongly detected or missed. Masked as long as the bounds flip often kept the tooltip inside the plot area; it would have become visible after removing that flip. The hook now takes a requiredFade Wrapper> containerin its options object — the caller explicitly passes the positioning ancestor the viewport clamp computes against. All 12 call sites migrated in the same session (one API, no fallback path — §7.14). See ADR-016.Ref
Changed (internal)#
usesignature: theTooltip Position optionsparameter is now required, withcontaineras a required field. Before v1.7.1 the whole options object was optional; theRef: Ref Object<HTMLElement | null> anchorflag stays optional within the object. Not publicly exported (internal toClamp @workspace/), so no breaking change for consumers — only package-internal call sites affected.active- charts Test suite — 3 flip-asserting tests removed, not reshaped. They tested functionality that deliberately disappears (per §7.14: do not "rescue" a test by asserting something else when the underlying behaviour has changed). Replaced by three new viewport-clamp tests that verify the cursor-follow agreement + the correct containerRef anchor. Removed tests, for traceability:
"flips to the left when first hover is near the right edge"(wasuse-)tooltip- position. test. tsx:116 "flips up when first hover is near the bottom edge"(wasuse-)tooltip- position. test. tsx:164 "clamps to left edge when mouse is already past the left boundary"(wasuse-)tooltip- position. test. tsx:188
[1.7.0] — 2026-05-22#
Changed (consumer-visible behavior)#
QR-5 (revised) — Path-C rect-bounded hit-test for Histogram + Waterfall. Before v1.7.0 both types fell back to a voronoi-only hit-test (Path B), which is less precise for rect-shaped elements — a hit now registers anywhere within the whole bin rect (Histogram) or step rect (Waterfall) instead of only near the centre. Path B remains additive as a fallback where the rect does not hit (consistent with the bar plugin since T2+T9-v2).
Added (internal)#
compute+Histogram Rects computeinWaterfall Rects src/. Shared rect-geometry helpers for Histogram + Waterfall — one source of rect bounds, shared by the renderer and the Path-C hit-test (the same philosophy as T1-v2'score/ series- plugins/_ shared. ts computefor bar; no drift possible).Bar Geometry compute: inputHistogram Rects {bins, bin→Values, value Axis Domain, project X, project Y} Bar. Math identical to the pre-v1.7.0 inline computation in HistogramSeries:Rect Position[] bar+X = min(x0, x1) bar,Width = abs(x1 - x0) bar+Y = min(value Pos, baseline Pixel) barviaHeight = abs(baseline Pixel - value Pos) resolve.Baseline compute: inputWaterfall Rects {data, positions, x→Key, bandwidth, project X, project Y} Bar. Math identical to the pre-v1.7.0 inline computation in WaterfallSeries:Rect Position[] py+Top = project Y(max(y0,y1)) bar(minimum 1px for zero-value steps).Height = max(abs(py Bottom - py Top), 1)
Changed (internal)#
HistogramSeries uses
computeinstead of an inline computation. The renderer + the histogram plugin's Path C now share one source of bin-rect bounds.Histogram Rects WaterfallSeries uses
computeinstead of an inline computation. The renderer + the waterfall plugin's Path C now share one source of step-rect bounds. (Waterfall Rects py/Top pyfor connector-Y and label-Y are still computed inline fromBottom pos.— those derived positions are not part of the rect bound itself.)y0/ y1 histogram-+plugin. tsx waterfall-now emitplugin. tsx BaralongsideRect[] Flat. Path C is additive — the Path B fallback is unchanged.Point[]
Scope revision (vs the original QR-5)#
The roadmap text before the step-0 inventory suggested "QR-5 rollout to 4 types: RangeBar/Histogram/Waterfall/BoxPlot — mechanical rollout of a proven pattern". The step-0 inventory refuted that assumption:
| Type | Pre-v1.7.0 status | QR-5 outcome |
|---|---|---|
| Histogram | Path B via emit | Path C added via shared helper |
| Waterfall | Path B via emit | Path C added via shared helper |
| RangeBar | No hit-test (deliberate Walt decision T2+T9-v2 §4.2.1: "Gantt-like use does not need flatpoint emission") | Deliberately kept excluded — parked as a loose observation |
| BoxPlot | No hit-test (deliberately no yKey shape; "no yKey; uses min/q1/median/q3/max instead") | Deliberately kept excluded — parked as a loose observation |
Adding Path C to RangeBar/BoxPlot would reopen deliberate earlier exclusions, not tidy things up for external use. Just as the OHLC hit-test asymmetry is parked, RangeBar + BoxPlot deliberately stay without a hit-test. An external consumer does not run into a loose end here — those types work as designed.
compute Waterfall Positions call duplication — an honest note#
The observation parked before v1.7.0 (from T3 phase 2): "three call sites each compute the same running totals". The step-0 analysis showed that this is in fact three callers of one shared function (the function itself has long lived in types/), not three duplicate implementations. A real "3 → 1" consolidation would mean extending the geometry context with pre-computed positions per series (data + cache in build). That is a substantial geometry-shape change outside the QR-5 scope.
What v1.7.0 did do:
Path C in the waterfall plugin uses the same
compute— no new copy. Effectively: 4 callers of 1 function (was 3); no deterioration and no new duplication.Waterfall Positions computeaccepts pre-computedWaterfall Rects positionsas input, so the helper itself does not callcompute— the caller decides whether/how to memoize.Waterfall Positions
The observation stands: a future session can extend the geometry context with pre-computed positions if the performance concern ever becomes demonstrable. Today compute is linear and not expensive enough to justify the scope extension.
Drift test pixel-identical#
Both renderer migrations are pixel-identical for existing behaviour. 2102/2102 tests green on identical assertions, no test adjusted. The deliberate behaviour change is the added Path-C hit-test on Histogram + Waterfall (more precise rect hit detection).
Stop moments — outcome#
Stop moment 1 (rect-geometry helper does not deliver renderer bounds): not triggered.
computeoutput identical to the pre-v1.7.0 HistogramSeries inline computation;Histogram Rects computelikewise.Waterfall Rects Stop moment 2 (
computecall sites diverge after "consolidation"): not triggered in the adjusted sense (no consolidation to 1 caller; see the honest note above).Waterfall Positions
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline; temporarily 19 after theactive- charts lint plotprop became unnecessary through the helper extraction, back to 18 after a destructuring cleanup).Height pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (3 changed + 0 new)#
Changed:
package.— 1.6.3 → 1.7.0json src/—core/ series- plugins/_ shared. ts compute+Histogram Rects compute+Waterfall Rects Histogram+Bin Like WaterfalladdedPosition Like src/— inline computation replaced bycore/ renderer/ series/ histogram- series. tsx compute; unusedHistogram Rects plotprop removed from destructuringHeight src/— inline computation replaced bycore/ renderer/ series/ waterfall- series. tsx computeWaterfall Rects src/— Path-C emit addedcore/ series- plugins/ histogram- plugin. tsx src/— Path-C emit addedcore/ series- plugins/ waterfall- plugin. tsx
Bump choice#
Minor (1.6.3 → 1.7.0) — a consumer-noticeable behaviour improvement (more precise hit detection on rect-shaped Histogram/Waterfall).
Remaining open topics after v1.7.0#
QR-2 — Parity-test category rollout (1 session, requires scope clarification)
QR-3 — Round-4 live smoke check (2-3 sessions)
Section 3 (MP-*) — only relevant for external npm publication
TEST-1 — UPO chart snapshot regression (1 session, not urgent)
Section 5/6 — Future chart types + features (growth)
New + existing parked observations (no open trigger):
RangeBar hit-test absence (deliberate choice T2+T9-v2 §4.2.1) — newly parked
BoxPlot hit-test absence (deliberate choice, no yKey) — newly parked
computecall duplication — stays parked; real consolidation requires a geometry-shape extensionWaterfall Positions OHLC hit-test asymmetry — parked, unchanged
C1 (
Axisextension) — parked, unchangedContext userename — parked, unchangedVoronoi → use Hit Test BarRect axis-id fields — parked, unchanged
[1.6.3] — 2026-05-22#
Added (internal)#
QR-10 remainder —
<SrinOnly Announcement> src/. A shared component for the keyboard-nav screen-reader live region. Before v1.6.3 this was copy-paste in 11 standalone wrappers (a 22-line block per wrapper, ~242 lines in total). After v1.6.3: one component, a 4-line call site per wrapper. API:core/ accessibility/ sr- only- announcement. tsx TSX<SrOnlyAnnouncement testId="pie-kbd-announcement"> {kbd.announcement}</SrOnlyAnnouncement>API shape choice:
testprop + children. All 11 call sites had exactly identical structure (Id role="status"+aria-+live="polite" aria-+ an identical sr-only clip style); the only differences were theatomic="true" data-value and the content expression. Children cleanly cover the dynamic content;testid testcovers the unique per-chart name. Hardcoded a11y attributes + clip style in the component (stop-moment-3 discipline: noId priority/assertiveprops until a consumer needs them).
Changed (internal)#
11 standalone wrappers migrated: HeatmapChart, PieChart, SankeyChart, SunburstChart, TreemapChart, TimelineChart, RadialBarChart, NightingaleChart, RadarChart, FunnelChart, OrgChart. Per wrapper:
22-line inline
<div>block replaced by a 4-line<SrcallOnly Announcement> Import of
Sradded next to the existingOnly Announcement core/importsaccessibility/
Pixel-identical: same a11y attributes (
role="status",aria-,live="polite" aria-), same sr-only clip style, sameatomic="true" data-values, same content. 2102/2102 tests green, no test adjusted.testid
Code size#
| Location | Pre-v1.6.3 | Post-v1.6.3 |
|---|---|---|
| 11 wrappers × inline 22-line block | ~242 lines copy-paste | 11 wrappers × 4-line call (~44 lines) |
sr- (new) | — | ~55 lines (incl. JSDoc) |
Net duplication reduction: ~242 lines of copy-paste → 55 lines of shared component + 44 lines of call sites = ~99 lines. Saving ~143 lines, plus a single source of truth for the a11y pattern.
Stop moment — outcome#
Stop moment (call sites split between API shapes, or the extraction changes sr-only output): not triggered. All 11 call sites were fully identical in structure and content pattern; test prop + children covered them 1-to-1 without any forcing.
QR-10 fully closed#
QR-10 had three sub-triggers:
use✓ (v1.6.2)Animation Complete Timer use— reconsidered + removed (v1.6.2); the inlineDark Mode isfallback was cleaned up as wellDark <Sr✓ (v1.6.3, this entry)Only Announcement>
No open QR-10 sub-triggers remain.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions, no test adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (12 changed + 1 new)#
New:
src/core/ accessibility/ sr- only- announcement. tsx
Changed:
package.— 1.6.2 → 1.6.3json 11 standalone wrappers: heatmap-chart, pie-chart, sankey-chart, sunburst-chart, treemap-chart, timeline-chart, radial-bar-chart, nightingale-chart, radar-chart, funnel-chart, org-chart (each: import added + 22-line block replaced by a 4-line call)
Remaining open topics#
QR-2 — Parity-test category rollout (1 session, requires scope clarification)
QR-3 — Round-4 live smoke check (2-3 sessions, browser verification)
QR-5 — Path-C rect hit-test rollout to RangeBar/Histogram/Waterfall/BoxPlot (1-2 sessions)
Section 3 (MP-*) — only relevant for external npm publication
TEST-1 — UPO chart snapshot regression (1 session, not urgent)
Section 5/6 — Future chart types + features (growth on request)
Plus parked observations (C1, OHLC hit-test asymmetry, use rename, compute 3-call-site duplication, BarRect axis-id fields).
[1.6.2] — 2026-05-22#
Added (internal)#
QR-10 —
useinAnimation Complete Timer src/. A shared hook that absorbs thehooks/ use- animation- complete- timer/ use- animation- complete- timer. ts onpattern: either fire immediately (animation disabled) or after aAnimation Complete?.({ type: "load" }) setmatched to the keyframe duration, with cleanup. Before v1.6.2 this lived as an identicalTimeout useblock inEffect HeatmapandChart Pie. The hook respects the pre-v1.6.2 stable-callback convention (Chart onnot in the deps — the caller passes a stable reference).Complete
Removed (internal)#
Inline
isdetection +Dark usefallback in HeatmapChart. Before v1.6.2 HeatmapChart had its ownDark Mode use+State usewithEffect Mutation+Observer matchto detect dark mode, plus a hardcodedMedia _/DARK _colour pair as a fallback whenLIGHT var(--color-did not resolve.chart- heatmap- min/ max) Reconsidered during the v1.6.2 session: HeatmapChart was the only consumer of that detection, and the fallback branch was never reached within Geneax (Tailwind v4 + shadcn tokens always resolve the variables). The inline detection existed strictly for standalone consumers without Geneax CSS — a scenario that MP-8 (
default-in ROADMAP §3) is supposed to solve at package level.colors. css Removed:
isstate +Dark usewithEffect Mutation/Observer matchinMedia HeatmapChart isprop onDark HeatmapandChart Inner Props HeatmapSeries Props Two
ispass-through locationsDark={is Dark} DEFAULT_constant inCOLOR_ RANGE_ DARK heatmap-(series. tsx _remains as the only sensible default without a dark/light condition)LIGHT The
isfallback branch inDark ? [...] : [...] resolvedColor Range
HeatmapChart now relies entirely on CSS variables, consistent with all 23 other chart types.
usehook stub (Dark Mode src/) — introduced earlier in this session as a duplication-reduction extract, but the reconsideration showed that HeatmapChart was the only consumer. The file has been reduced to a stub (hooks/ use- dark- mode/ use- dark- mode. ts export {}) with a deprecation comment; the folder + file may be physically removed by Walt.
Changed (internal)#
HeatmapChart + PieChart consume
useinstead of inlineAnimation Complete Timer useblocks. Identical behaviour (drift test pixel-identical: 2102/2102 tests green, no test adjusted). Two deadEffect useimports cleaned up.Effect
Documentation — ROADMAP §2 table drift fix#
QR-7 struck through in the §2 table — handled in v1.5.0 (T4), but the table still said "open". Status drift repaired.
QR-5 note extended: the status after T2+T9-v2 made explicit. The 4 non-bar rect types (RangeBar, Histogram, Waterfall, BoxPlot) do go through the capability pattern but emit no
bar— Path C stays bar-only. The actual rollout fell outside the T2+T9-v2 scope; QR-5 remains an open mini-workstream (no autonomous scope).Rects
Code size#
| Location | Pre-v1.6.2 | Post-v1.6.2 |
|---|---|---|
HeatmapChart use block for on | ~10 lines | 6 lines (use call) |
| PieChart likewise | ~10 lines | 6 lines |
| HeatmapChart inline dark-mode detection + fallback | ~30 lines (use+use+fallback branch) | gone |
HeatmapSeries dark fallback (DEFAULT_ + is choice) | ~6 lines | gone |
use- (new) | — | ~35 lines |
Net duplication reduction:
on: 2 inline implementations → 1 shared hookAnimation Complete is: 30+6 lines of detection+fallback gone, no replacement — the standalone concern moves to MP-8 (Dark default-), where it belongs. HeatmapChart's handling of theme awareness is now consistent with the other 23 chart types.colors. css
Pixel-identical#
Both extractions are purely mechanical refactors without behaviour change. 2102/2102 tests green on identical assertions, no test adjusted. Lint back to 18 warnings (same baseline as before T6.2).
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (6 changed + 1 new + 1 stub)#
New:
src/— shared hookhooks/ use- animation- complete- timer/ use- animation- complete- timer. ts
Stub (to be removed):
src/— deprecation stub (hooks/ use- dark- mode/ use- dark- mode. ts export {}); the folder + file can go
Changed:
package.— 1.6.1 → 1.6.2json src/—components/ heatmap- chart/ heatmap- chart. tsx useconsumed, deadAnimation Complete Timer useimport cleaned up,Effect isstate +Dark useimport + fallback branch removed,Dark Mode isprop pass-through to inner + series removedDark src/—components/ pie- chart/ pie- chart. tsx useconsumed, deadAnimation Complete Timer useimport cleaned upEffect src/—core/ renderer/ series/ heatmap- series. tsx isprop removed from interface + destructuring;Dark DEFAULT_removed;COLOR_ RANGE_ DARK _renamed toLIGHT DEFAULT_(single pair)COLOR_ RANGE docs/— QR-7 struck through, QR-5 note extendedROADMAP. md
QR-10 — two items completed + one reconsidered + one skipped#
QR-10 contained three items in the roadmap. Outcome of this session:
use— completed (shared hook, two consumers)Animation Complete Timer use— reconsidered during the session: the extraction was not a real duplication reduction (one consumer, one forced fallback route). The inline detection + fallback have been removed entirely; HeatmapChart now relies on CSS variables like the other 23 charts. Standalone concern moved to MP-8.Dark Mode <Sr— skipped (autonomous-session discipline; the component API choice requires Walt's input). Remains open in §2.Only Announcement>
Remaining open topics (after this session)#
QR-2 — Parity-test category rollout (1 session, requires scope clarification)
QR-3 — Round-4 live smoke check (2-3 sessions, browser verification)
QR-5 — Path-C rect hit-test rollout to 4 types (1-2 sessions, stays open)
QR-10 (remainder) —
<Srcomponent (0.5 session, API choice)Only Announcement> Section 3 (MP-*) — only relevant for external npm publication
TEST-1 — UPO chart snapshot regression (1 session, not urgent)
Section 5/6 — Future chart types + features (growth on request)
No architecture debt remains open. All remaining work is opportunistic, conditional, or growth.
[1.6.1] — 2026-05-22#
Changed (internal)#
T7-v3-mini —
Flat/Point. x Axis Id ypopulated in all producer paths. Since T7-v1 these fields have been optional on theAxis Id Flattype, but no producer populated them. T7-v3-mini fills them withPoint geometry.(xAxisId) and the resolved yAxisId (already available locally in each plug-in).axes. x. id The step-0 report corrected the roadmap claim:
5 FlatPoint emit sites, not "10+":
_shared. ts emit— 1 shared emit, covering 6 series types (line, area, scatter, histogram, waterfall, candlestick)Pad BFlat Points bar-— 1 Path-B emitplugin. tsx lollipop-— 1 emitplugin. tsx dumbbell-— 2 emits (low + high)plugin. tsx
No "voronoi fall-through" — T2+T9-v2 (v1.2.0) cleaned that up;
usehas since been a thin dispatch shell that emits only via plug-ins. The roadmap claim "10+ plug-in emit paths + voronoi fall-through" reflected the state before T2+T9-v2.Voronoi
Pixel-identical#
Flat were read by no consumer at all — this is purely additive data. 2102/2102 tests green on identical assertions, no test changed.
Observation outside the T7-v3-mini scope#
Bar (Path C, bar plugin) has no x fields in its type definition — only Flat has them. T7-v3-mini only touches FlatPoint. If future consumers (a multi-axis feature) want to read BarRect's axis id, a type extension is needed. Not critical; parked observation.
Stop moment — outcome#
Stop moment (axis id not cleanly available, or populating it changes behaviour): not triggered. All 5 emit sites already had the yAxisId resolved locally; xAxisId is available everywhere via geometry.; no test broke.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions, no test adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (5 changed)#
Changed:
package.— 1.6.0 → 1.6.1json src/—core/ series- plugins/_ shared. ts emitaddsPad BFlat Points x/Axis Id y(covers 6 series types)Axis Id src/— Path-B flatpointcore/ series- plugins/ bar- plugin. tsx src/core/ series- plugins/ lollipop- plugin. tsx src/— low + high (2 emits)core/ series- plugins/ dumbbell- plugin. tsx
T7 architecturally fully closed#
With T7-v3-mini, T7 is fully complete (all four sub-triggers: v1, v2, v2.1, v3). And with it, section 1 of the roadmap — architecture debt — is complete as a whole:
T7 (chart-context shape): v1.0.4 + v1.1.0 + v1.1.1 + v1.4.0 (Finding A via T3) + v1.6.1 (FlatPoint population)
T2+T9 (SeriesTypePlugin): v1.1.2 + v1.2.0
T1 (baseline + bar geometry): v1.3.0 + v1.3.1
T5 (axis config + use-scales): v1.3.2 + v1.3.3
T3 (tooltip context flow): v1.4.0 + v1.4.1
T4 (tooltip positioning): v1.5.0
T6 (defaults policy): v1.6.0
What remains is not debt#
Loose observations (no open trigger): C1 (AxisContext extension), OHLC hit-test asymmetry,
userename,Voronoi → use Hit Test compute3-call-site duplication, BarRect axis-id fieldsWaterfall Positions QR items: QR-2 (interactivity discoverability), QR-3 (zoom controls), QR-10 (... ) — not debt, opportunistic maintenance
Conditional branches: MP-* topics (npm-publishing prerequisites — only relevant for external release), Future chart types, Future features, TEST-1
Per-package test roadmap: active maintenance, not debt
[1.6.0] — 2026-05-22#
Changed (consumer-visible behavior)#
T6 — tooltip on by default + active point coupled to tooltip-enabled. Two coupled changes that make the hover experience coherent:
normalizeon by default:Tooltip Option undefined → {enabled: true}. Before T6 it wasundefined → undefined(off). Charts without an explicittooltipprop now show a tooltip on hover. Consumers who do not want a tooltip usetooltip={false}.Active point coupled to
is: the active-point circle now renders only when the tooltip is on. Before T6 the circle rendered unconditionally on every voronoi hit; the "floating circle without a tooltip" surprise is resolved.Tooltip Enabled tooltip={false}turns both off; the default config shows both.
Not touched: the other nine
normalize*Optionhelpers. KeyboardNavigation deliberately stays on by default (accessibility priority); DataLabel stays per chart type (the chart wrapper decides); the seven other opt-in features (Legend, Crosshair, Zoom, Navigator, Sync, ContextMenu, ParentLevel) stay opt-in.
Step-0 report — roadmap correction#
The pre-T6 ROADMAP text claimed "five normalize*Option helpers, uniformly off by default". The step-0 inventory showed:
Ten helpers, not five
Three groups (eight truly opt-in, KeyboardNavigation deliberately on by default, DataLabel per chart type)
The "active point without tooltip" surprise was not in a uniform policy but in a specific coupling mismatch in
handle:Hover setwas called unconditionally,Active Point(point) tooltip.conditionallyhandle Show Tooltip
T6 thereby resolves the actual mismatch (the coupling) and delivers the coherent default (tooltip on by default) as one cohesive fix. Walt's Option 1 over Option B (flip only) or Option C (coupling only): the flip and the coupling together give every chart without configuration the complete, expected hover experience.
Migration set#
| Category | Count | Effect | Migration needed? |
|---|---|---|---|
| Production dashboard (no tooltip prop) | 2 (session-, login- in admin) | Default-on tooltip on hover | No — desired behaviour |
| Production explicit (analytics + heatmap) | 4 | No change | n/a |
| Test-chart demos | ~92 chart usages across 3 view files | Most already had an explicit tooltip; charts without one get default-on in a demo context | No — exactly the demo's goal |
| 11 standalone wrappers (internal) | n/a | Flip automatically via normalize | No — automatic |
| Active-charts unit tests | 2102 tests | No test asserted normalize | No — no test adjusted |
No tooltip={false} migrations needed in production code or test charts. The affected set was large (in files) but fully unambiguous (all affected charts want default-on).
Stop moments — outcome#
Stop moment 1 (migration set large or ambiguous): not triggered. The set is large but not ambiguous — all affected categories want default-on.
Stop moment 2 (default-on tooltip renders meaninglessly): not triggered. All affected charts (2 dashboard charts + test-chart demos) render meaningful default tooltips.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions. No test adjusted for the flip.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (2 changed)#
Changed:
package.— 1.5.0 → 1.6.0json src/—utils/ normalize- options. ts normalize:Tooltip Option undefined → {enabled: true}(wasundefined)src/—components/ chart/ chart. tsx handle:Hover setplaced under theActive Point(point) iscondition; the active-point circle's render condition extended withTooltip Enabled isTooltip Enabled &&
T6 architecturally closed#
No open T6 sub-triggers remain. Roadmap text corrected to the actual state (ten helpers, three groups, the coupling finding).
Remaining open T topics#
T7-v3-mini — populate FlatPoint.xAxisId/yAxisId (~1 session, mechanical, not critical)
Loose observations: OHLC hit-test asymmetry, use rename, compute 3-call-site duplication. C1 parked.
All significant T topics completed (T7-v1+v2+v2.1 + T2+T9 + T1 + T5 + T3 + T4 + T6). Only the mechanical mini-trigger T7-v3 remains.
[1.5.0] — 2026-05-22#
Changed (consumer-visible behavior)#
T4 — tooltip positioning unified into one hook (
useinTooltip Position src/). Before T4 tooltip positioning lived in two parallel implementations: thehooks/ use- tooltip- position/ Tooltipcomponent inPositioner chart.(~86 lines, for 13 Cartesian types viatsx <Chart>) and the standaloneusehook (67 lines, for 11 non-Cartesian types). After T4: one hook with anTooltip Position anchoroption absorbs the deliberate differences;Clamp?: boolean Tooltipstays inPositioner chart.as an internal thin wrapper (~25 lines) that uses the hook internally for rendering properties (z-index, pointer-events, position:absolute). Not exported — an implementation detail oftsx <Chart>(U1 shape — seedocs/§ ADR-014).DECISIONS. md Three consumer-visible behaviour changes (Walt's UX choices, §8):
Default Y offset unified to
+TOOLTIP_(tooltip below the mouse) for all 24 chart types. Before T4 the 11 standalones hadGAP -TOOLTIP_(tooltip above the mouse); they now move to the Cartesian default. The 13 Cartesian charts are unchanged.GAP Left/top edge clamp on by default for all 24 chart types. Before T4 only the standalone hook had this clamp; the 13 Cartesian charts via
<Chart>could let tooltips stick out of their plot bounds. Latent bug fixed — no functional loss, only preventing off-bounds rendering.QR-7 — viewport edge clamp on by default for all 24 chart types. When hovering close to the viewport edge, the tooltip is systematically pushed back into
[0, window.. Before T4 this clamp was missing in both implementations; off-screen tooltips with narrow viewports or charts close to the viewport boundary. QR-7 handled.inner Width/ Height]
Preserved (deliberate differences — not unified away)#
Finding B anchor clamp (Cartesian only) — Path-C off-plot anchor protection from v9.0.0. Preserved via the
anchoroption thatClamp: true <Chart>'s wrapper passes to the hook. Standalones leave the option off by default (no Path-C equivalent).Coordinate system — Cartesian computes in plot coordinates (after the margin offset in the wrapper), standalones in container coordinates. Preserved — no unification.
Bounding area —
plotfor Cartesian via the wrapper,Width/ Height containerfor standalones. Preserved.Width/ Height Rendering properties (z-index, pointer-events, absolute positioning) — live in the
Tooltipwrapper; standalones render themselves with direct control. Preserved.Positioner
Drift test pixel-identical for the deliberately preserved differences#
Stop moment 1 (Finding B does not absorb cleanly): not triggered. The anchor option absorbs the Path-C protection cleanly without shape strain; the hook is unaware of the concept and applies the clamp only when explicitly asked.
Stop moment 2 (behaviour change outside the regression checklist): not triggered. All behaviour changes fell within the §9 checklist (default Y-offset shift for standalones; edge clamp added for Cartesian; viewport clamp added for all 24).
Existing tests stayed green on identical assertions — 2102/2102. The 5 existing use tests test flip-left, edge clamp, and flip-up; all four keep working under the new default Y offset because the flip condition (anchor + GAP + size > bounds) is symmetric for a +GAP/-GAP default — the flip outcome (tooltip on the other side of the mouse) is the same.
Code size#
| Location | Pre-T4 | Post-T4 |
|---|---|---|
chart. TooltipPositioner | ~86 lines (own flip logic, anchor clamp, useLayoutEffect, useState) | ~25 lines (thin wrapper, hook call + ref merge + style) |
use- | 67 lines (4-arg signature) | ~115 lines (5-arg with options + Finding B + QR-7 viewport clamp) |
| Total positioning layer | ~153 lines in two implementations | ~140 lines in one implementation + one thin wrapper |
Plus 11 standalone wrappers: no call-site changes — the 4-arg API is backwards-compatible (no options = anchor default).
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green on identical assertions; no test adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (1 central change + 1 consumer change)#
Changed:
package.— 1.4.1 → 1.5.0json src/— extended with thehooks/ use- tooltip- position/ use- tooltip- position. ts anchoroption, default Y offset unified toClamp +TOOLTIP_, QR-7 viewport clamp added viaGAP parentElement. get Bounding Client Rect() src/—components/ chart/ chart. tsx Tooltiprewritten as a thin wrapper aroundPositioner use; oldTooltip Position use/Layout Effect use/State TOOLTIP_imports cleaned upGAP
Not changed (backwards-compatible 4-arg API):
11 standalone wrappers (
treemap-,chart. tsx sunburst-,chart. tsx sankey-,chart. tsx radial-,bar- chart. tsx radar-,chart. tsx pie-,chart. tsx org-,chart. tsx nightingale-,chart. tsx heatmap-,chart. tsx funnel-,chart. tsx timeline-)chart. tsx
T4 architecturally closed#
One positioning implementation for all 24 chart types. QR-7 handled (⊆ T4). No open T4 sub-triggers remain.
Remaining open T topics#
T6 — Defaults-policy review (~1 session, standalone)
T7-v3-mini — populate FlatPoint.xAxisId/yAxisId (~1 session, mechanical, not critical)
Loose observations: OHLC hit-test asymmetry, use rename, compute 3-call-site duplication. C1 parked.
[1.4.1] — 2026-05-22#
Added (internal)#
T3 phase 2 —
getinTooltip Extras src/. A record/utility mapping that supplies the tooltip extras per series type (type-specific fields on top of the standard yKey emit). Outside the T2+T9 contract — Option B (seeutils/ series- tooltip- extras. ts docs/§ ADR-013). Fits the record boundary that T2+T9-v1 drew and T5-v2 reconfirmed (next toDECISIONS. md get,Series YKeys default,Approximation For Type animation-).types
Changed (internal)#
Two inline branches in
use→ one record call. Before T3-v2Chart Tooltip usehad anChart Tooltip if (seriesbranch (lines 214-233) + anOpt?. type === "candlestick") if (seriesbranch (lines 238-254). After T3-v2: oneOpt?. type === "waterfall") getcall (~3 lines), the result spreads into theTooltip Extras(series Opt, point, all Data) ohlc?/waterfall?fields on SingleTooltipData. Shotgun-surgery pattern removed from the tooltip layer.Pixel-identical for waterfall + candlestick: 2102/2102 tests green on identical assertions, no test adjusted.
OHLC brought along — asymmetry resolved (consumer-visible). Before T3-v2
seriesexplicitly excluded ohlc; ohlc tooltips showed only the yKey emit even though the type has the same 4-key shape (openKey/highKey/lowKey/closeKey) as candlestick. After T3-v2 ohlc shares the candlestick branch inOpt?. type === "candlestick" get— the same extraction logic, no separate ohlc branch (that would introduce new duplication).Tooltip Extras Broader context: this is the same category of inconsistency as the OHLC hit-test observation from T2+T9-v2 (ohlc systematically treated less richly than candlestick because of a historical parity lag). T3 phase 2 resolves the tooltip asymmetry; the hit-test asymmetry stays a loose observation — a different layer, not touched here.
Drift test pixel-identical#
Waterfall and candlestick produce identical tooltip output before and after the refactor. Drift test confirmed via the existing test suite (2094 pre-T3-v2 tests green after the change; 8 new tests for get → 2102 in total). The only deliberate behaviour change is bringing OHLC along (ohlc now gets extras), with its own test in series-.
Stop moments — outcome#
Stop moment 1 (the record-mapper shape strains on waterfall's
allcontext): not triggered. The optionalData allparameter carries the dataset context cleanly; candlestick + ohlc ignore it; waterfall consumes it.Data?: readonly Chart Datum[] Stop moment 2 (ohlc cannot share candlestick's branch): not triggered. The shared
case "candlestick": case "ohlc":works symmetrically — identical key shape, identical extraction logic.
Known, unresolved broader concern#
computeduplication — three production call sites (Waterfall Positions useviaChart Tooltip get,Tooltip Extras use,Scales waterfall-). T3 phase 2 touches one (the tooltip call site moves to the utility), but the duplication remains. A broader clean-up candidate for a later mini-trigger (comparable to theseries. tsx stack/Baseline Map computethat T1-v2 cleaned up, but now across three call sites instead of two). Not T3 scope.Baseline By Row
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2102/2102 green (2094 pre-T3-v2 + 8 new getTooltipExtras tests). No existing assertion adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (2 new + 2 changed)#
New:
src/—utils/ series- tooltip- extras. ts getutility (~110 lines)Tooltip Extras src/— 8 unit tests (candlestick + ohlc brought along + 4-key validation + waterfall with allData + waterfall without allData + line/bar/dumbbell fallbacks)utils/ series- tooltip- extras. test. ts
Changed:
package.— 1.4.0 → 1.4.1json src/— two inline branches (~50 lines) replaced by onehooks/ use- chart- tooltip/ use- chart- tooltip. ts getcall (~3 lines); imports cleaned up (Tooltip Extras compute,Waterfall Positions Waterfallno longer needed directly)Series Options
T3 architecturally closed#
T3 phase 1 (v1.4.0): Tooltip + Finding A clean-up + Y2 + P1.
T3 phase 2 (v1.4.1): get + OHLC brought along.
Positioning: T3-v2 (T2+T9 contract untouched; no T2+T9-v3). No open T3 sub-triggers remain.
C1 stays parked#
get has no Axis need; it only touches s + point + all. No C1 consumer delivered.
Remaining open T topics (after T3 closed)#
T4 — Tooltip-positioning unification (~2 sessions, standalone); QR-7 ⊆ T4
T6 — Defaults-policy review (~1 session, standalone)
T7-v3-mini — populate FlatPoint.xAxisId/yAxisId (~1 session, mechanical, not critical)
Loose observations: OHLC hit-test inconsistency (a different layer), use rename, compute 3-call-site duplication.
[1.4.0] — 2026-05-22#
Added (internal)#
T3 phase 1 —
TooltipinContext src/. Bundles axis info (hooks/ use- chart- tooltip/ tooltip- context. ts x,Axis Type y) and formatter defaults (Axis Type value) for the tooltip layer, on top of T7'sFormatter Axes/geometry context (G3 route). No second parallel axis shape — geometry stays the single source. Kept narrow per stop-moment-3 discipline: only fields with a demonstrable consumer in this session.Context Value T3-Y2 —
formatlog-aware. UnderYValue ythe default formatter usesAxis Type === "log" Intl.'sNumber Format notation: "compact"for values ≥1000 (10k/100k/1M) andmaximuminstead of fraction precision. Other axis types: pre-T3 behaviour unchanged (locale default with 0 or 2 decimals).Significant Digits T3 phase 1 test chart in
apps/— a log-axis line chart (stock index 1900-2020, log-scale domain). The hover tooltip now shows compact notation for large values without the consumer writing a customadmin/ app/ (main)/ test- charts- extended/_ components/ extended- charts- view. tsx value. Proves Y2 in the production tree.Formatter
Changed (internal)#
Finding A clean-up (T7-v3 part) — 12 plumbing locations gone. Before T3:
xfield onAxis Type? Single/Tooltip Data Shared/Tooltip Data Split(3 locations)Tooltip Data usederivedChart Tooltip xfromAxis Type options.(1 location) + 3 assign sitesaxes 5 read sites in
tooltip.+tsx split-tooltip- layer. tsx
After T3: all three TooltipData types carry a
context?: Tooltipfield (optional for standalone wrappers that have no Cartesian geometry).Context usebuildsChart Tooltip(geometry) contextviause. Read sites readChart Geometry(). axes. x. type data.(optional chain with an undefined fallback for the 11 standalone wrappers).context?. x Axis Type Single source of truth achieved:
axes.lives in one place (T7-v2's geometry), the tooltip layer consumes it via the context.x. type use— signature extended with a geometry parameter.Chart Tooltip(geometry: Chart Geometry) <Chart>call site updated. Drift test pixel-identical.T3-P1 —
Tooltiprestructured (BREAKING forRenderer Params renderer?consumers):Pre-T3 (11-field bag) Post-T3 (grouped) params.,datum params.,x Key params.,y Key params.,x Value params.,y Value params.,series Name params.,color params.,size Value? params.size Key? params.point.{datum, x Key, y Key, x Value, y Value, series Name, color, size Value?, size Key?} (no context) params.context?: Tooltip Context params.,entries? params.group Meta? unchanged In-package consumers migrated in the same commit (per §7.14):
bar-'s errorBars default formatter (chart. tsx params.→datum params.,point. datum params.→y Key params.)point. y Key tooltip.assertionstest. tsx 3
paramsbuild sites intooltip.+tsx split-tooltip- layer. tsx
renderer?consumer inventory: 11 files, all withinpackages/. No external consumer inactive- charts/ apps//admin web/auth/atlas/archivesor other packages. Stop moment 2 (renderer?consumer inventory larger than expected) not triggered.
Drift test pixel-identical#
The Finding A clean-up is a pure refactor: tooltips render bit-for-bit identically before and after for the existing test suite. 2094/2094 tests green, no assertion adjusted. The deliberate behaviour change is Y2's log-aware formatting for y, verified via the new test chart (existing tests use no log axes for tooltip assertions, so no test fell over).
Stop moments — outcome#
Stop moment 1 (Finding A clean-up not pixel-identical): ❌ not triggered. The geometry route delivers exactly the same info as the pre-T3
options.derivation.axes Stop moment 2 (
renderer?consumer inventory larger than expected): ❌ not triggered. 11 files, all within the package.Stop moment 3 (TooltipContext field bloat): ✅ active — three fields added (
x,Axis Type y,Axis Type value), each with a demonstrable consumer in this session. No preventive fields.Formatter
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2094/2094 green on identical assertions. No test adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm turbo run typecheck(monorepo) — 28/28 tasks green. The migratedbar-consumer compiles.chart grep "✅" docs/— 0 hits.ROADMAP. md
Files (3 new + 7 changed)#
New:
src/— TooltipContext typehooks/ use- chart- tooltip/ tooltip- context. ts Log-axis test chart (section added to
apps/)admin/.../ extended- charts- view. tsx (No separate test file — TooltipContext is tested via existing tooltip tests that use it in fixtures)
Changed:
package.— 1.3.3 → 1.4.0json src/— 3types/ interaction. ts xfields replaced byAxis Type? context?: TooltipContext src/—types/ tooltip- options. ts Tooltiprestructured (P1)Renderer Params src/— Finding A derivation gone; now buildshooks/ use- chart- tooltip/ use- chart- tooltip. ts Tooltipvia geometry; signatureContext useChart Tooltip(geometry) src/—hooks/ use- chart- tooltip/ use- chart- tooltip. test. ts makefixture added; 12 hook calls updatedGeometry src/—core/ overlays/ tooltip. tsx formatlog-aware; 5 read sites + 2 params build sites updatedYValue src/— 1 read site + 1 params build site updatedcore/ overlays/ split- tooltip- layer. tsx src/—core/ overlays/ tooltip. test. tsx makefixture; 7 TooltipData fixtures updatedContext src/—components/ chart/ chart. tsx usecallChart Tooltip(geometry) src/— errorBars valueFormattercomponents/ bar- chart/ bar- chart. tsx params.→datum params.point. datum
T3 status after this session#
T3 phase 1 completed. Open:
T3 phase 2 (an open decision point after this session): series-type data extraction in
use— the two inline branches for candlestick OHLC extraction and the waterfall positions recompute. Deserves its own capability-vs-record weighing analogous to T5-v2. Does it belong to T3-v2, or does it become T2+T9-v3? Decision follows after the verification phase.Chart Tooltip T7-v3 mini (populate FlatPoint.xAxisId/yAxisId) — open, not handled via T3 (separate per analysis §1.2). Mechanical work across 10+ plug-in paths.
The T7-v3 Finding A part is hereby completed. The x stopgap from T7-v1 has been permanently removed; the tooltip layer reads axes. via T7-v2's geometry context.
C1 stays parked#
T3 phase 1 delivers no consumer for an Axis field extension — axes. and axes. are already in T7's 5-field AxisContext. C1 waits for T2+T9-v3 or T7-v3 if those introduce concrete consumers.
[1.3.3] — 2026-05-22#
Added (internal)#
T5-v2 —
getutility inSeries YKeys src/. Per series type the utility supplies the yKeys array that builds the y-domain (4 fixed-keys mappings for candlestick/ohlc/box-plot/range-area/range-bar/dumbbell + 1 yKey default branch with a bar-errorBars sub-discriminator). Outside the T2+T9 contract — Option B fromutils/ series- y- keys. ts t5-§12, weighed on an equal footing with Option A (a 4th capability) and chosen for consistency with the T2+T9 precedent (functionally equivalent touse- scales- analyse. md defaultinApproximation For Type(s) use-, which T2+T9 also kept outside the contract).prepared- data. ts Test coverage: 18 unit tests in
series-for all five branches, the bar-errorBars sub-discriminator (incl. the stacked gate), and the yKey default fall-through for 6 series types.y- keys. test. ts
Changed (internal)#
use-series-type switch fully consolidated. Three call sites consume the sharedscales. ts get:Series YKeys Main y-loop (lines 156-205 → 5 lines): 5 inline branches with an
if/chain + the bar-errorBars sub-discriminator gone; oneelse ycall.Keys. push(... get Series YKeys(s)) Fallback y-loop (lines 258-272 → 5 lines): the pre-T5-v2 candlestick-only branch + yKey default gone; one
ycall.Keys. push(... get Series YKeys(s)) X-scale special (lines 100-108 → 4 lines): the per-series dumbbell/yKey branch gone; one
xcall. The cross-seriesYKeys. push(... get Series YKeys(s)) hasdetection stays upstream (a per-series lookup cannot carry a cross-series condition).Horizontal Numeric Value Axis
Behaviour improvements (latent bug fixes)#
Two places gained, via the shared helper, extra types under their care that were silently skipped before T5-v2:
The fallback loop now covers all types. Before T5-v2 only candlestick + the yKey default; now complete (ohlc, range-area, range-bar, box-plot, dumbbell, bar-errorBars sub-discriminator). The fallback loop is activated when
options.defines no yAxes — an edge-case path, but before T5-v2 the 5 excluded types would fall onto the defaultaxes [0, 1]domain.X-scale horizontal mode now covers bar's errorBars. Before T5-v2 the errorBars sub-discriminator was missing for horizontal-mode bar; the error whisker would be cut off at
y's range. By usingKey get, the x-axis extent and the y-axis extent consume the same set of keys.Series YKeys
Both are deliberate behaviour improvements, not unintended regressions. Existing tests cover neither combination — pixel-identical relative to pre-T5-v2 for all existing scenarios.
Main loop pixel-identical#
The main loop produces bit-for-bit the same yKeys as before T5-v2 for every type. Verified by 2094/2094 tests green on identical assertions.
Stop moments — outcome#
Stop moment 1 (bar's errorBars condition strains): ❌ not triggered. The mapper absorbs the condition cleanly.
Stop moment 2 (fallback-loop behaviour change): ✅ in effect, two behaviour improvements reported.
Stop moment 3 (main loop not pixel-identical): ❌ not triggered.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2094/2094 green (was 2076; +18 new tests).active- charts test pnpm --filter @workspace/— 18 warnings (same baseline).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
The main chain is closed#
With T5-v2 the main chain T7 → T2+T9 → T1 → T5 closes. Five workstreams completed one after another without shape strain, without breaking impact for external consumers:
T7 — chart-context shape + projection helpers
T2+T9 — series-type plug-in contract (capability pattern)
T1 — baseline resolution + bar-geometry consolidation
T5 — orientation-swap helper + yKeys extraction
Remaining open T topics fall outside the main chain: T3, T4, T6, T7-v3.
[1.3.2] — 2026-05-22#
Added (internal)#
T5-v1 —
compilehelper. Consolidates the orientation swap for the 4 horizontal-supporting Cartesian wrappers (Axis Pair bar-,chart dumbbell-,chart range-,bar- chart lollipop-). Replaces the pre-T5-v1 pattern in which each wrapper did an inlinechart horizontal ? valueswap withAxis : x Axis as nevercasts (QR-1).Signature (internal):
compile. The most compact form from t5-axis-analyse §6 (Option A1). Per-orientation defaults (Axis Pair(category Config, value Config, horizontal): { x: Axis Options, y: Axis Options } bottom/left×category/number) live in the helper; the consumer passes only the two configs + the orientation flag.Location:
src/, next to the existingutils/ compile- axis- config. ts compile. Not exported fromAxis Config src/.index. ts
Changed (internal)#
4 wrapper swap locations migrated to
compile. Per wrapper one 12-line block (inline swap + 4×Axis Pair as nevercast in bar, no casts in the other 3) is replaced by one 5-line helper call:Wrapper Pre-T5-v1 Post-T5-v1 bar-(lines 336-347)chart. tsx 12 lines swap + 2× as never5 lines helper call dumbbell-(lines 174-185)chart. tsx 12 lines swap 5 lines helper call range-(lines 147-158)bar- chart. tsx 12 lines swap 5 lines helper call lollipop-(lines 184-195)chart. tsx 12 lines swap 5 lines helper call compile-itself unchanged for its existingaxis- config. ts compileexport (it is reused internally byAxis Config compile). The 9 non-swap wrappers keep importingAxis Pair compiledirectly — unchanged.Axis Config
QR-1 handled#
QR-1's as never casts lived only in bar-'s two swap branches (the other three wrappers had no casts but did have the inversion-prone ternary). Both main risks — the casts and the 4× duplication of the ternary — are resolved:
No more
as neverin the wrappers; the cast does not exist in the helper implementation either, becausecompile's looseAxis Pair Axistype (Config Input Axis) accepts bar'sConfig | Axis Type | Record<string, unknown> | undefined valuespread without a bypass.Axis Merged One place for the swap, four wrappers consume it — a ternary inversion can only be made in the helper, no longer spread 4× across the code.
Stop moment 2 — C1 (Axis Context extension) not carried out in this session#
The briefing contained C1 as a second deliverable, gated by stop moment 2: do not add Axis fields without a demonstrable consumer in this session. Inventory of consumers in this session:
compileproducesAxis Pair Axis, notOptions Axis— no field need.Context The 4 migrated wrappers consume
Axis(downstream pipe viaOptions <Chart>→use→Scales build), notChart Geometry Axisdirectly.Context Existing T7 consumers (
use, T2+T9 plug-ins, T1 helpers) read only the 5 currentChart Geometry Axisfields — no need for an extension.Context
No demonstrable consumer → no extension. Stop-moment-2 discipline works as intended. C1 waits for a natural occasion when T3 (tooltip context flow), T7-v3 (FlatPoint.xAxisId), or T5 phase 2 (sub-problem B) introduces a concrete config-field consumer.
Behaviour preserved#
A pure internal refactor — all 4 swap wrappers produce identical compiled/compiled as before T5-v1. 2076/2076 tests green on identical assertions. The 9 non-swap wrappers untouched. No consumer-visible change.
Verification#
pnpm --filter @workspace/— 0 errors (and theactive- charts typecheck as nevercasts gone from the 4 wrappers — QR-1's type hole closed).pnpm --filter @workspace/— 2076/2076 green, no assertion adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (same baseline as pre-T5).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green — consumers unchanged (confirms the §6.3 analysis: not breaking).grep "✅" docs/— 0 hits.ROADMAP. md
Files (5 changed, 0 new)#
package.— version 1.3.1 → 1.3.2.json src/—utils/ compile- axis- config. ts compileadded +Axis Pair Axistype.Config Input src/— swap block via helper.components/ bar- chart/ bar- chart. tsx src/— swap block via helper.components/ dumbbell- chart/ dumbbell- chart. tsx src/— swap block via helper.components/ range- bar- chart/ range- bar- chart. tsx src/— swap block via helper.components/ lollipop- chart/ lollipop- chart. tsx
T5 status after this session#
T5-v1 (sub-problem A) completed. T5-v2 = sub-problem B (the use series-type switch refactor as a possible T2+T9 capability extension) is open. T5-v1.x = sub-problem C (Axis extension) waits for a demonstrable consumer and is therefore NOT a separate open trigger, but a "to be carried out when T3/T7-v3/phase 2 introduces a consumer" trigger.
[1.3.1] — 2026-05-22#
Added (internal)#
T1-v2 —
compute+Bar Geometry compute. Bar's three-layer coupling (renderer + hit-test + animation anchor) is consolidated into oneStack Baseline Map computehelper. TheBar Geometry compute↔Baseline By Row stackduplication betweenBaseline Map bar-andplugin. tsx <Svghas been eliminated — both now use the sharedRenderer> computehelper. CallsStack Baseline Map resolveinternally (RC1 — the helpers stay separate).Baseline Both helpers live in
src/next tocore/ series- plugins/_ shared. ts resolvefrom v1.3.0. Shape: GB (bulk geometry builder) from t1-analyse §6 — one call delivers all per-row rects. No fallback to GC was needed: the three consumers could read the same bulk output cleanly. The renderer-versus-hit-test asymmetry (a cosmetic gap of 3 px between grouped bars) is modelled via aBaseline cosmeticparameter: the renderer passes 3, the hit-test passes 0.Gap 15 new unit tests for the two helpers (
compute: 5 tests;Stack Baseline Map compute: 10 tests covering all four orientation×stacked combinations, the gap parameter, and validity edge cases).Bar Geometry
Changed (internal)#
bar-'sseries. tsx targetuseMemo — before T1-v2 this was a 4-branch if-chain (horizontal × stacked) with the baseline bug repaired per branch in v1.3.0. v1.3.1 replaces it with onePositions computecall withBar Geometry cosmetic. 110 lines of inline logic gone.Gap: 3 bar-'splugin. tsx emit— before T1-v2 this plug-in did its own rect building + had its ownHit Test Elements computehelper. v1.3.1 replaces both with aBaseline By Row computecall (withBar Geometry cosmeticfor the Path-C hit zone) and aGap: 0 computecall. ~70 lines of inline logic gone; the Path-B flatpoint and Path-C BarRect emit stay the plug-in's responsibility (geometry + bounds checks).Stack Baseline Map <Svg'sRenderer> stackuseMemo — before T1-v2 this was a ~25-line inline implementation. v1.3.1 replaces it with oneBaseline Map computecall. The duplication with bar-plugin's pre-T1-v1Stack Baseline Map compute(~19 lines) is thereby fully cleaned up.Baseline By Row
Pixel-identical#
A pure refactor — bar rendering and the bar hit-test produce bit-for-bit the same output as v1.3.0. 2076/2076 tests green on identical assertions; no test adjusted. Stop moment 1 (shape strains) not triggered: the three consumers consumed the bulk output (Bar) cleanly, with the cosmetic parameter as the only caller decision for modelling the renderer-vs-hit-test asymmetry.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2076/2076 green (was 2061; +15 new helper tests).active- charts test pnpm --filter @workspace/— 18 warnings (the same number as before T1-v2).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files#
Changed:
package.— version 1.3.0 → 1.3.1.json src/—core/ series- plugins/_ shared. ts compute+Stack Baseline Map computeadded. ~150 lines of new helper code.Bar Geometry src/— 15 new unit tests.core/ series- plugins/_ shared. test. ts src/—core/ series- plugins/ bar- plugin. tsx emitconsumes the helpers; its ownHit Test Elements computeremoved. ~70 lines inline gone, ~50 lines via helpers.Baseline By Row src/—core/ renderer/ series/ bar- series. tsx targetuseMemo viaPositions compute. ~110 lines inline gone.Bar Geometry src/—core/ renderer/ svg- renderer. tsx stackuseMemo viaBaseline Map compute. ~25 lines inline gone.Stack Baseline Map
T1 — fully completed#
With v1.3.1, T1 closes. Both helpers are live:
resolve(v1.3.0) — three consumers (bar non-stacked, histogram, lollipop stick). Fixes QR-6 (mixed-sign / non-zero-min y-domain rendering).Baseline compute+Bar Geometry compute(v1.3.1) — bar's three-layer coupling + the stack-baseline duplication cleaned up. A pure refactor without a consumer-visible change.Stack Baseline Map
No open T1 follow-up triggers. Roadmap order T2+T9 → T1 → T5 — only T5 is still open in this chain.
[1.3.0] — 2026-05-21#
Fixed (consumer-visible)#
QR-6 — negative-value rendering. Bar charts, histograms and lollipop charts with a mixed-sign y-domain (e.g.
[-50, +100]) or a non-zero-min y-domain (e.g.[20, 80]) now render visually correctly. Before T1 all three drew upward from the plot floor (bar, orHeight = plot Height - y Pos projectas a hardcoded baseline), which gave wrong results when 0 was not in the domain:X(0)/ project Y(0) Mixed-sign data: negative values appeared above the 0 line instead of below it.
Non-zero-min data: bars grew from the plot floor instead of from the natural bottom of the domain.
After T1,
resolvepicks the natural baseline (0 if 0∈domain, otherwise the domain min) and bars/sticks grow from there. Existing charts with domains that contain 0 (by far the majority) see no change — pixel-identical to v1.2.0.Baseline
Added (internal)#
T1-v1 —
resolve. A pure number resolver inBaseline src/. Input: a numeric domain + an optional caller override. Output: the baseline value (caller override → that; otherwise 0 if 0∈domain; otherwise the domain min). Pure, type-light, independently testable — consistent with thecore/ series- plugins/_ shared. ts bandwidthshape from T7-v2 (the caller does the projection itself).Of(axis Id) Test coverage: 14 new unit tests in
_for mixed-sign, non-zero-min, caller override (including the NaN fallback and an override outside the domain), an inverted-range domain, and a defensive non-numeric-domain fallback.shared. test. ts
Changed (internal)#
bar-non-stacked branches (lines ~186-242 in theseries. tsx targetuseMemo) — replaced by thePositions resolvepattern. Horizontal non-stacked:Baseline bar+X = Math. min(value Pos, baseline Pixel) bar. Vertical non-stacked: likewise on the y-axis. Stacked branches unchanged (they were already correct via the explicitWidth = abs(baseline Pixel - value Pos) stackprop).Baseline bar-plugin. tsx emit(~line 176):Hit Test Elements base→Val = is Stacked Series ? baseline Val : 0 basewhereVal = is Stacked Series ? baseline Val : non Stacked Baseline non. The value-axis domain is resolved viaStacked Baseline = resolve Baseline(value- axis- domain) geometry.(horizontal mode) oraxes. x geometry.(vertical mode).axes. ys. get(bar YAxis Id) histogram-series. tsx target(~lines 111-122):Positions bar→ a baseline-awareHeight = plot Height - bar Y Math./min Math.pattern viaabs resolve. The same pattern as bar's vertical non-stacked.Baseline(y Scale. domain()) lollipop-(~line 64):series. tsx baseline = (is→Horizontal ? project X(0) : project Y(0)) ?? 0 baseline = (is. The stick start is now baseline-aware instead of a hardcoded 0.Horizontal ? project X(resolve Baseline(x Scale. domain())) : project Y(resolve Baseline(y Scale. domain()))) ?? 0
Drift-test finding#
T1's first implementation session is a drift test on the sharpest capability point (bar's non-stacked + hit-test, where the baseline bug and bar's geometry meet). Passed cleanly:
Stop moment 1 (shape strains): not triggered. The BA shape (pure number resolver) fitted all three consumers without bending.
Stop moment 2 (contract grows): not triggered. No 4th input needed; no reason for a BB or BC shape.
Histogram + lollipop could come along in the same session because the drift test on bar validated the shape; their call form turned out to be trivial (~2 lines of diff per call site).
Behaviour preserved (where 0 ∈ domain)#
2061/2061 tests green on exactly the same assertions as before T1. Existing bar, histogram and lollipop tests use "happy path" domains that contain 0; for those cases the old and new behaviour produce pixel-identical output (verified via projecting project = plot in inverted scales). The behaviour change is thereby strictly isolated to mixed-sign / non-zero-min domains — exactly the intended fix.
No test adjusted to mask the behaviour change.
Lint side effect#
Lint warnings: 20 → 18. The use deps extension in histogram- (for project/project) cleared the pre-existing react- warning. Other warnings unchanged.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2061/2061 green, no assertion adjusted.active- charts test pnpm --filter @workspace/— 18 warnings (was 20).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files#
New:
src/— 14 unit tests forcore/ series- plugins/_ shared. test. ts resolve.Baseline
Changed:
package.— version 1.2.0 → 1.3.0.json src/—core/ series- plugins/_ shared. ts resolveadded.Baseline src/—core/ series- plugins/ bar- plugin. tsx base→Val = 0 resolve.Baseline(...) src/— non-stacked branches baseline-aware.core/ renderer/ series/ bar- series. tsx src/— baseline-aware Y rendering.core/ renderer/ series/ histogram- series. tsx src/— baseline-aware stick.core/ renderer/ series/ lollipop- series. tsx
What T1 still leaves open (for the next session)#
compute— bar's three-layer coupling (renderer + hit-test + animation anchor) + theBar Geometry compute/Baseline By Row stackduplication between the bar plugin andBaseline Map <Svg. A separate session, planned as M2 (see t1-analyse §8). Do not mark T1 as completed until that has also been carried out.Renderer>
QR-6 status#
QR-6 ⊆ T1, handled in v1.3.0. Negative-value rendering now works visually correctly for the three consumers (bar/histogram/lollipop). QR-6 removed as an open roadmap item.
[1.2.0] — 2026-05-21#
Added (internal)#
T2+T9-v2 — full Cartesian migration to the
Seriescontract. All 13 Cartesian series types now go through the plug-in registry. Twelve new plug-ins on top of the bar plugin (v1.1.2):Type Plugin Plug-in hitTest render accessibleColumns Sub-discrimination inside plug-in line✓ Path B ✓ ✓ single-yKey — area✓ Path B ✓ ✓ single-yKey — scatter✓ Path B + sizeKey ✓ ✓ single-yKey sizeKey passthrough histogram✓ Path B ✓ ✓ single-yKey — waterfall✓ Path B ✓ ✓ single-yKey — candlestick✓ Path B with closeoverrideKey ✓ ✓ fallback row (no yKey) closeKey resolution dumbbell✓ 2 flatpoints + halfBand + horizontal swap ✓ ✓ 2 columns (low + high) horizontal mode lollipop✓ 1 flatpoint + halfBand + horizontal swap ✓ ✓ single-yKey horizontal mode range-bar — (Walt decision: no hit-test) ✓ ✓ fallback row (no yKey) — range-area — (no yKey, no hit-test pre-T2+T9) ✓ ✓ fallback row — ohlc— (no yKey, no hit-test pre-T2+T9) ✓ ✓ fallback row — box-plot — (no yKey, no hit-test pre-T2+T9) ✓ ✓ fallback row — Four types omit
Hit— exactly the capability pattern that the phase-1 analysis §6.2 foresaw. The other capabilities (render + cols) are delivered. No forced no-ops.Test Capability src/—core/ series- plugins/_ shared. ts resolve+Default Left YAxis Id() emit. Six plug-ins (line, area, scatter, histogram, waterfall, candlestick) share the same Path-B implementation through this helper, parameterised with a yKey override + an optional sizeKey.Pad BFlat Points()
Changed (internal)#
use-— all inline series-type branches (dumbbell ~117 lines, lollipop ~70 lines, fall-through Path B ~70 lines, plus the earlier removed bar branch) are gone. The hook is now a thin dispatch-and-Delaunay shell of ~140 lines (was ~541).voronoi. ts get, theSeries Color Charttype, theScale project/X project/Y bandwidthdestructure, and the localOf yMap are gone — no longer used outside the plug-ins.Scales svg-—renderer. tsx switch (series(13 cases, ~280 lines) has been fully replaced by one plug-in dispatch (~50 lines). Bar's cross-series pre-compute (Opt. type) { case ... } bar,Series Indices stack) stays inBaseline Map <Svgand is passed to the bar plug-in viaRenderer> Render. 12 direct series-component imports gone; 10 unusedContext *Seriestype imports gone.Options accessible-—data- table. tsx get(was ~78 lines with dumbbell + bar-stackedPercentage + bar-errorBars + default) is now a 3-line plug-in dispatch with a fallback for unexpected types.Series Columns
Behaviour preserved (pixel-identical)#
2047/2047 tests green on identical assertions. No test adjusted to legitimise a refactor outcome. Stop moments not triggered:
No shape strain — all 12 types fitted the bar-template contract without a capability extension.
No contract growth —
Hit/Test Capability Render/Capability Accessible+ the R1 registry from v1.1.2 cover everything.Columns Capability No knock-on changes to other types — each type migration isolated itself within its plug-in.
Per-type findings (outliers)#
range-bar — has no
Hit. Walt decision from phase 1: Gantt-like use does not need flatpoint emission. The capability pattern handles this naturally.Test Capability range-area, ohlc, box-plot — also no
Hit. Before T2+T9 these types were silently skipped byTest Capability use's fall-through path (noVoronoi yfield, and only candlestick had an explicit sub-condition). Behaviour preserved: the plug-ins do not implementKey hit.Test OHLC observation (parked, no fix) — a pre-T2+T9 inconsistency: candlestick gets a hit-test (via an explicit
ischeck onCandlestick close), but OHLC does not — while the two are comparable. Pixel-identical discipline keeps this inconsistency in v1.2.0; flagged in the DONE entry for future consideration.Key lollipop
strokefield — before T2+T9,usereadVoronoi lol.defensively via a loose type cast, whilestroke ?? lol. fill ?? color Lollipopdeclares noSeries Options strokefield. The plug-in keeps that defensive lookup (cast through a union with{ stroke?: string }) for exactly equivalent behaviour.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2047/2047 green, identical assertions.active- charts test pnpm --filter @workspace/— 20 warnings (same baseline as pre-T2+T9-v2).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (13 new + 1 update + 3 changed)#
New:
src/— shared Path-B helper + axis resolvercore/ series- plugins/_ shared. ts src/— 12 plug-inscore/ series- plugins/ {line,area,scatter,histogram,waterfall,candlestick,ohlc,range- area,range- bar,box- plot,dumbbell,lollipop}-plugin. tsx
Update:
src/— all 13 plug-ins registeredcore/ series- plugins/ registry. ts
Changed:
package.— version 1.1.2 → 1.2.0json src/— switch body removed; now a thin dispatch shell (541 → ~140 lines)hooks/ use- voronoi/ use- voronoi. ts src/— 13-case switch replaced by plug-in dispatch; unused imports gonecore/ renderer/ svg- renderer. tsx src/—core/ accessibility/ accessible- data- table. tsx getnow a pure plug-in dispatchSeries Columns
use Voronoi → use Hit Test rename — postponed#
The API-impact suggestion from the roadmap (use, Voronoi) was not included in v1.2.0. The rename did not fall naturally out of the migration — use is now substantively a hit-test dispatcher, but the Delaunay build it also performs still justifies the Voronoi name. A separate decision for later.
[1.1.2] — 2026-05-21#
Added (internal)#
T2+T9-v1 — the
Seriescontract as a drift test on bar. The first implementation of the T2+T9 plug-in contract (seeType Plugin DECISIONS.§ ADR-010). A capability pattern for the three if/switch-style switches:md Hit— forTest Capability use-flatpoint/BarRect emissionvoronoi. ts Render— forCapability svg-series-element dispatchrenderer. tsx Accessible— forColumns Capability accessible-sr-only columnsdata- table. tsx
A plug-in implements the capabilities relevant to its type (optional fields). Records (
animation-,types. ts use-) keep their declarative form — not part of the contract, in line with §6/§7.2.prepared- data. ts R1 registry (compile-time barrel) in
src/. Static, tree-shakeable. A runtime register call is not supported in v1 (the public "Plugin contract" feature from roadmap §6 is a later, separate decision).core/ series- plugins/ registry. ts bar-as the first implementation. Bar touches all three plug-in-side capabilities and all three sub-discrimination variants (plugin. tsx stacked,stacked,Percentage error, plus theBars horizontalorientation). Sub-discrimination is handled inside the plug-in (no separate plug-ins per variant).
Changed (internal)#
use-— plug-in dispatch added at the front of the series loop. The bar branch (~162 inline lines) was removed; bar is now handled byvoronoi. ts bar. Other series types (dumbbell, lollipop, candlestick fall-through, scatter/line) stay inline — drift-test isolation.Plugin. hit Test. emit Hit Test Elements() svg-—renderer. tsx case "bar":in the series switch now reads frombar. The cross-series pre-compute (Plugin. render. render Series Element() bar,Series Indices stack) stays inBaseline Map <Svg(passes the array viaRenderer> Render). The directContext. stack Baseline Barimport was removed. The other 12 cases unchanged.Series accessible-—data- table. tsx getnow reads fromSeries Columns bar. The bar sub-branches (stackedPercentage + errorBars formatters, ~50 inline lines) are gone. The dumbbell branch and default fall-through unchanged.Plugin. accessible Columns. emit Accessible Columns()
Drift-test finding#
Passed cleanly. Stop moments 1/2/3 not triggered:
Stop moment 1 (shape strains): not triggered. The three capabilities carried the heterogeneous characters (a thick imperative hit-test, a thin declarative render, divergent sr-only columns) without bending. Optional fields on
Render(Context stack,Baseline bar,Series Count bar) for bar-specific pre-computes fitted naturally; other plug-ins do not consume them.Series Ordinal Stop moment 2 (contract grows): not triggered. The three capabilities + the R1 registry covered bar's full responsibility. No fourth capability needed.
Stop moment 3 (touches other series types): not triggered. The plug-in dispatch in each switch is a branch added at the front with an early
return/continue; other inline branches unchanged.
Natural convergence (input for T1)#
compute in the bar plugin is consumed by both emit and (implicitly) <Bar. Before T2+T9 the baseline computation was duplicated word for word in use- (~19 lines) and in svg-'s stack useMemo (~25 lines). In the plug-in it now lives as one implementation. <Svg keeps its own stack for the cross-series pre-compute (it passes the result via Render to the plug-in). The use-voronoi side uses the plug-in's helper directly.
This is input for T1: the natural convergence proves that the contract carries the shared-helper form that T1's compute is looking for. Not cleaned up in v1.1.2 (T1 scope), but reported as an architectural observation.
Tests#
No test assertions adjusted. 2047/2047 green — bar rendering and the bar hit-test pixel-identical. No new tests added; existing series tests cover the plug-in output via the same paths.
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2047/2047 green, identical assertions.active- charts test pnpm --filter @workspace/— 20 warnings (same baseline as pre-T2+T9).active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green.grep "✅" docs/— 0 hits.ROADMAP. md
Files (3 new + 4 changed)#
New:
src/— capability interfaces + SeriesTypePlugincore/ series- plugins/ types. ts src/— R1 compile-time barrelcore/ series- plugins/ registry. ts src/— bar plugin with 3 capabilitiescore/ series- plugins/ bar- plugin. tsx
Changed:
package.— version 1.1.1 → 1.1.2json src/— plug-in dispatch + bar-branch removalhooks/ use- voronoi/ use- voronoi. ts src/— bar-case dispatch + BarSeries import removalcore/ renderer/ svg- renderer. tsx src/— getSeriesColumns dispatch + bar sub-branch removalcore/ accessibility/ accessible- data- table. tsx
[1.1.1] — 2026-05-21#
Changed (internal)#
T7-v2.1 — clean-up of redundant local scale variables and utilities in
use-. Removed:voronoi. ts category+Scale valuelocal vars in the dumbbell path (lines 121-122 pre-v2.1).Scale lol+Category Scale lollocal vars in the lollipop path (lines 236-237 pre-v2.1).Value Scale category+Scale valuelocal vars in bar Path C (lines 328-329 pre-v2.1).Scale The
xlocal var at the top of the useMemo body (line 54 pre-v2.1, only still referenced by comment text).Scale The local
halfutility (lines 22-29 pre-v2.1).Band Of(scale) The local
bandwidthutility (lines 36-47 pre-v2.1).Of(scale, fallback, count)
Replaced by an inline route via the T7-v2
usehelpers. The correct axis id is passed per series path: the dumbbell path usesChart Geometry() yfor the horizontal-mode category band, the lollipop pathAxis Id lol, bar Path CYAxis Id bar. For bar Path C: an inline fallback (YAxis Id raw) reproduces the semantics of the pre-v2.1 localBandwidth > 0 ? raw Bandwidth : plot Dim / data Length bandwidthutility — exactly the pattern that T7-v2 already introduced forOf dumbbell-,series lollipop-andseries range-.bar- series The 4 lint warnings that T7-v2 parked as an observation are gone — lint output from 23 → 20 warnings.
Behaviour preserved#
A behaviour-preserving refactor. 2047/2047 tests green on exactly the same assertions as pre-v2.1; no test assertion adjusted. The stop moment was not triggered after the structural difference between the local bandwidth and the geometry helper bandwidth had been clarified beforehand (the local utility synthesised fallback for non-band scales; the geometry helper returns 0). The inline fallback in bar Path C reproduces the pre-v2.1 semantics.
Remaining lint observations (out of scope for v2.1)#
20 lint warnings remain, mainly in svg- (a leftover x variable) and waterfall- (useMemo missing deps) — both after the T7-v2 introduction. Not touched in v2.1 because the briefing explicitly named "only use-" as the scope boundary. Candidates for a possible later mini clean-up or to be picked up naturally with T2+T9 / T5.
[1.1.0] — 2026-05-21#
Added (internal)#
T7-v2 — projection helpers on
use. The (internal)Chart Geometry() Chartshape now carries three functions alongside the data:Geometry project— projects a value onto the x-axis, with internal category-string coercion and ordinal-time key conversion based onX(value): number | undefined axes..x. type project— likewise for y-axes, with an optionalY(value, axis Id?): number | undefined axisfor multi-y-axis charts (default-left fallback).Id bandwidth— the bandwidth of a band/point scale,Of(axis Id?): number 0for continuous scales.
The helpers live next to the data (the S2 shape from the analysis):
usereturnsChart Geometry() {axes, plot, project. Internal only — not exported fromX, project Y, bandwidth Of} src/. Test coverage: 14 unit tests in the newindex. ts use-(all axis-type branches + edge cases).chart- geometry. test. ts Chart+Geometry Providers ChartinGeometry Providers From Options src/__— test helpers that set up the provider tree for tests that render series renderers, overlays ortest- utils__/ chart- providers. tsx <Svgdirectly without going throughRenderer> <Chart>. Replaced hand-written providers per test.
Changed (internal)#
T7-v2 — 94-site
callmigration. All production call sites ofScale callhave been replaced byScale(scale, value) project/X(value) project, internalising the category-string coercion + ordinal-time key conversion. 14 files touched:Y(value) File Calls before → after core/overlays/ cross- lines. tsx 2 → 0 (+ local resolveX helper removed) core/overlays/ annotations. tsx 3 → 0 (+ local resolveX helper removed) core/renderer/ series/ series- accessors. ts 3 → 0 (the factory now accepts project/X projectas parameters)Y core/renderer/ series/ histogram- series. tsx 3 → 0 core/renderer/ series/ range- area- series. tsx 4 → 0 (+ inline ordinal-time branch removed) core/renderer/ series/ lollipop- series. tsx 5 → 0 core/renderer/ series/ dumbbell- series. tsx 6 → 0 core/renderer/ series/ candlestick- series. tsx 6 → 0 (+ ordinal-time ternary removed) core/renderer/ series/ ohlc- series. tsx 6 → 0 (+ ordinal-time ternary removed) core/renderer/ series/ range- bar- series. tsx 6 → 0 core/renderer/ series/ box- plot- series. tsx 7 → 0 core/renderer/ series/ waterfall- series. tsx 8 → 0 core/renderer/ series/ bar- series. tsx 14 → 0 (stack baseline stays a caller decision, per T1 scope) hooks/use- voronoi/ use- voronoi. ts 18 → 0 (4 composite ternary blocks became 1 line each) callstays as a low-level primitive inScale types/— used internally byscales. ts project/X project. No production call sites consume it directly any more.Y usesignature —Voronoi (data, options, x→Scale, y Scales, layout, visible Indices?) (data, options, geometry, visible. It receives the fullIndices?) Chart(axes + plot + helpers). Breaking for the exported hook; the only consumer isGeometry <Chart>(migrated in the same commit) + the test fixture inchart-.interaction. test. tsx Series-renderer props for
Line,Series Area,Series Scatter,Series Candlestick,Series Ohlc,Series Range— theArea Series xprop was removed. Series renderers now read the type viaAxis Type: Axis Type use(where needed) or via the helpers (which process it internally).Chart Geometry(). axes. x. type <Svgno longer passes the type to these 6 wrappers.Renderer> Crossprops +Lines Annotationsprops —x,Scale y,Scale xremoved. The components useAxis Type use. Sub-components (Chart Geometry() Cross,Line Line Cross) get a boundLine Band project: Projectprop instead ofFn scale + is.XOrdinal Time createfactory — signatureSeries Accessors (x→Key, y Key, x Scale, y Scale, x Axis Type) (x. The pure function now receives the helpers directly; no more internalKey, y Key, project X, project Y) Axisdiscrimination (handled by the helpers).Type Local
resolvehelpers removed in 2 files (X cross-,lines. tsx annotations.) — the central helper layer now covers the pre-processing. No "TODO later"; removed immediately per §7.14.tsx toimports removed from all production files exceptOrdinal Time Key use-itself (where it is used bychart- geometry. tsx project).Through
Tests#
14 new unit tests in
use-for projectX/projectY/bandwidthOf.chart- geometry. test. ts 12 test files updated with a
Chart/Geometry Providers Chartwrap so that their TestChart/svg helpers set up the hook tree thatGeometry Providers From Options useexpects. No test assertion adjusted to mask a refactor behaviour change (stop moment 3): 2047 tests run on exactly the same assertions as pre-v2.Chart Geometry()
Parked (observation)#
The
category/Scale value/Scale lol/Category Scale lollocal variables inValue Scale use-are unused after the migration (4 lint warnings). Per stop moment 1 from the briefing not cleaned up — the variables are still used byvoronoi. ts halfcalls, and the broader restructuring falls outside the T7-v2 scope. New trigger T7-v2.1 opened on the roadmap for this clean-up.Band Of(category Scale)
Verification#
pnpm --filter @workspace/— 0 errors.active- charts typecheck pnpm --filter @workspace/— 2047/2047 green.active- charts test pnpm --filter @workspace/— 23 warnings (the 4 unused locals above plus 19 pre-existing), well within the 100-cap.active- charts lint pnpm typecheck(full monorepo) — 38/38 tasks green, admin/web/auth/archives/atlas unchanged.grep "✅" docs/— 0 hits.ROADMAP. md
[1.0.4] — 2026-05-21#
Added (internal)#
T7-v1 — foundation shape for the architecture roadmap (see
DECISIONS.§ ADR-009). The new internal modulemd src/contains:hooks/ use- chart- geometry/ use- chart- geometry. tsx Axis(per-axis metadata:Context id,type,position,scale,reversed).Axes(Context Value x+ys: Map<Axis).Id, Axis Context> Plot(Context Value plot,Width plot,Height margin,adaptive).Axes+Context Plot(React contexts) and theContext usehook for future downstream consumers.Chart Geometry() build, a pure builder thatChart Geometry(options, scales, layout) <Chart>synthesises from theuse+Layout useoutput.Scales
Not publicly exported via
src/— an internal API until the shape has been validated through v2/v3.index. ts
Changed (internal)#
T7-v1 drift test —
usenow consumesVoronoi Axes+Context Value Plotinstead of looseContext Value (xparameters plus its ownScale, y Scales, layout) options.lookup. Concrete deduplication:axes The
xderivation (Axis Type options.) now lives once inaxes?. find(a => a. position === "bottom" || a. position === "top")?. type build.Chart Geometry usereadsVoronoi axes..x. type Four
ysites replaced by one locally derivedAxes. find(a => a. position === "left")?. id default.Left YAxis Id
Signature:
use→Voronoi(data, options, x Scale, y Scales, layout, visible Indices) use. Breaking for external consumers of the exported hook; the only consumer in the monorepo isVoronoi(data, options, axes, plot, visible Indices) <Chart>(migrated in the same commit) plus thechart-fixture (likewise). Runtime behaviour unchanged: 2033/2033 tests green without fixture changes to the assertions.interaction. test. tsx
Type additions#
T7-v1 F2 —
Flatgets optional fieldsPoint xandAxis Id?: string y. An additive type extension; no producer populates them yet (that is T7-v3, coupled to T3 for cleaning up the v9.0.0 Finding AAxis Id?: string xstopgap inAxis Type Tooltip). Consumers that do not know the fields keep working; consumers that want to read them getData undefined.
Unblocked#
T7-v2 (helpers + 119-site migration) and T7-v3 (populate the
Flatfields + Finding A clean-up) are open on the roadmap. v1 validated the shape via the sharpest duplication point (thePoint usexAxisType derivation), so v2/v3 can build on it without a shape revision.Scales ↔ use Voronoi
[1.0.3] — 2026-05-21#
Added#
MP-7 — seven options types now publicly exported via
src/. External adopters could not import these types before because they lived only in the internalindex. ts ./barrel:types Parent(axis options),Level Options Dumbbell+Series Options Lollipop(range series),Series Options Series(series),Animation Options Navigator+Handle Options Navigator(navigator),Outline Options Label(marker). A purely additive export — no contract change on existing types.Background Options TEST-2 — ten
.files added for the generic-preserving wrappers (test- d. tsx Pie,Chart Funnel,Chart Nightingale,Chart Radial,Bar Chart Radar,Chart Heatmap,Chart Treemap,Chart Sunburst,Chart Org,Chart Sankey). Verifies at compile time that the generic parameter propagates to theChart contextitem handler and to theMenu dataprop. Catches regressions where the runtime stays unchanged but the public type contract would silently break. Compiled bytsc --no; no runtime tests.Emit
Changed#
MP-9 —
GENEAX_renamed toCHART_ THEME ACTIVE_and the corresponding typeCHARTS_ THEME GeneaxtoChart Theme Active. The public named-export symbol carried a project brand name in a library API — resolved ahead of any external publication. File move:Charts Theme src/→themes/ geneax- theme. ts src/(plus the test file). 11 source imports, 7 JSDoc references,themes/ active- charts- theme. ts README.andmd docs/migrated in the same commit per §7.14. Breaking for the named-export consumer; zero external consumers outside the package (verified with a monorepo grep), so in-package-only impact. No backwards-compat shim left behind.DECISIONS. md MP-10 — CHANGELOG header extended with an explicit "A note on version-numbering" section documenting the version reset of 2026-05-18 (
1.0.0 → … → 9.1.0 → reset → 1.0.0). Prevents confusion on first reading for anyone consulting the git history or old tags.
Internal observations#
The three scale/rect primitives from MP-7 (
Bar,Rect Bandable,Scale Chart) are deliberately not publicly exported. Setting up anScale @workspace/subpath for these three types would be the first step of the MP-11 workstream (restructuring over-exposed internals); that structural decision falls outside the v1.0.3 scope and will be tackled properly in one go when MP-11 is picked up as a whole. Status quo: the three types remain available within the package via the internalactive- charts/ internals ./barrel; external consumers cannot import them.types
[1.0.2] — 2026-05-21#
Fixed#
QR-9 —
package.json "sideclashed with CSS imports in wrapper components (Effects": false import "../../etc.) and instyles/ accessibility. css" core/. Bundlers were allowed to tree-shake these imports → focus rings, animations, forced-colors and print styles could break silently for external consumers. Replaced byoverlays/ context- menu. tsx "sideso that only CSS imports are treated as side effects; JS/TS imports stay tree-shakeable.Effects": ["**/ *. css"] QR-12 — the heatmap min/max scan in
components/did an O(n)heatmap- chart/ heatmap- chart. tsx data.+ twomap(). filter() Math.per render, regardless of whethermin/ max(... spread) dataorcolorchanged. Wrapped in aKey useonMemo [data, color. No behaviour change — the gradient legend gets the same labels, just no longer recomputed per render. Relevant for large heatmaps (10k+ cells).Key]
Changed (internal)#
QR-8 — the generic-preserving cast rolled out to nine standalone wrappers following the HeatmapChart template (
Pie,Chart Funnel,Chart Nightingale,Chart Radial,Bar Chart Radar,Chart Treemap,Chart Sunburst,Chart Org,Chart Sankey).Chart forwardstrips the generic parameter; the cast reinstates it so thatRef <Piecompiles again and context-menu items receive a typed element. No runtime change (the sameChart<My Row>> forwardbinding), only the public TypeScript type. Five Categorical wrappers cast onRef TDatum extends Chart; Treemap/Sunburst/OrgChart cast onDatum TExtra extends Record<string, unknown>(OrgChart default{ title?: string }); SankeyChart casts on two genericsTNode. TimelineChart deliberately excluded —Extra, TLink Extra Timelinehas no generic parameter andChart Props Timelineis a fixed interface; introducing a generic is an API extension, not a mechanical cast, and falls outside the QR-8 scope (a candidate for a later mini-trigger).Event QR-11 — pre-1.0.0 BREAKING comment removed from
types/onchart- options. ts Chart. The inlineTheme Override v6.0.0 NEW semantics/v6.0.0 NEW fieldmarkers on thebackground/cardfields have been replaced by pure semantic descriptions ("Maps to--color-(outer chart canvas surface)" and "Maps tobackground --color-(tooltip/label surface)" respectively). The current semantics are already in the bullet list above; the version noise only confused readers of a v1.x baseline.card
Unblocked#
TEST-2 — type-d tests for the 9 standalone wrappers are now triggerable (without the generic-preserving cast they would have been pointless — they would match all non-generic
forwardtypes). Not part of this release.Ref
[1.0.1] — 2026-05-20#
Changed (internal)#
Type-cleanup: @typescript- warnings reduced from 26 → 0 across the package. No API surface changes, no runtime behaviour changes, no test changes. Two categories of fix:
Drop-as-unnecessary (5 occurrences in
utils/): theindicators. ts as anycasts on indicator-value assignments toChartwere redundant —Datum number | null | undefined(returned bycompute/Sma compute/Ema macd/Line signal) is already a validLine Chartvalue-type. Casts removed; TypeScript still accepts the assignments.Datum Refactor:
core/now uses the centralizedoverlays/ cross- lines. tsx callhelper fromScale types/instead of duplicating a localscales. ts Scaletype-alias and theFn = (value: any) => … as unknown as Scalecast. Same call-shape, one less inline-any.Fn Motivated
eslint-directives on intentional escape-hatches that cannot be expressed withoutdisable any:types/scales. ts Chartunion (blockScale /) — d3-scale* eslint- disable */ Range/Outputgenerics are deliberately unconstrained; narrowing tonumberwould forbid colour-output scales, narrowing tounknownwould break call-signatures at consumer sites. Runtime discriminators (typeof scale., …) remain the source of truth.bandwidth === 'function' types/scales. ts callhelper (2 directives) — the polymorphic call-signature on a heterogeneousScale Chartunion needsScale anyfor both input-domain and invocation. This is the centralized escape-hatch the helper exists to provide.core/(4 directives) — duck-typing access onaxes/ axis. tsx Chart(Scale 'ticks' in s,'domain' in s,'tick); runtimeFormat' in s typeof === 'function'guards remain the discriminator.components/sparkline/ sparkline. tsx Scale(1 directive) — bareFn scaleinfersLinear() Rangeas{}, so the call-site coercion?? numberneeds theany-typed indirection.components/(2 directives) andnightingale- chart/ nightingale- chart. tsx components/(2 directives) — d3-arc generators require a datum argument in their call-signature, but the generators here have all properties statically configured;radial- bar- chart/ radial- bar- chart. tsx null as anyis the documented datum-less invocation.
Fixed#
QR-4 cascade:
useinterpolatesCount Up 0 → targetunconditionally; when consumers passedmin > 0toLinearorGauge Gaugecombined withChart animation., the unclampedtype: 'count- up' animatedflowed into the geometry derivation and produced visual artifacts during early animation frames:Value LinearGauge:
frac = (animatedwent negative →Value - min) / (max - min) <rect>width/height attributes went negative → SVG rejected with a console warning.GaugeChart:
effectiveflowed throughValue For Angle value→ needle angle belowTo Angle() svg→ needle rendered far outside the gauge arc.Start Same mechanism as the fixed BulletChart bug (v9.0.0 Issue 3D). Fix: clamp
animatedtoValue [min, max]before deriving geometry. Visually-identical end-state (count-up still snaps to exacttargetat completion); only intermediate animation frames change.Latent bug — only triggered by
min > 0 + count-; default consumer paths (up min: 0) unaffected.
Added#
Linearregression-test: asserts non-negativeGauge <rect>width/height on first count-up frame whenmin > 0.Gaugeregression-test: asserts needle angle stays withinChart [svgon first count-up frame whenStart, svg End] min > 0.QR-1 scan report (horizontal-mode axis merges, ternary inversions,
as nevercasts in all Cartesian wrappers) drawn up as input for the T5 workstream. The findings were handled in T5-v1 (v1.3.2) viacompile; the scan report was removed in the docs clean-up of 2026-05-22.Axis Pair
[1.0.0] — 2026-05-18#
External-publishability milestone. Active-charts is now conceptually clean, has one unified API for every cross-cutting concern, and commits to an API-stability promise from this version onward. The version reset from 9.1.0 to 1.0.0 marks the transition from internal-only iteration to a publishable v1 baseline; the 1.x–9.x internal history lives on in the README opening section.
The headline change is the unified, element-aware context-menu API:
every chart-type in active-charts now surfaces a typed
Context whose action(element) handler receives
the chart-element under the cursor (cell, slice, point, node, link,
hierarchy node, …) at right-click time. Previously, only HeatmapChart
carried this — and via heatmap-specific types (T-GX-AC-01). This
release removes those specific types and reroutes every standalone
chart and the low-level <Chart> through one helper.
BREAKING#
ContextandMenu Item Contextare now generic on aMenu Options TElementparameter that defaults tovoid. Predicate-stylehiddenanddisabledaccept the element. Boolean values continue to work unchanged. Item handlers may continue to be written as() => void— TypeScript accepts the narrower signature thanks to parameter contravariance, so existing call sites compile without change.Removed:
Heatmap-specific item typesCell HeatmapandContext Menu Item<TDatum> Heatmap. Consumers useContext Menu Options<TDatum> ContextandMenu Item<Heatmap Cell<TDatum>> Contextinstead.Menu Options<Heatmap Cell<TDatum>> Heatmapitself is retained and exported.Cell<TDatum> Archetype mixins now narrow
contexton their TDatum/TExtra parameter:Menu Cartesian→Chart Props<TDatum> Context;Menu Options<Cartesian Point<TDatum>> Categorical→Chart Props<TDatum> Context;Menu Options<Categorical Slice<TDatum>> Hierarchical→Chart Props<TExtra> Context;Menu Options<Hierarchy Element<TExtra>> Graph→Chart Props Context.Menu Options<Sankey Element<TNode Extra, TLink Extra>> Basestays element-agnostic (Chart Props. context Menu Context) so scalar charts andMenu Options<any> <Chartcan pass plain toggles.Defaults> usesignature: was positionalStandalone Context Menu (context, is now a single options objectMenu Opt, chart Name, external Svg Ref?) {context. Wrappers that delegate to this hook supply aMenu, chart Name, external Svg Ref?, resolve Element?} resolvecallback that maps a MouseEvent (or the keyboard-focused index) to the chart'sElement TElement.
Added#
Element-shape exports for every archetype that publishes context data:
Heatmap,Cell Categorical,Slice Cartesian,Point Hierarchy,Element Sankey,Element Timeline,Element Radar. SeePoint MIGRATION-for the recipe and the README "Context menus" chapter for usage examples per chart-type.NOTES. md All eleven standalone wrappers (Heatmap, Pie, Funnel, Nightingale, RadialBar, Radar, Treemap, Sunburst, OrgChart, Sankey, Timeline) now resolve a typed element at right-click and at keyboard-activation time. Custom items with predicate-style
hidden/disabledcan branch on the element; sparse positions (no element under the cursor) hide the item by default, mirroring the original heatmap rule.All thirteen cartesian wrappers (Line, Area, Bar, Scatter, Histogram, Waterfall, Box-plot, Candlestick, Ohlc, Dumbbell, Lollipop, Range-area, Range-bar) surface a
Cartesianelement resolved from the active hover point.Point<TDatum>
Changed#
<Chart>(low-level) binds the active hover point into customcontexthandlers asMenu. items[] Cartesian. The default export submenu (PNG/SVG/CSV/PDF) and zoom/pan items are unchanged.Point ChartisDefaults Value. context Menu Feature— the cross-chart provider can carry common toggles (Option<Context Menu Options<any>> enabled,hide,Defaults png) for charts of any archetype.Scale
Notes for consumers#
The 9.1 migration recipe for heatmap is now obsolete; see
MIGRATION- § "0.x / 9.x → 1.0.0" for
the consolidated recipe (covers heatmap and every other chart-type).
[9.1.0] — 2026-05-18#
Adds cell-data pass-through to Heatmap's context menu
(T-GX-AC-01). Item handlers now receive the right-clicked cell
({datum, x, y, value}), enabling drill-down menus such as "open
records for this cell" or "open administrative place" without the
former <details> cell-list workaround under the chart.
See MIGRATION- for the consumer
migration recipe.
Added#
Heatmaptype on the public surface:Cell<TDatum> {datum, x, y, value}.datumisnullfor sparse cells (no data point at the hovered grid intersection).Heatmap— mirrors the cross-chartContext Menu Item<TDatum> Contextbut with cell-aware handlers:Menu Item action: (cell) => void,hidden: boolean | ((cell) => boolean),disabled: boolean | ((cell) => boolean).Heatmap— the new shape forContext Menu Options<TDatum> Heatmap. CarriesChart Props. context Menu items: Heatmapplus the cross-chartContext Menu Item<TDatum>[] enabled,hide,Defaults pngtoggles.Scale Heatmapis now generic-preserving via the standardChart forward+ as-cast pattern used byRef Area,Chart Bar, etc. Consumers writeChart <Heatmapto get typed cells in their menu handlers.Chart<My Datum>>
Changed#
Heatmapis nowChart Props. context Menu Feature(wasOption<Heatmap Context Menu Options<TDatum>> Feature). The shape change is opt-in: charts that only passOption<Context Menu Options> true/false/{enabled, hideare unaffected.Defaults, png Scale} Heatmapresolves the right-clicked cell from the SVG event coordinates (usingChart get+Bounding Client Rect MARGIN+cell/Width cell), captures it in component state, and binds it to every user-supplied item'sHeight action,hidden, anddisabledcallback at menu-render time.Heatmapis excluded fromChart <Chartcontext-merge for theDefaults> contextfield — its cell-aware items have no meaningful cross-chart baseline. Other fields (Menu tooltip,animation,accessibility, …) continue to merge normally.
Notes for consumers#
contextcontinues to produce the export defaults only — no behaviour change for the common case.Menu={true} The default
<Chartrendering, the long-press touch handler, and the existing keyboard-navigation paths (Matrix archetype) are unchanged.Context Menu> Sparse-cell handling: when a user right-clicks a grid intersection with no datum (the heatmap's
dataarray doesn't carry that combination),cell.isdatum null. Usehidden: (cell) => cell.to omit drill-down items that need a real row, or checkdatum === null cell.inside the action.datum != null The legacy
<details>cell-list drill-down workaround under the gap-analysis heatmap (apps/) was removed in this release. The same destinations are now reached via the cell-aware context menu.archives/.../ gap- analysis- heatmap. tsx
Inventory (for follow-up triggers)#
Other chart types still ship a context
prop whose items lack click-element context. Potential follow-up
triggers (none filed yet):
PieChart / Donut / Funnel / Nightingale — could carry a
Pieshape (label, value, datum, fraction) on items.Slice<TDatum> BarChart / Histogram / WaterfallChart — could carry a
Bar(x, y, datum, series).Point<TDatum> ScatterChart / BubbleChart / LineChart — could carry a
Point<TDatum>(x, y, datum, series).SankeyChart — node-vs-link click context.
TreemapChart / SunburstChart / OrgChart — node-with-path context.
GraphChart — node-vs-link click context.
The standalone use hook could grow a generic
element parameter to absorb this pattern across charts — same
"capture element at right-click, wrap items at render time" shape
used here.