All notable, user-facing changes to CanvasXpress are recorded here, newest first, for people who
depend on the library. Entries are generated from the Changes comment block at the top of
build/js/canvasXpress.init.js — the single source of truth,
written on every release — so this file can never fall behind the engine.
Only the most recent releases are shown here; the exhaustive per-version history is rendered on the site at https://canvasxpress.org/updates.html and linked at the bottom of this file.
Versioning & release channels
- Version numbers are sequential
MAJOR.MINORrelease identifiers (e.g.67.4), not semantic-versioning signals: aMAJORbump marks a release milestone, not a breaking change. Package registries and release tags carry the three-part formMAJOR.MINOR.0(e.g.67.4.0). Releases ship as changes are ready — often several in a week during active development. - Backward compatibility is the default. Releases add chart types, parameters, and bug fixes without breaking existing charts. The rare change that alters an existing default or the public API is called out explicitly in its own entry below, whatever the version number.
latest(npmcanvasxpress, CRANcanvasXpress, PyPIcanvasxpress) always points at the newest release and can move on any day.- Pinning for stability: enterprises that need a fixed target should pin an exact version
(e.g.
canvasxpress@67.4.0on npm,version="67.4"on CRAN/PyPI) and upgrade deliberately after reading this file. Nothing changes underneath a pinned version.
[71.7] — 2026-10-15
- Added sankeyHide (Sankey): names a sample annotation flagging links to drop; the ribbon is not drawn but the link stays in the flow graph, so node values, box heights and the showDataValues number are unchanged (#320).
[71.6] — 2026-10-15
- Grouped bar/boxplot ignored a filterUserData filter of raw sample names (dashboard cross-source narrowing): names resolved against group labels and the regroup kept old membership. Now filters ungrouped, regroups fresh.
- Kaplan-Meier kept the filtered curves after a reset: checkKMPlot recomputed only while variables were hidden (count differs), never when they all came back. The cache now records its visible-variable set and recomputes on any change.
- Ridgeline crashed (Object.keys of undefined) when ridgeBy named a sample (x) annotation: no Factor densities were built. Ridge offsets now apply only when they exist; otherwise a plain density is drawn.
- Histogram/Density/Ridgeline over data without variable (z) annotations asked setColorScheme for samples^2 colors (multiplier fell back to the sample count): 278k rows overflowed. The multiplier is now 1 without z.
[71.5] — 2026-10-14
- Clustered heatmap filter ignored after one pick: setDendrograms reused the cached full tree unless toDoFilter was set, so modifyFilter (filterSmpBy/filterVarBy) re-expanded filtered leaves. Fixed; order kept, tree hidden if filtered.
[71.4] — 2026-10-14
- Data table: date-typed sample (x) annotation columns in the transposed view now format as dates -- setDataTableCache's column-meta link was gated on !dataTableTransposed, where x names are never columns, so cells showed epoch millis.
- Oncoprint: the GISTIC 'no alteration' level ('') no longer becomes an empty-named sample annotation (crashed view:'table' on a name-keyed .width); the data table also guards headers missing from its column cache.
- Oncoprint: an explicit graphType 'Oncoprint' (or isOncoprint) on pre-built {y} object data now wires the data2/data3 CNA/MUT layers -- object data skips arrayToCX, so the request was a silent no-op and drew a plain heatmap.
- Oncoprint: ESC (masterReset) no longer demotes an auto-detected oncoprint to a plain heatmap -- the oncoprintCNA/MUT/EXP/Presorted layer wiring is chart identity (like scatterType) and is now carried across the reset.
- Oncoprint: switching a copy-number-only matrix to Oncoprint (Customizer/menu) now converts it to CNA layers; a CNA-only oncoprint no longer throws on the absent mutation layer.
[71.3] — 2026-10-12
- Vector fields: vectorColorValueColor now colours raw arrows (read the unsuffixed name, so it never applied); raw-mode vectorGroupPalette now colours the groups (passed to colorBy via colorKey; a user colorKey wins).
- OptionsWall: put/call flank axes auto-scale to the data again (unset optionsWallPut/CallMin/Max read as 0 via isNumeric(false), pinning 0..1 — premium curves were clipped); now isNumber(v, true).
- Maps: native XYZ raster basemap (canvasXpress.tiles.js) replaces Leaflet: showTiles + tileType/tileAlpha/tileZoomAlphaColor (leaflet* params removed); LRU cache, prev-zoom fallback, on-canvas attribution, no external load.
- Docs (#321): the existing public getSVGString() API, which returns the chart as a serialized SVG string (the programmatic form of the File menu Save as SVG), is now listed in the interface method inventory.
- Maps: panning no longer selects the region under the cursor -- endDrag's Map branch always cleared skipClick (pre-existing; Leaflet masked it) so mouseup fired a click; now preserved (a click still selects).
- Maps: legacy Leaflet config keys (useLeaflet/leafletTileType/leafletAlpha/leafletZoomAlphaColor) are input-only aliases of the tile* keys, so maps saved with the old names keep working after the Leaflet removal.
- Maps: several maps on one page no longer clobber each other's view -- topoJSON-less maps shared one geojson and wrote per-instance center/zoom/parts onto it (all but the last drew wrong; tiles exposed it); each now copies its geojson.
- Maps: orthographic moved to a native ES5 module (canvasXpress.geoNative.js); the vendored d3-geo port (canvasXpress.orthographic.js) removed (Mercator/Albers already in-house). Same seam, render-identical; map-13 pixel-gated.
- Sankey: dropped the vendored d3-collection (nest/Map) grouping for a native ES5 one; getD3Sankey renamed getSankeyLayout. Byte-identical output (19 sankey examples + ggplot alluvial fixtures unchanged); -147 lines.
- Scatter3D: new scatterType 'density' builds a 2D kernel-density surface from scattered points via the native kde2d engine and renders it as a rotatable 3D surface (reuses the function-surface path; no d3). Examples scatter3d-8..10.
- Network: de-d3'd the disjoint force-directed layout -- renamed manyBodyD3 -> networkManyBodyCharge and quadtreeD3 -> networkQuadtree and dropped the 'D3' naming/comments. Pure rename; layout output byte-identical.
- Contour: removed the dead, redundant d3-contour density engine (getD3Contour, ~649 lines, zero callers). 2D KDE density contours use the native kde2d + filledContour; no behavior change.
- Hierarchy: de-d3'd Tree/Circle-pack -- d3hierarchy.js -> hierarchyUtils.js, getD3{Hierarchy,Tree,CirclePack} -> get{Hierarchy,Tree,CirclePack}, 'd3' naming dropped. Pure rename, byte-identical.
- Scatter3D: fxSamples sets the fx/density-surface grid resolution (default 25, was hardcoded; higher = smoother + finer colour); 3D minor grid lines are now OFF by default (show3DMinorGrid; per-axis x/y/zAxisGridMinorShow refine).
- Scatter3D: fx surfaces no longer let setMinY/setMaxY (the fx domain) squish the vertical value axis — it auto-ranges; new aspect3D 'data' scales the 3D box to real per-axis data spans (true proportions; default 'cube' unchanged).
- Scatter3D: 3D axis major-tick count is now automatic (nice number per axis, bounded 4-10) not always 10 — also tightens bounds so the surface fills/centers the box; xAxis3DTicks/yAxis3DTicks/zAxis3DTicks set a target count.
- Scatter3D: new smoothSurface strokes each surface facet with its own fill colour so facets blend seamlessly (no wireframe/seams) for a soft look -- ON by default; set false to show outline3DColor edges. Best with a higher fxSamples.
- Scatter3D: 3D grid fix — each face's two directions now use their OWN axis tick count (grids meet at shared edges when counts differ) and the 2D grid rationale (grey GridMajor/Minor colour + width, not black); frame greyed.
- Scatter3D: *Axis3DTicks is now a TARGET (nice increment for ~N ticks + tight bounds) not a hard count, so a number no longer gives odd wide ranges; and the 3D plot box is larger (canvasWidth/3.3, was /4) to fill more of the panel.
- Scatter3D: 3D axis titles now lay letters along the PROJECTED axis baseline spaced by real pixel widths and follow the axis angle -- was advancing in 3D-coord units by a pixel width, which bunched/overlapped letters.
- Scatter3D: the three 3D axis titles now sit the same distance from their tick labels (a consistent lenX/3 gap) -- the Z title was farther out (step6 vs step4, and step varied per axis).
- Scatter3D: 3D tick labels skip when they would overlap the previous label on the same axis, so an axis viewed nearly edge-on (foreshortened) thins its labels instead of piling them up at the corner; normal views keep all labels.
- Scatter3D: 3D tick labels stay anchored to fixed box edges but skip when they would overlap ANY label already drawn this render -- same or other axis -- so a foreshortened axis thins instead of piling up, without flipping sides.
- Scatter3D: each axis title sits on the same anchored box edge as its ticks, a consistent gap out, so title and labels stay together.
- Scatter3D: zoom stays free but the 3D frame is now clipped (shared setClipArea, as 2D geoms) to the canvas minus the measured legend, so a zoomed-in plot is cut at the edge not painted over the legend/off-canvas; default unchanged.
- Reset (ESC / master reset) now preserves scatterType like graphType, so a 3D function surface or 3D bars no longer collapse to plain points on reset; setConfigDefaults was dropping it.
- Density Scatter3D rebuilds its kde2d surface on draw (idempotent) and pins its axes to the surface X/Y/Z columns, so an ESC/reset reconstitutes the surface with the correct Z axis, not an empty plot or a Z axis of density values.
[71.2] — 2026-10-08
- Customizer Meta panel: property slots (Color/Shape/Size/Group/Segregate/...) pre-fill at load with assigned factors as removable chips, so one can be removed at a time instead of clearing the whole slot; variable/strata shown empty.
- New themes = the canvasxpress.org style presets, same names: paper/slate/mono/sunrise (light, cx-based) and ocean/midnight/graphite/forest (always dark); one name styles the chart AND its widgets; in THEMES, Theme menu, Customizer.
- A chart with no theme on a page whose names a preset (the site's palette icon) takes that preset; explicit theme always wins. Generated 01c-theme-site.css re-tints the --cx-ui-* widget roles per preset.
- One style registry (CanvasXpress.styles + deriveStyleDef): theme also names a widgets look (modern/bms/colorful/rdc/ngs360; [chart, widgets]); dataUIStyle = default|minimal (soft borders); legacy stripped = dataTableStriped:false.
- Every chart theme now styles its widgets from its own accent (CanvasXpress.styles registry: aliases wsj/wallstreet/wallstreetjournal, ggplot/grey/gray/igray, paultol/ptol ... resolve to one style; wallstreet palette fixed).
[71.1] — 2026-10-07
- Site search (canvasxpress.org): the corpus now also indexes the companion repos (README + docs/ markdown), so results span the whole project; repo hits deep-link to GitHub in a new tab. Build/site only; no change to the core library.
- Docs site: four new feature-guide pages (Themes/Colors, Decorations, Statistical Fits & Clustering, Exporting) + Accessibility card on documentation.html, plus a TechArticle/hasPart LLM hook. Site only; no core-library change.
[71.0] — 2026-10-06
- Area graphs: new areaShowColumns (default off) draws a solid bar at each category over the band -- a TrendPlot (stacked bars + translucent area). areaColumnWidth sets bar width (0..1 of slot); composes with any areaType/areaStyle.
- Heatmap overlays: new "Violin" annotation-track type (overlay type:"Violin"), mirroring Boxplot -- a per-segment KDE violin spanning the band, honoring violinScale/violinTrim/violinQuantiles. Default-off; added to Customizer picker.
- Heatmap overlays: getOverlaysForTypePosition grouped EVERY track at a Boxplot/Violin band regardless of type, so a Boxplot/Violin next to a Default/Pie/Bar track clobbered its layout (crash); now groups only same-type tracks.
- Vector-field plots: scatterType "vectorfield" on Scatter2D (new velocity.js) = plotthis VelocityPlot + matplotlib quiver/streamplot; vectorFieldMode raw/grid/stream (arrows, grid quiver, RK4 streamlines by magnitude). Default-off.
- Hover: vector-field arrows/streamlines and clustree nodes/edges now show tooltips -- their custom marks carry ready-made HTML in idx[1], surfaced via a rawHTML branch in extractDataObjectFromIdentifiers (were silent).
- Vector-field raw arrows now colour via colorBy (group annotation): the standard group legend filters groups -- hidden arrows honour the Property legend key like the point loop (grid/stream magnitude ramps stay informational).
[70.9] — 2026-10-05
- Calculated fields: LOD aggregates sum(V,by=A), window calcs (cumsum/lag/movavg/rank/pcttotal, by=/order=), date fns (datetrunc/datediff/dateadd/fiscalyear, UTC; asof() not today()); grammar adds named args (=); grouped dialog picker.
- Scatter2D line hover: a lineBy="Series" line showed "undefined : undefined" because the Series tooltip handler matched the "Line:Series -
" identity; anchored it to ^Series: and show the group label on line hover. - Range decoration hover showed "null - null" for sample-based ranges (smp/smp2): the a||b||c tooltip chain never reached the sample-name branch because "null - null" is truthy; now uses sample names when values are absent.
- Multi-line labels (e.g. a two-line x-axis tick "12 am\nJul 31") ignored the requested baseline and were always centered, so they misaligned with single-line ticks; drawTextMultiple now honors top/bottom/middle.
- Footer "Get the data"/source links were unclickable: the bottom-toolbar trigger strip overlaps the footer band and sat above the link overlay; raised the overlay to --cx-z-footer-link (above the trigger, below the resizer).
- No tooltip when hovering a data point under a toolbar-trigger or resizer edge band (those strips sit above the events canvas): mousemoveDoc now forwards hovers over cX-Toolbar-Trigger/cX-Resizer to the canvas so tooltips still show.
- Drill-down hierarchies (drillHierarchy): click filters to the level value and regroups to the next annotation (Bar/Pie/Treemap group, Circular re-roots, Scatter2D colorBy); HTML breadcrumb, ESC steps up, peers follow; default-off.
- Clustree: new Scatter2D scatterType="clustree" layered clustering-tree from a label matrix or {nodes,edges}: node size=cells+id, colour=resolution; edges colour=count, opacity=in-prop, arrowed; resolution axis + legends; default-off.
[70.8] — 2026-10-05
- Locale number formats: numberFormat + tooltip/legend/dataLabel/*AxisNumberFormat (preset, sprintf or {locale,style,currency,decimals}); Intl-backed, memoized, axis widths follow; default off = legacy path; gate number-format.spec.js.
- Structured chart footer: notes/source(+sourceUrl link)/byline/'Get the data' CSV/footerAlign; canvas-drawn so exports keep it, DOM overlay for links; off by default, citation position unchanged; gate footer.spec.js.
- New showLineEndLabels (Line graphs): series name at each line's end in the series color, legend hidden, overlaps nudged apart, right margin reserved; drawn post-clip on Decorations; off by default; gate annotations.spec.js.
- Marker drag wrote NaN x/y for decorations.marker anchored by variable/sample (curX/curY live on the drawn copy, not the entry): isMarkerDrag/updateDragMarker now seed curX/curY from the grab point, so drag-to-place round-trips.
- New 'editorial' theme (horizontal-only light grid, no box/ticks, left title, muted greys, PaulTol palette) and responsiveRules ([{maxWidth,minWidth,config}]) width overrides that revert on resize; gate responsive.spec.js.
- Footer/citation bottom reservation was gated on citation only, so a footer without a citation overlapped the sample labels (1D + scatter dims); now reserved when either is set. Default off unchanged.
- Pie: new pieShape 'half' (semicircle, election donut with pieInnerRadius) and pieType 'parliament' (one dot per seat on a hemicycle, party wedges); new Pie context menu; gate chart-types.spec.js.
- Arrow dumbbells: new dumbbellArrowDirection 'order' (before -> after); default 'value' unchanged. Vertical arrow dumbbells drew no arrows (equal-x guard); they now point at the smaller value.
- Classed choropleths: colorSpectrumBreaksMethod jenks/quantile/equalWidth (new jenksBreaks, also a calc-field bin method) + stepped legend (colorLegendSteps); off by default; gate map-classify.spec.js.
- Binned colour: sample-column fills ignored colorSpectrumBreaks (legend used them: map and legend disagreed) and the quantised gradient table could shift values near a break up a class; now exact.
- Maps: showMapLabels (fit + no-overlap, contrast colour); mapBubbleColor + matching size legend; WORLD bubbles drew none (centroid table keyed ISO3); topoJSON without mapId crashed (now derived).
- Data table: dataTableColRenderer in-cell bars (zero baseline; summary/annotation rows excluded) and row sparklines; header menu bar toggle; dataTableColFormat takes number formats; gate datatable.spec.js.
- Data table: zero values rendered blank (value || ''), and column formats were applied to numeric-looking headers ('2023' showed as '2023.0'); both fixed.
- a11yDescription: author alt text replaces the generated aria-label (live region still announces data changes) and is added to SVG exports as role/title/desc; gate a11y-description.spec.js.
- a11y: updateData and menu/Customizer redraws never refreshed the canvas aria-label or live region (only full renders did), so both went stale; now refreshed on every data/config redraw.
- Save as PDF (File menu, Customizer, savePDF()): native vector PDF writer (pdf.js, no library); alt text tagged, chart JSON attached like PNG/SVG; PDFtoCX imports it; gate pdf-export.spec.js.
- Data table: in-cell bars/sparklines on numeric metadata (x/z) columns, e.g. a CSV's non-axis columns (the header menu offered them but drew nothing); each column draws only cells of its own kind.
- Data table: with both x and z metadata, the non-transposed table shifted z values up onto the x-annotation rows (last variable row blank/undefined); z rows are now padded in both orientations.
- Data table: clicking In-cell Bars / Remove In-cell Bars in a column header menu now closes the menu (it stayed open after the table redrew).
- Data table: pool rows/columns built beyond the data (first render starts at the default pool) showed as empty striped rows and empty header cells; now hidden. dataTableFitHeight shrinks a short table to its rows.
- Data table: dataTableFitWidth narrows a table to its visible columns (min 400px for the banner and toolbar), like dataTableFitHeight for rows; Bar #19 uses both.
- Data table: resizing a column did not update the horizontal scroller (the scroll image's style width won over its width attribute) until a view change; scroll width and offsets now follow the drag.
- Data table: with dataTableFitWidth, the table re-fits (grows or shrinks, capped at the chart width) when a column resize ends.
- dataUIStyle "minimal": stripped with every outer frame (data table, filters, Customizer, menus) in the inner hairline colour, nearly borderless, in light and dark.
- Data table: dataTableHeaderIcons (auto|always|hover) shows header menu, filter and resize icons only on hover (auto: with the minimal style); sort arrows and an active filter stay visible.
- dataUIStyle: rdc and ngs360 colours swapped (ngs360 blue, rdc grey; logos kept). Table scrollbar thumbs use each style's accent (cx, stripped, minimal, old, bms, rdc, ngs360 were 1.2-1.6:1 on the track; now >= 3:1).
- Pie: bottom legend no longer overlaps the pie (also in layouts, all items drawn, labels follow); legendColumns honoured; citation/footer reserved; parliament and upright half pies sized as half discs (fill the width).
- Gallery: new Time-Series set (25 Datawrapper/editorial charts) + 34 examples across 9 graph types, regenerated by every build from the DW reference specs (tools/dw-examples, build.py --dw-examples).
- Leaflet maps: several useLeaflet charts on one page (all-examples gallery) left all but one blank: loadScripts shared one index across callers; now per-call, and loadLeaflet loads once + queues callers.
- Config schema: optionsWallChain typed object, ribbonBy string-or-array, colorScheme options include its default 'User'; cxplot validate accepted none of these (engine always did). Rerun --schema.
- Circular connections collapsed to the centre when the innermost ring wasn't the central void (the #317 fix forced circularCenterProportion*r); now anchored at the captured inner-ring radius as a ratio of r (on-ring at any size).
[70.7] — 2026-09-30
- Removed the UI CSS variables deprecated in 70.6 (none used by CanvasXpress): --cx-color-extra-dark-gray/dark-gray/medium-gray, --cx-icon-class-a/b/c/r, --cx-color-PRESET[-hover] for 6 presets.
- bms dataUIStyle: strong accent #b529f2 -> #a325da, so accent text on its tint meets WCAG AA (4.76:1, was 3.99:1).
- Version-info tooltip was unreadable in dark UI: it set the surface background but no text colour, so text stayed dark; now uses --cx-ui-text.
- Dark UI: letter/sign icons (font size/style, #, +/-) had no fill so drew black; now --cx-ui-text. Muted #999 icon greys (cxc/cxcc) use --cx-ui-text-muted.
- Customizer: select boxes draw their own theme-coloured chevron, inset from the border (native arrow can't move); textarea return icon centred vertically; +/- spinner buttons spaced out.
- dataUIStyle: Customizer inputs and the password dialog follow each style's border (widgetsBorder applied always, so every style drew modern blue); cx and old now use the compact Customizer.
- dataUIStyle: DataTable frame/search and Customizer cards use the solid border like DataFilter cards (were faint in stripped/old/bms/colorful/ngs360); colorful Show/Hide Toolbar icon was missing.
- colorful dataUIStyle: base colour switched from purple #6a1b9a to blue #1565c0 (first uiAccentPalette colour) for borders, highlights, hovers, tables and odd-row striping; per-item accents unchanged.
- DataFilter: follows a live dataUIStyle switch (icons, colorful accents; selections kept); summary moved into the icon row (overlapped the first header); colorful graph icon visible; Filters label readable in dark.
- Dark UI keeps each dataUIStyle's accent hue (was one indigo): lightened to modern's dark contrast via getDarkAccent/--cx-ui-dark-accent*; colorful items, resize/loader, borders, links too.
- Sankey: showDataValues now appends each node's value to its label, e.g. 'Category A (27)'; off by default (issue #320).
- Code panel: new 'Show Julia code' (dataToJulia) beside R/Python/Cxplot in the menu, Customizer and toolbar, for the CanvasXpress.jl package; emits canvasxpress(Dict(...); key = value, ...).
- Dot graphs with setMin/setMax on the data edge pad the axis by one dot radius (true range kept in *AxisDataMin/Max) so edge dots aren't clipped; arrow dumbbells: head halved and capped to the shaft, shaft stops at the dot edges.
- Layout strips ignored stripTextFontStyle (scaleTextToFont rebuilt the font without the style); the style is re-applied, so bold/italic strip headers work. Default style empty, so unchanged otherwise.
- Bar/Stacked honour an explicit dataValuesFrequency ('first'|'last'|'ends') like Line graphs: only those bars get value labels (zero stack segments skipped). Unset = every bar, unchanged.
- Map markers: text callouts (type line|arrow|curved + x/y canvas fractions or textCoords [lat,lng]) with the 1D-marker connector, and shape 'none' for a callout without a symbol. Plain markers unchanged.
- Country table: Jersey (JEY), Guernsey (GGY), Isle of Man (IMN) added; aliases 'Republic of Serbia', 'North Macedonia', 'Vatican', so EUROPE.json features match data rows instead of an 'undefined' legend entry.
- Left/right-justified top legends (legendHorizontalJustification 0|1) were padded by half the widest label even for discrete legends (ramp-only intent), sitting ~30px off the plot edge; now ramps only.
- New legendJustifyTo ('panel' default | 'plot'): a justified top/bottom legend aligns to the panel edge or, like titleAlign plot::0, to the canvas edge; 1D (variable) top/bottom legends now honour the justification too.
- Bar showDataValues: a negative bar's label was offset as if the bar grew the positive way, landing inside the bar in its own colour (invisible); the offset now follows the value sign.
- New dataValuesHideOverlap (default false): in Stacked graphs a showDataValues label that does not fit its segment (width and height) is skipped instead of being drawn over the neighbours.
- dataTextColor 'auto' (Bar/Stacked showDataValues): black or white per bar/segment by WCAG luminance of its fill (getContrastingTextColor); outside bar labels keep the bar colour. Explicit colours unchanged.
- Variable legends on top/bottom use the factor-legend single-line layout in every path (faceted, colorBy 'variable', BarLine/AreaLine/DotLine/Stacked*Line), not only plain Bar/Line/Area/Stacked.
- Dual-axis graphs (BarLine/DotLine/Stacked*Line, and the AreaLine secondary line) draw point symbols on the line when lineDecoration is set explicitly ('symbol' = series shape); unset keeps plain lines.
[70.6] — 2026-09-29
- Fixed square tooltip-callout arrows in canvasXpress.min.css: the CSS minifier stripped '+' spacing and 0% units in calc()/min()/max(), voiding the rules; build now repairs them.
- UI tokens are now per chart: setUIColors wrote them on the root html element, so the last chart recoloured every other chart's widgets; new applyUITokens scopes them to each chart's cX-DOM.
- dataUIStyle presets unified + WCAG AA: widgets use --cx-ui-* roles (legacy vars still override), per-preset strong accent, --cx-border follows the preset; HTML widgets styled.
- CSS no longer restyles the host page: global rules (box-sizing, input[type=range/file], img/svg) now scoped to CanvasXpress DOM via :where([data-cx-ui-scope]).
- Calc/bin/aggregate/state dialogs rebuilt on shared UI primitives (.cX-Modal, .cX-Button, real button elements); dialogs removed on destroy(); workflow track distinct from its fill.
- Dark UI: theme cxdark/cxblue/auto or new widgetsTheme set data-cx-ui-theme; generated 01b-theme-dark.css swaps the --cx-ui-* roles, also for charts inside a dark container. Calc/aggregate field colours now meet AA.
- UI theme rules are zero-specificity (:where) so a host override like [data-cx-ui-scope] { --cx-ui-accent: #0f766e !important } also wins in dark mode. Theming documented on the Design page.
- One themed checkbox/radio control (05-ui-primitives.css) for every widget checkbox and radio, dark-aware; DataFilter checks were a near-invisible tint. bms/colorful chip tint softened to meet WCAG AA.
[70.5] — 2026-09-29
- highlightMode highlight/ghost now draw highlightSmp marks: adjustColorForHighlightSelection only checked highlightVar, so sample-only highlights never recolored (highlight) or faded with the rest (ghost). Focus unchanged.
- New static CanvasXpress.applyCalculatedFields(data, defs) and validateCalculatedField(formula, names): calculated fields on a data object with no chart, same safe parser, binning and placement as calculatedFields.
[70.4] — 2026-09-28
- Fix #274: colorLegendTitle now titles numeric colorBy (gradient) legends too, and their width fits it; before, only categorical colorBy used it. ggplot labels still take precedence.
[70.3] — 2026-09-28
- Self-contained vignettes render widgets again: the minified lib carries no contiguous "<table" token or raw NUL/non-ASCII bytes, so whole-file text post-processors (e.g. BiocStyle) can't corrupt the library. Identical output.
[70.2] — 2026-09-28
- New Scatter2D forecasts: showForecast fits ses/holt/hw (R HoltWinters, horizon+interval, axes extended), Forecast menu + Customizer; R autoplot(forecast)/geom_forecast render R's own rows. Off by default.
- Live streaming: new pushData(tick) appends samples, evicts oldest beyond streamWindow (drop-oldest), bumps dataVersion to refresh derived caches, then soft-redraws. streamWindow unset = no eviction (byte-identical), off by default.
[70.1] — 2026-09-25
- Grouped charts (e.g. Boxplot groupingFactors) no longer crash when a filter drops a whole group: the kept group order skips out-of-range indices and appends restored groups (shared setGroupIndices, legacy + tidy).
[70.0] — 2026-09-24
- transposeData keeps the authored xAxis/yAxis/zAxis (they name the transposed data) instead of resetting to the first two columns; transpose() gains an optional axes arg, UI transpose unchanged.
[69.9] — 2026-09-24
- VS Code/Positron notebooks: maximize/explore/customize sized to the webview (as tall as the notebook), jumping to its top; new isVSCode detection now expands them in place at notebook width and a screen-fitted height.
- Network #319: sparse forceDirected labels no longer blow up; nodeSize<1 kept fractional (not ceil/parseInt to 1px under unbounded scaleFactorX), minTextSize floor in screen px. Labels track node size; nodeScaleFontFactor applies.
[69.8] — 2026-09-23
- Circular: the top opening added the size-scaled margin (px) as degrees, so it widened with the plot (maximized or high printMagnification, #290); its angular share is now held at the first-render size.
- Color indicator: spectrum bins were fixed 10px strokes stepped colorBinWidth apart, so an enlarged indicator (high printMagnification) was striped with gaps; strokes now cover their bin and ticks scale with the print.
[69.7] — 2026-09-23
- Circular overlay bands and inter-track spacing now scale with the plot size (labels fill the bands), keeping proportion when maximized on screen or exported at high printMagnification; fonts also scale with printMagnification.
- Circular connections now anchor at the proportional inner-ring radius (circularCenterProportion * r), not the accumulated currentRadius whose fixed-pixel gaps don't scale, so endpoints no longer drift off the ring as the graph grows.
- Circular data-track radial axes size tick count to the track's radial length via the axis algorithm, not the full Cartesian set, so short tracks show fewer evenly spaced ticks (always min and max) and scales don't overlap at centre.
- Circular stacked tracks: a track's inner-edge tick label is dropped when the track below it sits within a label height, so the last tick of one scale no longer collides with the first tick of the next.
[69.6] — 2026-09-22
- Website/build: CHANGELOG.md is now generated from this log (recent 40 versions + link to the full update log) so it can no longer drift; governance license text corrected to R=GPL-3/Python=MIT and one site-copy superlative softened.
- Build: new build.py --publish-site publishes a running engine to canvasxpress.org (build + gate + repo push + website git pull) but skips npm/cdnjs publish, the release tag, and pinned artifacts - live on the site but unshipped.
[69.5] — 2026-09-18
- showSampleLegend is now honored instead of being a documented no-op: the sample legend bypassed the show check and its dimensions had a second entry point, so both are gated. Default true = unchanged rendering.
- Internal cleanup, no behavior change: stack() no longer duplicates its default branch for handleWheelEvent, and setLayoutRestore drops a dead commented legend block naming properties removed in the legend refactor.
- New opt-in dataSelection config: declarative named selections (samples/variables, any/all leaves) de-sugaring into filterSmpBy/filterVarBy, round-tripped by getConfig; absent = unchanged. DataFilters gains a visible/total summary.
- Internal cleanup, no behavior change: removed ~297 lines of dead commented-out code (lapack test harnesses, disabled debug/orientation blocks) across seven source files.
- Internal cleanup, no behavior change: dropped a cola debug console.log, collapsed the constant if(reset) gate in setGroupLabels, and folded a duplicate nodeColor registration into defText. Same defaults.
[69.4] — 2026-09-18
- Internal license-audit tooling: finer per-client breakdown in the Explorer and daily report, behind the existing Basic Auth.
[69.3] — 2026-09-18
- Website: the footer Admin link now points to the license-audit Explorer (/license-audit/explorer.html, Basic-Auth protected) and the legacy apps admin pages (dashboard/usage/users/ask) are decommissioned.
- Website: the Explorer gains an MCP Server section (status, per-call LLM prompt text, timing, tokens, cost) fed hourly by mcp-metrics-export.py; the MCP server now stores llm_calls + duration per call.
[69.2] — 2026-09-17
- Network: new lineEdgeBy parameter maps an edge attribute to a per-edge line style (solid/dashed/dotted/...), mirroring colorEdgeBy/sizeEdgeBy, with a lineKey override and a showLineEdgeLegend legend (#314).
- Network: fixed sizeEdgeBy never being validated/cleared when its attribute is absent from the data (validateNetworkPropertyValues checked a mis-cased 'SizeEdgeBy').
- Network: dashed/dotted edges now render in screen pixels instead of model units - edges are stroked under a scaleCanvas transform, so the dash pattern is divided by the layout scale (else short edges looked solid until hover).
- Network: the line-style legend (lineEdgeBy) draws a longer sample so the dash/dot pattern reads clearly instead of looking like a short solid stub.
- 1D graphs (Bar/Line/Dotplot/Boxplot/Area/Stacked): panning after a wheel-zoom no longer greys out samples that scroll back into view - drawIndicesAfterPannEvent now refreshes hidden flags via setSamplesVisible like the wheel path.
- New weightBy: a sample annotation or variable that weighs each sample in counts - Meter count/countTotal, the meterProgress ring (selected/total by weight) and pieBy slices. Off by default = plain count (R count(wt=)).
[69.1] — 2026-09-17
- Network: the generated decorations legend is no longer appended again when an imported config already carries it (customLegend grew by one per config round trip; roundtrip gate network-15).
[69.0] — 2026-09-15
- Scatter redraw performance: recording contexts skip the per-shape live reset (reset once before replay) and variable-keyed colorBy/shapeBy/patternBy are memoized across redraws; 150k-point redraw 650 -> 475 ms, byte-identical.
[68.9] — 2026-09-15
- TypeScript definitions: the npm package ships strict, generated types for CommonJS (.d.ts) and ESM (.d.mts): a closed graphType union and CXConfigFor
with the keys that apply to one graph type. - Config schema accuracy: THEMES lists every theme name setTheme accepts (paulTol, void, wallstreet, ...); widthFactor is a float; the *TickFormat parameters accept a string or an array.
- AI outbound policy: CanvasXpress.llmOutbound = false, or , blocks every request to the LLM service on the page; the chat then uses local autocomplete only.
- New yieldingRender option (default false): yields after data ingestion and paints large charts in short requestAnimationFrame slices so the page stays responsive; rendering becomes asynchronous, pixels are identical.
- Large-data performance: per-sample range metadata is computed in one pass and, above 2,000 samples, kept in a compact store built on demand; a 1M-sample scatter cold-renders about twice as fast with identical output.
- Reproducible layouts: the last unseeded Math.random calls in the layout path (network jiggles, 3D cola initial positions, the formula token rand) now use the seeded generator (randomSeed).
[68.8] — 2026-09-15
- TypeScript defs: build/js/canvasXpress.d.ts (public-API types) generated from the config schema via tools/schema/generate-dts.js on --schema; 1686 keys + enum unions, copied beside min.js. Types-only sidecar, 0 bytes in min.js.
- Python config types: data/schema/canvasxpress_config.py (CXConfig TypedDict, 1686 keys + enum Literals) generated from the config schema via tools/schema/generate-pyi.js on --schema, for Jupyter/VS Code (Pylance) authoring.
- R config catalog: R/canvasXpress/inst/config/config-params.json (1686 params + enum options) generated from the config schema via tools/schema/generate-rd.js on --schema; new cxConfigParams()/cxValidateConfig() read it.
- Config validation: Python validate_config() in canvasxpress_config.py (schema enums embedded) + R canvasXpress(validate=TRUE) opt-in calling cxValidateConfig(); both warn on unknown params/invalid enum values from the config schema.
[68.7] — 2026-09-14
- R ggplot.as.list: Aug-21 CRAN merge-back dropped dev blocks (GeomText built data/panel, errorScope, sankey/lodes, legend hoist, axis relabels, decompiled); restored - dodged text/errorbars in facets draw again.
[68.6] — 2026-09-14
- Python code panel: new toPythonLiteral/eventsToPython/canvasXpressToPython emit valid Python (escaped strings, None, CXEvent/CXEvents, quoted render_to, cleaned data); dataToPython rebuilt on them for script/Jupyter/Streamlit/Shiny.
- Fixed shrunken native title/subtitle/legend fonts (#313): setTheme sends every chart through gg_theme, so v65.8 ggFontScale (x0.68/x0.82) leaked onto native charts; now gated to isCxplot - only ggplot/cxplot scale, native/R keep 1.
[68.5] — 2026-09-10
- IO: de-duplicated XMLToJSON - gexf/metabase/cytoscape/kegg converters alias the canonical this.XMLToJSON via bind(this) instead of re-declaring byte-identical copies (~128 lines); wikipathway variant kept (different push).
- IO: SVGtoCX aliases the canonical this.parseXML instead of re-declaring an identical local copy (~20 lines); body uses no instance state so behavior is unchanged.
- Layout: d3-hierarchy port duplicated in circlePackUtils/treeUtils moved to shared canvasXpress.d3hierarchy.js (this.getD3Hierarchy); TreeNode inherits hierarchy.prototype. Net ~172 lines; Tree/Circular/Sunburst/Treemap verified.
- Ideogram: getShade cytoband-stain colour map duplicated in genome/circular moved to shared canvasXpress.ideogram.js (this.getIdeogramBandColor); both call it. Net ~20 lines; Genome/Circular ideograms verified.
- Removed ~347 lines of dead shims: self-disabling Blob.js + unused canvas-toBlob.js polyfills (save.js), IE ActiveXObject XML branch (IO.js), Presto-Opera wheel line. Native Blob/toBlob assumed; saveAs kept; probe-verified.
- Removed 4 dead vendored-scaffolding functions (0 callers): toMatrixFortran/loess_ifit (loess.js), putFortramMatrix/dlapy2 (lapack.js); ~38 lines. Full suite verified.
- WebCola prune: removed ~1,179 lines of unreachable cola.js (GridRouter/tangent-visibility/PowerGraph, gridify/route/prepareEdgeRouting) - never called, powerGraph never set. Layout/adaptor/Layout3D/packing kept; suite green.
[68.4] — 2026-09-10
- Autocomplete: derive the 169-entry modifier table from the config registry (doc.P) via a presentation-table join (deriveModifiersFromRegistry); enum options/phrases follow doc.P so new options auto-surface. Byte-identical to old.
[68.3] — 2026-09-10
- Config JSON Schema: option lists with a non-string sentinel (colorBy, lineBy and 62 others, which also take a column name) emit x-cx-options instead of enum, so the schema no longer rejects valid configs.
[68.2] — 2026-09-09
- Added a JSON Schema (draft 2020-12) for the full config object, generated from the internal CanvasXpress.doc registry via tools/schema/generate-config-schema.js and published at /schema/canvasxpress-config-{latest,version}.schema.json
[68.1] — 2026-09-09
- Docs/site: benchmarks page gains an "It loads more than it plots" section — CanvasXpress ingests and indexes the whole dataset (unplotted variables, annotations, metadata) for analytics, not just the drawn columns.
- Perf: deepFreeze uses a numeric-index fast path for arrays (avoids per-row getOwnPropertyNames allocation + string-keyed access on the data matrix), cutting source-data freeze ~9x on large scatter (40k: 54->6ms); result unchanged.
- Perf: getObjectArrayOrder caches the value->index hash for unfrozen arrays too (signature-validated by len+endpoints), collapsing ~30 pre-freeze rebuilds of the smps hash per render to 1; ~20% faster render at 40k, output identical.
- Perf: numeric-annotation metadata (assignNumericVisuals) bails at the 11th distinct value instead of unique()+sort of every cell (>10 distinct always uses colorBrew.vals); ~25% faster with many continuous annotations, levels same.
- Perf: validateData drops the throwaway JSON.stringify of the full data (early malformed-data guard); circular refs are still caught downstream at clone/freeze/serialize. Removes a full-matrix serialization from cold render.
- Perf: cloneObject (reproducible-code clone) reuses the plain-object/array check for array detection instead of a second toString probe per node; behavior/output byte-identical. Combined 5+6: ~20% faster cold render at 40k.
[68.0] — 2026-09-04
- Docs/site: comparison pages, SEO metadata/JSON-LD and llms.txt corrected to state 40+ chart types (not 60+) and describe the JavaScript library as source-available (not open-source), consistent with the license model.
[67.9] — 2026-09-04
- Spec: cxfigure/cxplot JSON Schemas promoted from draft to versioned 1.0; cxfigure documents more config keys with types (additionalProperties kept open per the forward-render guarantee); internal refs stripped.
- Performance: processDensityResults built its draw-order list with unshift() (O(n) each -> O(n^2)); now push()+reverse() (O(n)). Scatter redraw ~1069->628ms at 150k marks; byte-identical (markers 28/28, examples 485/485).
[67.8] — 2026-09-04
- Performance: getObjectArrayOrder memoizes its lookup for frozen arrays (8-slot LRU), so getSampleIndices/getVariableIndices stop rebuilding the order hash each redraw. Large-scatter warm redraw ~5x faster (1M 264->14ms).
- Performance: ungrouped/unique-name simple path (computeWrangledDataProjection) copies data.y.data directly instead of a getTidy+tidyPivotWider round-trip rebuilding the in-hand matrix. Cold render ~27% faster at 1M (3009->2190ms).
- Bugfix: Network #311 - a background pan released OUTSIDE the canvas no longer commits its offset (endDrag), so the network stops creeping off-screen/blanking and the offset stops compounding across drags. In-bounds pans unchanged.
For the complete release history of every version, see the full update log.