Plot Type Stability
Two sets, two different promises
PlotType in maidr/core/enum/plot_type.py names 44 plot types. They do not all carry the same promise, and nothing in the enum says so — which is what this page is for.
Fifteen of them predate the plot coverage roadmap (#345). Twenty-three were added by it, most inside about two weeks, and ROC, RUG, RIDGELINE, DIRECTED_GRAPH, PERCENTILE_BAND and PR_CURVE after it. None of the twenty-nine has been through a user study.
Elsewhere in these docs — the example gallery, the navigation and the supported-plots lists on the home page — an experimental type is marked experimental after its name; a type with no mark is stable. A list of plot types is also split in two, a Stable part followed by an Experimental part; the navigation puts the example pages about experimental types only in a group of their own after the stable ones; and a gallery page shows its stable charts before its experimental ones. The JavaScript core (Trace type stability) and maidr for R (Supported plot types) mark their own docs the same way.
Stable
PlotType |
emitted as | drawn by |
|---|---|---|
BAR |
bar |
ax.bar, sns.barplot |
BOX |
box |
ax.boxplot, sns.boxplot |
CANDLESTICK |
candlestick |
mplfinance |
COUNT |
count |
sns.countplot |
DODGED |
dodged_bar |
grouped bars |
HEAT |
heat |
ax.imshow, sns.heatmap |
HIST |
hist |
ax.hist, sns.histplot |
LINE |
line |
ax.plot, sns.lineplot |
PIE |
pie |
ax.pie |
SCATTER |
point |
ax.scatter, sns.scatterplot |
SMOOTH |
smooth |
sns.regplot, sns.lmplot |
STACKED |
stacked_bar |
stacked bars |
STEP |
step |
ax.step |
VIOLIN_BOX |
violin_box |
sns.violinplot |
VIOLIN_KDE |
violin_kde |
sns.violinplot |
These are what py-maidr was built around. Their extraction, their announcements and their keyboard model have been exercised by real users over real charts, and changing any of them changes behavior people already depend on.
Experimental
These are prototypes. Treat them as prototypes. They are under active development, they are unstable, and they have not been validated with the readers they are for.
PlotType |
emitted as |
|---|---|
ALLUVIAL |
alluvial |
AREA |
area |
BOXEN |
boxen |
CHOROPLETH |
choropleth |
CONTOUR |
contour |
DIRECTED_GRAPH |
directed_graph |
ERRORBAR |
error_bar |
FUNNEL |
funnel |
GANTT |
gantt |
GAUGE |
gauge |
HEXBIN |
hexbin |
ICICLE |
icicle |
LOLLIPOP |
lollipop |
NORMALIZED |
stacked_normalized_bar |
NORMALIZED_AREA |
stacked_normalized_area |
PARALLEL |
parallel_coordinates |
PERCENTILE_BAND |
percentile_band |
POLAR_AREA |
polar_area |
PR_CURVE |
pr_curve |
RADAR |
radar |
RIDGELINE |
ridgeline |
ROC |
roc |
RUG |
rug |
SANKEY |
sankey |
STACKED_AREA |
stacked_area |
SUNBURST |
sunburst |
TREEMAP |
treemap |
WATERFALL |
waterfall |
WORD_CLOUD |
word_cloud |
What that means, concretely:
- Under active development. These are being changed as they are used, not maintained against a settled specification.
- Unstable. Field names, announcement wording and navigation semantics may change without a deprecation period, including in a patch release. Code that depends on the exact shape one of these emits is pinned to the py-maidr version it was written against.
- Not validated. Each was measured against the chart it reads — that is what the issues and the tests record. But measuring that a reading is faithful to the drawing is a different claim from establishing that it is useful to a reader. Nobody has asked a blind or low-vision reader whether navigating a sunburst by depth, or hearing each parallel-coordinates axis on its own scale, is the right way to read one. Until that happens these are proposals about how a chart could be read — implemented, and measured against the drawing — rather than answers.
- Not a support commitment. A bug in one of these is worth reporting, and reporting it is not a promise that the current behavior will be kept.
If you are building something that has to keep working, build it on the stable set. If you are exploring what a chart could sound like, the experimental set is exactly what it is for — and feedback on it is the thing that would move a type out of this list.
Bokeh support
Bokeh figures (maidr.show(p) on a figure, gridplot, row or column) are read by an adapter that is itself experimental, whatever plot type it emits: a Bokeh bar chart emits the stable bar type, but which glyphs are read as what, the highlight drawn inside the Bokeh plot, and what is skipped may change without a deprecation period, including in a patch release. The tables above describe the types; this is about the reader of Bokeh documents, which has not been through a user study either. See the Bokeh gallery for what it reads today (#813). That includes how the page gets BokehJS: from Bokeh’s CDN, or inlined from the installed package with use_cdn=False so a saved page works offline.
plotnine support
plotnine charts (maidr.show(p) on a ggplot) are read by an adapter that is itself experimental, whatever plot type it emits: a plotnine bar chart emits the stable bar type, but which geoms, stats and positions are read as what, how a facet panel is titled, and what is skipped may change without a deprecation period, including in a patch release. It reads each layer’s data as plotnine computed it rather than the shapes it drew, and each facet panel becomes a subplot; a geom, a position such as a jitter, or a coordinate system it does not read is left out with a warning naming it. A geom_line of recall against precision, which it read as a line before, emits the experimental pr_curve, as does such a geom_path; a stat_summary(fun_data="median_hilow") ribbon and the median line drawn with it, a line before, emit the experimental percentile_band together. See the plotnine gallery for what it reads today (#814). Install it with pip install "maidr[plotnine]".
Excel support
Charts read out of an Excel workbook (maidr.read_excel_charts("book.xlsx")) come through a reader that is itself experimental, whatever plot type it emits: a column chart emits the stable bar type, but which Excel chart types are read as what, how an axis with no title is named, how a date or a number format is written, and what is skipped may change without a deprecation period, including in a patch release. Each chart is read from the copy of its values Excel keeps in the file and drawn again with matplotlib, and the reader follows that drawing, which keeps the workbook’s colors and titles but is not Excel’s own picture. Every chart type Excel draws is read; a histogram’s bins and a box’s quartiles are worked out as Excel works them out, and a map is read as the list of its regions, without their shapes. A chart part it cannot read is left out with a warning naming it. See the Excel gallery for what it reads today.
PowerPoint and Word support
Charts read out of a PowerPoint presentation (maidr.read_powerpoint_charts("deck.pptx")) or a Word document (maidr.read_word_charts("report.docx")) come through the same experimental reader as Excel’s, whatever plot type it emits: a chart on a slide or in a document is the chart part Excel draws, and it is read as the Excel support section describes, from the copy of its values the file keeps, with the workbook embedded beside it read only for the cells that copy leaves out. How a presentation’s slides and a document’s charts are ordered and named, and which parts of a document are read – its body, not its headers, footers, footnotes or comments – may change without a deprecation period too. See the PowerPoint and Word gallery for what it reads today.
TensorBoard support
The charts read out of a TensorBoard log directory (maidr.read_tensorboard_scalars("logs"), maidr.read_tensorboard_distributions("logs"), maidr.read_tensorboard_histograms("logs"), maidr.read_tensorboard_pr_curves("logs") and maidr.read_tensorboard_graph("logs")) come through a reader that is itself experimental, whatever type it emits: a scalar chart emits the stable line type, a distribution chart the experimental percentile_band, a histogram the experimental ridgeline, PR curves the experimental pr_curve and a Keras model’s graph the experimental directed_graph, but how runs and tags become charts, how smoothing is drawn, how a long run is thinned, how a histogram is binned, and what is skipped may change without a deprecation period, including in a patch release. Distributions and PR curves were read as the stable line type, nine lines and a line per run, until maidr.js gained the percentile_band and pr_curve traces; they changed type then. Each tag’s values are read from the event files, the values TensorBoard reads, and drawn again with matplotlib; the reader follows that drawing, not TensorBoard’s own. Scalars and histograms are read today, the Keras model graph (not TensorFlow’s op-level graph), PR curves, a profile’s step-time graph and top operations (with xprof installed), and distributions from the histograms and hyperparameter sweeps from the HParams plugin’s summaries, and the Embedding Projector’s embeddings, and a maidr tab in TensorBoard shows them all (pip install "maidr[tensorboard]"): images are tracked in #846. See the TensorBoard gallery for what it reads today.
Keras support
The training curves drawn from a Keras model (maidr.keras.plot_history() and maidr.keras.MaidrCallback) and the confusion matrix (maidr.keras.plot_confusion_matrix()) are experimental, although every chart they draw is a stable type, line or heat: how metrics become charts and lines, what the lines are called, how predictions become classes, and when the callback writes its page may change without a deprecation period, including in a patch release. The PR curve (maidr.keras.plot_pr_curve()) and the model graph (maidr.keras.plot_model()) are experimental twice over: they emit the experimental pr_curve and directed_graph types – the PR curve was a stable line until maidr.js gained the pr_curve trace – and what a point or a layer is announced with, how a nested model becomes a scope and how the graph is laid out may change the same way. See the Keras gallery for what they draw today.
Weights & Biases support
The training curves read from Weights & Biases runs (maidr.read_wandb_history()) come through a reader that is experimental, although every chart it draws is the stable line type: how runs and metrics become charts and lines, what a line is called, how a run file is read, which values count as numbers, and how a long run is thinned may change without a deprecation period, including in a patch release. A run’s file, run-<id>.wandb, is read by maidr itself, by the format the W&B SDK writes today, which is W&B’s to change; a run fetched from the server is read through wandb.Api. Numbers are read today: images, tables, histograms and W&B’s custom charts are not. See the W&B gallery for what it reads today.
MLflow support
The training curves read from MLflow runs (maidr.read_mlflow_metrics()), and the charts stored in a run (maidr.log_mlflow_chart()), are experimental, although every chart drawn is the stable line type: how runs and metrics become charts and lines, what a line is called, which metrics are left out, and what the stored page holds may change without a deprecation period, including in a patch release. See the MLflow gallery for what it reads today.
Drawn but not read
A few matplotlib calls draw marks that no PlotType reads, and that is deliberate rather than a gap. Each registers nothing, so a figure holding only that call falls back to a static picture, and a figure holding it beside a chart that does read keeps that chart’s reading untouched.
| call | why it is declined |
|---|---|
ax.quiver, ax.barbs, ax.streamplot |
a vector at a place carries a speed and a direction, and no trace holds both |
ax.triplot |
a mesh states which points were joined, not an order to walk |
ax.contourf, ax.tricontourf, ax.tripcolor |
a filled band spans two levels, so calling it one level’s curve is right for half its vertices |
ax.fill |
a closed polygon states no series; it is asked for as a shape, not a sequence |
ax.axhline, ax.axvline, ax.axline, ax.axhspan, ax.axvspan |
a reference mark at a fixed position annotates a chart rather than being one |
ax.table draws no marks at all, so a figure holding only a table is reported as empty, which is accurate.
Every other Axes method that draws marks is read as one of the types above: imshow, matshow, spy, specgram and hist2d as heat; stem, acorr and xcorr as lollipop; the spectra (psd, csd, cohere, magnitude_spectrum, angle_spectrum, phase_spectrum) as line; tricontour as contour. tests/core/plot/test_unread_families.py pins the whole sweep, so a call that starts registering nothing, or a decline that starts registering something, fails by name. The reasoning behind the ax.fill decline, the one that could be overturned, is recorded there and in #685.
How the split is derived
It is the diff of PlotType against d9f7aee, the last commit on main before #345 was filed:
git show d9f7aee:maidr/core/enum/plot_type.py | grep -oE '^\s+[A-Z_0-9]+ = "[a-z_0-9]+"'tests/core/test_plot_type_stability.py fails if a member of PlotType appears in neither table or in both, so a new plot type has to be placed deliberately rather than inherit either promise by being forgotten.
The same split upstream
The JavaScript core makes the same distinction over its own TraceType, with the same boundary and for the same reason. See Trace type stability in the core schema documentation.