Reproducible figure-interchange format + a Plotly-figure mapping — interop feedback welcome

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.