diff --git a/docs/matplotlib-compat-changelog.md b/docs/matplotlib-compat-changelog.md
index 925b5f67..f40e2a1e 100644
--- a/docs/matplotlib-compat-changelog.md
+++ b/docs/matplotlib-compat-changelog.md
@@ -242,5 +242,23 @@ colorbar domains) fully cleared.
using typed list reductions; the complete shippable Python package now type
checks cleanly.
+### Free-form layout parity — 2026-07-14 (Matplotlib 3.11.0 reference)
+
+- HTML output now places free-form panels absolutely at their figure
+ rectangles on a fixed-size canvas, matching the PNG compositor: `add_axes`
+ rects stack and overlap exactly as in Matplotlib (an inset `plt.axes([...])`
+ draws on top of a default axes instead of rendering as a side-by-side grid
+ panel), with document order providing Matplotlib's draw order. SVG export
+ gained the same absolute-placement path.
+- `Figure.subplots_adjust(left=, right=, top=, bottom=, wspace=, hspace=)` is
+ now implemented instead of raising `NotImplementedError` (which superseded
+ the earlier silent discard): the values update the figure's SubplotParams
+ frame, subplot grids resolve to per-cell figure rectangles through the
+ GridSpec geometry (wspace/hspace as fractions of the average cell size, like
+ Matplotlib), and all three exporters position panels at those rectangles.
+ Out-of-order frames (`left >= right`, `bottom >= top`) raise `ValueError`
+ as in Matplotlib. This clears the two loud PDSH `subplots_adjust` cells
+ (04.08 2×3 labeled grid, 04.10 5×5 zero-gap faces).
+
Future entries must identify the Matplotlib release/revision, inventory
additions or removals, and any compatibility-level changes.
diff --git a/docs/matplotlib-compat.md b/docs/matplotlib-compat.md
index d07bb468..3b613c51 100644
--- a/docs/matplotlib-compat.md
+++ b/docs/matplotlib-compat.md
@@ -64,16 +64,17 @@ dependency-free `triangles=` shorthand into Matplotlib's equivalent
| `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; `arrowprops` is approximated as callout text |
+| `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, `-|>` filled, `\|-\|`/brackets bar caps, `fancy`/`simple`/`wedge` filled tapered shafts sized by the text's mutation scale) and `connectionstyle` arc3/angle3/angle become quadratic curves (corner rounding approximated); `alpha` dims the arrow only. `bbox=` becomes label box styles (fill/edge/round corners/`pad`) on the HTML label; static exporters keep the plain label |
| `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 are position-exact; MaxN is a nice-step heuristic, not Matplotlib's edge-extension algorithm). 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 |
-| 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 |
+| `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(...)` is rejected because those frame/spacing values are not representable by this renderer; use `GridSpec(..., wspace=, hspace=)` for supported explicit layout |
+| `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 |
@@ -85,7 +86,7 @@ dependency-free `triangles=` shorthand into Matplotlib's equivalent
| `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, and seaborn-v0_8-white(grid) — reduced to the supported rcParams subset (colors, grid, cycle, line width, font size; per-sheet keys outside that subset are not carried). `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 PNG; HTML/SVG sequence free-form panels rather than overlaying the true rectangles |
+| `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
@@ -100,7 +101,9 @@ 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=...)` reduced to callout text,
+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.
diff --git a/docs/matplotlib-shim-todo.md b/docs/matplotlib-shim-todo.md
index 7e5b6dae..45e51fa3 100644
--- a/docs/matplotlib-shim-todo.md
+++ b/docs/matplotlib-shim-todo.md
@@ -163,7 +163,10 @@ The shim can be called complete for ordinary 2-D scripts when:
- [x] Implement `subplots_adjust()` parameters (`left`, `right`, `top`,
`bottom`, `wspace`, `hspace`) for HTML, PNG, and SVG grids. Evidence:
`tests/pyplot/test_layout_noops.py::test_subplots_adjust_records_supported_spacing_values`
- records all supported spacing values for grid exporters and rejects unknown kwargs.
+ verifies the adjusted SubplotParams frame resolves to per-cell figure
+ rectangles (and rejects unknown kwargs and out-of-order frames);
+ `tests/pyplot/test_layout_text_parity_fixes.py::test_subplots_adjust_positions_grid_panels_in_every_exporter`
+ verifies HTML/PNG/SVG all position the adjusted panels.
- [x] Implement `Figure.autofmt_xdate()` label rotation/alignment. Evidence:
`tests/pyplot/test_layout_noops.py::test_autofmt_xdate_rotates_x_tick_labels_on_all_axes`
verifies rotation and horizontal alignment state on every axes.
diff --git a/examples/pdsh/README.md b/examples/pdsh/README.md
index b6735f83..c396ba1c 100644
--- a/examples/pdsh/README.md
+++ b/examples/pdsh/README.md
@@ -57,16 +57,21 @@ signal. A "cell ok" also implies a non-empty `savefig` PNG export.
| 04.05 Histograms and Binnings | 10/10 | 10/10 |
| 04.06 Customizing Legends | 11/11 | 10/11 |
| 04.07 Customizing Colorbars | 13/13 | 13/13 |
-| 04.08 Multiple Subplots | 10/10 | 9/10 |
+| 04.08 Multiple Subplots | 10/10 | 10/10 |
| 04.09 Text and Annotation | 9/9 | 6/9 |
-| 04.10 Customizing Ticks | 11/11 | 10/11 |
+| 04.10 Customizing Ticks | 11/11 | 11/11 |
| 04.11 Settings and Stylesheets | 15/15 | 15/15 |
| 04.12 Three-Dimensional Plotting¹ | 17/17 | 7/17 |
| 04.14 Visualization with Seaborn² | 31/31 | 30/31 |
-| **Total** | **171/171** | **154/171 (90%)** |
+| **Total** | **171/171** | **156/171 (91%)** |
-Excluding the out-of-scope 3D notebook: 147/154 (95%). The first
-measurement, before the gap-closing pass, was 121/171 (71%).
+Excluding the out-of-scope 3D notebook: 149/154 (97%). The first
+measurement, before the gap-closing pass, was 121/171 (71%). The 04.08
+and 04.10 rows reflect the 2026-07-14 `subplots_adjust` implementation:
+both formerly rejected cells re-ran and exported (04.10's re-run used
+identically shaped stand-in images because the measurement sandbox
+cannot download the Olivetti faces; the previously recorded failure was
+the `subplots_adjust` rejection itself, raised before any drawing).
¹ 3D projections are outside xy's 2-D chart-method compatibility target
(see [docs/matplotlib-compat.md](../../docs/matplotlib-compat.md));
@@ -103,13 +108,12 @@ fallback onto matplotlib will ever be added).
## Remaining failures
-All 17 are one of: 3-D projection cells (10, loud rejections by
+All 15 are one of: 3-D projection cells (10, loud rejections by
design), the second-legend `Legend` class (1, a documented loud rejection),
-`subplots_adjust` calls whose values the renderer cannot honor (2, now loud
-rather than silently discarded), pandas
-`Series.plot(ax=ax)` datetime interop (3, a real gap — a dtype error
+pandas `Series.plot(ax=ax)` datetime interop (3, a real gap — a dtype error
inside the pandas plotting path), and `axhline(marker=)` via seaborn
-(1, loud rejection).
+(1, loud rejection). The two `subplots_adjust` cells counted here until
+2026-07-14 pass now that the shim implements the SubplotParams frame.
The 2026-07-14 fidelity follow-up also compared the affected PDSH cells
directly against Matplotlib 3.11: directional markers and `x`/`+` remain
diff --git a/examples/pdsh/pdsh_04_00_introduction_to_matplotlib.ipynb b/examples/pdsh/pdsh_04_00_introduction_to_matplotlib.ipynb
index f859b8e5..74808447 100644
--- a/examples/pdsh/pdsh_04_00_introduction_to_matplotlib.ipynb
+++ b/examples/pdsh/pdsh_04_00_introduction_to_matplotlib.ipynb
@@ -74,7 +74,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "import matplotlib.pyplot as mpl_mpl\n",
+ "import matplotlib as mpl_mpl\n",
"import matplotlib.pyplot as mpl_plt"
]
},
@@ -196,7 +196,7 @@
"\n",
"x = np.linspace(0, 10, 100)\n",
"\n",
- "fig = mpl_plt.figure()\n",
+ "mpl_fig = mpl_plt.figure()\n",
"mpl_plt.plot(x, np.sin(x), \"-\")\n",
"mpl_plt.plot(x, np.cos(x), \"--\");"
]
@@ -216,7 +216,7 @@
"\n",
"x = np.linspace(0, 10, 100)\n",
"\n",
- "fig = xy_plt.figure()\n",
+ "xy_fig = xy_plt.figure()\n",
"xy_plt.plot(x, np.sin(x), \"-\")\n",
"xy_plt.plot(x, np.cos(x), \"--\");"
]
@@ -244,7 +244,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig.savefig(\"my_figure.png\")"
+ "mpl_fig.savefig(\"my_figure.png\")"
]
},
{
@@ -258,7 +258,7 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig.savefig(\"my_figure.png\")"
+ "xy_fig.savefig(\"my_figure.png\")"
]
},
{
@@ -304,7 +304,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig.canvas.get_supported_filetypes()"
+ "mpl_fig.canvas.get_supported_filetypes()"
]
},
{
@@ -318,7 +318,7 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig.canvas.get_supported_filetypes()"
+ "xy_fig.canvas.get_supported_filetypes()"
]
},
{
@@ -412,13 +412,13 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "# First create a grid of plots\n",
- "# ax will be an array of two Axes objects\n",
- "fig, ax = mpl_plt.subplots(2)\n",
+ "# First create a mpl_grid of plots\n",
+ "# mpl_ax will be an array of two Axes objects\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(2)\n",
"\n",
"# Call plot() method on the appropriate object\n",
- "ax[0].plot(x, np.sin(x))\n",
- "ax[1].plot(x, np.cos(x));"
+ "mpl_ax[0].plot(x, np.sin(x))\n",
+ "mpl_ax[1].plot(x, np.cos(x));"
]
},
{
@@ -432,13 +432,13 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "# First create a grid of plots\n",
- "# ax will be an array of two Axes objects\n",
- "fig, ax = xy_plt.subplots(2)\n",
+ "# First create a xy_grid of plots\n",
+ "# xy_ax will be an array of two Axes objects\n",
+ "xy_fig, xy_ax = xy_plt.subplots(2)\n",
"\n",
"# Call plot() method on the appropriate object\n",
- "ax[0].plot(x, np.sin(x))\n",
- "ax[1].plot(x, np.cos(x));"
+ "xy_ax[0].plot(x, np.sin(x))\n",
+ "xy_ax[1].plot(x, np.cos(x));"
]
}
],
diff --git a/examples/pdsh/pdsh_04_01_simple_line_plots.ipynb b/examples/pdsh/pdsh_04_01_simple_line_plots.ipynb
index 16a3d8f2..1870139f 100644
--- a/examples/pdsh/pdsh_04_01_simple_line_plots.ipynb
+++ b/examples/pdsh/pdsh_04_01_simple_line_plots.ipynb
@@ -84,8 +84,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax = mpl_plt.axes()"
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_ax = mpl_plt.axes()"
]
},
{
@@ -99,8 +99,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax = xy_plt.axes()"
+ "xy_fig = xy_plt.figure()\n",
+ "xy_ax = xy_plt.axes()"
]
},
{
@@ -114,11 +114,11 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax = mpl_plt.axes()\n",
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_ax = mpl_plt.axes()\n",
"\n",
"x = np.linspace(0, 10, 1000)\n",
- "ax.plot(x, np.sin(x));"
+ "mpl_ax.plot(x, np.sin(x));"
]
},
{
@@ -132,11 +132,11 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax = xy_plt.axes()\n",
+ "xy_fig = xy_plt.figure()\n",
+ "xy_ax = xy_plt.axes()\n",
"\n",
"x = np.linspace(0, 10, 1000)\n",
- "ax.plot(x, np.sin(x));"
+ "xy_ax.plot(x, np.sin(x));"
]
},
{
@@ -570,9 +570,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes()\n",
- "ax.plot(x, np.sin(x))\n",
- "ax.set(xlim=(0, 10), ylim=(-2, 2), xlabel=\"x\", ylabel=\"sin(x)\", title=\"A Simple Plot\");"
+ "mpl_ax = mpl_plt.axes()\n",
+ "mpl_ax.plot(x, np.sin(x))\n",
+ "mpl_ax.set(xlim=(0, 10), ylim=(-2, 2), xlabel=\"x\", ylabel=\"sin(x)\", title=\"A Simple Plot\");"
]
},
{
@@ -586,9 +586,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes()\n",
- "ax.plot(x, np.sin(x))\n",
- "ax.set(xlim=(0, 10), ylim=(-2, 2), xlabel=\"x\", ylabel=\"sin(x)\", title=\"A Simple Plot\");"
+ "xy_ax = xy_plt.axes()\n",
+ "xy_ax.plot(x, np.sin(x))\n",
+ "xy_ax.set(xlim=(0, 10), ylim=(-2, 2), xlabel=\"x\", ylabel=\"sin(x)\", title=\"A Simple Plot\");"
]
}
],
diff --git a/examples/pdsh/pdsh_04_05_histograms_and_binnings.ipynb b/examples/pdsh/pdsh_04_05_histograms_and_binnings.ipynb
index 412b956a..1728c419 100644
--- a/examples/pdsh/pdsh_04_05_histograms_and_binnings.ipynb
+++ b/examples/pdsh/pdsh_04_05_histograms_and_binnings.ipynb
@@ -414,7 +414,7 @@
"data = np.vstack([x, y])\n",
"kde = gaussian_kde(data)\n",
"\n",
- "# evaluate on a regular grid\n",
+ "# evaluate on a regular mpl_grid\n",
"xgrid = np.linspace(-3.5, 3.5, 40)\n",
"ygrid = np.linspace(-6, 6, 40)\n",
"Xgrid, Ygrid = np.meshgrid(xgrid, ygrid)\n",
@@ -443,7 +443,7 @@
"data = np.vstack([x, y])\n",
"kde = gaussian_kde(data)\n",
"\n",
- "# evaluate on a regular grid\n",
+ "# evaluate on a regular xy_grid\n",
"xgrid = np.linspace(-3.5, 3.5, 40)\n",
"ygrid = np.linspace(-6, 6, 40)\n",
"Xgrid, Ygrid = np.meshgrid(xgrid, ygrid)\n",
diff --git a/examples/pdsh/pdsh_04_06_customizing_legends.ipynb b/examples/pdsh/pdsh_04_06_customizing_legends.ipynb
index dcc2346b..7279a288 100644
--- a/examples/pdsh/pdsh_04_06_customizing_legends.ipynb
+++ b/examples/pdsh/pdsh_04_06_customizing_legends.ipynb
@@ -111,11 +111,11 @@
"source": [
"# Matplotlib reference\n",
"x = np.linspace(0, 10, 1000)\n",
- "fig, ax = mpl_plt.subplots()\n",
- "ax.plot(x, np.sin(x), \"-b\", label=\"Sine\")\n",
- "ax.plot(x, np.cos(x), \"--r\", label=\"Cosine\")\n",
- "ax.axis(\"equal\")\n",
- "leg = ax.legend()"
+ "mpl_fig, mpl_ax = mpl_plt.subplots()\n",
+ "mpl_ax.plot(x, np.sin(x), \"-b\", label=\"Sine\")\n",
+ "mpl_ax.plot(x, np.cos(x), \"--r\", label=\"Cosine\")\n",
+ "mpl_ax.axis(\"equal\")\n",
+ "leg = mpl_ax.legend()"
]
},
{
@@ -130,11 +130,11 @@
"source": [
"# xy.pyplot\n",
"x = np.linspace(0, 10, 1000)\n",
- "fig, ax = xy_plt.subplots()\n",
- "ax.plot(x, np.sin(x), \"-b\", label=\"Sine\")\n",
- "ax.plot(x, np.cos(x), \"--r\", label=\"Cosine\")\n",
- "ax.axis(\"equal\")\n",
- "leg = ax.legend()"
+ "xy_fig, xy_ax = xy_plt.subplots()\n",
+ "xy_ax.plot(x, np.sin(x), \"-b\", label=\"Sine\")\n",
+ "xy_ax.plot(x, np.cos(x), \"--r\", label=\"Cosine\")\n",
+ "xy_ax.axis(\"equal\")\n",
+ "leg = xy_ax.legend()"
]
},
{
@@ -148,8 +148,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.legend(loc=\"upper left\", frameon=True)\n",
- "fig"
+ "mpl_ax.legend(loc=\"upper left\", frameon=True)\n",
+ "mpl_fig"
]
},
{
@@ -163,8 +163,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.legend(loc=\"upper left\", frameon=True)\n",
- "fig"
+ "xy_ax.legend(loc=\"upper left\", frameon=True)\n",
+ "xy_fig"
]
},
{
@@ -178,8 +178,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.legend(loc=\"lower center\", ncol=2)\n",
- "fig"
+ "mpl_ax.legend(loc=\"lower center\", ncol=2)\n",
+ "mpl_fig"
]
},
{
@@ -193,8 +193,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.legend(loc=\"lower center\", ncol=2)\n",
- "fig"
+ "xy_ax.legend(loc=\"lower center\", ncol=2)\n",
+ "xy_fig"
]
},
{
@@ -208,8 +208,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.legend(frameon=True, fancybox=True, framealpha=1, shadow=True, borderpad=1)\n",
- "fig"
+ "mpl_ax.legend(frameon=True, fancybox=True, framealpha=1, shadow=True, borderpad=1)\n",
+ "mpl_fig"
]
},
{
@@ -223,8 +223,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.legend(frameon=True, fancybox=True, framealpha=1, shadow=True, borderpad=1)\n",
- "fig"
+ "xy_ax.legend(frameon=True, fancybox=True, framealpha=1, shadow=True, borderpad=1)\n",
+ "xy_fig"
]
},
{
@@ -454,24 +454,24 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots()\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots()\n",
"\n",
"lines = []\n",
"styles = [\"-\", \"--\", \"-.\", \":\"]\n",
"x = np.linspace(0, 10, 1000)\n",
"\n",
"for i in range(4):\n",
- " lines += ax.plot(x, np.sin(x - i * np.pi / 2), styles[i], color=\"black\")\n",
- "ax.axis(\"equal\")\n",
+ " lines += mpl_ax.plot(x, np.sin(x - i * np.pi / 2), styles[i], color=\"black\")\n",
+ "mpl_ax.axis(\"equal\")\n",
"\n",
"# Specify the lines and labels of the first legend\n",
- "ax.legend(lines[:2], [\"line A\", \"line B\"], loc=\"upper right\")\n",
+ "mpl_ax.legend(lines[:2], [\"line A\", \"line B\"], loc=\"upper right\")\n",
"\n",
"# Create the second legend and add the artist manually\n",
"from matplotlib.legend import Legend as mpl_Legend # was matplotlib.legend\n",
"\n",
- "leg = mpl_Legend(ax, lines[2:], [\"line C\", \"line D\"], loc=\"lower right\")\n",
- "ax.add_artist(leg);"
+ "leg = mpl_Legend(mpl_ax, lines[2:], [\"line C\", \"line D\"], loc=\"lower right\")\n",
+ "mpl_ax.add_artist(leg);"
]
},
{
@@ -485,24 +485,24 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots()\n",
+ "xy_fig, xy_ax = xy_plt.subplots()\n",
"\n",
"lines = []\n",
"styles = [\"-\", \"--\", \"-.\", \":\"]\n",
"x = np.linspace(0, 10, 1000)\n",
"\n",
"for i in range(4):\n",
- " lines += ax.plot(x, np.sin(x - i * np.pi / 2), styles[i], color=\"black\")\n",
- "ax.axis(\"equal\")\n",
+ " lines += xy_ax.plot(x, np.sin(x - i * np.pi / 2), styles[i], color=\"black\")\n",
+ "xy_ax.axis(\"equal\")\n",
"\n",
"# Specify the lines and labels of the first legend\n",
- "ax.legend(lines[:2], [\"line A\", \"line B\"], loc=\"upper right\")\n",
+ "xy_ax.legend(lines[:2], [\"line A\", \"line B\"], loc=\"upper right\")\n",
"\n",
"# Create the second legend and add the artist manually\n",
"from xy.pyplot import Legend as xy_Legend # was matplotlib.legend\n",
"\n",
- "leg = xy_Legend(ax, lines[2:], [\"line C\", \"line D\"], loc=\"lower right\")\n",
- "ax.add_artist(leg);"
+ "leg = xy_Legend(xy_ax, lines[2:], [\"line C\", \"line D\"], loc=\"lower right\")\n",
+ "xy_ax.add_artist(leg);"
]
}
],
diff --git a/examples/pdsh/pdsh_04_07_customizing_colorbars.ipynb b/examples/pdsh/pdsh_04_07_customizing_colorbars.ipynb
index 805b1d6a..0701e7fd 100644
--- a/examples/pdsh/pdsh_04_07_customizing_colorbars.ipynb
+++ b/examples/pdsh/pdsh_04_07_customizing_colorbars.ipynb
@@ -223,9 +223,9 @@
" cmap = grayscale_cmap(cmap)\n",
" grayscale = cmap(np.arange(cmap.N))\n",
"\n",
- " fig, ax = mpl_plt.subplots(2, figsize=(6, 2), subplot_kw=dict(xticks=[], yticks=[]))\n",
- " ax[0].imshow([colors], extent=[0, 10, 0, 1])\n",
- " ax[1].imshow([grayscale], extent=[0, 10, 0, 1])"
+ " mpl_fig, mpl_ax = mpl_plt.subplots(2, figsize=(6, 2), subplot_kw=dict(xticks=[], yticks=[]))\n",
+ " mpl_ax[0].imshow([colors], extent=[0, 10, 0, 1])\n",
+ " mpl_ax[1].imshow([grayscale], extent=[0, 10, 0, 1])"
]
},
{
@@ -264,9 +264,9 @@
" cmap = grayscale_cmap(cmap)\n",
" grayscale = cmap(np.arange(cmap.N))\n",
"\n",
- " fig, ax = xy_plt.subplots(2, figsize=(6, 2), subplot_kw=dict(xticks=[], yticks=[]))\n",
- " ax[0].imshow([colors], extent=[0, 10, 0, 1])\n",
- " ax[1].imshow([grayscale], extent=[0, 10, 0, 1])"
+ " xy_fig, xy_ax = xy_plt.subplots(2, figsize=(6, 2), subplot_kw=dict(xticks=[], yticks=[]))\n",
+ " xy_ax[0].imshow([colors], extent=[0, 10, 0, 1])\n",
+ " xy_ax[1].imshow([grayscale], extent=[0, 10, 0, 1])"
]
},
{
@@ -491,8 +491,8 @@
"\n",
"digits = load_digits(n_class=6)\n",
"\n",
- "fig, ax = mpl_plt.subplots(8, 8, figsize=(6, 6))\n",
- "for i, axi in enumerate(ax.flat):\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(8, 8, figsize=(6, 6))\n",
+ "for i, axi in enumerate(mpl_ax.flat):\n",
" axi.imshow(digits.images[i], cmap=\"binary\")\n",
" axi.set(xticks=[], yticks=[])"
]
@@ -513,8 +513,8 @@
"\n",
"digits = load_digits(n_class=6)\n",
"\n",
- "fig, ax = xy_plt.subplots(8, 8, figsize=(6, 6))\n",
- "for i, axi in enumerate(ax.flat):\n",
+ "xy_fig, xy_ax = xy_plt.subplots(8, 8, figsize=(6, 6))\n",
+ "for i, axi in enumerate(xy_ax.flat):\n",
" axi.imshow(digits.images[i], cmap=\"binary\")\n",
" axi.set(xticks=[], yticks=[])"
]
diff --git a/examples/pdsh/pdsh_04_08_multiple_subplots.ipynb b/examples/pdsh/pdsh_04_08_multiple_subplots.ipynb
index 60480278..e77f32a8 100644
--- a/examples/pdsh/pdsh_04_08_multiple_subplots.ipynb
+++ b/examples/pdsh/pdsh_04_08_multiple_subplots.ipynb
@@ -126,9 +126,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))\n",
- "ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))\n",
+ "mpl_fig = mpl_plt.figure()\n",
+ "ax1 = mpl_fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))\n",
+ "ax2 = mpl_fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))\n",
"\n",
"x = np.linspace(0, 10)\n",
"ax1.plot(np.sin(x))\n",
@@ -146,9 +146,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))\n",
- "ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))\n",
+ "xy_fig = xy_plt.figure()\n",
+ "ax1 = xy_fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))\n",
+ "ax2 = xy_fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))\n",
"\n",
"x = np.linspace(0, 10)\n",
"ax1.plot(np.sin(x))\n",
@@ -210,11 +210,11 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "fig.subplots_adjust(hspace=0.4, wspace=0.4)\n",
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_fig.subplots_adjust(hspace=0.4, wspace=0.4)\n",
"for i in range(1, 7):\n",
- " ax = fig.add_subplot(2, 3, i)\n",
- " ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha=\"center\")"
+ " mpl_ax = mpl_fig.add_subplot(2, 3, i)\n",
+ " mpl_ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha=\"center\")"
]
},
{
@@ -228,11 +228,11 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "fig.subplots_adjust(hspace=0.4, wspace=0.4)\n",
+ "xy_fig = xy_plt.figure()\n",
+ "xy_fig.subplots_adjust(hspace=0.4, wspace=0.4)\n",
"for i in range(1, 7):\n",
- " ax = fig.add_subplot(2, 3, i)\n",
- " ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha=\"center\")"
+ " xy_ax = xy_fig.add_subplot(2, 3, i)\n",
+ " xy_ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha=\"center\")"
]
},
{
@@ -258,7 +258,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(2, 3, sharex=\"col\", sharey=\"row\")"
+ "mpl_fig, mpl_ax = mpl_plt.subplots(2, 3, sharex=\"col\", sharey=\"row\")"
]
},
{
@@ -272,7 +272,7 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(2, 3, sharex=\"col\", sharey=\"row\")"
+ "xy_fig, xy_ax = xy_plt.subplots(2, 3, sharex=\"col\", sharey=\"row\")"
]
},
{
@@ -289,8 +289,8 @@
"# axes are in a two-dimensional array, indexed by [row, col]\n",
"for i in range(2):\n",
" for j in range(3):\n",
- " ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha=\"center\")\n",
- "fig"
+ " mpl_ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha=\"center\")\n",
+ "mpl_fig"
]
},
{
@@ -307,8 +307,8 @@
"# axes are in a two-dimensional array, indexed by [row, col]\n",
"for i in range(2):\n",
" for j in range(3):\n",
- " ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha=\"center\")\n",
- "fig"
+ " xy_ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha=\"center\")\n",
+ "xy_fig"
]
},
{
@@ -334,7 +334,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "grid = mpl_plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)"
+ "mpl_grid = mpl_plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)"
]
},
{
@@ -348,7 +348,7 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "grid = xy_plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)"
+ "xy_grid = xy_plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)"
]
},
{
@@ -362,10 +362,10 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "mpl_plt.subplot(grid[0, 0])\n",
- "mpl_plt.subplot(grid[0, 1:])\n",
- "mpl_plt.subplot(grid[1, :2])\n",
- "mpl_plt.subplot(grid[1, 2]);"
+ "mpl_plt.subplot(mpl_grid[0, 0])\n",
+ "mpl_plt.subplot(mpl_grid[0, 1:])\n",
+ "mpl_plt.subplot(mpl_grid[1, :2])\n",
+ "mpl_plt.subplot(mpl_grid[1, 2]);"
]
},
{
@@ -379,10 +379,10 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "xy_plt.subplot(grid[0, 0])\n",
- "xy_plt.subplot(grid[0, 1:])\n",
- "xy_plt.subplot(grid[1, :2])\n",
- "xy_plt.subplot(grid[1, 2]);"
+ "xy_plt.subplot(xy_grid[0, 0])\n",
+ "xy_plt.subplot(xy_grid[0, 1:])\n",
+ "xy_plt.subplot(xy_grid[1, :2])\n",
+ "xy_plt.subplot(xy_grid[1, 2]);"
]
},
{
@@ -403,11 +403,11 @@
"x, y = rng.multivariate_normal(mean, cov, 3000).T\n",
"\n",
"# Set up the axes with GridSpec\n",
- "fig = mpl_plt.figure(figsize=(6, 6))\n",
- "grid = mpl_plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)\n",
- "main_ax = fig.add_subplot(grid[:-1, 1:])\n",
- "y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax)\n",
- "x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax)\n",
+ "mpl_fig = mpl_plt.figure(figsize=(6, 6))\n",
+ "mpl_grid = mpl_plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)\n",
+ "main_ax = mpl_fig.add_subplot(mpl_grid[:-1, 1:])\n",
+ "y_hist = mpl_fig.add_subplot(mpl_grid[:-1, 0], xticklabels=[], sharey=main_ax)\n",
+ "x_hist = mpl_fig.add_subplot(mpl_grid[-1, 1:], yticklabels=[], sharex=main_ax)\n",
"\n",
"# Scatter points on the main axes\n",
"main_ax.plot(x, y, \"ok\", markersize=3, alpha=0.2)\n",
@@ -438,11 +438,11 @@
"x, y = rng.multivariate_normal(mean, cov, 3000).T\n",
"\n",
"# Set up the axes with GridSpec\n",
- "fig = xy_plt.figure(figsize=(6, 6))\n",
- "grid = xy_plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)\n",
- "main_ax = fig.add_subplot(grid[:-1, 1:])\n",
- "y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax)\n",
- "x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax)\n",
+ "xy_fig = xy_plt.figure(figsize=(6, 6))\n",
+ "xy_grid = xy_plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)\n",
+ "main_ax = xy_fig.add_subplot(xy_grid[:-1, 1:])\n",
+ "y_hist = xy_fig.add_subplot(xy_grid[:-1, 0], xticklabels=[], sharey=main_ax)\n",
+ "x_hist = xy_fig.add_subplot(xy_grid[-1, 1:], yticklabels=[], sharex=main_ax)\n",
"\n",
"# Scatter points on the main axes\n",
"main_ax.plot(x, y, \"ok\", markersize=3, alpha=0.2)\n",
diff --git a/examples/pdsh/pdsh_04_09_text_and_annotation.ipynb b/examples/pdsh/pdsh_04_09_text_and_annotation.ipynb
index 5ecfba3e..6a7adfc4 100644
--- a/examples/pdsh/pdsh_04_09_text_and_annotation.ipynb
+++ b/examples/pdsh/pdsh_04_09_text_and_annotation.ipynb
@@ -51,7 +51,7 @@
"source": [
"# Matplotlib reference\n",
"import matplotlib.pyplot as mpl_plt\n",
- "import matplotlib.pyplot as mpl_mpl\n",
+ "import matplotlib as mpl_mpl\n",
"\n",
"mpl_plt.style.use(\"default\")\n",
"import numpy as np\n",
@@ -188,8 +188,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax);"
+ "mpl_fig, mpl_ax = mpl_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=mpl_ax);"
]
},
{
@@ -203,8 +203,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax);"
+ "xy_fig, xy_ax = xy_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=xy_ax);"
]
},
{
@@ -218,27 +218,27 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax)\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=mpl_ax)\n",
"\n",
"# Add labels to the plot\n",
"style = dict(size=10, color=\"gray\")\n",
"\n",
- "ax.text(\"2012-1-1\", 3950, \"New Year's Day\", **style)\n",
- "ax.text(\"2012-7-4\", 4250, \"Independence Day\", ha=\"center\", **style)\n",
- "ax.text(\"2012-9-4\", 4850, \"Labor Day\", ha=\"center\", **style)\n",
- "ax.text(\"2012-10-31\", 4600, \"Halloween\", ha=\"right\", **style)\n",
- "ax.text(\"2012-11-25\", 4450, \"Thanksgiving\", ha=\"center\", **style)\n",
- "ax.text(\"2012-12-25\", 3850, \"Christmas \", ha=\"right\", **style)\n",
+ "mpl_ax.text(\"2012-1-1\", 3950, \"New Year's Day\", **style)\n",
+ "mpl_ax.text(\"2012-7-4\", 4250, \"Independence Day\", ha=\"center\", **style)\n",
+ "mpl_ax.text(\"2012-9-4\", 4850, \"Labor Day\", ha=\"center\", **style)\n",
+ "mpl_ax.text(\"2012-10-31\", 4600, \"Halloween\", ha=\"right\", **style)\n",
+ "mpl_ax.text(\"2012-11-25\", 4450, \"Thanksgiving\", ha=\"center\", **style)\n",
+ "mpl_ax.text(\"2012-12-25\", 3850, \"Christmas \", ha=\"right\", **style)\n",
"\n",
"# Label the axes\n",
- "ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
+ "mpl_ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
"\n",
"# Format the x-axis with centered month labels\n",
- "ax.xaxis.set_major_locator(mpl_mpl.dates.MonthLocator())\n",
- "ax.xaxis.set_minor_locator(mpl_mpl.dates.MonthLocator(bymonthday=15))\n",
- "ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())\n",
- "ax.xaxis.set_minor_formatter(mpl_mpl.dates.DateFormatter(\"%h\"));"
+ "mpl_ax.xaxis.set_major_locator(mpl_mpl.dates.MonthLocator())\n",
+ "mpl_ax.xaxis.set_minor_locator(mpl_mpl.dates.MonthLocator(bymonthday=15))\n",
+ "mpl_ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())\n",
+ "mpl_ax.xaxis.set_minor_formatter(mpl_mpl.dates.DateFormatter(\"%h\"));"
]
},
{
@@ -252,27 +252,27 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax)\n",
+ "xy_fig, xy_ax = xy_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=xy_ax)\n",
"\n",
"# Add labels to the plot\n",
"style = dict(size=10, color=\"gray\")\n",
"\n",
- "ax.text(\"2012-1-1\", 3950, \"New Year's Day\", **style)\n",
- "ax.text(\"2012-7-4\", 4250, \"Independence Day\", ha=\"center\", **style)\n",
- "ax.text(\"2012-9-4\", 4850, \"Labor Day\", ha=\"center\", **style)\n",
- "ax.text(\"2012-10-31\", 4600, \"Halloween\", ha=\"right\", **style)\n",
- "ax.text(\"2012-11-25\", 4450, \"Thanksgiving\", ha=\"center\", **style)\n",
- "ax.text(\"2012-12-25\", 3850, \"Christmas \", ha=\"right\", **style)\n",
+ "xy_ax.text(\"2012-1-1\", 3950, \"New Year's Day\", **style)\n",
+ "xy_ax.text(\"2012-7-4\", 4250, \"Independence Day\", ha=\"center\", **style)\n",
+ "xy_ax.text(\"2012-9-4\", 4850, \"Labor Day\", ha=\"center\", **style)\n",
+ "xy_ax.text(\"2012-10-31\", 4600, \"Halloween\", ha=\"right\", **style)\n",
+ "xy_ax.text(\"2012-11-25\", 4450, \"Thanksgiving\", ha=\"center\", **style)\n",
+ "xy_ax.text(\"2012-12-25\", 3850, \"Christmas \", ha=\"right\", **style)\n",
"\n",
"# Label the axes\n",
- "ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
+ "xy_ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
"\n",
"# Format the x-axis with centered month labels\n",
- "ax.xaxis.set_major_locator(xy_mpl.dates.MonthLocator())\n",
- "ax.xaxis.set_minor_locator(xy_mpl.dates.MonthLocator(bymonthday=15))\n",
- "ax.xaxis.set_major_formatter(xy_plt.NullFormatter())\n",
- "ax.xaxis.set_minor_formatter(xy_mpl.dates.DateFormatter(\"%h\"));"
+ "xy_ax.xaxis.set_major_locator(xy_mpl.dates.MonthLocator())\n",
+ "xy_ax.xaxis.set_minor_locator(xy_mpl.dates.MonthLocator(bymonthday=15))\n",
+ "xy_ax.xaxis.set_major_formatter(xy_plt.NullFormatter())\n",
+ "xy_ax.xaxis.set_minor_formatter(xy_mpl.dates.DateFormatter(\"%h\"));"
]
},
{
@@ -298,13 +298,13 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(facecolor=\"lightgray\")\n",
- "ax.axis([0, 10, 0, 10])\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(facecolor=\"lightgray\")\n",
+ "mpl_ax.axis([0, 10, 0, 10])\n",
"\n",
- "# transform=ax.transData is the default, but we'll specify it anyway\n",
- "ax.text(1, 5, \". Data: (1, 5)\", transform=ax.transData)\n",
- "ax.text(0.5, 0.1, \". Axes: (0.5, 0.1)\", transform=ax.transAxes)\n",
- "ax.text(0.2, 0.2, \". Figure: (0.2, 0.2)\", transform=fig.transFigure);"
+ "# transform=mpl_ax.transData is the default, but we'll specify it anyway\n",
+ "mpl_ax.text(1, 5, \". Data: (1, 5)\", transform=mpl_ax.transData)\n",
+ "mpl_ax.text(0.5, 0.1, \". Axes: (0.5, 0.1)\", transform=mpl_ax.transAxes)\n",
+ "mpl_ax.text(0.2, 0.2, \". Figure: (0.2, 0.2)\", transform=mpl_fig.transFigure);"
]
},
{
@@ -318,13 +318,13 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(facecolor=\"lightgray\")\n",
- "ax.axis([0, 10, 0, 10])\n",
+ "xy_fig, xy_ax = xy_plt.subplots(facecolor=\"lightgray\")\n",
+ "xy_ax.axis([0, 10, 0, 10])\n",
"\n",
- "# transform=ax.transData is the default, but we'll specify it anyway\n",
- "ax.text(1, 5, \". Data: (1, 5)\", transform=ax.transData)\n",
- "ax.text(0.5, 0.1, \". Axes: (0.5, 0.1)\", transform=ax.transAxes)\n",
- "ax.text(0.2, 0.2, \". Figure: (0.2, 0.2)\", transform=fig.transFigure);"
+ "# transform=xy_ax.transData is the default, but we'll specify it anyway\n",
+ "xy_ax.text(1, 5, \". Data: (1, 5)\", transform=xy_ax.transData)\n",
+ "xy_ax.text(0.5, 0.1, \". Axes: (0.5, 0.1)\", transform=xy_ax.transAxes)\n",
+ "xy_ax.text(0.2, 0.2, \". Figure: (0.2, 0.2)\", transform=xy_fig.transFigure);"
]
},
{
@@ -338,9 +338,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.set_xlim(0, 2)\n",
- "ax.set_ylim(-6, 6)\n",
- "fig"
+ "mpl_ax.set_xlim(0, 2)\n",
+ "mpl_ax.set_ylim(-6, 6)\n",
+ "mpl_fig"
]
},
{
@@ -354,9 +354,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.set_xlim(0, 2)\n",
- "ax.set_ylim(-6, 6)\n",
- "fig"
+ "xy_ax.set_xlim(0, 2)\n",
+ "xy_ax.set_ylim(-6, 6)\n",
+ "xy_fig"
]
},
{
@@ -382,17 +382,17 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots()\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots()\n",
"\n",
"x = np.linspace(0, 20, 1000)\n",
- "ax.plot(x, np.cos(x))\n",
- "ax.axis(\"equal\")\n",
+ "mpl_ax.plot(x, np.cos(x))\n",
+ "mpl_ax.axis(\"equal\")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"local maximum\", xy=(6.28, 1), xytext=(10, 4), arrowprops=dict(facecolor=\"black\", shrink=0.05)\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"local minimum\",\n",
" xy=(5 * np.pi, -1),\n",
" xytext=(2, -6),\n",
@@ -411,17 +411,17 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots()\n",
+ "xy_fig, xy_ax = xy_plt.subplots()\n",
"\n",
"x = np.linspace(0, 20, 1000)\n",
- "ax.plot(x, np.cos(x))\n",
- "ax.axis(\"equal\")\n",
+ "xy_ax.plot(x, np.cos(x))\n",
+ "xy_ax.axis(\"equal\")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"local maximum\", xy=(6.28, 1), xytext=(10, 4), arrowprops=dict(facecolor=\"black\", shrink=0.05)\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"local minimum\",\n",
" xy=(5 * np.pi, -1),\n",
" xytext=(2, -6),\n",
@@ -440,11 +440,11 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax)\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=mpl_ax)\n",
"\n",
"# Add labels to the plot\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"New Year's Day\",\n",
" xy=(\"2012-1-1\", 4100),\n",
" xycoords=\"data\",\n",
@@ -453,7 +453,7 @@
" arrowprops=dict(arrowstyle=\"->\", connectionstyle=\"arc3,rad=-0.2\"),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"Independence Day\",\n",
" xy=(\"2012-7-4\", 4250),\n",
" xycoords=\"data\",\n",
@@ -464,7 +464,7 @@
" arrowprops=dict(arrowstyle=\"->\"),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"Labor Day Weekend\",\n",
" xy=(\"2012-9-4\", 4850),\n",
" xycoords=\"data\",\n",
@@ -472,7 +472,7 @@
" xytext=(0, -20),\n",
" textcoords=\"offset points\",\n",
")\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"\",\n",
" xy=(\"2012-9-1\", 4850),\n",
" xytext=(\"2012-9-7\", 4850),\n",
@@ -483,7 +483,7 @@
" },\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"Halloween\",\n",
" xy=(\"2012-10-31\", 4600),\n",
" xycoords=\"data\",\n",
@@ -494,7 +494,7 @@
" ),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"Thanksgiving\",\n",
" xy=(\"2012-11-25\", 4500),\n",
" xycoords=\"data\",\n",
@@ -505,7 +505,7 @@
")\n",
"\n",
"\n",
- "ax.annotate(\n",
+ "mpl_ax.annotate(\n",
" \"Christmas\",\n",
" xy=(\"2012-12-25\", 3850),\n",
" xycoords=\"data\",\n",
@@ -518,14 +518,14 @@
" arrowprops=dict(arrowstyle=\"wedge,tail_width=0.5\", alpha=0.1),\n",
")\n",
"# Label the axes\n",
- "ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
+ "mpl_ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
"\n",
"# Format the x-axis with centered month labels\n",
- "ax.xaxis.set_major_locator(mpl_mpl.dates.MonthLocator())\n",
- "ax.xaxis.set_minor_locator(mpl_mpl.dates.MonthLocator(bymonthday=15))\n",
- "ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())\n",
- "ax.xaxis.set_minor_formatter(mpl_mpl.dates.DateFormatter(\"%h\"))\n",
- "ax.set_ylim(3600, 5400);"
+ "mpl_ax.xaxis.set_major_locator(mpl_mpl.dates.MonthLocator())\n",
+ "mpl_ax.xaxis.set_minor_locator(mpl_mpl.dates.MonthLocator(bymonthday=15))\n",
+ "mpl_ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())\n",
+ "mpl_ax.xaxis.set_minor_formatter(mpl_mpl.dates.DateFormatter(\"%h\"))\n",
+ "mpl_ax.set_ylim(3600, 5400);"
]
},
{
@@ -539,11 +539,11 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(figsize=(12, 4))\n",
- "births_by_date.plot(ax=ax)\n",
+ "xy_fig, xy_ax = xy_plt.subplots(figsize=(12, 4))\n",
+ "births_by_date.plot(ax=xy_ax)\n",
"\n",
"# Add labels to the plot\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"New Year's Day\",\n",
" xy=(\"2012-1-1\", 4100),\n",
" xycoords=\"data\",\n",
@@ -552,7 +552,7 @@
" arrowprops=dict(arrowstyle=\"->\", connectionstyle=\"arc3,rad=-0.2\"),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"Independence Day\",\n",
" xy=(\"2012-7-4\", 4250),\n",
" xycoords=\"data\",\n",
@@ -563,7 +563,7 @@
" arrowprops=dict(arrowstyle=\"->\"),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"Labor Day Weekend\",\n",
" xy=(\"2012-9-4\", 4850),\n",
" xycoords=\"data\",\n",
@@ -571,7 +571,7 @@
" xytext=(0, -20),\n",
" textcoords=\"offset points\",\n",
")\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"\",\n",
" xy=(\"2012-9-1\", 4850),\n",
" xytext=(\"2012-9-7\", 4850),\n",
@@ -582,7 +582,7 @@
" },\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"Halloween\",\n",
" xy=(\"2012-10-31\", 4600),\n",
" xycoords=\"data\",\n",
@@ -593,7 +593,7 @@
" ),\n",
")\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"Thanksgiving\",\n",
" xy=(\"2012-11-25\", 4500),\n",
" xycoords=\"data\",\n",
@@ -604,7 +604,7 @@
")\n",
"\n",
"\n",
- "ax.annotate(\n",
+ "xy_ax.annotate(\n",
" \"Christmas\",\n",
" xy=(\"2012-12-25\", 3850),\n",
" xycoords=\"data\",\n",
@@ -617,14 +617,14 @@
" arrowprops=dict(arrowstyle=\"wedge,tail_width=0.5\", alpha=0.1),\n",
")\n",
"# Label the axes\n",
- "ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
+ "xy_ax.set(title=\"USA births by day of year (1969-1988)\", ylabel=\"average daily births\")\n",
"\n",
"# Format the x-axis with centered month labels\n",
- "ax.xaxis.set_major_locator(xy_mpl.dates.MonthLocator())\n",
- "ax.xaxis.set_minor_locator(xy_mpl.dates.MonthLocator(bymonthday=15))\n",
- "ax.xaxis.set_major_formatter(xy_plt.NullFormatter())\n",
- "ax.xaxis.set_minor_formatter(xy_mpl.dates.DateFormatter(\"%h\"))\n",
- "ax.set_ylim(3600, 5400);"
+ "xy_ax.xaxis.set_major_locator(xy_mpl.dates.MonthLocator())\n",
+ "xy_ax.xaxis.set_minor_locator(xy_mpl.dates.MonthLocator(bymonthday=15))\n",
+ "xy_ax.xaxis.set_major_formatter(xy_plt.NullFormatter())\n",
+ "xy_ax.xaxis.set_minor_formatter(xy_mpl.dates.DateFormatter(\"%h\"))\n",
+ "xy_ax.set_ylim(3600, 5400);"
]
}
],
diff --git a/examples/pdsh/pdsh_04_10_customizing_ticks.ipynb b/examples/pdsh/pdsh_04_10_customizing_ticks.ipynb
index 2374fcb3..4bc3db36 100644
--- a/examples/pdsh/pdsh_04_10_customizing_ticks.ipynb
+++ b/examples/pdsh/pdsh_04_10_customizing_ticks.ipynb
@@ -96,9 +96,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes(xscale=\"log\", yscale=\"log\")\n",
- "ax.set(xlim=(1, 1e3), ylim=(1, 1e3))\n",
- "ax.grid(True);"
+ "mpl_ax = mpl_plt.axes(xscale=\"log\", yscale=\"log\")\n",
+ "mpl_ax.set(xlim=(1, 1e3), ylim=(1, 1e3))\n",
+ "mpl_ax.grid(True);"
]
},
{
@@ -112,9 +112,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes(xscale=\"log\", yscale=\"log\")\n",
- "ax.set(xlim=(1, 1e3), ylim=(1, 1e3))\n",
- "ax.grid(True);"
+ "xy_ax = xy_plt.axes(xscale=\"log\", yscale=\"log\")\n",
+ "xy_ax.set(xlim=(1, 1e3), ylim=(1, 1e3))\n",
+ "xy_ax.grid(True);"
]
},
{
@@ -128,8 +128,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "print(ax.xaxis.get_major_locator())\n",
- "print(ax.xaxis.get_minor_locator())"
+ "print(mpl_ax.xaxis.get_major_locator())\n",
+ "print(mpl_ax.xaxis.get_minor_locator())"
]
},
{
@@ -143,8 +143,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "print(ax.xaxis.get_major_locator())\n",
- "print(ax.xaxis.get_minor_locator())"
+ "print(xy_ax.xaxis.get_major_locator())\n",
+ "print(xy_ax.xaxis.get_minor_locator())"
]
},
{
@@ -158,8 +158,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "print(ax.xaxis.get_major_formatter())\n",
- "print(ax.xaxis.get_minor_formatter())"
+ "print(mpl_ax.xaxis.get_major_formatter())\n",
+ "print(mpl_ax.xaxis.get_minor_formatter())"
]
},
{
@@ -173,8 +173,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "print(ax.xaxis.get_major_formatter())\n",
- "print(ax.xaxis.get_minor_formatter())"
+ "print(xy_ax.xaxis.get_major_formatter())\n",
+ "print(xy_ax.xaxis.get_minor_formatter())"
]
},
{
@@ -200,13 +200,13 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes()\n",
+ "mpl_ax = mpl_plt.axes()\n",
"rng = np.random.default_rng(1701)\n",
- "ax.plot(rng.random(50))\n",
- "ax.grid()\n",
+ "mpl_ax.plot(rng.random(50))\n",
+ "mpl_ax.grid()\n",
"\n",
- "ax.yaxis.set_major_locator(mpl_plt.NullLocator())\n",
- "ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())"
+ "mpl_ax.yaxis.set_major_locator(mpl_plt.NullLocator())\n",
+ "mpl_ax.xaxis.set_major_formatter(mpl_plt.NullFormatter())"
]
},
{
@@ -220,13 +220,13 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes()\n",
+ "xy_ax = xy_plt.axes()\n",
"rng = np.random.default_rng(1701)\n",
- "ax.plot(rng.random(50))\n",
- "ax.grid()\n",
+ "xy_ax.plot(rng.random(50))\n",
+ "xy_ax.grid()\n",
"\n",
- "ax.yaxis.set_major_locator(xy_plt.NullLocator())\n",
- "ax.xaxis.set_major_formatter(xy_plt.NullFormatter())"
+ "xy_ax.yaxis.set_major_locator(xy_plt.NullLocator())\n",
+ "xy_ax.xaxis.set_major_formatter(xy_plt.NullFormatter())"
]
},
{
@@ -240,8 +240,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(5, 5, figsize=(5, 5))\n",
- "fig.subplots_adjust(hspace=0, wspace=0)\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots(5, 5, figsize=(5, 5))\n",
+ "mpl_fig.subplots_adjust(hspace=0, wspace=0)\n",
"\n",
"# Get some face data from Scikit-Learn\n",
"from sklearn.datasets import fetch_olivetti_faces\n",
@@ -250,9 +250,9 @@
"\n",
"for i in range(5):\n",
" for j in range(5):\n",
- " ax[i, j].xaxis.set_major_locator(mpl_plt.NullLocator())\n",
- " ax[i, j].yaxis.set_major_locator(mpl_plt.NullLocator())\n",
- " ax[i, j].imshow(faces[10 * i + j], cmap=\"binary_r\")"
+ " mpl_ax[i, j].xaxis.set_major_locator(mpl_plt.NullLocator())\n",
+ " mpl_ax[i, j].yaxis.set_major_locator(mpl_plt.NullLocator())\n",
+ " mpl_ax[i, j].imshow(faces[10 * i + j], cmap=\"binary_r\")"
]
},
{
@@ -266,8 +266,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(5, 5, figsize=(5, 5))\n",
- "fig.subplots_adjust(hspace=0, wspace=0)\n",
+ "xy_fig, xy_ax = xy_plt.subplots(5, 5, figsize=(5, 5))\n",
+ "xy_fig.subplots_adjust(hspace=0, wspace=0)\n",
"\n",
"# Get some face data from Scikit-Learn\n",
"from sklearn.datasets import fetch_olivetti_faces\n",
@@ -276,9 +276,9 @@
"\n",
"for i in range(5):\n",
" for j in range(5):\n",
- " ax[i, j].xaxis.set_major_locator(xy_plt.NullLocator())\n",
- " ax[i, j].yaxis.set_major_locator(xy_plt.NullLocator())\n",
- " ax[i, j].imshow(faces[10 * i + j], cmap=\"binary_r\")"
+ " xy_ax[i, j].xaxis.set_major_locator(xy_plt.NullLocator())\n",
+ " xy_ax[i, j].yaxis.set_major_locator(xy_plt.NullLocator())\n",
+ " xy_ax[i, j].imshow(faces[10 * i + j], cmap=\"binary_r\")"
]
},
{
@@ -304,7 +304,7 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig, ax = mpl_plt.subplots(4, 4, sharex=True, sharey=True)"
+ "mpl_fig, mpl_ax = mpl_plt.subplots(4, 4, sharex=True, sharey=True)"
]
},
{
@@ -318,7 +318,7 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig, ax = xy_plt.subplots(4, 4, sharex=True, sharey=True)"
+ "xy_fig, xy_ax = xy_plt.subplots(4, 4, sharex=True, sharey=True)"
]
},
{
@@ -333,10 +333,10 @@
"source": [
"# Matplotlib reference\n",
"# For every axis, set the x and y major locator\n",
- "for axi in ax.flat:\n",
+ "for axi in mpl_ax.flat:\n",
" axi.xaxis.set_major_locator(mpl_plt.MaxNLocator(3))\n",
" axi.yaxis.set_major_locator(mpl_plt.MaxNLocator(3))\n",
- "fig"
+ "mpl_fig"
]
},
{
@@ -351,10 +351,10 @@
"source": [
"# xy.pyplot\n",
"# For every axis, set the x and y major locator\n",
- "for axi in ax.flat:\n",
+ "for axi in xy_ax.flat:\n",
" axi.xaxis.set_major_locator(xy_plt.MaxNLocator(3))\n",
" axi.yaxis.set_major_locator(xy_plt.MaxNLocator(3))\n",
- "fig"
+ "xy_fig"
]
},
{
@@ -381,16 +381,16 @@
"source": [
"# Matplotlib reference\n",
"# Plot a sine and cosine curve\n",
- "fig, ax = mpl_plt.subplots()\n",
+ "mpl_fig, mpl_ax = mpl_plt.subplots()\n",
"x = np.linspace(0, 3 * np.pi, 1000)\n",
- "ax.plot(x, np.sin(x), lw=3, label=\"Sine\")\n",
- "ax.plot(x, np.cos(x), lw=3, label=\"Cosine\")\n",
+ "mpl_ax.plot(x, np.sin(x), lw=3, label=\"Sine\")\n",
+ "mpl_ax.plot(x, np.cos(x), lw=3, label=\"Cosine\")\n",
"\n",
- "# Set up grid, legend, and limits\n",
- "ax.grid(True)\n",
- "ax.legend(frameon=False)\n",
- "ax.axis(\"equal\")\n",
- "ax.set_xlim(0, 3 * np.pi);"
+ "# Set up mpl_grid, legend, and limits\n",
+ "mpl_ax.grid(True)\n",
+ "mpl_ax.legend(frameon=False)\n",
+ "mpl_ax.axis(\"equal\")\n",
+ "mpl_ax.set_xlim(0, 3 * np.pi);"
]
},
{
@@ -405,16 +405,16 @@
"source": [
"# xy.pyplot\n",
"# Plot a sine and cosine curve\n",
- "fig, ax = xy_plt.subplots()\n",
+ "xy_fig, xy_ax = xy_plt.subplots()\n",
"x = np.linspace(0, 3 * np.pi, 1000)\n",
- "ax.plot(x, np.sin(x), lw=3, label=\"Sine\")\n",
- "ax.plot(x, np.cos(x), lw=3, label=\"Cosine\")\n",
+ "xy_ax.plot(x, np.sin(x), lw=3, label=\"Sine\")\n",
+ "xy_ax.plot(x, np.cos(x), lw=3, label=\"Cosine\")\n",
"\n",
- "# Set up grid, legend, and limits\n",
- "ax.grid(True)\n",
- "ax.legend(frameon=False)\n",
- "ax.axis(\"equal\")\n",
- "ax.set_xlim(0, 3 * np.pi);"
+ "# Set up xy_grid, legend, and limits\n",
+ "xy_ax.grid(True)\n",
+ "xy_ax.legend(frameon=False)\n",
+ "xy_ax.axis(\"equal\")\n",
+ "xy_ax.set_xlim(0, 3 * np.pi);"
]
},
{
@@ -428,9 +428,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.xaxis.set_major_locator(mpl_plt.MultipleLocator(np.pi / 2))\n",
- "ax.xaxis.set_minor_locator(mpl_plt.MultipleLocator(np.pi / 4))\n",
- "fig"
+ "mpl_ax.xaxis.set_major_locator(mpl_plt.MultipleLocator(np.pi / 2))\n",
+ "mpl_ax.xaxis.set_minor_locator(mpl_plt.MultipleLocator(np.pi / 4))\n",
+ "mpl_fig"
]
},
{
@@ -444,9 +444,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.xaxis.set_major_locator(xy_plt.MultipleLocator(np.pi / 2))\n",
- "ax.xaxis.set_minor_locator(xy_plt.MultipleLocator(np.pi / 4))\n",
- "fig"
+ "xy_ax.xaxis.set_major_locator(xy_plt.MultipleLocator(np.pi / 2))\n",
+ "xy_ax.xaxis.set_minor_locator(xy_plt.MultipleLocator(np.pi / 4))\n",
+ "xy_fig"
]
},
{
@@ -475,8 +475,8 @@
" return rf\"${N // 2}\\pi$\"\n",
"\n",
"\n",
- "ax.xaxis.set_major_formatter(mpl_plt.FuncFormatter(format_func))\n",
- "fig"
+ "mpl_ax.xaxis.set_major_formatter(mpl_plt.FuncFormatter(format_func))\n",
+ "mpl_fig"
]
},
{
@@ -505,8 +505,8 @@
" return rf\"${N // 2}\\pi$\"\n",
"\n",
"\n",
- "ax.xaxis.set_major_formatter(xy_plt.FuncFormatter(format_func))\n",
- "fig"
+ "xy_ax.xaxis.set_major_formatter(xy_plt.FuncFormatter(format_func))\n",
+ "xy_fig"
]
},
{
diff --git a/examples/pdsh/pdsh_04_11_settings_and_stylesheets.ipynb b/examples/pdsh/pdsh_04_11_settings_and_stylesheets.ipynb
index bd6dd0d1..bb522a12 100644
--- a/examples/pdsh/pdsh_04_11_settings_and_stylesheets.ipynb
+++ b/examples/pdsh/pdsh_04_11_settings_and_stylesheets.ipynb
@@ -127,30 +127,30 @@
"source": [
"# Matplotlib reference\n",
"# use a gray background\n",
- "fig = mpl_plt.figure(facecolor=\"white\")\n",
- "ax = mpl_plt.axes(facecolor=\"#E6E6E6\")\n",
- "ax.set_axisbelow(True)\n",
+ "mpl_fig = mpl_plt.figure(facecolor=\"white\")\n",
+ "mpl_ax = mpl_plt.axes(facecolor=\"#E6E6E6\")\n",
+ "mpl_ax.set_axisbelow(True)\n",
"\n",
"# draw solid white gridlines\n",
"mpl_plt.grid(color=\"w\", linestyle=\"solid\")\n",
"\n",
"# hide axis spines\n",
- "for spine in ax.spines.values():\n",
+ "for spine in mpl_ax.spines.values():\n",
" spine.set_visible(False)\n",
"\n",
"# hide top and right ticks\n",
- "ax.xaxis.tick_bottom()\n",
- "ax.yaxis.tick_left()\n",
+ "mpl_ax.xaxis.tick_bottom()\n",
+ "mpl_ax.yaxis.tick_left()\n",
"\n",
"# lighten ticks and labels\n",
- "ax.tick_params(colors=\"gray\", direction=\"out\")\n",
- "for tick in ax.get_xticklabels():\n",
+ "mpl_ax.tick_params(colors=\"gray\", direction=\"out\")\n",
+ "for tick in mpl_ax.get_xticklabels():\n",
" tick.set_color(\"gray\")\n",
- "for tick in ax.get_yticklabels():\n",
+ "for tick in mpl_ax.get_yticklabels():\n",
" tick.set_color(\"gray\")\n",
"\n",
"# control face and edge color of histogram\n",
- "ax.hist(x, edgecolor=\"#E6E6E6\", color=\"#EE6666\");"
+ "mpl_ax.hist(x, edgecolor=\"#E6E6E6\", color=\"#EE6666\");"
]
},
{
@@ -165,30 +165,30 @@
"source": [
"# xy.pyplot\n",
"# use a gray background\n",
- "fig = xy_plt.figure(facecolor=\"white\")\n",
- "ax = xy_plt.axes(facecolor=\"#E6E6E6\")\n",
- "ax.set_axisbelow(True)\n",
+ "xy_fig = xy_plt.figure(facecolor=\"white\")\n",
+ "xy_ax = xy_plt.axes(facecolor=\"#E6E6E6\")\n",
+ "xy_ax.set_axisbelow(True)\n",
"\n",
"# draw solid white gridlines\n",
"xy_plt.grid(color=\"w\", linestyle=\"solid\")\n",
"\n",
"# hide axis spines\n",
- "for spine in ax.spines.values():\n",
+ "for spine in xy_ax.spines.values():\n",
" spine.set_visible(False)\n",
"\n",
"# hide top and right ticks\n",
- "ax.xaxis.tick_bottom()\n",
- "ax.yaxis.tick_left()\n",
+ "xy_ax.xaxis.tick_bottom()\n",
+ "xy_ax.yaxis.tick_left()\n",
"\n",
"# lighten ticks and labels\n",
- "ax.tick_params(colors=\"gray\", direction=\"out\")\n",
- "for tick in ax.get_xticklabels():\n",
+ "xy_ax.tick_params(colors=\"gray\", direction=\"out\")\n",
+ "for tick in xy_ax.get_xticklabels():\n",
" tick.set_color(\"gray\")\n",
- "for tick in ax.get_yticklabels():\n",
+ "for tick in xy_ax.get_yticklabels():\n",
" tick.set_color(\"gray\")\n",
"\n",
"# control face and edge color of histogram\n",
- "ax.hist(x, edgecolor=\"#E6E6E6\", color=\"#EE6666\");"
+ "xy_ax.hist(x, edgecolor=\"#E6E6E6\", color=\"#EE6666\");"
]
},
{
@@ -364,11 +364,11 @@
"# Matplotlib reference\n",
"def hist_and_lines():\n",
" np.random.seed(0)\n",
- " fig, ax = mpl_plt.subplots(1, 2, figsize=(11, 4))\n",
- " ax[0].hist(np.random.randn(1000))\n",
+ " mpl_fig, mpl_ax = mpl_plt.subplots(1, 2, figsize=(11, 4))\n",
+ " mpl_ax[0].hist(np.random.randn(1000))\n",
" for i in range(3):\n",
- " ax[1].plot(np.random.rand(10))\n",
- " ax[1].legend([\"a\", \"b\", \"c\"], loc=\"lower left\")"
+ " mpl_ax[1].plot(np.random.rand(10))\n",
+ " mpl_ax[1].legend([\"a\", \"b\", \"c\"], loc=\"lower left\")"
]
},
{
@@ -384,11 +384,11 @@
"# xy.pyplot\n",
"def hist_and_lines():\n",
" np.random.seed(0)\n",
- " fig, ax = xy_plt.subplots(1, 2, figsize=(11, 4))\n",
- " ax[0].hist(np.random.randn(1000))\n",
+ " xy_fig, xy_ax = xy_plt.subplots(1, 2, figsize=(11, 4))\n",
+ " xy_ax[0].hist(np.random.randn(1000))\n",
" for i in range(3):\n",
- " ax[1].plot(np.random.rand(10))\n",
- " ax[1].legend([\"a\", \"b\", \"c\"], loc=\"lower left\")"
+ " xy_ax[1].plot(np.random.rand(10))\n",
+ " xy_ax[1].legend([\"a\", \"b\", \"c\"], loc=\"lower left\")"
]
},
{
diff --git a/examples/pdsh/pdsh_04_12_three_dimensional_plotting.ipynb b/examples/pdsh/pdsh_04_12_three_dimensional_plotting.ipynb
index d5170a80..0e1e0330 100644
--- a/examples/pdsh/pdsh_04_12_three_dimensional_plotting.ipynb
+++ b/examples/pdsh/pdsh_04_12_three_dimensional_plotting.ipynb
@@ -108,8 +108,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax = mpl_plt.axes(projection=\"3d\")"
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")"
]
},
{
@@ -123,8 +123,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax = xy_plt.axes(projection=\"3d\")"
+ "xy_fig = xy_plt.figure()\n",
+ "xy_ax = xy_plt.axes(projection=\"3d\")"
]
},
{
@@ -150,19 +150,19 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
"\n",
"# Data for a three-dimensional line\n",
"zline = np.linspace(0, 15, 1000)\n",
"xline = np.sin(zline)\n",
"yline = np.cos(zline)\n",
- "ax.plot3D(xline, yline, zline, \"gray\")\n",
+ "mpl_ax.plot3D(xline, yline, zline, \"gray\")\n",
"\n",
"# Data for three-dimensional scattered points\n",
"zdata = 15 * np.random.random(100)\n",
"xdata = np.sin(zdata) + 0.1 * np.random.randn(100)\n",
"ydata = np.cos(zdata) + 0.1 * np.random.randn(100)\n",
- "ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap=\"Greens\");"
+ "mpl_ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap=\"Greens\");"
]
},
{
@@ -176,19 +176,19 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
"\n",
"# Data for a three-dimensional line\n",
"zline = np.linspace(0, 15, 1000)\n",
"xline = np.sin(zline)\n",
"yline = np.cos(zline)\n",
- "ax.plot3D(xline, yline, zline, \"gray\")\n",
+ "xy_ax.plot3D(xline, yline, zline, \"gray\")\n",
"\n",
"# Data for three-dimensional scattered points\n",
"zdata = 15 * np.random.random(100)\n",
"xdata = np.sin(zdata) + 0.1 * np.random.randn(100)\n",
"ydata = np.cos(zdata) + 0.1 * np.random.randn(100)\n",
- "ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap=\"Greens\");"
+ "xy_ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap=\"Greens\");"
]
},
{
@@ -258,12 +258,12 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.contour3D(X, Y, Z, 40, cmap=\"binary\")\n",
- "ax.set_xlabel(\"x\")\n",
- "ax.set_ylabel(\"y\")\n",
- "ax.set_zlabel(\"z\");"
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.contour3D(X, Y, Z, 40, cmap=\"binary\")\n",
+ "mpl_ax.set_xlabel(\"x\")\n",
+ "mpl_ax.set_ylabel(\"y\")\n",
+ "mpl_ax.set_zlabel(\"z\");"
]
},
{
@@ -277,12 +277,12 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.contour3D(X, Y, Z, 40, cmap=\"binary\")\n",
- "ax.set_xlabel(\"x\")\n",
- "ax.set_ylabel(\"y\")\n",
- "ax.set_zlabel(\"z\");"
+ "xy_fig = xy_plt.figure()\n",
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.contour3D(X, Y, Z, 40, cmap=\"binary\")\n",
+ "xy_ax.set_xlabel(\"x\")\n",
+ "xy_ax.set_ylabel(\"y\")\n",
+ "xy_ax.set_zlabel(\"z\");"
]
},
{
@@ -296,8 +296,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax.view_init(60, 35)\n",
- "fig"
+ "mpl_ax.view_init(60, 35)\n",
+ "mpl_fig"
]
},
{
@@ -311,8 +311,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax.view_init(60, 35)\n",
- "fig"
+ "xy_ax.view_init(60, 35)\n",
+ "xy_fig"
]
},
{
@@ -338,10 +338,10 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "fig = mpl_plt.figure()\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.plot_wireframe(X, Y, Z)\n",
- "ax.set_title(\"wireframe\");"
+ "mpl_fig = mpl_plt.figure()\n",
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.plot_wireframe(X, Y, Z)\n",
+ "mpl_ax.set_title(\"wireframe\");"
]
},
{
@@ -355,10 +355,10 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "fig = xy_plt.figure()\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.plot_wireframe(X, Y, Z)\n",
- "ax.set_title(\"wireframe\");"
+ "xy_fig = xy_plt.figure()\n",
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.plot_wireframe(X, Y, Z)\n",
+ "xy_ax.set_title(\"wireframe\");"
]
},
{
@@ -372,9 +372,9 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\")\n",
- "ax.set_title(\"surface\");"
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\")\n",
+ "mpl_ax.set_title(\"surface\");"
]
},
{
@@ -388,9 +388,9 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\")\n",
- "ax.set_title(\"surface\");"
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\")\n",
+ "xy_ax.set_title(\"surface\");"
]
},
{
@@ -412,8 +412,8 @@
"Y = r * np.cos(theta)\n",
"Z = f(X, Y)\n",
"\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\");"
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\");"
]
},
{
@@ -435,8 +435,8 @@
"Y = r * np.cos(theta)\n",
"Z = f(X, Y)\n",
"\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\");"
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=\"viridis\", edgecolor=\"none\");"
]
},
{
@@ -498,8 +498,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.scatter(x, y, z, c=z, cmap=\"viridis\", linewidth=0.5);"
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.scatter(x, y, z, c=z, cmap=\"viridis\", linewidth=0.5);"
]
},
{
@@ -513,8 +513,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.scatter(x, y, z, c=z, cmap=\"viridis\", linewidth=0.5);"
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.scatter(x, y, z, c=z, cmap=\"viridis\", linewidth=0.5);"
]
},
{
@@ -528,8 +528,8 @@
"outputs": [],
"source": [
"# Matplotlib reference\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.plot_trisurf(x, y, z, cmap=\"viridis\", edgecolor=\"none\");"
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.plot_trisurf(x, y, z, cmap=\"viridis\", edgecolor=\"none\");"
]
},
{
@@ -543,8 +543,8 @@
"outputs": [],
"source": [
"# xy.pyplot\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.plot_trisurf(x, y, z, cmap=\"viridis\", edgecolor=\"none\");"
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.plot_trisurf(x, y, z, cmap=\"viridis\", edgecolor=\"none\");"
]
},
{
@@ -673,12 +673,12 @@
"\n",
"tri = mpl_Triangulation(np.ravel(w), np.ravel(theta))\n",
"\n",
- "ax = mpl_plt.axes(projection=\"3d\")\n",
- "ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=\"Greys\", linewidths=0.2)\n",
- "ax.set_xlim(-1, 1)\n",
- "ax.set_ylim(-1, 1)\n",
- "ax.set_zlim(-1, 1)\n",
- "ax.axis(\"off\");"
+ "mpl_ax = mpl_plt.axes(projection=\"3d\")\n",
+ "mpl_ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=\"Greys\", linewidths=0.2)\n",
+ "mpl_ax.set_xlim(-1, 1)\n",
+ "mpl_ax.set_ylim(-1, 1)\n",
+ "mpl_ax.set_zlim(-1, 1)\n",
+ "mpl_ax.axis(\"off\");"
]
},
{
@@ -697,12 +697,12 @@
"\n",
"tri = xy_Triangulation(np.ravel(w), np.ravel(theta))\n",
"\n",
- "ax = xy_plt.axes(projection=\"3d\")\n",
- "ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=\"Greys\", linewidths=0.2)\n",
- "ax.set_xlim(-1, 1)\n",
- "ax.set_ylim(-1, 1)\n",
- "ax.set_zlim(-1, 1)\n",
- "ax.axis(\"off\");"
+ "xy_ax = xy_plt.axes(projection=\"3d\")\n",
+ "xy_ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=\"Greys\", linewidths=0.2)\n",
+ "xy_ax.set_xlim(-1, 1)\n",
+ "xy_ax.set_ylim(-1, 1)\n",
+ "xy_ax.set_zlim(-1, 1)\n",
+ "xy_ax.axis(\"off\");"
]
}
],
diff --git a/examples/pdsh/pdsh_04_14_visualization_with_seaborn.ipynb b/examples/pdsh/pdsh_04_14_visualization_with_seaborn.ipynb
index 047c5b82..1eba86a0 100644
--- a/examples/pdsh/pdsh_04_14_visualization_with_seaborn.ipynb
+++ b/examples/pdsh/pdsh_04_14_visualization_with_seaborn.ipynb
@@ -318,8 +318,8 @@
"# Matplotlib reference\n",
"tips[\"tip_pct\"] = 100 * tips[\"tip\"] / tips[\"total_bill\"]\n",
"\n",
- "grid = sns.FacetGrid(tips, row=\"sex\", col=\"time\", margin_titles=True)\n",
- "grid.map(mpl_plt.hist, \"tip_pct\", bins=np.linspace(0, 40, 15));"
+ "mpl_grid = sns.FacetGrid(tips, row=\"sex\", col=\"time\", margin_titles=True)\n",
+ "mpl_grid.map(mpl_plt.hist, \"tip_pct\", bins=np.linspace(0, 40, 15));"
]
},
{
@@ -335,8 +335,8 @@
"# xy.pyplot\n",
"tips[\"tip_pct\"] = 100 * tips[\"tip\"] / tips[\"total_bill\"]\n",
"\n",
- "grid = sns.FacetGrid(tips, row=\"sex\", col=\"time\", margin_titles=True)\n",
- "grid.map(xy_plt.hist, \"tip_pct\", bins=np.linspace(0, 40, 15));"
+ "xy_grid = sns.FacetGrid(tips, row=\"sex\", col=\"time\", margin_titles=True)\n",
+ "xy_grid.map(xy_plt.hist, \"tip_pct\", bins=np.linspace(0, 40, 15));"
]
},
{
diff --git a/examples/pdsh/sync_dual_engine_notebooks.py b/examples/pdsh/sync_dual_engine_notebooks.py
index 3cbc0db9..17d75a20 100644
--- a/examples/pdsh/sync_dual_engine_notebooks.py
+++ b/examples/pdsh/sync_dual_engine_notebooks.py
@@ -12,6 +12,10 @@
ROOT = Path(__file__).resolve().parent
SHARED_ID = "xy-pdsh-dual-engine-intro"
IMPORT_REPLACEMENTS = {
+ # xy.pyplot serves as both `plt` and `mpl` (it carries the `dates` module
+ # etc.); the reference engine needs the real top-level package for `mpl`.
+ # Ordered before the generic rule so it wins for the `mpl` alias.
+ "import xy.pyplot as mpl\n": "import matplotlib as mpl\n",
"import xy.pyplot as ": "import matplotlib.pyplot as ",
"from xy.pyplot import LinearSegmentedColormap": (
"from matplotlib.colors import LinearSegmentedColormap"
@@ -28,6 +32,24 @@
"Triangulation",
"cycler",
)
+# Cross-cell engine state: these variables are written in one cell and read in
+# a later cell of the same engine, so the paired cells must not share them —
+# an unprefixed `grid = xy_plt.GridSpec(...)` overwrote the matplotlib cell's
+# `grid` and fed an xy object into real matplotlib. Prefixed like the aliases,
+# but never in kwarg (`ax=ax`), attribute (`ax.grid(...)`), or string
+# (`plt.rc("grid", ...)`) positions.
+STATE_NAMES = ("fig", "ax", "grid")
+
+
+def _prefix_state(line: str, prefix: str) -> str:
+ for name in STATE_NAMES:
+ line = re.sub(
+ rf"""(? list[Path]:
@@ -52,7 +74,7 @@ def _xy_cells(notebook: dict) -> list[dict]:
if metadata.get("xy_pdsh_auto_show") is True:
cell["source"] = cell["source"][:-3]
for index, line in enumerate(cell.get("source", [])):
- for name in ENGINE_NAMES:
+ for name in (*ENGINE_NAMES, *STATE_NAMES):
line = re.sub(rf"\bxy_{re.escape(name)}\b", name, line)
for name in ENGINE_NAMES[2:]:
line = line.replace(f" import {name} as {name}", f" import {name}")
@@ -81,6 +103,7 @@ def _engine_cell(cell: dict, engine: str) -> dict:
line = line.replace(old, new)
for name in ENGINE_NAMES:
line = re.sub(rf"\b{re.escape(name)}\b", f"{prefix}_{name}", line)
+ line = _prefix_state(line, prefix)
for name in ENGINE_NAMES[2:]:
line = line.replace(f" import {prefix}_{name}", f" import {name} as {prefix}_{name}")
replaced.append(line)
diff --git a/js/src/50_chartview.js b/js/src/50_chartview.js
index b45f52ef..a5efb936 100644
--- a/js/src/50_chartview.js
+++ b/js/src/50_chartview.js
@@ -80,6 +80,19 @@ const FC_CONTEXT_GOVERNOR = {
if (over <= 0) break;
if (view._releaseContext()) over -= 1;
}
+ if (over <= 0) return;
+ // Every remaining live view is on screen (a dense subplot grid). Release
+ // least-recently-visible ones anyway: the snapshot stand-in keeps them
+ // looking rendered, and pointer entry revives them. Letting the browser
+ // LRU-evict instead blanks visible charts with no recovery until the
+ // page scrolls (§28: data-fc-ctx stays legible either way).
+ const visible = live
+ .filter((view) => view._ctxVisible)
+ .sort((a, b) => (a._ctxSeenSeq || 0) - (b._ctxSeenSeq || 0));
+ for (const view of visible) {
+ if (over <= 0) break;
+ if (view._releaseContext()) over -= 1;
+ }
},
acquired(requester) {
requester._ctxPendingReservation = false;
@@ -145,7 +158,14 @@ class ChartView {
const rect = this.fluid || this.fluidH ? el.getBoundingClientRect() : null;
const cw = this.fluid ? Math.round(rect.width) || 640 : spec.width; // 0 = hidden; RO corrects
const ch = this.fluidH ? Math.round(rect.height) || 420 : spec.height;
- this.size = { w: Math.max(120, cw), h: Math.max(120, ch) };
+ // Fluid floors stay high (a collapsed/hidden container must not produce a
+ // degenerate chart), but explicit sizes are honored down to a tiny floor:
+ // dense pyplot subplot grids legitimately build sub-120px panels whose
+ // plot boxes must land exactly on their matplotlib rects.
+ this.size = {
+ w: Math.max(this.fluid ? 120 : 48, cw),
+ h: Math.max(this.fluidH ? 120 : 48, ch),
+ };
this._layout();
this._buildDom(el);
@@ -588,6 +608,7 @@ class ChartView {
this.root.dataset.fcContextState = "ready";
this._scheduleViewRequest(this.view, { delay: 0 });
this.draw();
+ this._dropContextSnapshot(); // live frame is back; retire the stand-in
this._dispatchChartEvent("context_restored", {
loss_count: this._contextLossCount,
restore_count: this._contextRestoreCount,
@@ -604,6 +625,7 @@ class ChartView {
if (this._destroyed || !this.gl || this._glLost || this.gl.isContextLost()) return false;
const ext = this.gl.getExtension("WEBGL_lose_context");
if (!ext) return false;
+ this._snapshotBeforeRelease();
this._ctxReleasedExt = ext;
this._ctxReleases += 1;
this._glLost = true; // synchronous: the lost *event* arrives as a task
@@ -614,6 +636,44 @@ class ChartView {
return true;
}
+ // Freeze the current frame into a 2D stand-in before the GL context goes
+ // away. This is what lets the governor release *visible* views: an
+ // over-budget panel keeps showing its last frame as a static image
+ // (matplotlib-style) instead of blanking, and pointer entry swaps the live
+ // context back in. The draw must be synchronous and in the same task as the
+ // copy — the default drawing buffer does not persist between frames.
+ _snapshotBeforeRelease() {
+ try {
+ if (this._raf) cancelAnimationFrame(this._raf);
+ this._raf = null;
+ this._rafKeepPick = true; // pick FBO stays valid; only the color buffer is read
+ this._drawNow();
+ let snap = this._ctxSnapshot;
+ if (!snap) {
+ snap = this._ctxSnapshot = document.createElement("canvas");
+ snap.dataset.fcCtxSnapshot = "";
+ }
+ snap.width = this.canvas.width;
+ snap.height = this.canvas.height;
+ snap.style.cssText = this.canvas.style.cssText;
+ snap.style.pointerEvents = "none";
+ snap.getContext("2d").drawImage(this.canvas, 0, 0);
+ this.canvas.before(snap);
+ // Chrome composites a lost-context canvas as an opaque broken-image
+ // tile, which would sit on top of the stand-in. Events still reach the
+ // root, so pointer-entry revival keeps working.
+ this.canvas.style.visibility = "hidden";
+ } catch (_err) {
+ this._dropContextSnapshot(); // released view degrades to blank, as before
+ }
+ }
+
+ _dropContextSnapshot() {
+ this.canvas.style.visibility = "";
+ if (this._ctxSnapshot) this._ctxSnapshot.remove();
+ this._ctxSnapshot = null;
+ }
+
// Re-acquire on scroll-into-view. Governed releases undo via
// restoreContext() -> the existing restored handler rebuilds; a real
// browser eviction cannot be force-restored, so the canvas is swapped for a
@@ -667,6 +727,7 @@ class ChartView {
}
this._scheduleViewRequest(this.view, { delay: 0 });
this.draw();
+ this._dropContextSnapshot();
}
// Visibility feed for the governor: tracks least-recently-visible order and
@@ -674,6 +735,12 @@ class ChartView {
// view (25% rootMargin = pre-warm hysteresis; release is demand-driven only,
// so fast scrolling never thrashes contexts).
_armContextVisibilityWatch() {
+ // A released-while-visible view (snapshot stand-in) revives on pointer
+ // entry — visibility alone can't distinguish it from its neighbors, and
+ // touching a chart is the interaction signal that it needs to be live.
+ this._listen(this.root, "pointerenter", () => {
+ if (this._glLost && !this._destroyed) this._recoverContext();
+ });
if (typeof IntersectionObserver === "undefined") {
this._ctxVisible = true; // no observer: never treat as releasable
return;
@@ -725,6 +792,7 @@ class ChartView {
}
this._positionReductionBadges();
this._positionColorbar();
+ this._fitModebar();
this._pickDirty = true;
this.draw();
this._scheduleViewRequest();
@@ -1983,9 +2051,11 @@ class ChartView {
const { x0, x1, y0, y1 } = this.view;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
- const bg = this.theme.bg;
- if (bg) gl.clearColor(bg[0] * bg[3], bg[1] * bg[3], bg[2] * bg[3], bg[3]);
- else gl.clearColor(0, 0, 0, 0);
+ // Always transparent: this canvas sits ABOVE the chrome canvas over the
+ // plot rect, so an opaque --chart-bg clear here would occlude everything
+ // chrome draws inside the plot (grid, bands, rules, arrows). The plot
+ // background paints at the bottom of the stack in _drawChrome instead.
+ gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
for (const g of this.gpuTraces) {
@@ -2243,7 +2313,19 @@ class ChartView {
gl.uniform4f(u("u_view"), vx0 ?? x0, vx1 ?? x1, vy0 ?? y0, vy1 ?? y1);
gl.uniform1i(u("u_xmode"), this._axisMode(g.xAxis));
gl.uniform1i(u("u_ymode"), this._axisMode(g.yAxis));
- gl.uniform4f(u("u_gridRange"), h.xRange[0], h.xRange[1], h.yRange[0], h.yRange[1]);
+ // Grid row/column 0 anchors to the bottom/left edge of the grid rect in
+ // *display* orientation — the raster/SVG exporters' convention (the shim's
+ // imshow pre-flips rows for origin='upper' assuming it). A reversed axis
+ // flips the data direction, not the buffer, so swap the sampled range to
+ // keep the anchoring; without this an inverted-y imshow rendered upside
+ // down on canvas while every export of the same spec was upright.
+ const xrev = (vx0 ?? x0) > (vx1 ?? x1);
+ const yrev = (vy0 ?? y0) > (vy1 ?? y1);
+ gl.uniform4f(
+ u("u_gridRange"),
+ h.xRange[xrev ? 1 : 0], h.xRange[xrev ? 0 : 1],
+ h.yRange[yrev ? 1 : 0], h.yRange[yrev ? 0 : 1],
+ );
gl.uniform1f(u("u_opacity"), this._fillOpacity(g.trace.style));
gl.uniform1i(u("u_truecolor"), h.truecolor ? 1 : 0);
gl.activeTexture(gl.TEXTURE0);
@@ -2826,6 +2908,13 @@ class ChartView {
}
const p = this.plot;
+ // Plot background (--chart-bg) paints here, at the bottom of the chrome
+ // canvas, so the grid and annotation shapes drawn next stay visible and
+ // the transparent marks canvas above shows all of it (§36 theming).
+ if (this.theme.bg) {
+ ctx.fillStyle = cssColor(this.theme.bg);
+ ctx.fillRect(p.x, p.y, p.w, p.h);
+ }
const xAxis = this._axis("x");
const yAxis = this._axis("y");
const hideX = this._axisTickLabelStrategy(xAxis) === "none";
@@ -3268,8 +3357,14 @@ class ChartView {
const [x0, x1] = h.xRange;
const [y0, y1] = h.yRange;
if (dataX < x0 || dataX > x1 || dataY < y0 || dataY > y1) return null;
- const col = Math.min(h.w - 1, Math.max(0, Math.floor(((dataX - x0) / (x1 - x0)) * h.w)));
- const row = Math.min(h.h - 1, Math.max(0, Math.floor(((dataY - y0) / (y1 - y0)) * h.h)));
+ // Mirror _drawHeatmap's display-orientation anchoring: on a reversed axis
+ // buffer row/column 0 sits at the opposite end of the data range.
+ const [ax0, ax1] = this._axisRange(g.xAxis) ?? [this.view.x0, this.view.x1];
+ const [ay0, ay1] = this._axisRange(g.yAxis) ?? [this.view.y0, this.view.y1];
+ const fx = ((ax0 ?? this.view.x0) > (ax1 ?? this.view.x1)) ? (x1 - dataX) : (dataX - x0);
+ const fy = ((ay0 ?? this.view.y0) > (ay1 ?? this.view.y1)) ? (y1 - dataY) : (dataY - y0);
+ const col = Math.min(h.w - 1, Math.max(0, Math.floor((fx / (x1 - x0)) * h.w)));
+ const row = Math.min(h.h - 1, Math.max(0, Math.floor((fy / (y1 - y0)) * h.h)));
return { trace: g.trace.id, index: row * h.w + col, g, heatmap: { row, col }, synthetic: true };
}
diff --git a/js/src/51_annotations.js b/js/src/51_annotations.js
index 64a309b3..66c30f24 100644
--- a/js/src/51_annotations.js
+++ b/js/src/51_annotations.js
@@ -3,6 +3,136 @@
// shape fills, and collision-nudged labels. Split out of 50_chartview.js;
// augments the prototype so `this.*` is unchanged.
+// Annotation style keys consumed by the canvas shape (shaft/head/marker
+// geometry and paint) — never forwarded to the DOM label as CSS.
+const FC_ANNOTATION_SHAPE_STYLE_KEYS = new Set([
+ "color",
+ "label_color",
+ "width",
+ "head_size",
+ "head_style",
+ "tail_style",
+ "shaft_width_start",
+ "shaft_width_end",
+ "curve",
+ "angle_a",
+ "angle_b",
+ "gap_start",
+ "gap_end",
+ "dash",
+ "span_start",
+ "span_end",
+ "size",
+ "symbol",
+ "stroke_color",
+ "stroke_width",
+ "coordinate_space",
+]);
+
+// Arrow path geometry shared by every arrow/callout draw (mirrored by the
+// static exporters in python/xy/_arrowgeom.py — keep the two in sync):
+// an optional quadratic control point from `curve` (matplotlib arc3 rad,
+// bulge as a fraction of chord length) or `angle_a`/`angle_b` (matplotlib
+// angle3/angle departure/arrival angles in degrees, y-up screen space —
+// control point at the ray intersection), then `gap_start`/`gap_end` px
+// trims along the path tangents (label/point clearance).
+function fcArrowGeometry(x0, y0, x1, y1, style) {
+ const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : null);
+ const angleA = num(style.angle_a);
+ const angleB = num(style.angle_b);
+ const curve = num(style.curve);
+ let cx = null;
+ let cy = null;
+ if (angleA !== null && angleB !== null) {
+ const a = (-angleA * Math.PI) / 180; // spec angles are y-up; canvas is y-down
+ const b = (-angleB * Math.PI) / 180;
+ const denom = Math.cos(a) * Math.sin(b) - Math.sin(a) * Math.cos(b);
+ if (Math.abs(denom) > 1e-6) {
+ const t = ((x1 - x0) * Math.sin(b) - (y1 - y0) * Math.cos(b)) / denom;
+ cx = x0 + t * Math.cos(a);
+ cy = y0 + t * Math.sin(a);
+ }
+ } else if (curve) {
+ const dx = x1 - x0;
+ const dy = y1 - y0;
+ // arc3 rad > 0 bulges to the chord's left in matplotlib's y-up plane.
+ cx = (x0 + x1) / 2 + curve * dy;
+ cy = (y0 + y1) / 2 - curve * dx;
+ }
+ const toward = (px, py, qx, qy) => {
+ const d = Math.hypot(qx - px, qy - py) || 1;
+ return [(qx - px) / d, (qy - py) / d];
+ };
+ const t0 = cx === null ? toward(x0, y0, x1, y1) : toward(x0, y0, cx, cy);
+ const t1 = cx === null ? toward(x1, y1, x0, y0) : toward(x1, y1, cx, cy);
+ const gapStart = Math.max(0, num(style.gap_start) || 0);
+ const gapEnd = Math.max(0, num(style.gap_end) || 0);
+ const span = Math.hypot(x1 - x0, y1 - y0);
+ const trim = gapStart + gapEnd < span * 0.9;
+ const p0 = trim ? [x0 + gapStart * t0[0], y0 + gapStart * t0[1]] : [x0, y0];
+ const p1 = trim ? [x1 + gapEnd * t1[0], y1 + gapEnd * t1[1]] : [x1, y1];
+ // Tangent INTO each endpoint (head/tail orientation).
+ const dir1 = cx === null ? toward(p0[0], p0[1], p1[0], p1[1]) : toward(cx, cy, p1[0], p1[1]);
+ const dir0 = cx === null ? toward(p1[0], p1[1], p0[0], p0[1]) : toward(cx, cy, p0[0], p0[1]);
+ return { p0, p1, control: cx === null ? null : [cx, cy], dir0, dir1 };
+}
+
+// The shaft as a point list (quadratic Bézier sampled when curved).
+function fcArrowShaftPoints(geom, samples = 24) {
+ const [x0, y0] = geom.p0;
+ const [x1, y1] = geom.p1;
+ if (!geom.control) return [[x0, y0], [x1, y1]];
+ const [cx, cy] = geom.control;
+ const points = [];
+ for (let i = 0; i <= samples; i++) {
+ const t = i / samples;
+ const u = 1 - t;
+ points.push([u * u * x0 + 2 * u * t * cx + t * t * x1, u * u * y0 + 2 * u * t * cy + t * t * y1]);
+ }
+ return points;
+}
+
+// The polyline with `trim` px of arclength removed from its end (a tapered
+// shaft ends at the head BASE — a full-length shaft would swallow the head).
+function fcTrimPolylineEnd(points, trim) {
+ if (!(trim > 0) || points.length < 2) return points;
+ const out = points.slice();
+ let remaining = trim;
+ while (out.length >= 2) {
+ const [ax, ay] = out[out.length - 2];
+ const [bx, by] = out[out.length - 1];
+ const seg = Math.hypot(bx - ax, by - ay);
+ if (seg > remaining) {
+ const t = 1 - remaining / seg;
+ out[out.length - 1] = [ax + t * (bx - ax), ay + t * (by - ay)];
+ return out;
+ }
+ remaining -= seg;
+ out.pop();
+ }
+ return out;
+}
+
+// The shaft as a filled polygon whose width interpolates from w0 to w1
+// (matplotlib's fancy/simple/wedge arrowstyles are filled tapered shafts).
+function fcTaperPolygon(points, w0, w1) {
+ const left = [];
+ const right = [];
+ const count = points.length;
+ for (let i = 0; i < count; i++) {
+ const [px, py] = points[i];
+ const [ax, ay] = points[Math.max(0, i - 1)];
+ const [bx, by] = points[Math.min(count - 1, i + 1)];
+ const d = Math.hypot(bx - ax, by - ay) || 1;
+ const nx = -(by - ay) / d;
+ const ny = (bx - ax) / d;
+ const half = (w0 + (w1 - w0) * (i / Math.max(1, count - 1))) / 2;
+ left.push([px + half * nx, py + half * ny]);
+ right.push([px - half * nx, py - half * ny]);
+ }
+ return left.concat(right.reverse());
+}
+
Object.assign(ChartView.prototype, {
_annotationPaint(style, fallback) {
return safeCssPaint(this.root, style && style.color, fallback);
@@ -54,8 +184,7 @@ Object.assign(ChartView.prototype, {
_drawArrowLine(ctx, x0, y0, x1, y1, style) {
if (![x0, y0, x1, y1].every(Number.isFinite)) return;
- const angle = Math.atan2(y1 - y0, x1 - x0);
- const head = Math.max(7, this._styleNumber(style, "head_size", 8));
+ const geom = fcArrowGeometry(x0, y0, x1, y1, style);
ctx.save();
ctx.globalAlpha = this._styleNumber(style, "opacity", 1);
ctx.strokeStyle = this._annotationPaint(style, [0.4, 0.44, 0.52, 1]);
@@ -63,23 +192,69 @@ Object.assign(ChartView.prototype, {
ctx.lineWidth = Math.max(0.5, this._styleNumber(style, "width", 1.5));
ctx.setLineDash(Array.isArray(style.dash) ? style.dash :
(typeof style.dash === "string" ? style.dash.split(",").map(Number) : []));
+ const w0 = Number(style.shaft_width_start);
+ const w1 = Number(style.shaft_width_end);
+ const headStyle = style.head_style || "triangle";
+ const head = Math.max(4, this._styleNumber(style, "head_size", 8));
+ if (Number.isFinite(w0) || Number.isFinite(w1)) {
+ let points = fcArrowShaftPoints(geom);
+ if (headStyle === "triangle") {
+ points = fcTrimPolylineEnd(points, head * Math.cos(Math.PI / 6));
+ }
+ const polygon = fcTaperPolygon(
+ points,
+ Number.isFinite(w0) ? w0 : 1,
+ Number.isFinite(w1) ? w1 : 1
+ );
+ ctx.beginPath();
+ ctx.moveTo(polygon[0][0], polygon[0][1]);
+ for (let i = 1; i < polygon.length; i++) ctx.lineTo(polygon[i][0], polygon[i][1]);
+ ctx.closePath();
+ ctx.fill();
+ } else {
+ ctx.beginPath();
+ ctx.moveTo(geom.p0[0], geom.p0[1]);
+ if (geom.control) ctx.quadraticCurveTo(geom.control[0], geom.control[1], geom.p1[0], geom.p1[1]);
+ else ctx.lineTo(geom.p1[0], geom.p1[1]);
+ ctx.stroke();
+ }
+ this._drawArrowEnd(ctx, geom.p1, geom.dir1, headStyle, head);
+ this._drawArrowEnd(ctx, geom.p0, geom.dir0, style.tail_style || "none", head);
+ ctx.restore();
+ },
+
+ // One arrow endpoint decoration. dir is the unit tangent INTO the point;
+ // styles mirror matplotlib arrowstyles: "triangle" (filled, "-|>"/fancy),
+ // "v" (open stroke, "->"), "bar" ("|-|" caps), "none".
+ _drawArrowEnd(ctx, point, dir, endStyle, head) {
+ if (endStyle === "none") return;
+ const [px, py] = point;
+ const angle = Math.atan2(dir[1], dir[0]);
ctx.beginPath();
- ctx.moveTo(x0, y0);
- ctx.lineTo(x1, y1);
- ctx.stroke();
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(
- x1 - head * Math.cos(angle - Math.PI / 6),
- y1 - head * Math.sin(angle - Math.PI / 6)
- );
- ctx.lineTo(
- x1 - head * Math.cos(angle + Math.PI / 6),
- y1 - head * Math.sin(angle + Math.PI / 6)
- );
+ if (endStyle === "bar") {
+ ctx.moveTo(px - (head / 2) * Math.sin(angle), py + (head / 2) * Math.cos(angle));
+ ctx.lineTo(px + (head / 2) * Math.sin(angle), py - (head / 2) * Math.cos(angle));
+ ctx.stroke();
+ return;
+ }
+ const wing = (side) => [
+ px - head * Math.cos(angle - side * Math.PI / 6),
+ py - head * Math.sin(angle - side * Math.PI / 6),
+ ];
+ const [ax, ay] = wing(1);
+ const [bx, by] = wing(-1);
+ if (endStyle === "v") {
+ ctx.moveTo(ax, ay);
+ ctx.lineTo(px, py);
+ ctx.lineTo(bx, by);
+ ctx.stroke();
+ return;
+ }
+ ctx.moveTo(px, py);
+ ctx.lineTo(ax, ay);
+ ctx.lineTo(bx, by);
ctx.closePath();
ctx.fill();
- ctx.restore();
},
_drawAnnotationShapes(ctx) {
@@ -226,15 +401,30 @@ Object.assign(ChartView.prototype, {
const dx = Number.isFinite(Number(ann.dx)) ? Number(ann.dx) : 0;
const dy = Number.isFinite(Number(ann.dy)) ? Number(ann.dy) : 0;
const anchor = ann.anchor === "middle" ? "-50%" : ann.anchor === "end" ? "-100%" : "0";
+ // matplotlib's va: the anchor y is the text center/bottom, not its top.
+ const va = style.vertical_align;
+ const vAnchor =
+ va === "center" || va === "middle" ? "-50%" : va === "bottom" ? "-100%" : "0";
// Structural inline only (position telegraphs the anchor); font + default
// color live in the defeatable :where() stylesheet so utility classes win.
+ // width:max-content: shrink-to-fit for an absolutely positioned label is
+ // capped by the distance to the containing block's edge, so an end/middle
+ // anchored label near the right edge would wrap word-per-word BEFORE the
+ // translate(-100%) shift moves it back inside.
d.style.cssText =
`position:absolute;left:${px + dx}px;top:${py + dy}px;` +
- `transform:translate(${anchor},0);pointer-events:none;` +
- `white-space:pre-line;text-align:center;`;
+ `transform:translate(${anchor},${vAnchor});pointer-events:none;` +
+ `white-space:pre-line;text-align:center;width:max-content;`;
this._applySlot(d, "annotation_label");
this._applyClass(d, ann.class_name);
- this._applyStyle(d, style);
+ // Shape-geometry/paint keys style the canvas shape, not the label DOM —
+ // e.g. an arrow's shaft `width` must not become CSS width on the label.
+ const labelStyle = {};
+ for (const [key, value] of Object.entries(style)) {
+ if (FC_ANNOTATION_SHAPE_STYLE_KEYS.has(key)) continue;
+ labelStyle[key] = value;
+ }
+ this._applyStyle(d, labelStyle);
// Only pin color inline when the annotation asked for one — otherwise the
// stylesheet's --chart-annotation-text default stays overridable by CSS.
if (style && (style.label_color || style.color)) {
diff --git a/js/src/53_interaction.js b/js/src/53_interaction.js
index 989bbe78..b0b566a3 100644
--- a/js/src/53_interaction.js
+++ b/js/src/53_interaction.js
@@ -308,9 +308,25 @@ Object.assign(ChartView.prototype, {
this._setView(this.view0, { animate: true });
});
root.appendChild(bar);
+ this._fitModebar();
this._setDragMode(this.dragMode);
},
+ // The modebar is unusable chrome once the plot box can't contain it — in a
+ // dense subplot grid the overflowing buttons alone grew scrollbars on every
+ // panel. Hidden is a fit state, not removal: a fluid resize re-checks and
+ // can bring the bar back. Wheel/drag zoom and pan keep working without it.
+ _fitModebar() {
+ const bar = this._modebar;
+ if (!bar) return;
+ bar.style.top = `${this.plot.y + 4}px`;
+ bar.style.left = `${this.plot.x + 4}px`;
+ bar.style.display = "flex"; // measurable before the verdict
+ const fits =
+ bar.offsetWidth + 8 <= this.plot.w && bar.offsetHeight + 8 <= this.plot.h;
+ if (!fits) bar.style.display = "none";
+ },
+
_setDragMode(mode) {
this.dragMode = mode;
// Cursor telegraphs the gesture (grab for pan, crosshair for box-zoom) but
diff --git a/python/xy/_arrowgeom.py b/python/xy/_arrowgeom.py
new file mode 100644
index 00000000..499a1900
--- /dev/null
+++ b/python/xy/_arrowgeom.py
@@ -0,0 +1,181 @@
+"""Arrow-annotation path geometry shared by the SVG and raster exporters.
+
+Mirrors ``fcArrowGeometry`` in ``js/src/51_annotations.js`` — keep the two in
+sync. Style keys: ``curve`` (matplotlib arc3 rad — quadratic bulge as a
+fraction of chord length), ``angle_a``/``angle_b`` (matplotlib angle3/angle
+departure/arrival angles, degrees, y-up screen space — the control point is
+the ray intersection), ``gap_start``/``gap_end`` (px trims along the path
+tangents for label/point clearance), ``head_style``/``tail_style``
+(``triangle``/``v``/``bar``/``none``) and ``head_size``.
+"""
+
+from __future__ import annotations
+
+import math
+from typing import Any, Optional
+
+
+def _number(value: Any) -> Optional[float]:
+ try:
+ number = float(value)
+ except (TypeError, ValueError):
+ return None
+ return number if math.isfinite(number) else None
+
+
+def arrow_geometry(
+ x0: float, y0: float, x1: float, y1: float, style: dict[str, Any]
+) -> dict[str, Any]:
+ angle_a = _number(style.get("angle_a"))
+ angle_b = _number(style.get("angle_b"))
+ curve = _number(style.get("curve"))
+ control: Optional[tuple[float, float]] = None
+ if angle_a is not None and angle_b is not None:
+ a = -angle_a * math.pi / 180 # spec angles are y-up; pixels are y-down
+ b = -angle_b * math.pi / 180
+ denom = math.cos(a) * math.sin(b) - math.sin(a) * math.cos(b)
+ if abs(denom) > 1e-6:
+ t = ((x1 - x0) * math.sin(b) - (y1 - y0) * math.cos(b)) / denom
+ control = (x0 + t * math.cos(a), y0 + t * math.sin(a))
+ elif curve:
+ dx, dy = x1 - x0, y1 - y0
+ # arc3 rad > 0 bulges to the chord's left in matplotlib's y-up plane.
+ control = ((x0 + x1) / 2 + curve * dy, (y0 + y1) / 2 - curve * dx)
+
+ def toward(px: float, py: float, qx: float, qy: float) -> tuple[float, float]:
+ d = math.hypot(qx - px, qy - py) or 1.0
+ return ((qx - px) / d, (qy - py) / d)
+
+ t0 = toward(x0, y0, *control) if control else toward(x0, y0, x1, y1)
+ t1 = toward(x1, y1, *control) if control else toward(x1, y1, x0, y0)
+ gap_start = max(0.0, _number(style.get("gap_start")) or 0.0)
+ gap_end = max(0.0, _number(style.get("gap_end")) or 0.0)
+ trim = gap_start + gap_end < math.hypot(x1 - x0, y1 - y0) * 0.9
+ p0 = (x0 + gap_start * t0[0], y0 + gap_start * t0[1]) if trim else (x0, y0)
+ p1 = (x1 + gap_end * t1[0], y1 + gap_end * t1[1]) if trim else (x1, y1)
+ # Tangent INTO each endpoint (head/tail orientation).
+ dir1 = toward(*control, *p1) if control else toward(*p0, *p1)
+ dir0 = toward(*control, *p0) if control else toward(*p1, *p0)
+ return {"p0": p0, "p1": p1, "control": control, "dir0": dir0, "dir1": dir1}
+
+
+def shaft_points(geom: dict[str, Any], samples: int = 24) -> list[tuple[float, float]]:
+ """The shaft as a polyline (quadratic Bézier sampled when curved)."""
+ (x0, y0), (x1, y1) = geom["p0"], geom["p1"]
+ control = geom["control"]
+ if control is None:
+ return [(x0, y0), (x1, y1)]
+ cx, cy = control
+ points = []
+ for index in range(samples + 1):
+ t = index / samples
+ u = 1.0 - t
+ points.append(
+ (
+ u * u * x0 + 2 * u * t * cx + t * t * x1,
+ u * u * y0 + 2 * u * t * cy + t * t * y1,
+ )
+ )
+ return points
+
+
+def end_decoration(
+ point: tuple[float, float],
+ direction: tuple[float, float],
+ end_style: str,
+ head: float,
+) -> Optional[dict[str, Any]]:
+ """One endpoint decoration: {'kind': 'fill'|'stroke', 'points': [...]}.
+
+ ``direction`` is the unit tangent INTO the point; styles mirror matplotlib
+ arrowstyles: triangle (filled, "-|>"/fancy), v (open stroke, "->"),
+ bar ("|-|" caps), none.
+ """
+ if end_style == "none":
+ return None
+ px, py = point
+ angle = math.atan2(direction[1], direction[0])
+ if end_style == "bar":
+ return {
+ "kind": "stroke",
+ "points": [
+ (px - (head / 2) * math.sin(angle), py + (head / 2) * math.cos(angle)),
+ (px + (head / 2) * math.sin(angle), py - (head / 2) * math.cos(angle)),
+ ],
+ }
+ wings = [
+ (
+ px - head * math.cos(angle - side * math.pi / 6),
+ py - head * math.sin(angle - side * math.pi / 6),
+ )
+ for side in (1, -1)
+ ]
+ if end_style == "v":
+ return {"kind": "stroke", "points": [wings[0], (px, py), wings[1]]}
+ return {"kind": "fill", "points": [(px, py), wings[0], wings[1]]}
+
+
+def taper_polygon(
+ points: list[tuple[float, float]], width_start: float, width_end: float
+) -> list[tuple[float, float]]:
+ """The shaft polyline as a filled polygon whose width interpolates from
+ ``width_start`` to ``width_end`` (matplotlib's fancy/simple/wedge
+ arrowstyles are filled tapered shafts, not stroked lines)."""
+ count = len(points)
+ left: list[tuple[float, float]] = []
+ right: list[tuple[float, float]] = []
+ for index, (px, py) in enumerate(points):
+ ax, ay = points[max(0, index - 1)]
+ bx, by = points[min(count - 1, index + 1)]
+ d = math.hypot(bx - ax, by - ay) or 1.0
+ nx, ny = -(by - ay) / d, (bx - ax) / d
+ half = (width_start + (width_end - width_start) * (index / max(1, count - 1))) / 2
+ left.append((px + half * nx, py + half * ny))
+ right.append((px - half * nx, py - half * ny))
+ return left + right[::-1]
+
+
+def trim_polyline_end(points: list[tuple[float, float]], trim: float) -> list[tuple[float, float]]:
+ """The polyline with ``trim`` px of arclength removed from its end."""
+ if trim <= 0 or len(points) < 2:
+ return points
+ remaining = trim
+ out = list(points)
+ while len(out) >= 2:
+ (ax, ay), (bx, by) = out[-2], out[-1]
+ seg = math.hypot(bx - ax, by - ay)
+ if seg > remaining:
+ t = 1.0 - remaining / seg
+ out[-1] = (ax + t * (bx - ax), ay + t * (by - ay))
+ return out
+ remaining -= seg
+ out.pop()
+ return out[:2] if len(out) >= 2 else points[:1] * 2
+
+
+def arrow_shapes(
+ x0: float, y0: float, x1: float, y1: float, style: dict[str, Any]
+) -> dict[str, Any]:
+ """Shaft polyline (or taper polygon) + endpoint decorations for one
+ arrow/callout spec."""
+ geom = arrow_geometry(x0, y0, x1, y1, style)
+ head = max(4.0, _number(style.get("head_size")) or 8.0)
+ head_style = str(style.get("head_style") or "triangle")
+ shaft = shaft_points(geom)
+ width_start = _number(style.get("shaft_width_start"))
+ width_end = _number(style.get("shaft_width_end"))
+ taper = None
+ if width_start is not None or width_end is not None:
+ if head_style == "triangle":
+ # matplotlib construction: the shaft ends at the head BASE and the
+ # head spans base→tip — a full-length shaft would swallow the head.
+ shaft = trim_polyline_end(shaft, head * math.cos(math.pi / 6))
+ taper = taper_polygon(shaft, width_start or 1.0, width_end or 1.0)
+ return {
+ "shaft": None if taper else shaft,
+ "taper": taper,
+ "head": end_decoration(geom["p1"], geom["dir1"], head_style, head),
+ "tail": end_decoration(
+ geom["p0"], geom["dir0"], str(style.get("tail_style") or "none"), head
+ ),
+ }
diff --git a/python/xy/_raster.py b/python/xy/_raster.py
index f9be9b98..b31dd8ea 100644
--- a/python/xy/_raster.py
+++ b/python/xy/_raster.py
@@ -18,6 +18,7 @@
import numpy as np
from . import _png, _scene
+from ._arrowgeom import arrow_shapes as _arrow_shapes
from ._svg import (
_AXIS,
_AXIS_GRID_DASHES,
@@ -809,50 +810,57 @@ def _emit_annotations(cmd, annotations, sx, sy, plot, width, height):
_rect_pts(x0, y0, x1, y1),
_rgba(style.get("color"), "#64748b", float(style.get("opacity", 0.14))),
)
- elif ann.get("kind") == "arrow":
- x0, y0 = float(sx(float(ann["x0"]))), float(sy(float(ann["y0"])))
- x1, y1 = float(sx(float(ann["x1"]))), float(sy(float(ann["y1"])))
+ elif ann.get("kind") in ("arrow", "callout"):
+ if ann.get("kind") == "arrow":
+ x0, y0 = float(sx(float(ann["x0"]))), float(sy(float(ann["y0"])))
+ x1, y1 = float(sx(float(ann["x1"]))), float(sy(float(ann["y1"])))
+ else: # pointer from the offset label back to the data point
+ x1, y1 = float(sx(float(ann["x"]))), float(sy(float(ann["y"])))
+ x0, y0 = x1 + float(ann.get("dx", 0.0)), y1 + float(ann.get("dy", 0.0))
if all(np.isfinite(v) for v in (x0, y0, x1, y1)):
- angle = np.arctan2(y1 - y0, x1 - x0)
- head = max(7.0, float(style.get("head_size", 8.0)))
- cmd.stroke(
- [(x0, y0), (x1, y1)],
- max(0.5, float(style.get("width", 1.5))),
- color,
- dash=(
- [float(value) for value in style["dash"].split(",")]
- if isinstance(style.get("dash"), str)
- else style.get("dash")
- ),
- )
- cmd.fill(
- [
- (x1, y1),
- (
- x1 - head * float(np.cos(angle - np.pi / 6)),
- y1 - head * float(np.sin(angle - np.pi / 6)),
- ),
- (
- x1 - head * float(np.cos(angle + np.pi / 6)),
- y1 - head * float(np.sin(angle + np.pi / 6)),
+ shapes = _arrow_shapes(x0, y0, x1, y1, style)
+ stroke_width = max(0.5, float(style.get("width", 1.5)))
+ if shapes["taper"] is not None:
+ cmd.fill(shapes["taper"], color)
+ else:
+ cmd.stroke(
+ shapes["shaft"],
+ stroke_width,
+ color,
+ dash=(
+ [float(value) for value in style["dash"].split(",")]
+ if isinstance(style.get("dash"), str)
+ else style.get("dash")
),
- ],
- color,
- )
- if ann.get("kind") == "text" and ann.get("text"):
+ )
+ for decoration in (shapes["head"], shapes["tail"]):
+ if decoration is None:
+ continue
+ if decoration["kind"] == "fill":
+ cmd.fill(decoration["points"], color)
+ else:
+ cmd.stroke(decoration["points"], stroke_width, color)
+ if ann.get("kind") in ("text", "callout") and ann.get("text"):
x, y = _annotation_point(ann, style, sx, sy, plot, width, height)
anchor = {"start": 0, "middle": 1, "end": 2}.get(ann.get("anchor"), 0)
font_size = float(style.get("font_size", 11))
lines = str(ann["text"]).splitlines() or [""]
line_height = font_size * 1.2
first_y = y - (len(lines) - 1) * line_height / 2
+ vertical_align = style.get("vertical_align")
+ if vertical_align in ("center", "middle"):
+ first_y += font_size * 0.35
+ elif vertical_align == "top":
+ first_y += font_size * 0.8
+ # A callout's `color` paints its arrow; the label prefers its own.
+ label_color = style.get("label_color") or style.get("color")
for index, line in enumerate(lines):
cmd.text(
x + float(ann.get("dx", 0.0)),
first_y + index * line_height + float(ann.get("dy", 0.0)),
anchor,
font_size,
- _rgba(style.get("color"), _TEXT, float(style.get("opacity", 1.0))),
+ _rgba(label_color, _TEXT, float(style.get("opacity", 1.0))),
line,
)
diff --git a/python/xy/_svg.py b/python/xy/_svg.py
index 4565d56c..012e6b52 100644
--- a/python/xy/_svg.py
+++ b/python/xy/_svg.py
@@ -27,6 +27,7 @@
import numpy as np
from . import _png
+from ._arrowgeom import arrow_shapes as _arrow_shapes
from .config import DEFAULT_PALETTE
@@ -1309,34 +1310,43 @@ def _annotation_svg(annotations, sx, sy, plot, width, height):
f'