maxplotlibx 0.1.5__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maxplotlibx-0.1.7/PKG-INFO +360 -0
- maxplotlibx-0.1.7/README.md +323 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/pyproject.toml +1 -1
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/canvas/canvas.py +845 -20
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/subfigure/line_plot.py +1463 -20
- maxplotlibx-0.1.7/src/maxplotlib/tests/test_canvas.py +951 -0
- maxplotlibx-0.1.7/src/maxplotlib/tests/test_plotly_backend.py +338 -0
- maxplotlibx-0.1.7/src/maxplotlib/tests/test_styles_and_colors.py +61 -0
- maxplotlibx-0.1.7/src/maxplotlibx.egg-info/PKG-INFO +360 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlibx.egg-info/SOURCES.txt +1 -0
- maxplotlibx-0.1.5/PKG-INFO +0 -160
- maxplotlibx-0.1.5/README.md +0 -123
- maxplotlibx-0.1.5/src/maxplotlib/tests/test_canvas.py +0 -471
- maxplotlibx-0.1.5/src/maxplotlib/tests/test_plotly_backend.py +0 -86
- maxplotlibx-0.1.5/src/maxplotlibx.egg-info/PKG-INFO +0 -160
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/LICENSE +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/setup.cfg +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/backends/matplotlib/utils.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/backends/plotext/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/backends/plotext/figure.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/backends/plotly/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/backends/plotly/utils.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/canvas/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/colors/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/colors/colors.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/linestyle/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/linestyle/linestyle.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/objects/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/objects/node.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/objects/path.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/subfigure/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/subfigure/subfigure.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/tests/test_flame_chart.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/tests/test_gantt_chart.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/tests/test_imports.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/tests/test_plot.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/tests/test_plotext.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/utils/__init__.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlib/utils/options.py +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlibx.egg-info/dependency_links.txt +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlibx.egg-info/requires.txt +0 -0
- {maxplotlibx-0.1.5 → maxplotlibx-0.1.7}/src/maxplotlibx.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maxplotlibx
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Summary: A reproducible plotting module with various backends and export options.
|
|
5
|
+
Author: Max
|
|
6
|
+
Project-URL: Source, https://github.com/max-models/maxplotlib
|
|
7
|
+
Keywords: matplotlib
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: matplotlib
|
|
13
|
+
Requires-Dist: pint
|
|
14
|
+
Requires-Dist: plotly
|
|
15
|
+
Requires-Dist: plotext
|
|
16
|
+
Requires-Dist: tikzfigure[vis]>=0.3.0
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
Requires-Dist: coverage; extra == "test"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: myst-parser; extra == "docs"
|
|
22
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
23
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
24
|
+
Requires-Dist: nbsphinx; extra == "docs"
|
|
25
|
+
Requires-Dist: ipykernel; extra == "docs"
|
|
26
|
+
Requires-Dist: nbconvert; extra == "docs"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: maxplotlibx[docs,test]; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Requires-Dist: black[jupyter]; extra == "dev"
|
|
31
|
+
Requires-Dist: isort; extra == "dev"
|
|
32
|
+
Requires-Dist: jupyterlab; extra == "dev"
|
|
33
|
+
Requires-Dist: nbstripout; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
35
|
+
Requires-Dist: pyproject-fmt; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Maxlotlib
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Maxplotlib
|
|
42
|
+
|
|
43
|
+
A clean, expressive wrapper around **Matplotlib**, **Plotly**,
|
|
44
|
+
**plotext**, and **tikzfigure** for producing publication-quality
|
|
45
|
+
figures with minimal boilerplate. Swap backends without rewriting your
|
|
46
|
+
data — render the same canvas as a crisp PNG, an interactive Plotly
|
|
47
|
+
chart, a terminal-native plotext figure, or camera-ready **TikZ** code
|
|
48
|
+
for LaTeX.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
``` bash
|
|
53
|
+
pip install maxplotlibx
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Showcase
|
|
57
|
+
|
|
58
|
+
### Quickstart
|
|
59
|
+
|
|
60
|
+
<div id="fig-showcase-1">
|
|
61
|
+
|
|
62
|
+
``` python
|
|
63
|
+
import numpy as np
|
|
64
|
+
from maxplotlib import Canvas
|
|
65
|
+
|
|
66
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
67
|
+
y = np.sin(x)
|
|
68
|
+
|
|
69
|
+
canvas, ax = Canvas.subplots()
|
|
70
|
+
ax.plot(x, y)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Figure 1
|
|
74
|
+
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
Plot the figure with the default (matplotlib) backend:
|
|
78
|
+
|
|
79
|
+
``` python
|
|
80
|
+
canvas.show()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
For Matplotlib-specific customization, pass method calls declaratively.
|
|
86
|
+
Figure methods run once and axes methods run for every subplot,
|
|
87
|
+
providing access to any Matplotlib API without requiring a maxplotlib
|
|
88
|
+
wrapper:
|
|
89
|
+
|
|
90
|
+
``` python
|
|
91
|
+
canvas.plot(matplotlib_customizations={
|
|
92
|
+
"figure": {
|
|
93
|
+
"suptitle": "My figure",
|
|
94
|
+
},
|
|
95
|
+
"axes": {
|
|
96
|
+
"tick_params": {
|
|
97
|
+
"axis": "both",
|
|
98
|
+
"which": "major",
|
|
99
|
+
"length": 6,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For dynamic customization, the same option also accepts a function:
|
|
106
|
+
|
|
107
|
+
``` python
|
|
108
|
+
def customize(fig, axes):
|
|
109
|
+
fig.suptitle("My figure")
|
|
110
|
+
for ax in axes.flat:
|
|
111
|
+
ax.tick_params(axis="both", which="major", length=6)
|
|
112
|
+
|
|
113
|
+
canvas.plot(matplotlib_customizations=customize)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Axis Label and Tick Styling
|
|
117
|
+
|
|
118
|
+
Axis labels, titles, and tick appearance accept Matplotlib-style keyword
|
|
119
|
+
arguments:
|
|
120
|
+
|
|
121
|
+
``` python
|
|
122
|
+
canvas.set_xlabel("Time", fontsize=12, fontweight="bold", labelpad=10)
|
|
123
|
+
canvas.set_ylabel("Duration", color="darkblue")
|
|
124
|
+
canvas.set_title("Runtime", fontsize=14, color="navy")
|
|
125
|
+
canvas.tick_params(
|
|
126
|
+
axis="both",
|
|
127
|
+
which="major",
|
|
128
|
+
labelsize=10,
|
|
129
|
+
colors="darkgreen",
|
|
130
|
+
length=6,
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Common axis controls and figure-level layout settings are also
|
|
135
|
+
available:
|
|
136
|
+
|
|
137
|
+
``` python
|
|
138
|
+
canvas.set_facecolor("whitesmoke")
|
|
139
|
+
canvas.set_axisbelow(True)
|
|
140
|
+
canvas.margins(x=0.05, y=0.1)
|
|
141
|
+
canvas.minorticks_on()
|
|
142
|
+
canvas.invert_yaxis()
|
|
143
|
+
canvas.supxlabel("Shared x label")
|
|
144
|
+
canvas.supylabel("Shared y label")
|
|
145
|
+
canvas.subplots_adjust(left=0.15, bottom=0.15)
|
|
146
|
+
canvas.tight_layout()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Secondary Y-Axis
|
|
150
|
+
|
|
151
|
+
Use `Canvas.twinx()` to add a second y-axis that shares the primary
|
|
152
|
+
x-axis:
|
|
153
|
+
|
|
154
|
+
``` python
|
|
155
|
+
twin_canvas, primary = Canvas.subplots()
|
|
156
|
+
secondary = twin_canvas.twinx()
|
|
157
|
+
|
|
158
|
+
primary.plot(x, np.sin(x), color="tab:blue")
|
|
159
|
+
secondary.plot(x, 100 * np.cos(x), color="tab:red")
|
|
160
|
+
primary.set_ylabel("sin(x)", color="tab:blue")
|
|
161
|
+
secondary.set_ylabel("100 cos(x)", color="tab:red")
|
|
162
|
+
|
|
163
|
+
twin_canvas.show()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Secondary y-axes are currently supported by the Matplotlib and Plotly
|
|
167
|
+
backends.
|
|
168
|
+
|
|
169
|
+
### Plotly field plots and tables
|
|
170
|
+
|
|
171
|
+
Several Matplotlib field and annotation APIs map directly to interactive
|
|
172
|
+
Plotly traces, including pseudocolor plots, sparsity patterns,
|
|
173
|
+
triangular grids, and tables:
|
|
174
|
+
|
|
175
|
+
``` python
|
|
176
|
+
plotly_canvas, plotly_ax = Canvas.subplots()
|
|
177
|
+
plotly_ax.pcolor(x, x, np.outer(np.sin(x), np.cos(x)))
|
|
178
|
+
plotly_ax.spy([[1, 0, 1], [0, 1, 0], [1, 0, 1]])
|
|
179
|
+
plotly_ax.table(cellText=[["A", "B"], ["1", "2"]])
|
|
180
|
+
plotly_canvas.show(backend="plotly")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Plotly raises `NotImplementedError` for primitives without a faithful
|
|
184
|
+
equivalent instead of silently dropping them. To render the supported
|
|
185
|
+
parts of a mixed canvas, explicitly opt into skipping unsupported
|
|
186
|
+
primitives:
|
|
187
|
+
|
|
188
|
+
``` python
|
|
189
|
+
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Render the same line graph directly in the terminal with the `plotext`
|
|
193
|
+
backend:
|
|
194
|
+
|
|
195
|
+
``` python
|
|
196
|
+
terminal_fig = canvas.plot(backend="plotext")
|
|
197
|
+
print(terminal_fig.build(keep_colors=False))
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Runtime
|
|
201
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
202
|
+
1.00┤ ▗▄▞▀▀▀▀▀▙▄▖ │
|
|
203
|
+
│ ▗▄▀▘ ▝▀▄ │
|
|
204
|
+
│ ▗▞▘ ▀▄ │
|
|
205
|
+
0.67┤ ▟▀ ▀▄ │
|
|
206
|
+
│ ▄▛ ▚▖ │
|
|
207
|
+
0.33┤ ▗▞ ▝▄ │
|
|
208
|
+
│ ▄▀ ▚▖ │
|
|
209
|
+
│▗▞▘ ▀▄ │
|
|
210
|
+
0.00┤▀ ▝▚▖ ▞│
|
|
211
|
+
│ ▀▄ ▗▞▘│
|
|
212
|
+
│ ▝▚ ▄▀ │
|
|
213
|
+
-0.33┤ ▀▖ ▞▘ │
|
|
214
|
+
│ ▝▚ ▟▀ │
|
|
215
|
+
-0.67┤ ▀▄ ▄▛ │
|
|
216
|
+
│ ▀▄ ▗▞▘ │
|
|
217
|
+
│ ▀▄▖ ▗▄▀▘ │
|
|
218
|
+
-1.00┤ ▝▀▜▄▄▄▄▄▞▀▘ │
|
|
219
|
+
└┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
|
|
220
|
+
0.0 1.6 3.1 4.7 6.3
|
|
221
|
+
Duration Time
|
|
222
|
+
|
|
223
|
+
Or plot with the TikZ backend:
|
|
224
|
+
|
|
225
|
+
``` python
|
|
226
|
+
canvas.show(backend="tikzfigure")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+

|
|
230
|
+
|
|
231
|
+
### Horizontal Subplots with TikZ Backend
|
|
232
|
+
|
|
233
|
+
The tikzfigure backend supports creating side-by-side subplots (1×n
|
|
234
|
+
layouts):
|
|
235
|
+
|
|
236
|
+
``` python
|
|
237
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
238
|
+
canvas, (ax1, ax2) = Canvas.subplots(ncols=2, width="10cm", ratio=0.3)
|
|
239
|
+
|
|
240
|
+
ax1.plot(x, np.sin(x), color="royalblue")
|
|
241
|
+
ax1.set_title("sin(x)")
|
|
242
|
+
|
|
243
|
+
ax2.plot(x, np.cos(x), color="tomato")
|
|
244
|
+
ax2.set_title("cos(x)")
|
|
245
|
+
|
|
246
|
+
canvas.suptitle("Trigonometric Functions")
|
|
247
|
+
canvas.show(backend="tikzfigure") # Generates LaTeX subfigures
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
<div id="fig-showcase-subplots">
|
|
251
|
+
|
|
252
|
+
<img
|
|
253
|
+
src="README_files/figure-commonmark/fig-showcase-subplots-output-1.png"
|
|
254
|
+
id="fig-showcase-subplots" />
|
|
255
|
+
|
|
256
|
+
Figure 2
|
|
257
|
+
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
**Note:** Only horizontal layouts (1×n) are currently supported with the
|
|
261
|
+
tikzfigure backend. Vertical/grid layouts will raise
|
|
262
|
+
`NotImplementedError`. See the tutorials for more examples.
|
|
263
|
+
|
|
264
|
+
### Terminal Backend with plotext
|
|
265
|
+
|
|
266
|
+
The `plotext` backend is designed for terminal-first workflows. It
|
|
267
|
+
currently supports line plots, scatter plots, bars, filled regions,
|
|
268
|
+
error bars, reference lines, text/annotations, labels/titles, log axes,
|
|
269
|
+
layers, matrix-style `imshow()` rendering, common patches, and
|
|
270
|
+
multi-subplot canvases.
|
|
271
|
+
|
|
272
|
+
``` python
|
|
273
|
+
x = np.linspace(1, 10, 40)
|
|
274
|
+
|
|
275
|
+
canvas, ax = Canvas.subplots()
|
|
276
|
+
ax.plot(x, np.sqrt(x), color="cyan", label="sqrt(x)")
|
|
277
|
+
ax.errorbar(x[::8], np.sqrt(x[::8]), yerr=0.15, color="yellow", label="samples")
|
|
278
|
+
ax.set_title("Terminal plot")
|
|
279
|
+
ax.set_xlabel("x")
|
|
280
|
+
ax.set_ylabel("y")
|
|
281
|
+
ax.set_xscale("log")
|
|
282
|
+
ax.set_legend(True)
|
|
283
|
+
|
|
284
|
+
canvas.show(backend="plotext")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Terminal plot
|
|
288
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
289
|
+
3.16┤ ▞▞ sqrt(x) ▄▞│
|
|
290
|
+
│ │▗▄▞▀ │
|
|
291
|
+
│ ▄┼▘ │
|
|
292
|
+
2.79┤ ▄▀▀ │
|
|
293
|
+
│ ┼▀▀ │
|
|
294
|
+
2.42┤ ▗▞▀▀│ │
|
|
295
|
+
│ ▗▞▀▀▘ │
|
|
296
|
+
│ ▗▄┼▄▀▘ │
|
|
297
|
+
2.04┤ ▗▄▀▘ │ │
|
|
298
|
+
│ ▗▄▞▀▘ │
|
|
299
|
+
│ │ ▄▄▀▀▘ │
|
|
300
|
+
1.67┤ ▗▄▄┼▀▀ │
|
|
301
|
+
│ ▗▄▄▞▀▀▘ │
|
|
302
|
+
1.30┤ ▄▄▄▄▀▀▀▘ │
|
|
303
|
+
│ ▄▄▞▀▀ │
|
|
304
|
+
│┼ ▗▄▄▞▀▀▀▀▀ │
|
|
305
|
+
0.93┤│▀▀▘ │
|
|
306
|
+
└┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘
|
|
307
|
+
1.0 1.8 3.2 5.6 10.0
|
|
308
|
+
y x
|
|
309
|
+
|
|
310
|
+
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x110a30550>
|
|
311
|
+
|
|
312
|
+
### Layers
|
|
313
|
+
|
|
314
|
+
<div id="fig-showcase-2">
|
|
315
|
+
|
|
316
|
+
``` python
|
|
317
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
318
|
+
|
|
319
|
+
canvas, ax = Canvas.subplots(width="10cm", ratio=0.55)
|
|
320
|
+
|
|
321
|
+
ax.plot(x, np.sin(x), color="steelblue", label=r"$\sin(x)$", layer=0)
|
|
322
|
+
ax.plot(x, np.cos(x), color="tomato", label=r"$\cos(x)$", layer=1)
|
|
323
|
+
ax.plot(
|
|
324
|
+
x,
|
|
325
|
+
np.sin(x) * np.cos(x),
|
|
326
|
+
color="seagreen",
|
|
327
|
+
label=r"$\sin(x)\cos(x)$",
|
|
328
|
+
linestyle="dashed",
|
|
329
|
+
layer=2,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
ax.set_xlabel("x")
|
|
333
|
+
ax.set_legend(True)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Figure 3
|
|
337
|
+
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
Show layer 0 only, then layers 0 and 1, then everything:
|
|
341
|
+
|
|
342
|
+
``` python
|
|
343
|
+
canvas.show(layers=[0])
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+

|
|
347
|
+
|
|
348
|
+
(<Figure size 590.551x324.803 with 1 Axes>,
|
|
349
|
+
array([[<Axes: xlabel='x'>]], dtype=object))
|
|
350
|
+
|
|
351
|
+
Show all layers:
|
|
352
|
+
|
|
353
|
+
``` python
|
|
354
|
+
canvas.show()
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+

|
|
358
|
+
|
|
359
|
+
(<Figure size 590.551x324.803 with 1 Axes>,
|
|
360
|
+
array([[<Axes: xlabel='x'>]], dtype=object))
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Maxlotlib
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Maxplotlib
|
|
5
|
+
|
|
6
|
+
A clean, expressive wrapper around **Matplotlib**, **Plotly**,
|
|
7
|
+
**plotext**, and **tikzfigure** for producing publication-quality
|
|
8
|
+
figures with minimal boilerplate. Swap backends without rewriting your
|
|
9
|
+
data — render the same canvas as a crisp PNG, an interactive Plotly
|
|
10
|
+
chart, a terminal-native plotext figure, or camera-ready **TikZ** code
|
|
11
|
+
for LaTeX.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
``` bash
|
|
16
|
+
pip install maxplotlibx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Showcase
|
|
20
|
+
|
|
21
|
+
### Quickstart
|
|
22
|
+
|
|
23
|
+
<div id="fig-showcase-1">
|
|
24
|
+
|
|
25
|
+
``` python
|
|
26
|
+
import numpy as np
|
|
27
|
+
from maxplotlib import Canvas
|
|
28
|
+
|
|
29
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
30
|
+
y = np.sin(x)
|
|
31
|
+
|
|
32
|
+
canvas, ax = Canvas.subplots()
|
|
33
|
+
ax.plot(x, y)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Figure 1
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
Plot the figure with the default (matplotlib) backend:
|
|
41
|
+
|
|
42
|
+
``` python
|
|
43
|
+
canvas.show()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
For Matplotlib-specific customization, pass method calls declaratively.
|
|
49
|
+
Figure methods run once and axes methods run for every subplot,
|
|
50
|
+
providing access to any Matplotlib API without requiring a maxplotlib
|
|
51
|
+
wrapper:
|
|
52
|
+
|
|
53
|
+
``` python
|
|
54
|
+
canvas.plot(matplotlib_customizations={
|
|
55
|
+
"figure": {
|
|
56
|
+
"suptitle": "My figure",
|
|
57
|
+
},
|
|
58
|
+
"axes": {
|
|
59
|
+
"tick_params": {
|
|
60
|
+
"axis": "both",
|
|
61
|
+
"which": "major",
|
|
62
|
+
"length": 6,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For dynamic customization, the same option also accepts a function:
|
|
69
|
+
|
|
70
|
+
``` python
|
|
71
|
+
def customize(fig, axes):
|
|
72
|
+
fig.suptitle("My figure")
|
|
73
|
+
for ax in axes.flat:
|
|
74
|
+
ax.tick_params(axis="both", which="major", length=6)
|
|
75
|
+
|
|
76
|
+
canvas.plot(matplotlib_customizations=customize)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Axis Label and Tick Styling
|
|
80
|
+
|
|
81
|
+
Axis labels, titles, and tick appearance accept Matplotlib-style keyword
|
|
82
|
+
arguments:
|
|
83
|
+
|
|
84
|
+
``` python
|
|
85
|
+
canvas.set_xlabel("Time", fontsize=12, fontweight="bold", labelpad=10)
|
|
86
|
+
canvas.set_ylabel("Duration", color="darkblue")
|
|
87
|
+
canvas.set_title("Runtime", fontsize=14, color="navy")
|
|
88
|
+
canvas.tick_params(
|
|
89
|
+
axis="both",
|
|
90
|
+
which="major",
|
|
91
|
+
labelsize=10,
|
|
92
|
+
colors="darkgreen",
|
|
93
|
+
length=6,
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Common axis controls and figure-level layout settings are also
|
|
98
|
+
available:
|
|
99
|
+
|
|
100
|
+
``` python
|
|
101
|
+
canvas.set_facecolor("whitesmoke")
|
|
102
|
+
canvas.set_axisbelow(True)
|
|
103
|
+
canvas.margins(x=0.05, y=0.1)
|
|
104
|
+
canvas.minorticks_on()
|
|
105
|
+
canvas.invert_yaxis()
|
|
106
|
+
canvas.supxlabel("Shared x label")
|
|
107
|
+
canvas.supylabel("Shared y label")
|
|
108
|
+
canvas.subplots_adjust(left=0.15, bottom=0.15)
|
|
109
|
+
canvas.tight_layout()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Secondary Y-Axis
|
|
113
|
+
|
|
114
|
+
Use `Canvas.twinx()` to add a second y-axis that shares the primary
|
|
115
|
+
x-axis:
|
|
116
|
+
|
|
117
|
+
``` python
|
|
118
|
+
twin_canvas, primary = Canvas.subplots()
|
|
119
|
+
secondary = twin_canvas.twinx()
|
|
120
|
+
|
|
121
|
+
primary.plot(x, np.sin(x), color="tab:blue")
|
|
122
|
+
secondary.plot(x, 100 * np.cos(x), color="tab:red")
|
|
123
|
+
primary.set_ylabel("sin(x)", color="tab:blue")
|
|
124
|
+
secondary.set_ylabel("100 cos(x)", color="tab:red")
|
|
125
|
+
|
|
126
|
+
twin_canvas.show()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Secondary y-axes are currently supported by the Matplotlib and Plotly
|
|
130
|
+
backends.
|
|
131
|
+
|
|
132
|
+
### Plotly field plots and tables
|
|
133
|
+
|
|
134
|
+
Several Matplotlib field and annotation APIs map directly to interactive
|
|
135
|
+
Plotly traces, including pseudocolor plots, sparsity patterns,
|
|
136
|
+
triangular grids, and tables:
|
|
137
|
+
|
|
138
|
+
``` python
|
|
139
|
+
plotly_canvas, plotly_ax = Canvas.subplots()
|
|
140
|
+
plotly_ax.pcolor(x, x, np.outer(np.sin(x), np.cos(x)))
|
|
141
|
+
plotly_ax.spy([[1, 0, 1], [0, 1, 0], [1, 0, 1]])
|
|
142
|
+
plotly_ax.table(cellText=[["A", "B"], ["1", "2"]])
|
|
143
|
+
plotly_canvas.show(backend="plotly")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Plotly raises `NotImplementedError` for primitives without a faithful
|
|
147
|
+
equivalent instead of silently dropping them. To render the supported
|
|
148
|
+
parts of a mixed canvas, explicitly opt into skipping unsupported
|
|
149
|
+
primitives:
|
|
150
|
+
|
|
151
|
+
``` python
|
|
152
|
+
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Render the same line graph directly in the terminal with the `plotext`
|
|
156
|
+
backend:
|
|
157
|
+
|
|
158
|
+
``` python
|
|
159
|
+
terminal_fig = canvas.plot(backend="plotext")
|
|
160
|
+
print(terminal_fig.build(keep_colors=False))
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Runtime
|
|
164
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
165
|
+
1.00┤ ▗▄▞▀▀▀▀▀▙▄▖ │
|
|
166
|
+
│ ▗▄▀▘ ▝▀▄ │
|
|
167
|
+
│ ▗▞▘ ▀▄ │
|
|
168
|
+
0.67┤ ▟▀ ▀▄ │
|
|
169
|
+
│ ▄▛ ▚▖ │
|
|
170
|
+
0.33┤ ▗▞ ▝▄ │
|
|
171
|
+
│ ▄▀ ▚▖ │
|
|
172
|
+
│▗▞▘ ▀▄ │
|
|
173
|
+
0.00┤▀ ▝▚▖ ▞│
|
|
174
|
+
│ ▀▄ ▗▞▘│
|
|
175
|
+
│ ▝▚ ▄▀ │
|
|
176
|
+
-0.33┤ ▀▖ ▞▘ │
|
|
177
|
+
│ ▝▚ ▟▀ │
|
|
178
|
+
-0.67┤ ▀▄ ▄▛ │
|
|
179
|
+
│ ▀▄ ▗▞▘ │
|
|
180
|
+
│ ▀▄▖ ▗▄▀▘ │
|
|
181
|
+
-1.00┤ ▝▀▜▄▄▄▄▄▞▀▘ │
|
|
182
|
+
└┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
|
|
183
|
+
0.0 1.6 3.1 4.7 6.3
|
|
184
|
+
Duration Time
|
|
185
|
+
|
|
186
|
+
Or plot with the TikZ backend:
|
|
187
|
+
|
|
188
|
+
``` python
|
|
189
|
+
canvas.show(backend="tikzfigure")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+

|
|
193
|
+
|
|
194
|
+
### Horizontal Subplots with TikZ Backend
|
|
195
|
+
|
|
196
|
+
The tikzfigure backend supports creating side-by-side subplots (1×n
|
|
197
|
+
layouts):
|
|
198
|
+
|
|
199
|
+
``` python
|
|
200
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
201
|
+
canvas, (ax1, ax2) = Canvas.subplots(ncols=2, width="10cm", ratio=0.3)
|
|
202
|
+
|
|
203
|
+
ax1.plot(x, np.sin(x), color="royalblue")
|
|
204
|
+
ax1.set_title("sin(x)")
|
|
205
|
+
|
|
206
|
+
ax2.plot(x, np.cos(x), color="tomato")
|
|
207
|
+
ax2.set_title("cos(x)")
|
|
208
|
+
|
|
209
|
+
canvas.suptitle("Trigonometric Functions")
|
|
210
|
+
canvas.show(backend="tikzfigure") # Generates LaTeX subfigures
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
<div id="fig-showcase-subplots">
|
|
214
|
+
|
|
215
|
+
<img
|
|
216
|
+
src="README_files/figure-commonmark/fig-showcase-subplots-output-1.png"
|
|
217
|
+
id="fig-showcase-subplots" />
|
|
218
|
+
|
|
219
|
+
Figure 2
|
|
220
|
+
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
**Note:** Only horizontal layouts (1×n) are currently supported with the
|
|
224
|
+
tikzfigure backend. Vertical/grid layouts will raise
|
|
225
|
+
`NotImplementedError`. See the tutorials for more examples.
|
|
226
|
+
|
|
227
|
+
### Terminal Backend with plotext
|
|
228
|
+
|
|
229
|
+
The `plotext` backend is designed for terminal-first workflows. It
|
|
230
|
+
currently supports line plots, scatter plots, bars, filled regions,
|
|
231
|
+
error bars, reference lines, text/annotations, labels/titles, log axes,
|
|
232
|
+
layers, matrix-style `imshow()` rendering, common patches, and
|
|
233
|
+
multi-subplot canvases.
|
|
234
|
+
|
|
235
|
+
``` python
|
|
236
|
+
x = np.linspace(1, 10, 40)
|
|
237
|
+
|
|
238
|
+
canvas, ax = Canvas.subplots()
|
|
239
|
+
ax.plot(x, np.sqrt(x), color="cyan", label="sqrt(x)")
|
|
240
|
+
ax.errorbar(x[::8], np.sqrt(x[::8]), yerr=0.15, color="yellow", label="samples")
|
|
241
|
+
ax.set_title("Terminal plot")
|
|
242
|
+
ax.set_xlabel("x")
|
|
243
|
+
ax.set_ylabel("y")
|
|
244
|
+
ax.set_xscale("log")
|
|
245
|
+
ax.set_legend(True)
|
|
246
|
+
|
|
247
|
+
canvas.show(backend="plotext")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Terminal plot
|
|
251
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
252
|
+
3.16┤ ▞▞ sqrt(x) ▄▞│
|
|
253
|
+
│ │▗▄▞▀ │
|
|
254
|
+
│ ▄┼▘ │
|
|
255
|
+
2.79┤ ▄▀▀ │
|
|
256
|
+
│ ┼▀▀ │
|
|
257
|
+
2.42┤ ▗▞▀▀│ │
|
|
258
|
+
│ ▗▞▀▀▘ │
|
|
259
|
+
│ ▗▄┼▄▀▘ │
|
|
260
|
+
2.04┤ ▗▄▀▘ │ │
|
|
261
|
+
│ ▗▄▞▀▘ │
|
|
262
|
+
│ │ ▄▄▀▀▘ │
|
|
263
|
+
1.67┤ ▗▄▄┼▀▀ │
|
|
264
|
+
│ ▗▄▄▞▀▀▘ │
|
|
265
|
+
1.30┤ ▄▄▄▄▀▀▀▘ │
|
|
266
|
+
│ ▄▄▞▀▀ │
|
|
267
|
+
│┼ ▗▄▄▞▀▀▀▀▀ │
|
|
268
|
+
0.93┤│▀▀▘ │
|
|
269
|
+
└┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘
|
|
270
|
+
1.0 1.8 3.2 5.6 10.0
|
|
271
|
+
y x
|
|
272
|
+
|
|
273
|
+
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x110a30550>
|
|
274
|
+
|
|
275
|
+
### Layers
|
|
276
|
+
|
|
277
|
+
<div id="fig-showcase-2">
|
|
278
|
+
|
|
279
|
+
``` python
|
|
280
|
+
x = np.linspace(0, 2 * np.pi, 200)
|
|
281
|
+
|
|
282
|
+
canvas, ax = Canvas.subplots(width="10cm", ratio=0.55)
|
|
283
|
+
|
|
284
|
+
ax.plot(x, np.sin(x), color="steelblue", label=r"$\sin(x)$", layer=0)
|
|
285
|
+
ax.plot(x, np.cos(x), color="tomato", label=r"$\cos(x)$", layer=1)
|
|
286
|
+
ax.plot(
|
|
287
|
+
x,
|
|
288
|
+
np.sin(x) * np.cos(x),
|
|
289
|
+
color="seagreen",
|
|
290
|
+
label=r"$\sin(x)\cos(x)$",
|
|
291
|
+
linestyle="dashed",
|
|
292
|
+
layer=2,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
ax.set_xlabel("x")
|
|
296
|
+
ax.set_legend(True)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Figure 3
|
|
300
|
+
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
Show layer 0 only, then layers 0 and 1, then everything:
|
|
304
|
+
|
|
305
|
+
``` python
|
|
306
|
+
canvas.show(layers=[0])
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+

|
|
310
|
+
|
|
311
|
+
(<Figure size 590.551x324.803 with 1 Axes>,
|
|
312
|
+
array([[<Axes: xlabel='x'>]], dtype=object))
|
|
313
|
+
|
|
314
|
+
Show all layers:
|
|
315
|
+
|
|
316
|
+
``` python
|
|
317
|
+
canvas.show()
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+

|
|
321
|
+
|
|
322
|
+
(<Figure size 590.551x324.803 with 1 Axes>,
|
|
323
|
+
array([[<Axes: xlabel='x'>]], dtype=object))
|