Skip to content

Latest commit

 

History

History
136 lines (116 loc) · 16.8 KB

File metadata and controls

136 lines (116 loc) · 16.8 KB

matplotlib compatibility (xy.pyplot)

import xy.pyplot as plt   # the one-line change

xy.pyplot is a shim over the declarative composition API: every call translates onto xy.chart(...) and friends, so shim charts get the same engine — native Rust compute, binary transport, WebGL2 rendering, screen-bounded cost — with matplotlib's calling conventions.

The claim, precisely: every method in the Matplotlib 3.11 Axes Plotting section is present on both xy.pyplot.Axes and the stateful xy.pyplot namespace. The reviewed matplotlib_311_plotting.json snapshot locks that inventory to the pinned upstream documentation, while the executable compatibility corpus in tests/pyplot/corpus/ covers representative calls from every family. This is 100% 2-D chart-method coverage; it is not a claim to reproduce Matplotlib's renderer, transforms, or full Artist graph.

The generated method-by-method compatibility matrix is sourced from that snapshot, executable corpus calls, and compatibility.json. CI fails if the generated matrix is stale, installs the released matplotlib==3.11.0 wheel, and asserts every snapshot method exists on its Axes. The dev revision recorded in the snapshot is informational: CI no longer compares the snapshot against an upstream Matplotlib checkout.

The dual-engine runner executes every corpus case in a fresh process. Its reference harness only normalizes renderer-specific HTML export and xy's dependency-free triangles= shorthand into Matplotlib's equivalent Triangulation positional form; chart data and plotting options are unchanged.

Approximation levels

  • Exact geometry: material data-space geometry and returned numeric values are intended to match Matplotlib.
  • Equivalent semantics: user intent and data results match, using xy-owned artists, containers, and renderer behavior.
  • Visual approximation: the visible chart family is retained, but styling, layout, or artist details can differ across renderers.
  • Accepted no-op: a documented option is validated and retained without a visible effect; this is used only when a stable output guarantee is tested.
  • Optional interop: behavior accepts real Matplotlib objects only when Matplotlib is installed; it is tested in the dedicated reference CI job.
  • Unsupported: the shim rejects the call or option with an actionable error rather than silently discarding it.

Supported surface

matplotlib notes
plt.plot / ax.plot format strings ('r--o'), multiple series per call, implicit x, label=, lw=, ls=, alpha=, marker face/edge styling, directional ^/v/</> triangles and distinct +/x glyphs, markevery, and dependency-free affine data transforms (Affine2D + ax.transData); axes/figure-fraction transforms on data artists, partial fill styles, and cap/join policies fail loudly
scatter(x, y, s=, c=, cmap=, vmin=, vmax=, alpha=, marker=, edgecolors=, plotnonfinite=) s (pt², area) maps to pixel diameter; array c becomes a color encoding and explicit paired color bounds are retained; custom norms/marker paths fail loudly
bar, barh, grouped_bar, bar_label string categories, stacking bases, Matplotlib 3.11 grouped-bar containers and labels
hist(bins=, range=, density=, cumulative=, weights=, orientation=, stacked=) Returns computed counts/edges; bar, step, and stepfilled families render in both vertical and horizontal orientations
hist2d, hexbin, ecdf 2D uniform binning uses the native Rust kernel; hexbin uses Matplotlib's two-offset-grid nearest-center assignment and six-triangle data-space cells, supports C, arbitrary scalar reducers, and mincnt, and retains only the bounded lattice rather than source points
boxplot, violinplot, bxp, violin, errorbar Boxplots support notches, bootstrap/user confidence intervals, median overrides (drawn median only; notch CIs stay data-derived like Matplotlib), percentile/custom whiskers, cap widths, sym, and component colors/widths/alpha — dashed component linestyles fail loudly. Violins support Scott/Silverman/scalar/callable Gaussian-KDE bandwidths, quantiles, and low/high sides; the default (bw_method omitted) uses the native histogram violin mark, whose shape differs from the explicit KDE path
fill_between(x, y1, y2, where=, step=) / fill_betweenx Masks are split into finite contiguous polygons; step geometry is expanded exactly
stackplot All four baselines are computed by the native stacked-bounds kernel
imshow / pcolormesh (cmap=, vmin=/vmax=, origin=) imshow defaults to rcParams['image.origin']; nearest stays cell-exact and Matplotlib's smoothing mode names all collapse to the shim's single bounded gradient upsampling (a visual approximation, not per-mode kernels) and apply to scalar data only — RGB(A) truecolor arrays render unresampled — while unsupported stages/transforms fail loudly. Uniform meshes retain the texture fast path; nonuniform and curvilinear grids use native quad-to-triangle expansion
step, stairs, stem, eventplot Compact step/stem/segment marks; no Python-side vertex expansion
contour / contourf / clabel Native marching squares over rectilinear grids; warped grids route through native Delaunay/marching-triangle kernels; automatic labels repeat at bounded, separated positions along each level (line knockout for inline=True remains a visual approximation)
quiver, barbs, streamplot Native vector endpoint/arrowhead and bounded streamline kernels feeding one instanced segment mark. Barbs are a visual approximation: magnitude maps to a bounded tick count, not WMO 50/10/5 increments. Streamplot always uses the shim's own bounded fixed-step integrator (identical output with or without Matplotlib installed, but paths approximate Matplotlib's adaptive ones); start_points, integration_direction, array widths/colors and num_arrows are honored, and remaining non-default integration options fail loudly
tripcolor, triplot, tricontour, tricontourf Explicit topology or native dependency-free Delaunay triangulation; indexed geometry and isolines stay in Rust
pie / pie_label Native pie/donut tessellation and the Matplotlib 3.11 PieContainer (values, fracs, grouped text labels)
axhline / axvline / axhspan / axvspan, text, annotate, table Fractional span bounds plus data/axes/figure text coordinates are supported. annotate(arrowprops=) draws real arrows in every output: offset-point text becomes an engine callout (arrow pinned from label to point across zoom), data-coordinate text an arrow annotation; date-string coordinates convert on datetime axes. Arrowstyles map to head/tail shapes (-> open V, `-
from xy.pyplot import FacetGrid (seaborn-shaped) Row/column small multiples with seaborn's map contract (subset → activate panel → call the pyplot function), shared domains, edge-only axis labels, top-row column titles, and margin_titles=True rotated row titles. hue=/palette=, col_wrap=, map_dataframe, and add_legend fail loudly
xlabel / ylabel / title / suptitle Suptitles are retained in HTML and multi-panel PNG/SVG
legend() loc, columns, title/font size/colors, frame styling, borderpad, labelspacing, fancybox, framealpha, and shadow are retained across browser and static output. loc='best' chooses the least occupied corner from bounded samples of the current data
grid(True/False) toggles the grid via the theme
xlim / ylim, axis scales, invert_xaxis/yaxis linear/log are native; symlog/logit/asinh use dependency-free monotone data transforms with inverse limit/tick semantics. Automatic linear ticks include Matplotlib's 2.5 step and use uniform decimal padding across a tick set; locations refresh as data arrives. Artist get_data() reflects the transformed space; logit masks values at/outside (0, 1)
set_major_locator / set_major_formatter, plt.NullLocator/FixedLocator/MultipleLocator/MaxNLocator/LinearLocator/LogLocator, plt.NullFormatter/FixedFormatter/FuncFormatter/FormatStrFormatter/StrMethodFormatter/ScalarFormatter xy-owned re-implementations resolved at build time against live data limits (Null/Fixed/Multiple/Linear are position-exact; MaxN/Auto port Matplotlib's MaxNLocator._raw_ticks — same step tables, edge extension, and offset handling — with nbins="auto" budgeted from the estimated plot rect like Axis.get_tick_space(); Log remains approximate). Third-party locator objects work if they implement tick_values(vmin, vmax); minor locators/formatters are retained for round-tripping but minor ticks do not render, except that a labeled minor pair under a blanked major formatter (the centered date-label idiom) is promoted to the drawn tick set
plt.dates.MonthLocator/YearLocator/DayLocator/DateFormatter xy-owned equivalents of the matplotlib.dates classes gallery scripts use; they locate and format in the engine's canonical ms-since-epoch axis unit (not Matplotlib's day floats), and interval approximates rrule by epoch-anchored occurrence counting
datetime, timedelta, and string coordinates datetime inputs use the engine's automatic date ticks, timedeltas are bounded to elapsed seconds, and common strings use categorical ticks; the general Matplotlib units registry is intentionally out of scope. pandas datetime plotting (series.plot(ax=ax)) works against that contract: get_{x,y}data(orig=False) returns ms-since-epoch floats, and pandas' period-ordinal tickers (TimeSeries_Date*) are accepted as no-ops so the native date ticks keep rendering
xticks(positions, labels, rotation=) / tick_params(labelrotation=) Exact positions and strings render in browser, PNG, and SVG
twinx(), secondary_xaxis(), secondary_yaxis() second data axes and linked tick-only secondary axes with callable forward/inverse conversions. Secondary-axis ticks are evenly spaced conversions of the primary domain (not Matplotlib's secondary-unit locators) and currently reach the interactive HTML client only — PNG/SVG export does not draw them yet
fig, ax = plt.subplots(); plt.subplots(n, m, figsize=, dpi=, squeeze=, sharex=, sharey=) Grid renders as CSS-grid HTML and stitched PNG/SVG; shared axes use common domains and live linked pan/zoom. Figure.subplots_adjust(left=, right=, top=, bottom=, wspace=, hspace=) moves the SubplotParams frame: the grid resolves to explicit figure rectangles and every exporter (HTML, PNG, SVG) positions panels at those rectangles
fig.add_subplot(2, 2, 1) / add_subplot(221)
gca / gcf / sca / figure(num) / close(...) matplotlib's implicit-state semantics
savefig('x.png' / '.svg' / '.html', dpi=) Browser-free PNG/SVG supports both single and multi-panel figures; file-like targets require an explicit format= and unsupported metadata/layout/export formats fail loudly
plt.show() notebooks: inline HTML display; scripts: opens the default browser
Artists: set_data / set_ydata / set_color / set_label / set_linewidth / remove mutating a handle rebuilds the chart on next render
Colors single letters, C0C9, tab:*, gray '0.5', RGB(A) tuples, any CSS color
plt.cm.* / plt.colormaps[...] / cmap= names viridis, plasma, inferno, magma, cividis, gray, turbo, coolwarm, Blues, RdYlGn, RdGy, jet, rainbow, Spectral, aliases, and true *_r reversal (RdGy/jet render from 11-stop anchor tables sampled from Matplotlib 3.11, linearly interpolated)
LinearSegmentedColormap.from_list / ListedColormap Python-side callables (cmap(np.arange(cmap.N)) → RGBA) for scripts that colormap values themselves; they cannot be passed as cmap= to plotting calls (no engine table), which fails loudly
plt.colorbar() / fig.colorbar() / plt.clim() / plt.gci() Returns a live handle (set_label, set_ticks); with no mappable it uses the current image the way pyplot does. ticks=/extend= render in PNG and SVG (the HTML colorbar stays a minimal gradient without tick text); clim retargets the mappable's color window and any colorbar derived from it
rcParams Figure size/DPI, line width/marker size, image cmap/origin, axes color cycle, and all four axes.spines.* switches affect every exporter. Pyplot axes default to Matplotlib's four-sided box and each spine can be hidden independently. The chrome keys (axes face/edge/label/title styles, font family/size, tick colors/sizes, legend defaults, figure facecolor) reach the HTML renderer and multi-panel PNG stitching; single-chart PNG and SVG export currently render their own fixed chrome and ignore them. Unknown keys warn once
plt.style.use(...) / plt.style.context(...) "default", "xy", bounded rcParam dictionaries, ordered lists, and the stock sheets fivethirtyeight, ggplot, bmh, dark_background, grayscale, seaborn-v0_8-white(grid), seaborn-v0_8-darkgrid, and seaborn-v0_8-deep — reduced to the supported rcParams subset (colors, grid, cycle, line width, font size; per-sheet keys outside that subset are not carried). The darkgrid sheet mirrors seaborn's axes_style (including patch.edgecolor: white + patch.force_edgecolor, which give hist/bar patches their white separators); -deep installs seaborn's classic color cycle. context() snapshots and restores. Unknown sheet names fail precisely
plt.GridSpec(r, c, wspace=, hspace=, width_ratios=) + slice specs Spans (grid[0, 1:], grid[:-1, 0]) and custom spacing resolve to explicit figure rectangles using Matplotlib's SubplotParams frame; default-geometry single cells keep the uniform grid. Spanning layouts position exactly in HTML, PNG, and SVG: free-form panels (including add_axes rects and insets) render absolutely at their figure rectangles in every exporter, with later axes stacked above earlier ones
add_subplot(spec, sharex=, sharey=, xticklabels=[], ...) per-axes sharing aliases the axis-property store (static domains, as twiny does), not Matplotlib's live Grouper; get_shared_x_axes() reflects it

Outside 2-D chart-method compatibility

Polar/3D projections, FuncAnimation, arbitrary third-party Artist graphs, non-affine transform graphs, and blitting are not part of this 2-D chart-method target. Bounded shim-owned Axes Artist views, children, containers, removal, affine data transforms, coordinate spaces, and linked secondary axes are supported.

Unknown keyword arguments on supported calls raise TypeError naming the offending keyword. Known material options that the native marks cannot honor raise NotImplementedError, with these documented exceptions that are accepted as visual approximations rather than rejected: the barbs glyph and imshow smoothing collapse above, annotate(arrowprops=...) connection curves and fancy/wedge outlines drawn as quadratic-curve tapered fills rather than Matplotlib's exact patch paths, bbox= boxes drawn only by the HTML label, and errorbar limit flags rendered as one-sided bars without Matplotlib's caret arrows.

Sharp edges

  • Custom Matplotlib marker paths, arbitrary clipping graphs, and unsupported collection gradients are rejected rather than silently approximated.
  • The shim's figure/axes bookkeeping adds ~10µs per figure over the declarative API (measured: +9% at 10k points, +2% at 100k, +0.6% at 1M); tests/pyplot/test_perf_guardrail.py gates this relationship in CI.

Boundaries (enforced by tests/pyplot/test_boundaries.py)

The shim lives entirely in python/xy/pyplot/; no engine module imports it, importing xy never loads it, and importing the shim never loads the widget stack or real matplotlib.

Maintenance

The upstream revision and method inventory are updated together. When moving the pin, check out the proposed Matplotlib revision and run:

python scripts/sync_matplotlib_compat.py --upstream path/to/matplotlib --update-snapshot
python scripts/sync_matplotlib_compat.py

Review the snapshot and generated matrix diff as an API change. Release-level changes are recorded in the compatibility changelog.