|
1 | 1 | # maxplotlib |
2 | 2 |
|
3 | | -This is a wrapper for matplotlib so I can produce figures with consistent formatting. It also has some pretty nice additions such as using layers and exporting to tikz. |
| 3 | + |
| 4 | +# maxplotlib |
| 5 | + |
| 6 | +A clean, expressive wrapper around **Matplotlib** for producing |
| 7 | +publication-quality figures with minimal boilerplate. Swap backends |
| 8 | +without rewriting your data — render the same canvas as a crisp PNG, an |
| 9 | +interactive Plotly chart, or camera-ready **TikZ** code for LaTeX. |
4 | 10 |
|
5 | 11 | ## Install |
6 | 12 |
|
7 | | -Create and activate python environment |
| 13 | +``` bash |
| 14 | +python -m venv env && source env/bin/activate |
| 15 | +pip install maxplotlibx |
| 16 | +``` |
| 17 | + |
| 18 | +For development extras (tests, docs, linting): |
8 | 19 |
|
| 20 | +``` bash |
| 21 | +pip install "maxplotlibx[dev]" |
9 | 22 | ``` |
10 | | -python -m venv env |
11 | | -source env/bin/activate |
12 | | -pip install --upgrade pip |
| 23 | + |
| 24 | +## Showcase |
| 25 | + |
| 26 | +``` python |
| 27 | +import numpy as np |
| 28 | +from maxplotlib import Canvas |
| 29 | + |
| 30 | +rng = np.random.default_rng(0) |
| 31 | +x = np.linspace(0, 2 * np.pi, 300) |
| 32 | + |
| 33 | +canvas, axes = Canvas.subplots(nrows=2, ncols=2, width="18cm", ratio=0.65) |
| 34 | + |
| 35 | +# ── top-left: multi-line with legend ────────────────────────────────────────── |
| 36 | +ax = axes[0][0] |
| 37 | +ax.plot(x, np.sin(x), color="royalblue", linewidth=2, label=r"$\sin(x)$") |
| 38 | +ax.plot(x, np.cos(x), color="tomato", linewidth=2, |
| 39 | + linestyle="dashed", label=r"$\cos(x)$") |
| 40 | +ax.plot(x, np.sin(2 * x) * 0.5, color="seagreen", linewidth=1.5, |
| 41 | + linestyle="dotted", label=r"$\frac{1}{2}\sin(2x)$") |
| 42 | +ax.set_xlabel("x") |
| 43 | +ax.set_ylabel("amplitude") |
| 44 | +ax.set_title("Line plots") |
| 45 | +ax.set_legend(True) |
| 46 | +ax.set_grid(True) |
| 47 | + |
| 48 | +# ── top-right: scatter coloured by distance ──────────────────────────────────── |
| 49 | +ax = axes[0][1] |
| 50 | +n = 250 |
| 51 | +sx = rng.standard_normal(n) |
| 52 | +sy = rng.standard_normal(n) |
| 53 | +ax.scatter(sx, sy, c=np.hypot(sx, sy), s=25, alpha=0.8) |
| 54 | +ax.set_xlabel("x") |
| 55 | +ax.set_ylabel("y") |
| 56 | +ax.set_title("Scatter — coloured by distance") |
| 57 | +ax.set_aspect("equal") |
| 58 | + |
| 59 | +# ── bottom-left: bar chart ──────────────────────────────────────────────────── |
| 60 | +ax = axes[1][0] |
| 61 | +months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| 62 | + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
| 63 | +temps = [3, 4, 8, 13, 18, 22, 24, 23, 18, 12, 7, 4] |
| 64 | +month_idx = np.arange(len(months)) |
| 65 | +ax.bar(month_idx, temps, color="steelblue", width=0.7, label="avg °C") |
| 66 | +ax.set_xticks(month_idx, labels=months) |
| 67 | +ax.set_xlabel("month") |
| 68 | +ax.set_ylabel("temperature (°C)") |
| 69 | +ax.set_title("Bar chart") |
| 70 | + |
| 71 | +# ── bottom-right: fill_between confidence band ──────────────────────────────── |
| 72 | +ax = axes[1][1] |
| 73 | +mean = np.sin(x) * np.exp(-x / (2 * np.pi)) |
| 74 | +std = 0.15 + 0.1 * np.abs(np.cos(x)) |
| 75 | +ax.plot(x, mean, color="darkorchid", linewidth=2, label="mean") |
| 76 | +ax.fill_between(x, mean - std, mean + std, alpha=0.25, color="darkorchid", |
| 77 | + label="±1 σ") |
| 78 | +ax.set_xlabel("x") |
| 79 | +ax.set_ylabel("y") |
| 80 | +ax.set_title("Confidence band") |
| 81 | +ax.set_legend(True) |
| 82 | +ax.set_grid(True) |
| 83 | + |
| 84 | +canvas.suptitle("maxplotlib — four plot types, one canvas") |
| 85 | +fig, _ = canvas.plot() |
| 86 | +fig |
13 | 87 | ``` |
14 | 88 |
|
15 | | -Install the code and requirements with pip |
| 89 | +<div id="fig-showcase"> |
| 90 | + |
| 91 | +<div class="cell-output cell-output-display" execution_count="2"> |
| 92 | + |
| 93 | +<div id="fig-showcase-1"> |
| 94 | + |
| 95 | +<img src="README_files/figure-commonmark/fig-showcase-output-1.png" |
| 96 | +data-ref-parent="fig-showcase" /> |
| 97 | + |
| 98 | +(a) maxplotlib showcase — four plot types, one canvas. |
| 99 | + |
| 100 | +</div> |
| 101 | + |
| 102 | +</div> |
| 103 | + |
| 104 | +<div class="cell-output cell-output-display"> |
| 105 | + |
| 106 | +<div id="fig-showcase-2"> |
| 107 | + |
| 108 | +<img src="README_files/figure-commonmark/fig-showcase-output-2.png" |
| 109 | +id="fig-showcase-2" data-ref-parent="fig-showcase" /> |
| 110 | + |
| 111 | +(b) |
| 112 | + |
| 113 | +</div> |
| 114 | + |
| 115 | +</div> |
| 116 | + |
| 117 | +Figure 1 |
| 118 | + |
| 119 | +</div> |
| 120 | + |
| 121 | +## Quick start |
16 | 122 |
|
| 123 | +### One-liner with `Canvas.subplots()` |
| 124 | + |
| 125 | +`Canvas.subplots()` mirrors `plt.subplots()` but returns a `Canvas` and |
| 126 | +subplot axes that speak the maxplotlib API. |
| 127 | + |
| 128 | +``` python |
| 129 | +import numpy as np |
| 130 | +from maxplotlib import Canvas |
| 131 | + |
| 132 | +x = np.linspace(0, 2 * np.pi, 200) |
| 133 | + |
| 134 | +canvas, ax = Canvas.subplots(width="10cm", ratio=0.55) |
| 135 | +ax.plot(x, np.sin(x), color="royalblue", label=r"$\sin(x)$", linewidth=2) |
| 136 | +ax.plot(x, np.cos(x), color="tomato", label=r"$\cos(x)$", linewidth=2, |
| 137 | + linestyle="dashed") |
| 138 | +ax.set_xlabel("x") |
| 139 | +ax.set_ylabel("y") |
| 140 | +ax.set_legend(True) |
| 141 | +ax.set_grid(True) |
| 142 | +canvas.show() |
| 143 | +``` |
| 144 | + |
| 145 | +### Canvas-level shortcut API |
| 146 | + |
| 147 | +For single-subplot figures every plot method is available directly on |
| 148 | +the `Canvas` object — no need to grab an axes handle: |
| 149 | + |
| 150 | +``` python |
| 151 | +canvas = Canvas(ratio=0.5, fontsize=12) |
| 152 | +canvas.add_line(x, np.sin(x), label="sin", color="steelblue") |
| 153 | +canvas.add_line(x, np.cos(x), label="cos", color="darkorange", linestyle="dashed") |
| 154 | +canvas.set_xlabel("angle (rad)") |
| 155 | +canvas.set_ylabel("amplitude") |
| 156 | +canvas.set_legend(True) |
| 157 | +canvas.show() |
| 158 | +``` |
| 159 | + |
| 160 | +## Key features |
| 161 | + |
| 162 | +### Multiple backends — same API |
| 163 | + |
| 164 | +| Backend | How to use | Output | |
| 165 | +|----|----|----| |
| 166 | +| `matplotlib` (default) | `canvas.show()` | inline PNG / static file | |
| 167 | +| `plotly` | `fig = canvas.plot(backend='plotly')` | interactive HTML widget | |
| 168 | +| `tikzfigure` | `tz = canvas.plot(backend='tikzfigure')` | LaTeX `\draw` commands | |
| 169 | + |
| 170 | +``` python |
| 171 | +# Render as interactive Plotly figure |
| 172 | +fig = canvas.plot(backend="plotly") |
| 173 | +fig.show() |
| 174 | + |
| 175 | +# Export TikZ code for a LaTeX document |
| 176 | +tikz = canvas.plot(backend="tikzfigure") |
| 177 | +print(tikz.generate_tikz()) |
17 | 178 | ``` |
18 | | -pip install -e . |
| 179 | + |
| 180 | +### Layers |
| 181 | + |
| 182 | +Tag each plot call with a `layer=` integer. Then render any subset — |
| 183 | +handy for step-by-step derivations, lecture slides, or overlays. |
| 184 | + |
| 185 | +``` python |
| 186 | +canvas, ax = Canvas.subplots() |
| 187 | +ax.plot(x, np.sin(x), layer=0, label=r"$\sin(x)$", color="steelblue") |
| 188 | +ax.plot(x, np.cos(x), layer=1, label=r"$\cos(x)$", color="tomato") |
| 189 | +ax.plot(x, np.sin(x) * np.cos(x), layer=2, |
| 190 | + label=r"$\sin(x)\cos(x)$", color="seagreen", linestyle="dashed") |
| 191 | + |
| 192 | +canvas.show(layers=[0]) # only sin |
| 193 | +canvas.show(layers=[0, 1]) # sin + cos |
| 194 | +canvas.show() # everything |
19 | 195 | ``` |
20 | 196 |
|
21 | | -Additional dependencies for developers can be installed with |
| 197 | +### Size and typography |
22 | 198 |
|
| 199 | +Pass physical dimensions and a font size; maxplotlib converts to the |
| 200 | +correct `figsize` automatically. |
| 201 | + |
| 202 | +``` python |
| 203 | +canvas = Canvas(width="17cm", ratio=0.5, fontsize=11) |
23 | 204 | ``` |
24 | | -pip install -e ".[dev]" |
| 205 | + |
| 206 | +`ratio` accepts a float **or** the string `"golden"` (default) for the |
| 207 | +golden ratio. |
| 208 | + |
| 209 | +### Saving figures |
| 210 | + |
| 211 | +``` python |
| 212 | +canvas.savefig("figure.pdf") # vector PDF |
| 213 | +canvas.savefig("figure.png") # raster PNG |
| 214 | +canvas.savefig("figure.svg") # SVG |
25 | 215 | ``` |
26 | 216 |
|
27 | | -Some examples can be found in `tutorials/` |
| 217 | +## Plot types |
| 218 | + |
| 219 | +| Method | Description | |
| 220 | +|-----------------------------------|--------------------------------| |
| 221 | +| `ax.plot(x, y)` | Line plot | |
| 222 | +| `ax.scatter(x, y)` | Scatter plot | |
| 223 | +| `ax.bar(categories, values)` | Bar chart | |
| 224 | +| `ax.fill_between(x, y1, y2)` | Shaded band | |
| 225 | +| `ax.errorbar(x, y, yerr)` | Error bars | |
| 226 | +| `ax.axhline(y)` / `ax.axvline(x)` | Reference lines | |
| 227 | +| `ax.annotate(text, xy)` | Annotation with optional arrow | |
| 228 | + |
| 229 | +## Tutorials |
| 230 | + |
| 231 | +Step-by-step Jupyter notebooks in `tutorials/`: |
| 232 | + |
| 233 | +| Notebook | Topic | |
| 234 | +|----------------------|----------------------------------| |
| 235 | +| `tutorial_01` | Quick start | |
| 236 | +| `tutorial_02` | Multiple subplots | |
| 237 | +| `tutorial_03` | All plot types | |
| 238 | +| `tutorial_04` | Colours, linestyles, markers | |
| 239 | +| `tutorial_05` | Axes, ticks, scales, annotations | |
| 240 | +| `tutorial_06` | Layers | |
| 241 | +| `tutorial_07_tikz` | TikZ backend | |
| 242 | +| `tutorial_08_plotly` | Plotly backend | |
| 243 | + |
| 244 | +## License |
| 245 | + |
| 246 | +MIT — see [`LICENSE`](LICENSE). |
0 commit comments