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.
[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.
[67.7] — 2026-09-03
- Integrations: Jupyter widget (anywidget), %%cxplot magic, Streamlit/Dash, VS Code renderer, Quarto, Observable, CanvasXpressBio (Bioc); site adds governance/support/changelog/LICENSE pages + guide; release tags X.Y.0
[67.6] — 2026-09-03
- Accessibility (WCAG 1.1.1, 1.3.1): updateA11yDataTable adds a visually-hidden, screen-reader table of the y matrix next to the canvas (aria-describedby, td corner + th scope headers, bounded 200x50). Offscreen only; no pixel change.
- Accessibility (WCAG 4.1.3): a polite aria-live status region announces "Chart updated" + the new summary when the chart's data/config changes (silent on first render and on unchanged re-renders). Offscreen; no pixel change.
- Accessibility (WCAG 2.1.1): canvas is keyboard-focusable (tabindex=0); arrow keys/Home/End rove marks announcing "
, : " via the live region + a DOM focus outline (no pixels); honors disableKeys/disableArrowKeys. - Accessibility (WCAG 2.1.1): menu/toolbar/customizer widgets are keyboard-operable — data-callback-click elements get tabindex+role=button (dynamic via MutationObserver); Enter/Space activates, Escape closes menus. No pixel change.
- Accessibility (WCAG 2.4.7, 2.3.3): an injected :focus-visible outline shows keyboard focus on the canvas + widgets; prefers-reduced-motion is honored — render transitions skip when the OS requests it. No pixel change.
- Accessibility (WCAG 4.1.2): icon widgets given role=button now also get an aria-label (from their title or humanized callback), and the data-table corner is a
not an empty . No pixel change. - Fixed a chart opened in table view (view:'table') stuck on "Rows: 0 Cols: 0": the row draw runs before the count is set, so setInitialView now refreshes updateDataTableDimensionsInfo after the grid is built (fixes grid.html).
- Licensing: header banner reworded to the Community model — free incl. commercial while the attribution mark stays; a commercial license removes it; mark/key circumvention barred (DMCA 1201 / EU 2001/29/EC). Behavior unchanged.
- Accessibility (WCAG 1.4.1/1.4.3/1.4.11): new theme "highcontrast" — solid white ground, pure-black text/titles/axes/borders/guides + pattern line decoration so series read without colour. Additive; other themes unaffected.
[67.5] — 2026-09-02
- Spec-as-a-standard: getConfig() now stamps a schemaVersion (factory.specSchemaVersion, '1.0') on the serialized figure, and materialized cxplot snapshots carry it too, so a figure is self-describing. Additive; render unchanged.
- Spec-as-a-standard: new CanvasXpress.cxdiff(a,b) + specsEqual(a,b) — a pure structural diff of two serialized figures (added/removed/changed paths, schemaVersion ignored by default) for the round-trip guarantee and reviewers.
- Spec-as-a-standard: new CanvasXpress.provenanceHash(figure) + canonicalJSON(value) — an optional, verifiable fingerprint over a canonicalized figure (stable key order, volatile fields stripped) so a figure in a report is checkable.
- UpSet: configureOverlays now guards the derived 'Size'/'Sets' overlays with an indexOf check, so re-importing a saved UpSet config (round-trip) no longer accumulates duplicate smpOverlays/varOverlays entries. First render unchanged.
- Network: a background pan released outside the canvas no longer flings the view off-screen (blank). updateDragNetwork clamps the pointer to the canvas before deriving panningX/Y, so endDrag commits a bounded offset (#311, regression).
[67.4] — 2026-09-02
- Parquet decompressors hoisted to a reusable IO.getDecompressors(); new public seams gunzip(bytes[,len]) (len recovered from gzip trailer) and decompress(bytes) (magic-byte sniff: gzip/zstd, else passthrough).
- New IO.untar(bytes) walks a USTAR/GNU/pax tar into [{name,bytes,size}] regular-file entries (dirs/metadata skipped, GNU/pax long names honored); .tar.gz = untar(gunzip(bytes)). Adds IO.u8ToStr UTF-8 decode helper.
- New IO.resolveArchive(entries) picks data vs config from an archive: manifest (JSON string data: names a sibling), config+data pair, or single file, else ambiguous. Adds archiveEntryFormat/isArchiveDataName/classifyArchiveJson.
- loadFile handles .tar/.tgz/.tar.gz (processArchiveFile: gunzip+untar+resolveArchive) and .gz (processGzipFile); IO.archiveToCXObject converts the data entry (delimited/json/parquet) with archive config applied, then updateRemoteData.
- loadFile also handles .zip (processArchiveFile sniffs PK): new IO.unzipArchive (name avoids utilsGeneral.unzip) walks the central directory, extracting stored + deflate entries via IO.inflateRaw, which gzip-wraps DEFLATE for codec.
- Archive entries in other formats (svg/png/geojson/gml/gexf/xml/esv) now load too: loadArchiveDescriptor delegates them to their existing process*File handlers via a synthesized File (delimited/json/parquet still convert in place).
- Archives also load from a URL (data:".zip/.tar/.tar.gz/.gz"): loadRemoteData fetches arraybuffer, unarchives, converts via archiveToCXObject (now also handles svg/png); graph/map formats in a URL archive stay drop-only for now.
- Fixed #312: with multiple charts on a page, a chart's hover tooltip lingered (up to infoTimeOut, 3s) after moving to another chart. getSetCurrentObject now resets the outgoing chart's info span when the current chart changes.
[67.3] — 2026-09-01
- R/Shiny Network: this.setTimeout binds its callback under isR (bare setTimeout dropped this), so the self-rescheduling resetAspectRatioProperties during layout no longer throws "not a function" each tick and blanks the canvas (#311).
[67.2] — 2026-09-01
- R/Shiny Network: a node no longer sticks to the cursor after release. mouseupDoc's isR ownership guard is now overridden while this chart has an active drag/move/pan/lasso (hasActiveInteraction), so resetFlags/endDrag always run.
[67.1] — 2026-08-31
- New graphType OptionsWall: price+options composite — candlestick center (CX's first candlesticks), put/call flanks (IV|premium area curve + volume bars) on a shared vertical strike axis, and a dashed spot ruler across all 3 panels.
[67.0] — 2026-08-31
- R/Shiny: a canvasXpress no longer breaks other draggable widgets (e.g. sliderInput). mouseupDoc swallows a document mouseup only when the drag started inside this chart's own DOM (isOwnEventTarget), replacing the buggy /shiny/i guard.
- isDataFiltered no longer throws on ESC/reset when varIndices/smpIndices or data.y is absent (non-tabular graphs on dashboards); it now guards those and returns false.
- Violin Boxplot: wheel-zoom no longer throws in setRangeData when leading violins scroll out of view. The ydensity range loop now skips groups/violins with no density entry instead of reading .density.xy on undefined.
- OptionsWall: new optionsWallPutMin/Max + optionsWallCallMin/Max pin each flank's value axis to a fixed range (unset = auto 0..expiry max), so a multi-expiry slider holds the put/call metric axes constant for cross-date comparison.
- OptionsWall: left gutter now sized from the drawn price-axis labels (domain nice-numbers), not the per-expiry strike ladder, so two expiries/instances with the same price axis get the same left margin instead of drifting.
- OptionsWall: new optionsWallCenterSpot (default off) centers the value axis on the spot price (radius covers the farther side) so roughly equal strikes fall above and below it while keeping every candle visible.
- New transitionType 'crossfade' (default 'morph'): showTransition dissolves the old frame into the new one via an overlay canvas (old faded out over the drawn new frame) — smooth for composites whose marks change count.
- HiDPI: resizeCanvas sets the pixel ratio via an absolute setTransform, not a relative scale(), so a same-size re-render can't compound it (browsers no-op canvas.width=sameValue) — fixes the graph sometimes rendering 2x, top-left only.
[66.9] — 2026-08-28
- Theme reset (customizeThemeDifinitions) now seeds smpTextColor/smpTitleColor and varTextColor/varTitleColor to the default greys/black, so sample and variable labels/titles have a consistent baseline before per-theme overrides apply.
[66.8] — 2026-08-27
- Themes classic, minimal, light, cx/canvasxpress, ggblanket, highcharts, paultol, void, linedraw and bw now default the meter card to a white fill (meterCardBackgroundColor) with a greyish #cccccc border (meterCardBorderColor).
- Themes dark and excel now default meterCardBackgroundColor to their panel background (#7f7f7f, #C0C0C0) so the meter card fill matches the panel; dark also matches meterCardBorderColor to it (#7f7f7f) so the card border blends in.
- cxdark and cxblue themes now set varTextColor and varTitleColor to white, matching their existing smpTextColor/smpTitleColor overrides, so heatmap variable labels/titles are legible on the dark panel.
- Native multi-panel matrix: a {datasets, isGGMatrix|isPatchwork} spec renders its cols x rows grid in-core via CanvasXpress.matrixHost (one child cell per dataset), no longer needing the R wrapper. Gated; single plots untouched.
- Native matrix perimeter axes: matrixHost stamps a ggMatrixRole (row/col) per cell; resetGgmatrix keeps the y-axis on the left column and x-axis on the bottom row, interior blank. Cell canvas wrapped in a div so the grid lays out.
- Native matrix panel alignment: matrixHost reduces cell getDesiredMargins to a uniform max, feeds setPlotLeft/Top/X/Y back so panel bodies share one origin/size. setPlotArea now honors setPlot* (2D; no-op unless set) like the 1D path.
- Native matrix host is reachable via getObject(target) (own registry), owns group setDimensions (resize pins each cell box + re-reconciles, keeping alignment) and getImage (composites all cells into one PNG). Legends stay suppressed.
- isGGMatrix is now a plain boolean flag (defBoolean) not the R longestString margin string; the R converter emits per-cell isGGMatrix=TRUE and drops longestString (alignment measured at render time). Old string specs still work.
- Native matrix: the R htmlwidget wrapper delegates the ggmatrix/patchwork grid to CanvasXpress.matrixHost instead of hand-building a blank-axis table, so canvasXpress(ggpairs(...)) gets perimeter axes and aligned bodies.
- Native matrix: faceted cells (segregateVariablesBy/SamplesBy) are exempted from the setPlot* reconcile -- the layout-compartment system does not compose with the body override and cramped them; they render at their natural layout.
- Native matrix: the alignment reconcile is per-column/per-row now, not a matrix-wide max, so an interior column with no y-axis reserves its own small left and fills the cell instead of an empty gutter; cells still align per column.
- Native matrix: faceted-cell axis titles (drawLabelsTitlesLayout) ignored the perimeter role, so an interior faceted cell leaked its x-title. Titles now gated on ggMatrixRole: y-title first column, x-title last row; else hidden.
- Native matrix: setLayoutBottom reserved a faceted cell's x-axis height even when the role hid it, leaving empty space below the interior cell. That reserve is gated on ggMatrixRole now, so a role-hidden x-axis takes no space.
- Native matrix: a perimeter faceted cell now shows its shared axis -- the reconcile forces the facet axis flags on (x last row, y first column) and invalidates its cached layout so layoutRestore re-captures them; the ticks then draw.
- Native matrix: a faceted histogram derives no xAxisTitle. ggMatrixXTitle() uses the segregating category (column identity) for a perimeter faceted cell, feeding get2DXAxisHeight (reserve height) and the draw; title clears the ticks.
- cxplot empty-box rule: a ggplot cell with no data and only geom_text/geom_label layers (ggpairs correlation panel) no longer falls through to a Bar; it renders an empty box with the labels stacked and centered (align:center).
- Native matrix: a faceted cell aligns to its column (gtable math). ggMatrixLeft/Right set its facet frame to columnReserve-ownReserve; the facet re-adds ownReserve at draw, so the panel lands at columnReserve exactly, at any size.
- Native matrix: resetGgmatrix keep-sets are orientation-aware. A vertical 1D cell maps its bottom axis to smp* and left to xAxis*, so first column keeps xAxisShow and bottom row keeps showSampleNames+smpTitle, not the 2D flags.
- cxplot geom_bar count of a categorical (x = fill = factor, stat=count, e.g. ggpairs sex diagonal) aggregates to one bar per level (summaryType sum) not N raw samples, and keeps the per-level x-axis labels (not hidden as a duplicate).
- cxplot overlapping areas/densities: draw2DAreaData reversed the color-by levels so the FIRST level drew on top; ggplot draws in factor order (LAST on top). The reverse is native-only now, so cxplot matches ggplot z-order.
- Native matrix cell parity (isGGMatrix): boxplot outliers capped at 6px (was boxWidth/10, huge on wide boxes); scatter markers 2.1x; diagonal density draws in ggplot factor order (last level on top).
[66.7] — 2026-08-27
- Heatmap cell click now colors the cell's row/column axis labels with the selection/highlight color, matching selected elements in other broadcast-linked graphs; honors broadcastGroup, no change without a selection.
- Heatmap now outlines selected cells: whole column when only a variable is selected, whole row when only a sample, intersection cells when both; entity-resolved so scatter/bar/pie/treemap broadcasts all mark. None without a selection.
- Fixed gradient SVG icons (cx/cxdark/cxblue theme tiles) rendering blank when several share a page: reused gradient ids made every url(#id) resolve to the first. $cXI now suffixes each icon's gradient ids per-instance.
- Added meterCardBackground and meterCardBorder attributes for the meterCard frame fill/border; unchanged except on cxdark, cxblue, economist, solarized, wsj and stata, which set both to their panel background so the card blends in.
- Meter hover highlights the visible mark on every tile and type (ring, dial, bars, light, card): each type's marks group with the tile hit-area, highlightCanvas skips paint-nothing hit-areas and no longer clips meter marks to tile 1.
- Meter tiles now broadcast on click: grouped tiles select their level's samples (was a no-op); single ungrouped meters (new MeterAll id) select every sample they aggregate. Both feed CanvasXpress.selector so linked charts light up.
- meterCard now suppresses the panel-background window/border (as it already did for DOE meters), so a panel-border theme like excel no longer draws a rectangle slicing through the dial and title; the per-tile card is the only frame.
- Ring meter sizing now fits the largest dial in the card interior (title + margins reserved, stroke-aware so it never spills), so portrait tiles (small dashboard cards) get a bigger ring and larger centered value with even margins.
- Network single click / drag now only selects & highlights nodes (via highlighted edges), no square or resize handles. Double click a node for the resize box + handles; showNodeResizeHandles true always shows them.
- Network node drag no longer fires a spurious click that selects the dragged node on release: endDragNetwork now preserves skipClick like endDrag (#271/#297).
- Fixed sizeNodeBy nodeEdges sizing all nodes equally: updateNetworkMetaData summarized the frozen this.data.nodes, missing computed nodeEdges which lives on the render nodes; targeted property registers now read via getNodes/getEdges.
- Circular networks with sizeNodeBy no longer overlap large nodes with neighbors: nodeSize is shrunk by the minimal neighbor-clearance factor so marks stay within their angular slice; centers unchanged, non-crowded charts untouched.
- workflowBy timeline widget no longer overflows the canvas and sits higher, tucked under the plot: the reserved plot band (2D, 1D-plot, Map) and its upward offset are both 130px, clearing the 101px widget plus a small margin.
[66.5] — 2026-08-26
- Fixed R example generator (get_R_props) to double-quote list() names that are not syntactic R names (e.g. "06h", "1st") so cX-function.R sources without a syntax error. (#306)
- Regenerated the golden ggplot fixtures so test-other-ggplot-fixtures.R matches the current (CRAN-merged) ggplot.as.list converter output; the goldens were stale, not the converter. (#307)
- Fixed bottom sample title clipping on short/wide 1D vertical plots: clamp the title baseline (draw1DYLayoutSampleTitle) so its width-auto-scaled glyphs never spill past marginBottom; no-op when it already fits.
- Added opt-in "auto" theme: only theme:"auto" follows the OS prefers-color-scheme (dark -> cxdark, light -> cx), re-theming live on OS flip. Unset/"none" and all other themes are unchanged and never react to the OS.
- cxdark theme panel/plot background changed from pure black #000000 to #0f1115 to match the site's OS dark-mode page background so charts blend into dark pages.
- Added "cxblue" theme: a clone of cxdark whose panel/plot background is a dark navy blue (#1e3a8a) instead of near-black; white axis/text styling is inherited from cxdark.
- Categorical overlay names now draw in legendTextColor (not overlayTextColor) so they follow the theme and match the legend (e.g. white on cxdark/cxblue); continuous overlay names keep their swatch colour. No change on light themes.
- Added theme icons c_canvasxpressdark, c_canvasxpressblue, c_ggblanket, c_bw, c_classic and c_minimal (source in svg.py) and wired them into the customizer Theme panel (5 columns x 3 rows) so those themes are selectable there.
[66.4] — 2026-08-25
- Fixed clicking a data point after destroy() throwing "cannot set property of undefined" at CanvasXpress.selector.groups: destroy's selector reset omitted the groups key the static initializer and resetSelected create.
- Added meterRingValueDigits (default 6): a ring meter's centered value font is fitted to a fixed-digit reference string, not the value, so same-size tiles share one font whether they read "1" or "250000"; 0 restores per-value fitting.
- Fixed ESC/master-reset throwing "Cannot read properties of null (reading 'graphType')" in removeHistogram when a DOE reset ran with no histogram active: removeHistogram now no-ops (per its contract) when meta.data.density is null.
- Added page-level saved states: savePageState/applyPageState/deletePageState/getPageStates share one named shelfStates set across all instances (or a broadcastGroup); Save-state dialog gains a "Save across all graphs" checkbox.
- Fixed broadcast DataFilter not shown in peer DataFilter panels: broadcastDataFilter filtered peer data but left panel meta (clicked/toDoFilter) untouched; syncBroadcastFilterPanel mirrors it, resolving names across peer var/smp axes.
- ESC now restores the last GLOBAL saved state across every graph together when one exists (via applyPageState) instead of only the focused graph; with no page-level state ESC clears to defaults as before.
- ESC/masterReset no longer deletes saved states on reset graphs: setConfigDefaults wiped the shelfStates array on every peer; masterReset carries the shelfStates LIST across the reset and clears shelfActiveState so none shows active.
- Fixed applyPageState restoring only one graph: each applyShelfState runs masterReset, which broadcast the reset and clobbered already-restored peers; a CanvasXpress.applyingPageState flag keeps masterReset self-only during fan-out.
- Selecting a GLOBAL saved state from any single graph's list now restores every graph: applyShelfState delegates a pageState-tagged entry to applyPageState (guarded against re-entrancy) instead of restoring only the clicked chart.
- Table-view instances now honor global states: ESC checks page state before the table-reset branch, and applyShelfState rebuilds the table by toggling the view ('canvas'->'table') after restore (masterReset had left "Rows: 0").
- Fixed a table stranded in canvas view ("Rows: 0") after a reset: setConfigDefaults flips view to the 'canvas' default, so masterReset now keys its table-grid rebuild off the PRE-reset view and toggles 'canvas'->'table' to repopulate.
- Fixed the datatable jumping to n-1 rows on a funnel toggle after restoring a saved state: saveShelfState/applyShelfState now snapshot/restore toDoFilter, so the funnel rebuilds from the FULL restored filter, not just the new toggle.
- Deleting a GLOBAL state via its × now removes it from every graph, matching save/restore-all: deleteShelfState delegates a pageState entry to deletePageState (guarded against re-entrancy during the fan-out).
- User "Reset filters" is now page-wide (broadcastFilterReset): clears filters on every broadcastGroup peer and drops the active-state mark everywhere (reset=diverge, saved states kept). Internal resetDataFilter stays local.
- DataFilter broadcast now filters peers by the origin's resolved visible NAMES (broadcastVisibleNames), not by re-applying its annotation filter — so metadata filters broadcast to every peer, not just those sharing the annotation.
- DOE composites now reflect a broadcast filter: raw modifyFilter left the DOE unfiltered, so broadcastVisibleNames routes an isDOE peer through updateDataFilter (removeDOE -> filter -> createDOE), guarded against re-entrant fanout.
- Filtering FROM a DOE (clicking a segment) now broadcasts to the other graphs: handleDOEClick calls broadcastDataFilter, and the broadcast is no longer gated on toDoFilter (a DOE click sets indices directly, leaving it undefined).
- Fixed ESC removing the DOE when pressed on another graph: masterReset's fanout gave a DOE peer the generic reset (tore down the composite, no rebuild); it now resets a DOE peer through resetDOE (removeDOE -> reset -> createDOE).
- Fixed the DOE first (Data) cell flipping orientation on re-render (reset/refilter): drawLayoutCompartmentsDOE had its per-cell state restore commented out, so annotation cells' graphOrientation='vertical' leaked; restore reinstated.
- ESC on a DOE now resets the other graphs too (it had only reset the DOE): masterReset takes a fanout flag the ESC handler sets, so a DOE runs the group reset (still resetDOE-only for internal filter/state resets).
- Fixed broadcast peers stuck at n-1 after re-checking a DataFilter item: each broadcast rebuilds every peer (clear toDoFilter, re-apply the origin's visible names via updateDataFilter), so unfiltering clears peers; no accumulation.
[66.3] — 2026-08-24
- Fixed the Explore/Filters panel showing empty Variables/Samples sections for Meter charts: loadDataFilter() switch had no 'Meter' case, so no sample/variable filters (e.g. Species) were built.
- Fixed Meter not updating on data filter: summarizeMeter() computed its value from the full data row, ignoring smpIndices, so the gauge stayed at the unfiltered total. Now subsets values/group annotations by the visible indices.
- Fixed Explore Meta Data (createDOE) crashing for Meter charts: the primary cell's layout data is false but three meter fns dereferenced layoutData.y.meter, aborting the draw. Now falls back to the real dataset.
- Fixed the Meter drawing empty in its DOE cell: summarizeMeter cached the tile size before the DOE layout assigned the primary cell its dimensions (0 wide). Added refreshMeterTileDimensions, recomputed at draw time.
- Fixed 'number' Meter tiles labeled with the variable name (e.g. mpg) instead of the grouping-factor level when split by a groupingFactor; each grouped tile now shows its level, falling back to the variable when ungrouped.
- Added a new 'ring' meterType: a thick KPI circle around a lightly tinted disc with the value centered and the title above; ring thickness scales with meterThickness. Registered in the meterType option enum.
- Added a new 'ring' meterType parameter meterRingTitlePosition (top|topLeft) to place the title centered above the circle or in the card's top-left corner.
- Meter/Bullet consolidation groundwork: added resolveIndicatorRanges() as the shared range/target model; Meter and Bullet now route through it (no behavior change, verified byte-identical).
- graphType:'Bullet' with meterCard now renders an aggregated card-tile grid (summaryType + groupingFactors -> tiles) via the Meter renderer; orientation via meterType. Per-sample bullets are unchanged.
- Meter radial marks (gauge/speedometer/digital) now draw a target/reference tick when bulletTargetVarName is set (summarized on the meter's basis) - the same target concept bullets have. Meters without a target are unchanged.
- Grouped Meter tiles now join selection + broadcast: each carries a Property:
: :x hit-area, so clicking selects that level's samples and broadcasts to linked charts; unselected tiles veil. isSelected got a Meter branch. - Added meterProgress (ring meter): a selection-aware progress ring - a grey track with an accent arc sized to the fraction of records selected via broadcast, the selected count centered. Default off.
- Added broadcastFilter (default true): a DataFilter selection now fires modifyFilter('guess','like') on each broadcastGroup peer. smps/vars filters use only the origin's real names so stray names can't wipe afterRender charts.
- Derived charts now reflect DataFilter/broadcast/reset: meterProgress counts VISIBLE samples; Treemap/Pie re-derive their frozen block at draw time; Scatter resets density; loadDataFilter gained 'Treemap'; tables refresh live.
[66.2] — 2026-08-21
- Unified highlightMode and selectionMode on the same options (highlight | ghost | focus); focus greys the non-chosen marks (grey lightness = highlightGreyOut, default 0.15) keeping the chosen in full color (storytelling focus technique).
- Added highlightBy predicate: populates highlightVar/highlightSmp from data (stat aggregation or annotation field) before render and auto-enables focus mode.
- Merged highlightGreyOut (on/off flag) and highlightGreyOutTransparency into a single float highlightGreyOut: 0 disables, >0 is the mouseover grey-overlay proportion.
- Added single emphasis color highlightColor; varHighlightColor / smpHighlightColor / nodeHighlightColor / selectionColor now inherit it (kept as deprecated per-target overrides, none removed).
- Added dashboardsServiceURL and connectorsServiceURL config (LLM class), mirroring llmServiceURL; default to the built-in www.canvasxpress.org/dashboards/ and /connectors/ (override with your own localhost:8200/8300).
- Fixed getConfig round-trip for aliased graph types: it now restores the public graphType and graphName params (e.g. KaplanMeier/CDF/Bin) instead of saving the internal Scatter2D form, so a saved KaplanMeier reloads as a KM plot rather than a bare scatter.
[66.1] — 2026-08-19
- LLM example generator supports Oncoprint: registered as a graph with a gene x sample alteration-matrix dataset, a graphTypeDatasets template, and an initConfig Oncoprint -> Heatmap alias so configs render the mutation/CNA layers.
- Fixed a programmatic alteration matrix rendering as a blank numeric heatmap: initializeData now carries the oncoprint flags (oncoprintCNA/oncoprintMUT/oncoprintPresorted/overlaysThickness) from the converted config onto the instance.
[66.0] — 2026-08-19
- Delimited oncoprint ingest: dropped/URL CSV/TSV alteration matrices auto-route through arrayToCX to the Heatmap oncoprint renderer (mutation + CNA layers, genes as rows); needs >=1 mutation token so integer matrices stay heatmaps.
- Oncoprint colours from one cBioPortal alteration registry (label+colour+layer), so all four CNA depths stay distinct (Amp, Gain, Shallow/Deep deletion) and mutations colour by type; legacy codings keep the red/blue + Greens fallback.
- Oncoprint gains an optional mRNA expression layer (oncoprintEXP) drawn as a cell border (mRNA high pale red, mRNA low pale blue) so it frames the cell without competing with the CNA background or mutation bar; in the legend too.
- Oncoprint shows the per-row alteration frequency as text on the right (e.g. 83%), like cBioPortal; on by default (oncoprintAlteredText). Column padded so the % shows; the axis sorts by that count descending when its summary is shown.
- Delimited oncoprint ingest now orders like cBioPortal: genes by alteration frequency, samples by the track-by-track cascade (getOncoprintSortOrder), baked in and kept via oncoprintPresorted so the render skips its default sort.
- Fixed the oncoprint legend clipping/looking truncated: labels sit inside their column (no per-column overflow), short legends drop empty columns so they centre, and multi-row legends balance their keys (7 keys become 4+3, not 6+1).
- Oncoprints hide the variable (sample) names by default like cBioPortal (one column per sample); an explicit showVariableNames in the user config is still honoured. Default text font sizes reduced by 1px across the board.
- Fixed showHeatmapOncoprint drawing a blank oncoprint panel for the bottom/right positions: the secondary heatmap grid now honours the panel offset (rowOffset) instead of always drawing over the primary slot.
- showHeatmapOncoprint now shows the per-row alteration % (a Text overlay draws even when the split halves the row below the font height) and the heatmap colour indicator (drawn for the combo, still skipped for a pure oncoprint).
- Oncoprint layout: the top margin reserves the length of overlay names drawn above the plot so rotated names are not clipped; a pure oncoprint no longer reserves heatmap-indicator space on the right, removing the wasted right gap.
- Overlay value axes now thin their ticks to what fits the available pixel span (measuring each label) instead of a fixed count, so narrow overlay tracks no longer show overlapping tick labels.
- The bottom margin now reserves the length of value-axis overlay names drawn below the plot (Text/Boolean overlays excluded) so the legend box no longer clips the axis name (e.g. "Annt2") in oncoprints with bottom overlays.
- In the heatmap+oncoprint combo with the heatmap on top (oncoprintHeatmapPosition 'top'), sample overlays now shift onto the lower oncoprint panel so the per-gene '% Altered' sits next to the oncoprint instead of the heatmap.
- Removed the oncoprintHeatmapSwitch property (config, geom guard and the 'Oncoprint <-> Heatmap' context-menu item); use showHeatmapOncoprint to show the oncoprint and its expression heatmap together instead of swapping between them.
- Fixed fixed-layout dot columns (Dumbbell/Cleveland/Lollipop and arrow/bullet/cleveland/connected/line/stacked dotplots) drifting off their sample guides: origin now anchors to scales.smp.map, fixing the 3+ series mis-centring.
- Fixed xAxisTime/yAxisTime on integer-year columns: getTimeAxis keeps bare 4-digit years in raw-year units (were read as epoch-ms => NaN/blank axis) and labels show the year; invalid tick dates fall back to numeric, not a throw.
- Fixed remote/dataframe loads throwing "isOncoprintMatrix is not a function": the delimited-oncoprint helpers are now self-contained local functions in initIO (not this.* utilsGeneral methods), so arrayToCX works on the bare constructor too.
- Fixed isValidURL rejecting a bare root path with underscores (e.g. '/my_file.csv'): the filename was read as the host, whose class barred '_', so it loaded as inline data and threw. Underscore now allowed in that class.
For the complete release history of every version, see the full update log.
⇧