Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ canvas.show()

![](README_files/figure-commonmark/cell-3-output-1.png)

Use `canvas.plot(x, y)` to add data directly to a canvas. When you want
to explicitly render an already-built canvas, use `canvas.render(...)`.
The older `canvas.plot(backend=...)` spelling is still supported for
compatibility, but emits a `FutureWarning`.

For Matplotlib-specific customization, pass method calls declaratively.
Figure methods run once and axes methods run for every subplot,
providing access to any Matplotlib API without requiring a maxplotlib
Expand Down Expand Up @@ -149,14 +154,14 @@ parts of a mixed canvas, explicitly opt into skipping unsupported
primitives:

``` python
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
plotly_canvas.render(backend="plotly", allow_unsupported=True)
```

Render the same line graph directly in the terminal with the `plotext`
backend:

``` python
terminal_fig = canvas.plot(backend="plotext")
terminal_fig = canvas.render(backend="plotext")
print(terminal_fig.build(keep_colors=False))
```

Expand Down Expand Up @@ -270,7 +275,7 @@ canvas.show(backend="plotext")
1.0 1.8 3.2 5.6 10.0
y x

<maxplotlib.backends.plotext.figure.PlotextFigure at 0x110a30550>
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x10ee1ce10>

### Layers

Expand Down
9 changes: 7 additions & 2 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ Plot the figure with the default (matplotlib) backend:
canvas.show()
```

Use `canvas.plot(x, y)` to add data directly to a canvas. When you want to
explicitly render an already-built canvas, use `canvas.render(...)`. The older
`canvas.plot(backend=...)` spelling is still supported for compatibility, but
emits a `FutureWarning`.

For Matplotlib-specific customization, pass method calls declaratively. Figure
methods run once and axes methods run for every subplot, providing access to
any Matplotlib API without requiring a maxplotlib wrapper:
Expand Down Expand Up @@ -149,13 +154,13 @@ of a mixed canvas, explicitly opt into skipping unsupported primitives:

```{python}
#| output: false
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
plotly_canvas.render(backend="plotly", allow_unsupported=True)
```

Render the same line graph directly in the terminal with the `plotext` backend:

```{python}
terminal_fig = canvas.plot(backend="plotext")
terminal_fig = canvas.render(backend="plotext")
print(terminal_fig.build(keep_colors=False))
```

Expand Down
67 changes: 57 additions & 10 deletions src/maxplotlib/canvas/canvas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import re
import warnings
from dataclasses import dataclass
from typing import Mapping

Expand Down Expand Up @@ -1686,7 +1687,7 @@ def savefig(
layers = []
for layer in self.layers:
layers.append(layer)
fig, axs = self.plot(
fig, axs = self._render(
show=False,
backend="matplotlib",
savefig=True,
Expand All @@ -1707,7 +1708,7 @@ def savefig(
savefig_kwargs = {"dpi": self.dpi} if self.dpi is not None else {}
self._matplotlib_fig.savefig(full_filepath, **savefig_kwargs)
else:
fig, axs = self.plot(
fig, axs = self._render(
backend="matplotlib",
savefig=True,
layers=layers,
Expand All @@ -1721,7 +1722,7 @@ def savefig(
layers = []
for layer in self.layers:
layers.append(layer)
figure = self.plot(
figure = self._render(
backend="plotext",
savefig=False,
layers=layers,
Expand All @@ -1735,7 +1736,7 @@ def savefig(
full_filepath = filename
else:
full_filepath = f"{filename_no_extension}_{layers}.{extension}"
figure = self.plot(
figure = self._render(
backend="plotext",
savefig=False,
layers=layers,
Expand All @@ -1749,7 +1750,7 @@ def savefig(
for layer in self.layers:
layers.append(layer)
full_filepath = f"{filename_no_extension}_{layers}{extension}"
fig = self.plot(
fig = self._render(
backend="plotly",
savefig=False,
layers=layers,
Expand All @@ -1763,7 +1764,7 @@ def savefig(
full_filepath = filename
else:
full_filepath = f"{filename_no_extension}_{layers}{extension}"
fig = self.plot(
fig = self._render(
backend="plotly",
savefig=False,
layers=layers,
Expand All @@ -1776,12 +1777,12 @@ def savefig(
raise NotImplementedError(
"Layer-by-layer rendering is not supported for tikzfigure backend"
)
fig = self.plot(backend="tikzfigure", savefig=False)
fig = self._render(backend="tikzfigure", savefig=False)
fig.savefig(filename)
if verbose:
print(f"Saved {filename}")

def plot(
def _render(
self,
backend: Backends = "matplotlib",
savefig: bool = False,
Expand Down Expand Up @@ -1844,6 +1845,52 @@ def plot(
else:
raise ValueError(f"Invalid backend: {backend}")

def plot(self, *args, backend=None, **kwargs):
"""Add a line, or render when called with backend options.

``canvas.plot(x, y, **style)`` is the convenient direct plotting form.
Rendering is named explicitly by ``canvas.render(...)``; the legacy
``canvas.plot(backend=...)`` form remains supported.
"""
explicit_render = backend is not None or (args and isinstance(args[0], str))
if args and not isinstance(args[0], str):
if len(args) < 2:
raise TypeError("plot(x, y) requires both x and y data")
if len(args) > 2:
raise TypeError("plot() accepts only x and y positional data")
layer = kwargs.pop("layer", 0)
row = kwargs.pop("row", None)
col = kwargs.pop("col", None)
self.add_line(args[0], args[1], layer=layer, row=row, col=col, **kwargs)
return self
if args:
if len(args) > 1:
raise TypeError(
"plot() accepts at most one backend positional argument"
)
if backend is not None:
raise TypeError("backend was provided both positionally and by keyword")
backend = args[0]
if backend is None:
backend = "matplotlib"
if explicit_render:
warnings.warn(
"canvas.plot(backend=...) is deprecated; use "
"canvas.render(backend=...) instead",
FutureWarning,
stacklevel=2,
)
return self._render(backend=backend, **kwargs)

def render(self, *args, **kwargs):
"""Render the canvas using the selected backend.

This is the explicit name for the operation historically exposed as
``Canvas.plot(backend=...)``. The latter remains available for
backwards compatibility.
"""
return self._render(*args, **kwargs)

def show(
self,
backend: Backends = "matplotlib",
Expand Down Expand Up @@ -1883,7 +1930,7 @@ def show(
if backend == "matplotlib":
if verbose:
print("Generating Matplotlib figure for display...")
fig, axes = self.plot(
fig, axes = self._render(
backend="matplotlib",
savefig=False,
layers=layers,
Expand Down Expand Up @@ -2621,5 +2668,5 @@ def __str__(self):
c = Canvas(ncols=2, nrows=2)
sp = c.add_subplot()
sp.plot([0, 1, 2, 3], [0, 1, 4, 9], label="Line 1")
c.plot(backend="matplotlib")
c.render(backend="matplotlib")
print("done")
35 changes: 34 additions & 1 deletion src/maxplotlib/tests/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def test_canvas_show_uses_ipython_display_in_jupyter(monkeypatch):
monkeypatch.setitem(sys.modules, "IPython", ipython)
monkeypatch.setitem(sys.modules, "IPython.display", ipython_display)
monkeypatch.setattr(plt, "close", lambda value: closed.append(value))
monkeypatch.setattr(Canvas, "plot", lambda *args, **kwargs: (fig, object()))
monkeypatch.setattr(Canvas, "_render", lambda *args, **kwargs: (fig, object()))
monkeypatch.setattr(plt, "show", lambda: pytest.fail("pyplot.show was called"))

canvas = Canvas()
Expand Down Expand Up @@ -880,6 +880,39 @@ def test_axis_getters_reflect_configured_state():
assert canvas.get_ymargin() == 0.2


def test_render_is_the_explicit_rendering_alias():
from maxplotlib import Canvas

canvas = Canvas()
canvas.add_line([0, 1], [0, 1])

rendered = canvas.render(backend="plotly")

assert rendered is not None


def test_plot_adds_line_data_when_given_x_and_y():
from maxplotlib import Canvas

canvas = Canvas()
result = canvas.plot([0, 1], [1, 2], color="purple", label="line")

assert result is canvas
assert canvas.render(backend="plotly").data[0].name == "line"


def test_legacy_plot_backend_form_warns():
import pytest

from maxplotlib import Canvas

canvas = Canvas()
canvas.add_line([0, 1], [0, 1])

with pytest.warns(FutureWarning, match=r"canvas\.render"):
canvas.plot(backend="plotly")


def test_matplotlib_postprocess_can_customize_figure_and_axes():
import matplotlib.pyplot as plt
from matplotlib.colors import to_rgba
Expand Down
20 changes: 10 additions & 10 deletions tutorials/tutorial_01.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"y = np.sin(x)\n",
"\n",
"canvas = Canvas()\n",
"canvas.add_line(x, y)\n",
"canvas.plot(x, y)\n",
"canvas.show(backend=BACKEND)"
]
},
Expand All @@ -101,13 +101,13 @@
"source": [
"canvas = Canvas()\n",
"\n",
"canvas.add_line(\n",
"canvas.plot(\n",
" x, np.sin(x), label=\"sin(x)\", color=\"royalblue\", linestyle=\"solid\", linewidth=2\n",
")\n",
"canvas.add_line(\n",
"canvas.plot(\n",
" x, np.cos(x), label=\"cos(x)\", color=\"tomato\", linestyle=\"dashed\", linewidth=2\n",
")\n",
"canvas.add_line(\n",
"canvas.plot(\n",
" x,\n",
" np.sin(2 * x),\n",
" label=\"sin(2x)\",\n",
Expand Down Expand Up @@ -144,8 +144,8 @@
"source": [
"canvas = Canvas(ratio=0.5, fontsize=12)\n",
"\n",
"canvas.add_line(x, np.sin(x), label=\"sin(x)\", color=\"steelblue\")\n",
"canvas.add_line(x, np.cos(x), label=\"cos(x)\", color=\"darkorange\", linestyle=\"dashed\")\n",
"canvas.plot(x, np.sin(x), label=\"sin(x)\", color=\"steelblue\")\n",
"canvas.plot(x, np.cos(x), label=\"cos(x)\", color=\"darkorange\", linestyle=\"dashed\")\n",
"\n",
"canvas.set_xlabel(\"angle (rad)\")\n",
"canvas.set_ylabel(\"amplitude\")\n",
Expand Down Expand Up @@ -183,8 +183,8 @@
" legend=True,\n",
")\n",
"\n",
"canvas.add_line(x, np.sin(x), label=\"sin\", color=\"royalblue\")\n",
"canvas.add_line(x, x / (2 * np.pi), label=\"x/2π\", color=\"coral\", linestyle=\"dashed\")\n",
"canvas.plot(x, np.sin(x), label=\"sin\", color=\"royalblue\")\n",
"canvas.plot(x, x / (2 * np.pi), label=\"x/2π\", color=\"coral\", linestyle=\"dashed\")\n",
"\n",
"canvas.show(backend=BACKEND)"
]
Expand All @@ -209,7 +209,7 @@
"source": [
"canvas = Canvas(ratio=0.5)\n",
"ax = canvas.add_subplot(xlabel=\"x\", ylabel=\"sin(x)\", grid=True)\n",
"canvas.add_line(x, np.sin(x), color=\"steelblue\")\n",
"canvas.plot(x, np.sin(x), color=\"steelblue\")\n",
"\n",
"canvas.savefig(\"tutorial_01_output.png\")\n",
"print(\"Figure saved to tutorial_01_output.png\")"
Expand All @@ -225,7 +225,7 @@
"| Task | Code |\n",
"|---|---|\n",
"| Create a canvas | `canvas = Canvas()` |\n",
"| Add a line | `canvas.add_line(x, y, label=..., color=..., linestyle=...)` |\n",
"| Add a line | `canvas.plot(x, y, label=..., color=..., linestyle=...)` |\n",
"| Optional Matplotlib-style axes | `canvas, ax = Canvas.subplots()` |\n",
"| Labels / title | `canvas.set_xlabel()`, `canvas.set_ylabel()`, `canvas.set_title()` |\n",
"| Legend / grid | `canvas.set_legend(True)`, `canvas.set_grid(True)` |\n",
Expand Down
6 changes: 3 additions & 3 deletions tutorials/tutorial_02.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@
"canvas.add_subplot(row=0, col=0, title=\"Left\", xlabel=\"x\", ylabel=\"sin\")\n",
"canvas.add_subplot(row=0, col=1, title=\"Right\", xlabel=\"x\", ylabel=\"cos\")\n",
"\n",
"canvas.add_line(x, np.sin(x), row=0, col=0, color=\"royalblue\", label=\"sin\")\n",
"canvas.add_line(x, np.cos(x), row=0, col=1, color=\"tomato\", label=\"cos\")\n",
"canvas.plot(x, np.sin(x), row=0, col=0, color=\"royalblue\", label=\"sin\")\n",
"canvas.plot(x, np.cos(x), row=0, col=1, color=\"tomato\", label=\"cos\")\n",
"\n",
"canvas.set_legend(True, row=0, col=0)\n",
"canvas.set_legend(True, row=0, col=1)\n",
Expand All @@ -346,7 +346,7 @@
"| Get subplot | `canvas.subplot(r, c)` or `canvas[r, c]` |\n",
"| Loop panels | `for row, col, sp in canvas.iter_subplots()` |\n",
"| Figure title | `canvas.suptitle('...')` |\n",
"| Route plot | `canvas.add_line(x, y, row=r, col=c)` |\n",
"| Route plot | `canvas.plot(x, y, row=r, col=c)` |\n",
"\n",
"Next: **Tutorial 03** covers all the available plot types."
]
Expand Down
Loading
Loading