Skip to content

Commit 38fc954

Browse files
authored
Improve configuration (#60)
* plotting config * fmt
1 parent 5b310ae commit 38fc954

8 files changed

Lines changed: 251 additions & 66 deletions

File tree

‎README.md‎

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ to explicitly render an already-built canvas, use `canvas.render(...)`.
5050
The older `canvas.plot(backend=...)` spelling is still supported for
5151
compatibility, but emits a `FutureWarning`.
5252

53+
Add several lines at once with shared styling:
54+
55+
``` python
56+
canvas.plot_many(
57+
[(x, np.sin(x)), (x, np.cos(x))],
58+
labels=["sin(x)", "cos(x)"],
59+
linewidth=2,
60+
)
61+
```
62+
63+
Common figure and axis settings can be grouped with `configure()`:
64+
65+
``` python
66+
canvas.configure(
67+
title="Trigonometry",
68+
xlabel="Angle",
69+
ylabel="Value",
70+
grid=True,
71+
facecolor="whitesmoke",
72+
)
73+
```
74+
5375
For Matplotlib-specific customization, pass method calls declaratively.
5476
Figure methods run once and axes methods run for every subplot,
5577
providing access to any Matplotlib API without requiring a maxplotlib
@@ -165,26 +187,27 @@ terminal_fig = canvas.render(backend="plotext")
165187
print(terminal_fig.build(keep_colors=False))
166188
```
167189

190+
Trigonometry
168191
Runtime
169-
┌─────────────────────────────────────────────────────────────────────────┐
170-
1.00┤ ▗▄▞▀▀▀▀▀▙▄▖ │
171-
│ ▗▄▀▘ ▝▀▄ │
172-
│ ▗▞▘ ▀▄ │
173-
0.67┤ ▟▀ ▀▄ │
174-
│ ▄▛ ▚▖ │
175-
0.33┤ ▗▞ ▝▄ │
176-
│ ▄▀ ▚▖ │
177-
│▗▞▘ ▀▄ │
178-
0.00┤▀ ▝▚▖ ▞│
179-
│ ▀▄ ▗▞▘│
180-
│ ▝▚ ▄▀ │
181-
-0.33┤ ▀▖ ▞▘ │
182-
│ ▝▚ ▟▀ │
183-
-0.67┤ ▀▄ ▄▛ │
184-
│ ▀▄ ▗▞▘ │
185-
│ ▀▄▖ ▗▄▀▘ │
186-
-1.00┤ ▝▀▜▄▄▄▄▄▞▀▘ │
187-
└┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
192+
┌┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┐
193+
1.00┼ ▞▞ sin(x) ──▗▄▞▀▀▀▀▀▙▄▖────────────┼─────────────────┼─────────────▄▄▀▀▀┤
194+
│ ▞▞ cos(x) ▄▀▘ │ ▝▀▄ │ │ ▄▞▀ ││
195+
││ ▜▄▘ │ ▀▄ │ │ ▄▛ ││
196+
0.67┼┼──────▟▀─▀▄──────┼─────────▀▄──────┼─────────────────┼──────▄▀─────────┼┤
197+
││ ▄▛ ▝▚▖ │ ▚▖ │ │ ▗▞▘ ││
198+
0.33┼┼──▗▞────────▀▖───┼────────────▝▄───┼─────────────────┼───▗▀────────────┼┤
199+
││ ▄▀ ▝▚ │ ▚▖ │ │ ▞▘ ││
200+
│▗▞▘ ▀▖│ ▀▄│ │▗▀ ││
201+
0.00┼▞────────────────▝▙────────────────▝▚▖────────────────▟▘────────────────▄┤
202+
││ │▜▖ │▀▄ ▗▛│ ▗▞▘│
203+
││ │ ▝▙ │ ▝▚ ▟▘ │ ▄▀ ││
204+
-0.33┼┼─────────────────┼───▚▖────────────┼───▀▖───────▗▞───┼─────────────▞▘──┼┤
205+
││ │ ▝▄▖ │ ▝▚ ▗▄▘ │ ▟▀ ││
206+
-0.67┼┼─────────────────┼──────▀▖─────────┼──────▀▄─▗▀──────┼─────────▄▛──────┼┤
207+
││ │ ▝▜▄ │ ▄▛▘ │ ▗▞▘ ││
208+
││ │ ▀▄▖ │ ▗▄▀ ▀▄▖ │ ▗▄▀▘ ││
209+
-1.00┼┼─────────────────┼────────────▝▀▚▄▄▄▄▄▞▀▘───────▝▀▜▄▄▄▄▄▞▀▘────────────┼┤
210+
└┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼┘
188211
0.0 1.6 3.1 4.7 6.3
189212
Duration Time
190213

@@ -194,7 +217,7 @@ Or plot with the TikZ backend:
194217
canvas.show(backend="tikzfigure")
195218
```
196219

197-
![](README_files/figure-commonmark/cell-12-output-1.png)
220+
![](README_files/figure-commonmark/cell-14-output-1.png)
198221

199222
### Horizontal Subplots with TikZ Backend
200223

@@ -275,7 +298,7 @@ canvas.show(backend="plotext")
275298
1.0 1.8 3.2 5.6 10.0
276299
y x
277300

278-
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x10ee1ce10>
301+
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x1102a0690>
279302

280303
### Layers
281304

@@ -311,7 +334,7 @@ Show layer 0 only, then layers 0 and 1, then everything:
311334
canvas.show(layers=[0])
312335
```
313336

314-
![](README_files/figure-commonmark/cell-16-output-1.png)
337+
![](README_files/figure-commonmark/cell-18-output-1.png)
315338

316339
(<Figure size 590.551x324.803 with 1 Axes>,
317340
array([[<Axes: xlabel='x'>]], dtype=object))
@@ -322,7 +345,7 @@ Show all layers:
322345
canvas.show()
323346
```
324347

325-
![](README_files/figure-commonmark/cell-17-output-1.png)
348+
![](README_files/figure-commonmark/cell-19-output-1.png)
326349

327350
(<Figure size 590.551x324.803 with 1 Axes>,
328351
array([[<Axes: xlabel='x'>]], dtype=object))

‎README.qmd‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ explicitly render an already-built canvas, use `canvas.render(...)`. The older
4747
`canvas.plot(backend=...)` spelling is still supported for compatibility, but
4848
emits a `FutureWarning`.
4949

50+
Add several lines at once with shared styling:
51+
52+
```{python}
53+
#| output: false
54+
canvas.plot_many(
55+
[(x, np.sin(x)), (x, np.cos(x))],
56+
labels=["sin(x)", "cos(x)"],
57+
linewidth=2,
58+
)
59+
```
60+
61+
Common figure and axis settings can be grouped with `configure()`:
62+
63+
```{python}
64+
#| output: false
65+
canvas.configure(
66+
title="Trigonometry",
67+
xlabel="Angle",
68+
ylabel="Value",
69+
grid=True,
70+
facecolor="whitesmoke",
71+
)
72+
```
73+
5074
For Matplotlib-specific customization, pass method calls declaratively. Figure
5175
methods run once and axes methods run for every subplot, providing access to
5276
any Matplotlib API without requiring a maxplotlib wrapper:
38.5 KB
Loading

‎src/maxplotlib/canvas/canvas.py‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,47 @@ def add_line(
459459
**kwargs,
460460
)
461461

462+
def plot_many(
463+
self,
464+
series,
465+
labels=None,
466+
layer=0,
467+
row: int | None = None,
468+
col: int | None = None,
469+
**kwargs,
470+
):
471+
"""Add several lines to the canvas and return the canvas.
472+
473+
``series`` is an iterable of ``(x, y)`` pairs. Shared line keyword
474+
arguments are passed through ``kwargs``; individual labels can be
475+
supplied with ``labels``.
476+
"""
477+
if labels is not None:
478+
labels = list(labels)
479+
count = 0
480+
for index, values in enumerate(series):
481+
count += 1
482+
try:
483+
x, y = values
484+
except (TypeError, ValueError) as exc:
485+
raise ValueError("each series entry must be an (x, y) pair") from exc
486+
line_kwargs = dict(kwargs)
487+
if labels is not None:
488+
if index >= len(labels):
489+
raise ValueError("labels must contain one label per series")
490+
line_kwargs["label"] = labels[index]
491+
self.add_line(
492+
x,
493+
y,
494+
layer=layer,
495+
row=row,
496+
col=col,
497+
**line_kwargs,
498+
)
499+
if labels is not None and len(labels) != count:
500+
raise ValueError("labels must contain one label per series")
501+
return self
502+
462503
def _get_or_create_subplot(self, row, col):
463504
"""Return the subplot at (row, col), creating it if needed."""
464505
if row is not None and col is not None:
@@ -985,6 +1026,60 @@ def set_title(
9851026
"""Set the title and text properties for a subplot."""
9861027
self._get_or_create_subplot(row, col).set_title(title, **kwargs)
9871028

1029+
def configure(
1030+
self,
1031+
*,
1032+
title=None,
1033+
xlabel=None,
1034+
ylabel=None,
1035+
grid=None,
1036+
facecolor=None,
1037+
axisbelow=None,
1038+
margins=None,
1039+
xscale=None,
1040+
yscale=None,
1041+
xlim=None,
1042+
ylim=None,
1043+
tight_layout=False,
1044+
row: int | None = None,
1045+
col: int | None = None,
1046+
):
1047+
"""Apply common figure and axis settings, returning the canvas.
1048+
1049+
``title``, ``xlabel``, and ``ylabel`` are figure-level settings. Axis
1050+
settings are applied to the selected subplot, or the default subplot
1051+
when ``row`` and ``col`` are omitted. Use ``tight_layout=True`` for a
1052+
final layout pass before rendering.
1053+
"""
1054+
if title is not None:
1055+
self.suptitle(title)
1056+
if xlabel is not None:
1057+
self.supxlabel(xlabel)
1058+
if ylabel is not None:
1059+
self.supylabel(ylabel)
1060+
if grid is not None:
1061+
self.set_grid(grid, row=row, col=col)
1062+
if facecolor is not None:
1063+
self.set_facecolor(facecolor, row=row, col=col)
1064+
if axisbelow is not None:
1065+
self.set_axisbelow(axisbelow, row=row, col=col)
1066+
if margins is not None:
1067+
if isinstance(margins, dict):
1068+
self.margins(row=row, col=col, **margins)
1069+
else:
1070+
self.margins(margins, row=row, col=col)
1071+
if xscale is not None:
1072+
self.set_xscale(xscale, row=row, col=col)
1073+
if yscale is not None:
1074+
self.set_yscale(yscale, row=row, col=col)
1075+
if xlim is not None:
1076+
self.set_xlim(*xlim, row=row, col=col)
1077+
if ylim is not None:
1078+
self.set_ylim(*ylim, row=row, col=col)
1079+
if tight_layout:
1080+
self.tight_layout()
1081+
return self
1082+
9881083
def set_xlim(
9891084
self, left=None, right=None, row: int | None = None, col: int | None = None
9901085
):

0 commit comments

Comments
 (0)