|
| 1 | +# Matplotlib import support and roadmap |
| 2 | + |
| 3 | +`Canvas.from_matplotlib(source, strict=False, **canvas_kwargs)` accepts an |
| 4 | +in-memory Matplotlib figure, axes, or rectangular axes array. This checklist |
| 5 | +covers the intended import surface. It is a roadmap, not a promise that every |
| 6 | +item already works on every rendering backend. |
| 7 | + |
| 8 | +Checked items are implemented within their stated scope. Unless explicitly |
| 9 | +stated otherwise, fidelity checks below refer to the **Matplotlib backend**. |
| 10 | +Complex built-in artists are retained as detached, editable native geometry; |
| 11 | +these entries deliberately raise on backends that cannot represent them. |
| 12 | +Portable entries continue to support the other renderers, with the limitations |
| 13 | +listed below. Unchecked items are |
| 14 | +pending or partial. Importing drawn geometry does not recover the original |
| 15 | +samples, plotting call, callback, or statistical model. Matplotlib, Plotly, |
| 16 | +TikZ, and plotext have different rendering capabilities; import success does |
| 17 | +not guarantee that every backend can render the result identically. |
| 18 | + |
| 19 | +## Inputs and ownership |
| 20 | + |
| 21 | +- [x] Whole `Figure`, individual `Axes`, flat lists/arrays, and 2D lists/arrays. |
| 22 | +- [x] Explicit 2D array order; infer ordinary grids from flat input. |
| 23 | +- [x] Reject empty, ragged, non-axes, duplicate, and mixed-figure inputs. |
| 24 | +- [x] Snapshot plot arrays and styles without reparenting source artists. |
| 25 | +- [x] Canvas keyword overrides for figure size, DPI, and other canvas options. |
| 26 | +- [x] Warnings for recognized unsupported content; strict mode raises. |
| 27 | +- [x] Structured import report with artist identities, severity, and fallbacks. |
| 28 | +- [ ] Complete detection of unsupported style properties in strict mode. |
| 29 | +- [ ] Version compatibility matrix and fixtures across supported Matplotlib versions. |
| 30 | +- [x] Optional serialized figure input with an explicit trust boundary. |
| 31 | + |
| 32 | +PNG/JPEG input and reconstruction of original data from PDF/SVG are separate |
| 33 | +features, not part of this object importer. Arbitrary custom Python artists |
| 34 | +need an adapter API or an explicit raster fallback; universal semantic |
| 35 | +conversion is not possible. |
| 36 | + |
| 37 | +## Figure and layout |
| 38 | + |
| 39 | +- [x] Ordinary rectangular subplot grids and individual axes. |
| 40 | +- [x] Figure dimensions and export DPI defaults. |
| 41 | +- [x] Figure title and shared axis label text. |
| 42 | +- [x] One `twinx` and one `twiny` per primary subplot; import only selected axes. |
| 43 | +- [ ] Full twin-axis spine positioning, multiple twins, and cross-backend parity. |
| 44 | + Matplotlib supports multiple twins and spine positions; backend parity is pending. |
| 45 | +- [x] Shared-axis relationships and linked limits after import. |
| 46 | +- [x] Preserve omitted/empty grid cells without drawing extra empty axes. |
| 47 | +- [x] Spanning cells, subplot mosaics, nested GridSpec, and subfigures |
| 48 | + (subfigure rectangles/decorations are flattened into the destination figure). |
| 49 | +- [x] Grid width/height ratios, margins, spacing, constrained/tight layout |
| 50 | + for ordinary/nested GridSpec; subfigures retain their snapshot rectangles. |
| 51 | +- [x] Arbitrary axes rectangles, overlapping axes, inset axes, inset-zoom |
| 52 | + connectors (`indicate_inset_zoom`/`indicate_inset`, Matplotlib 3.10+). The |
| 53 | + connector spans two Axes and recomputes its geometry from live limits on |
| 54 | + every draw, so it is rebuilt against the reconstructed parent/inset pair |
| 55 | + rather than snapshotted. Matplotlib < 3.10's tuple-returning API still falls |
| 56 | + back to native geometry. |
| 57 | +- [x] Secondary axes with forward/inverse coordinate functions. |
| 58 | +- [x] Figure backgrounds, frame styling, and figure-level text/patches/images. |
| 59 | +- [x] Figure title/shared-label typography and placement. |
| 60 | +- [x] Figure-level legends and shared colorbars. |
| 61 | + |
| 62 | +Irregular grids use one row of addressable Canvas slots and retain the source |
| 63 | +axes rectangles when rendered with Matplotlib. A 2D input array defines its own |
| 64 | +layout; slots occupied only by a twin remain empty rather than drawing extra axes. |
| 65 | +Ordinary/nested GridSpec layout engines reflow on resize. Subfigure layout-engine |
| 66 | +reflow and full cross-backend layout parity remain pending. |
| 67 | + |
| 68 | +## Lines, markers, and collections |
| 69 | + |
| 70 | +- [x] Numeric 2D lines, markers, NaN gaps, line/marker colors and widths. |
| 71 | +- [x] Step draw styles in imported line entries (Matplotlib rendering). |
| 72 | +- [x] Horizontal/vertical reference lines with fractional extents (Matplotlib). |
| 73 | +- [x] Plain line collections, including `hlines`/`vlines`, as individual lines. |
| 74 | +- [x] Standard scatter marker shapes, sizes, scalar colors, and per-point colors. |
| 75 | +- [x] Scatter colormaps and normalization copied for Matplotlib rendering. |
| 76 | +- [x] Preserve date/time, categorical, quantity/unit converters and formatters. |
| 77 | +- [x] Custom dash sequences, cap/join styles, markevery, and gap colors. |
| 78 | +- [x] Infinite `axline` semantics, event plots, stem containers. |
| 79 | +- [x] Scalar-mapped line collections, offset collections, per-point transforms. |
| 80 | +- [ ] Custom scatter paths and hollow markers on every backend. |
| 81 | +- [ ] Match scatter area/size semantics and normalization on every backend. |
| 82 | +- [x] Preserve ordering between artist types at equal z-order. |
| 83 | + |
| 84 | +## Bars, errors, fills, and statistical plots |
| 85 | + |
| 86 | +- [x] Vertical/horizontal bars, positions, dimensions, baselines, and styling. |
| 87 | +- [x] Stacked/grouped bars as their resolved rectangles. |
| 88 | +- [x] Histogram bars as geometry (original samples are unavailable). |
| 89 | +- [x] Error-bar containers with data lines: symmetric/asymmetric x/y errors, |
| 90 | + caps, colors, widths, and labels; avoid duplicated component artists. |
| 91 | +- [x] Error-only plots and bar errors as line/cap geometry, without inventing |
| 92 | + asymmetric centers that the source no longer retains. |
| 93 | +- [x] Simple data-coordinate polygon collections, including `fill_between`, |
| 94 | + `fill_betweenx`, and stackplot regions, as filled polygon geometry. |
| 95 | +- [x] Data-coordinate polygon patches and stairs values/edges/baseline. |
| 96 | +- [x] Error limits/arrows, subsampled errors, and independently styled components. |
| 97 | +- [ ] Restore semantic fill boundaries/where masks when recoverable. |
| 98 | + Drawn paths and retained container metadata are preserved; Matplotlib usually |
| 99 | + does not retain the original where-mask or samples. |
| 100 | +- [x] Spans in blended coordinates; general rectangles, circles, ellipses, wedges. |
| 101 | +- [x] Compound polygons, holes, curved paths, arbitrary PathPatch geometry. |
| 102 | +- [x] Box plots, violin plots, pie/donut charts, hist2d, hexbin. |
| 103 | +- [x] Preserve statistical groupings when containers/metadata retain them: |
| 104 | + `subplot.import_groups` and entry `source_container_ids` retain bar, errorbar, |
| 105 | + and stem memberships, labels, orientation, and available data values. |
| 106 | + |
| 107 | +## Images, fields, and colorbars |
| 108 | + |
| 109 | +- [x] `imshow` arrays, extent, origin, colormap, normalization, interpolation |
| 110 | + copied into entries (Matplotlib rendering). |
| 111 | +- [ ] Match image extent/origin, RGB(A), masks, alpha, and interpolation on all backends. |
| 112 | +- [ ] Nonlinear normalization, clim, under/over/bad colors on all backends. |
| 113 | +- [x] `pcolor`, `pcolormesh`, nonuniform grids, QuadMesh, triangular meshes. |
| 114 | +- [x] Contours, filled contours, levels, labels, and contour topology. |
| 115 | +- [x] Quiver, barbs, streamplots, vector-field keys. |
| 116 | +- [x] Axes colorbars tied to the correct image/scatter/mesh mappable. |
| 117 | +- [x] Colorbar orientation, label, ticks, limits, extend, and normalization. |
| 118 | +- [x] Multiple/shared colorbars and explicit colorbar axes. |
| 119 | + |
| 120 | +## Text and annotations |
| 121 | + |
| 122 | +- [x] Data-coordinate text, font family/size/weight/style, color, alignment, |
| 123 | + rotation (backend support varies). |
| 124 | +- [x] Data-coordinate annotations with optional arrows and independent copied |
| 125 | + arrow properties; references to other patches are explicitly rejected. |
| 126 | +- [x] Center title and x/y labels with basic typography and label padding. |
| 127 | +- [x] Text boxes, multiline spacing, math/TeX fidelity, wrapping, clipping |
| 128 | + through Matplotlib (TeX still requires the source environment’s TeX setup). |
| 129 | +- [ ] Axes/figure fractions, point/pixel offsets, blended and callable coordinates. |
| 130 | +- [ ] Annotation arrows with full backend parity and artist-relative coordinates. |
| 131 | +- [x] Left/right titles, custom title/label positions, offset text. |
| 132 | +- [x] AnnotationBbox, OffsetBox, tables, and other composite text artists. |
| 133 | + |
| 134 | +## Axes, ticks, grids, and legends |
| 135 | + |
| 136 | +- [x] Limits including reversed limits; default linear/log scales. |
| 137 | +- [x] Explicit major tick locations/labels with FixedLocator. |
| 138 | +- [x] Basic grid visibility, axes visibility, background, aspect, axisbelow. |
| 139 | +- [x] Basic per-axes legend visibility and artist labels. |
| 140 | +- [x] Non-default log bases; symlog, logit, asinh, function/custom scales. |
| 141 | +- [x] Automatic/fixed minor ticks, locator/formatter configuration and units. |
| 142 | +- [x] Tick placement, direction, length, width, color, rotation, and font styling. |
| 143 | +- [x] Per-axis major/minor grid visibility and line styling. |
| 144 | +- [x] Spine visibility, colors, widths, bounds, and positions. |
| 145 | +- [x] Autoscale flags, sticky edges, margins, adjustable/anchor/box aspect. |
| 146 | +- [x] Legend order, renamed labels, proxy handles, multiple legends, grouping. |
| 147 | +- [x] Legend location/anchor, columns, title, typography, frame and spacing. |
| 148 | + |
| 149 | +## Transforms, metadata, and advanced axes |
| 150 | + |
| 151 | +- [x] General affine/nonlinear/blended transforms, coordinate rebinding. |
| 152 | +- [x] Clip paths/boxes, path effects, rasterization, sketch settings, filters. |
| 153 | +- [x] Hidden artists retained as hidden editable entries. |
| 154 | +- [ ] Artist IDs, URLs, picking, metadata, and accessibility descriptions. |
| 155 | +- [x] Polar, geographic/custom projections, axisartist and parasite axes via |
| 156 | + native axes snapshots; custom projection classes must remain available. |
| 157 | +- [x] 3D lines/scatter, surfaces, wireframes, collections, camera/projection. |
| 158 | +- [ ] Animations, widgets, callbacks, and interactive state (separate adapters). |
| 159 | + Static artist state is copied; arbitrary callback closures and GUI event loops |
| 160 | + cannot be reconstructed from a figure’s drawn geometry. |
| 161 | +- [x] Optional raster fallback for unsupported artists with explicit loss reporting. |
| 162 | + |
| 163 | +## Validation and next priorities |
| 164 | + |
| 165 | +- [x] Tests for all three input forms, source independence, strict/warning modes. |
| 166 | +- [x] Matplotlib reconstruction tests for supported geometry and axis settings. |
| 167 | +- [x] Plotly smoke/geometry tests for representative supported imports. |
| 168 | +- [x] Image comparisons with tolerances, plus representative portable export |
| 169 | + tests for Matplotlib PNG/SVG, Plotly HTML, plotext text, and TikZ source. |
| 170 | +- [x] Large figures, empty/masked data, performance and memory benchmarks. |
| 171 | + |
| 172 | +New import controls: |
| 173 | + |
| 174 | +```python |
| 175 | +canvas = Canvas.from_matplotlib(fig, strict=True) |
| 176 | +report = canvas.import_report.to_dict() # identities, severity, fallback, backends |
| 177 | +canvas = Canvas.from_matplotlib("figure.pickle", trusted=True) |
| 178 | +canvas = Canvas.from_matplotlib(fig, fallback="raster") |
| 179 | +``` |
| 180 | + |
| 181 | +`trusted=True` is required before any pickle is read. Pickles can execute code; |
| 182 | +load only files whose producer you trust and use a matching Matplotlib version. |
| 183 | +`fallback="native"` (default) retains built-in native geometry with rebound |
| 184 | +transforms. `fallback="skip"` warns (or raises in strict mode) for those artists. |
| 185 | +`fallback="raster"` snapshots the selected figure content and records the loss of |
| 186 | +editable data and vector geometry; it supports Matplotlib and Plotly. Callback |
| 187 | +functions and custom scales remain Python objects, not portable serialization. |
| 188 | +Strict mode checks import losses, not universal rendering parity. |
| 189 | + |
| 190 | +Remaining priorities are cross-backend fidelity, subfigure reflow, portable |
| 191 | +projection adapters, and live interaction adapters. Source metadata does not |
| 192 | +usually retain original fill masks or box/violin samples; these are imported as |
| 193 | +geometry without inventing lost statistical inputs. |
| 194 | + |
| 195 | +Reference APIs: [Matplotlib artists](https://matplotlib.org/stable/tutorials/artists.html), |
| 196 | +[containers](https://matplotlib.org/stable/api/container_api.html), and |
| 197 | +[annotations](https://matplotlib.org/stable/users/explain/text/annotations.html). |
| 198 | + |
| 199 | + |
| 200 | +Compatibility fixtures live in `test_matplotlib_import.py` and |
| 201 | +`test_matplotlib_import_extended.py`. The CI matrix covers Matplotlib 3.8, 3.9, |
| 202 | +3.10 and 3.11 on Python 3.11. Snapshots use version-sensitive Matplotlib state; |
| 203 | +there is no cross-version pickle compatibility promise. |
| 204 | + |
| 205 | +Run `python scripts/benchmark_matplotlib_import.py --points 100000 --panels 4` |
| 206 | +for reproducible timing/allocation observations. A local Matplotlib 3.11.1 run |
| 207 | +imported 400,000 points in 0.41 s with 8.75 MiB peak Python allocations and rendered |
| 208 | +in 0.79 s. These are observations, not performance guarantees; native allocations |
| 209 | +outside Python are excluded from the memory measurement. |
0 commit comments