We maintain cxspec, a versioned, self-describing figure format: a single portable JSON object that carries a chart’s data and the spec to render it, with a stated forward-render/compatibility policy, a structural round-trip guarantee, and an optional provenance hash. Schemas (JSON Schema 2020-12) and conformance fixtures are public: GitHub - neuhausi/cxspec · GitHub
The goal is interchange, not a silo — a figure should be able to move between tools and still reproduce. We already ship a dependency-free vega-lite → cxfigure converter (cxspec/converters/vega-lite at main · neuhausi/cxspec · GitHub), and we’re looking at a plotly figure JSON ⇄ cxfigure mapping next: traces ⇄ a data matrix + per-series config, layout ⇄ axis/title/legend config.
We’d appreciate feedback from this community on the trace types and layout constructs that don’t have a clean equivalent, and on any prior art for exchanging figures between Plotly and other libraries. Not proposing anyone change anything — just trying to make figures portable.
Here are some trace types that Plotly supports that many other plotting libraries do not:
| Category |
Plotly traces |
| 3D |
scatter3d, surface, mesh3d, cone, streamtube, isosurface, volume |
| Polar / radar |
scatterpolar, barpolar |
| Ternary |
scatterternary, scatterternaryternary contours |
| Hierarchical |
treemap, sunburst, icicle |
| Flow / relational |
sankey, parcoords (parallel coordinates), parcats (parallel categories) |
| Business |
waterfall, funnel, funnelarea, indicator (gauge, bullet, KPI delta) |
| Financial |
candlestick, ohlc |
| Density / field |
contour, histogram2dcontour, densitymap |
| Carpet |
carpet, scattercarpet, contourcarpet |
| Other |
table, violin |
Thanks, Robert — that’s a really useful checklist. Here’s how each maps to a CanvasXpress graph type. The large majority round-trip cleanly:
| Plotly trace |
CanvasXpress graphType |
scatter3d, surface, mesh3d |
Scatter3D (surface/mesh are render modes of it) |
scatterpolar, barpolar |
Radar / Circular |
treemap |
Treemap |
sunburst, icicle |
Sunburst (icicle = rectangular layout of the same tree) |
sankey |
Sankey |
parcoords |
ParallelCoordinates |
parcats |
Alluvial |
waterfall |
Waterfall |
indicator |
Meter / Bullet |
candlestick, ohlc |
OptionsWall |
contour, histogram2dcontour |
Contour (+ Bin/Hex for 2D density) |
densitymap |
Map + density overlay |
table |
data-table facility |
violin |
Violin |
The ones with no clean equivalent — and I’d rather the format declare these unsupported than fake them — are the volumetric/vector-field and specialized-coordinate traces: cone, streamtube, isosurface, volume, scatterternary, funnel/funnelarea, and the whole carpet family (carpet, scattercarpet, contourcarpet).
Worth noting the mapping runs the other way too — CanvasXpress carries a number of chart types Plotly has no native trace for, which is part of why a shared interchange format is interesting rather than just a re-skin. A few: Network (force/graph layouts), Genome (genome-browser tracks), KaplanMeier (survival curves), Circular (Circos-style), Chord, Alluvial/Ribbon, Venn & Upset, Volcano, Correlation matrices, Ridgeline, Streamgraph, and Fish (clonal-evolution). So cxfigure has to be expressive enough to describe those without leaning on Plotly’s trace vocabulary — the fidelity flag (exact/approximated/unsupported) cuts both ways.
Does the unsupported set match your read? The carpet family in particular I’d like a gut check on.
I believe all those chart types can be created with various parameter combinations using existing traces. Many of them don’t have dedicated trace types in Plotly.js because they can already be created from existing traces. For example a Correlation matrix can be represented by a heatmap or splom trace. A volcano plot is ultimately a scatter trace with some data preprocessing.
If there is a specific one you can’t find material for online, definitely feel free to reach out!
That’s a fair correction, and it gets at the real design question — thank you. You’re right that at the trace level almost everything is reachable: a volcano plot is a scatter with thresholding, a correlation matrix is a heatmap/splom, a KM curve is a stepped scatter, a Circos plot is polar line/area work, and so on. Plotly is deliberately economical about dedicated trace types for exactly that reason.
The distinction I care about for cxspec is semantic intent vs. rendered marks. If a volcano plot serializes as “a scatter trace with some preprocessing,” then what round-trips is the picture, not the meaning — the fold-change/-log10(p) roles, the significance thresholds, the up/down/NS partition, the label rules. A consumer that receives it can redraw the pixels but can’t re-derive the figure if the data changes, ask “which genes are significant,” or restyle it as a volcano. Same story for KaplanMeier (risk tables, censoring marks, CI bands) or Sankey (the flow-conservation model behind the ribbons).
So I think we actually agree on the mechanism and differ only on what the format should store. My aim is that cxspec carry the semantic type plus its parameters, and separately describe how it decomposes into primitive marks — so a Plotly consumer can always fall back to scatter+preprocessing (your point, and a great lossless-floor guarantee), while a semantics-aware consumer keeps the higher-level object. The fidelity flag then means “did the intent survive,” not just “did the marks survive.”
The genuinely-unsupported list I gave (cone/streamtube/isosurface/volume, scatterternary, carpet) is the reverse case — those are coordinate-system/geometry primitives CanvasXpress doesn’t have, so they can’t even be composed on our side yet. I’ll take you up on the offer there; carpet especially, I’d like to compare notes on.