plotform 0.1.0__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.
- plotform-0.1.0/CHANGELOG.md +24 -0
- plotform-0.1.0/LICENSE +24 -0
- plotform-0.1.0/MANIFEST.in +9 -0
- plotform-0.1.0/PKG-INFO +267 -0
- plotform-0.1.0/README.md +232 -0
- plotform-0.1.0/docs/RELEASING.md +125 -0
- plotform-0.1.0/docs/images/capsules.png +0 -0
- plotform-0.1.0/docs/images/contact-sheet.png +0 -0
- plotform-0.1.0/docs/images/overview.png +0 -0
- plotform-0.1.0/examples/geometry_check.py +70 -0
- plotform-0.1.0/examples/native_matplotlib.py +31 -0
- plotform-0.1.0/examples/showcase.py +698 -0
- plotform-0.1.0/pyproject.toml +68 -0
- plotform-0.1.0/setup.cfg +4 -0
- plotform-0.1.0/src/plotform/__init__.py +53 -0
- plotform-0.1.0/src/plotform/api.py +47 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformMono-OFL.txt +93 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformMono-Regular.ttf +0 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformMono-TRADEMARKS.md +17 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformSans-OFL.txt +93 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformSans-Rounded.ttf +0 -0
- plotform-0.1.0/src/plotform/assets/fonts/PlotformSans-TRADEMARKS.md +17 -0
- plotform-0.1.0/src/plotform/assets/fonts/README.md +30 -0
- plotform-0.1.0/src/plotform/colorbar_utils.py +75 -0
- plotform-0.1.0/src/plotform/fonts.py +56 -0
- plotform-0.1.0/src/plotform/legend_utils.py +199 -0
- plotform-0.1.0/src/plotform/palette.py +103 -0
- plotform-0.1.0/src/plotform/rounded.py +519 -0
- plotform-0.1.0/src/plotform/theme.py +168 -0
- plotform-0.1.0/src/plotform/three_d.py +40 -0
- plotform-0.1.0/src/plotform.egg-info/PKG-INFO +267 -0
- plotform-0.1.0/src/plotform.egg-info/SOURCES.txt +46 -0
- plotform-0.1.0/src/plotform.egg-info/dependency_links.txt +1 -0
- plotform-0.1.0/src/plotform.egg-info/requires.txt +12 -0
- plotform-0.1.0/src/plotform.egg-info/top_level.txt +1 -0
- plotform-0.1.0/tests/test_api.py +44 -0
- plotform-0.1.0/tests/test_capsule_reference.py +57 -0
- plotform-0.1.0/tests/test_colorbar.py +56 -0
- plotform-0.1.0/tests/test_fonts.py +24 -0
- plotform-0.1.0/tests/test_legend.py +147 -0
- plotform-0.1.0/tests/test_palette.py +43 -0
- plotform-0.1.0/tests/test_public_api.py +44 -0
- plotform-0.1.0/tests/test_rounded.py +179 -0
- plotform-0.1.0/tests/test_theme.py +79 -0
- plotform-0.1.0/tools/check_sdist.py +46 -0
- plotform-0.1.0/tools/check_version.py +29 -0
- plotform-0.1.0/tools/make_doc_images.py +84 -0
- plotform-0.1.0/tools/prepare_fonts.py +98 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Plotform are documented here. The project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/) while it remains pre-1.0.
|
|
5
|
+
|
|
6
|
+
## 0.1.0 — Initial public release
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Minimalist global and scoped Matplotlib themes.
|
|
11
|
+
- Vivid categorical palettes and three continuous colormaps.
|
|
12
|
+
- Native-style `legend`, `bar`, `barh`, `colorbar`, and `annotate` helpers.
|
|
13
|
+
- Exact capsule annotations, circular bar caps, and separate squircle geometry.
|
|
14
|
+
- Solid and soft pill legends with adaptive text contrast.
|
|
15
|
+
- Vertical capsule colorbars with external ticks.
|
|
16
|
+
- Minimal 3D styling and preserved face shading.
|
|
17
|
+
- Bundled Plotform Sans and Plotform Mono fonts with licenses and attribution.
|
|
18
|
+
- A 27-chart comparison gallery, geometry reference board, and native Matplotlib example.
|
|
19
|
+
- CI, package build checks, and trusted-publishing release workflows.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- The optional compatibility layer. Use Plotform when themed output is wanted,
|
|
24
|
+
or ordinary Matplotlib when a script must run without the package.
|
plotform-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Econ01
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Bundled font software is licensed separately under the SIL Open Font License.
|
|
24
|
+
See src/plotform/assets/fonts/ for the font licenses and attributions.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
recursive-include docs *.md
|
|
6
|
+
recursive-include docs/images *.png
|
|
7
|
+
recursive-include examples *.py
|
|
8
|
+
recursive-include tools *.py
|
|
9
|
+
recursive-include src/plotform/assets/fonts *.ttf *.txt *.md
|
plotform-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plotform
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A minimalist Matplotlib theme and plotting toolkit with customizable palettes, capsule-shaped chart elements, and bundled fonts.
|
|
5
|
+
Author: Econ01
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Econ01/plotform
|
|
8
|
+
Project-URL: Repository, https://github.com/Econ01/plotform
|
|
9
|
+
Project-URL: Issues, https://github.com/Econ01/plotform/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Econ01/plotform/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: matplotlib,plotting,visualization,theme,capsule,publication
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: matplotlib>=3.8
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: fonttools>=4.50; extra == "dev"
|
|
30
|
+
Requires-Dist: pillow>=9; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
33
|
+
Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# Plotform
|
|
37
|
+
|
|
38
|
+
[](https://github.com/Econ01/plotform/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/plotform/)
|
|
40
|
+
[](https://pypi.org/project/plotform/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
43
|
+
**A minimalist Matplotlib theme and plotting toolkit with customizable palettes, capsule-shaped chart elements, and bundled fonts.**
|
|
44
|
+
|
|
45
|
+
Plotform combines a vivid, minimalist visual style with native-style helpers for pill legends, circularly rounded bars, capsule annotations, vertical capsule colorbars, and clean 3D plots. Fonts are bundled, so system font installation is not required.
|
|
46
|
+
|
|
47
|
+
[](https://github.com/Econ01/plotform/tree/main/examples)
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Python 3.10 or newer
|
|
52
|
+
- Matplotlib 3.8 or newer
|
|
53
|
+
- NumPy 1.24 or newer
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python -m pip install plotform
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For development from a source checkout:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python -m pip install -e ".[dev]"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import matplotlib.pyplot as plt
|
|
71
|
+
import plotform as tm
|
|
72
|
+
|
|
73
|
+
tm.apply_theme()
|
|
74
|
+
|
|
75
|
+
fig, ax = plt.subplots()
|
|
76
|
+
ax.plot([1, 2, 3], [2, 4, 3], label="Example")
|
|
77
|
+
ax.set(title="A minimalist chart", xlabel="Time", ylabel="Signal")
|
|
78
|
+
tm.legend(ax)
|
|
79
|
+
|
|
80
|
+
fig.savefig("chart.png", dpi=800)
|
|
81
|
+
fig.savefig("chart.pdf")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`apply_theme()` resets Matplotlib's style before applying Plotform and changes process-global settings. Call it once before creating figures. To preserve and later restore another style, use the scoped form:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
with tm.theme():
|
|
88
|
+
fig, ax = plt.subplots()
|
|
89
|
+
ax.plot([1, 2, 3], [2, 4, 3], label="Example")
|
|
90
|
+
tm.legend(ax)
|
|
91
|
+
fig.savefig("chart.png")
|
|
92
|
+
# Previous rcParams are restored here.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Plain Matplotlib, without Plotform
|
|
96
|
+
|
|
97
|
+
Scripts that must run without the package do not need a compatibility layer. Use native Matplotlib directly:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import matplotlib.pyplot as plt
|
|
101
|
+
|
|
102
|
+
fig, ax = plt.subplots()
|
|
103
|
+
ax.bar([0, 1, 2], [3, 5, 4], label="Example")
|
|
104
|
+
ax.legend()
|
|
105
|
+
fig.savefig("plot.png")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`examples/native_matplotlib.py` is a complete instructor-friendly example. Plotform is an opt-in enhancement, not a requirement for these scripts.
|
|
109
|
+
|
|
110
|
+
## Native-style helpers
|
|
111
|
+
|
|
112
|
+
| Helper | Purpose |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `tm.legend(ax)` | Solid or soft pill legends with adaptive text contrast |
|
|
115
|
+
| `tm.bar(x, height, ax=ax)` | Vertical bars with circular caps |
|
|
116
|
+
| `tm.barh(y, width, ax=ax)` | Horizontal bars with circular caps |
|
|
117
|
+
| `tm.colorbar(image, ax=ax)` | Vertical capsule colorbar with external ticks |
|
|
118
|
+
| `tm.annotate(text, xy, ax=ax)` | Exact capsule annotation |
|
|
119
|
+
| `tm.style_3d(ax)` | Minimal 3D panes, grids, ticks, and viewing angle |
|
|
120
|
+
|
|
121
|
+
`legend()` uses the current axes when `ax` is omitted. `colorbar()` uses the current figure and axes. Advanced geometry remains available through `plotform.rounded`; font utilities remain available through `plotform.fonts`.
|
|
122
|
+
|
|
123
|
+
## Fonts
|
|
124
|
+
|
|
125
|
+
- **Plotform Sans**, derived from Google Sans Flex with `ROND=100` and default weight 400.
|
|
126
|
+
- **Plotform Mono**, derived from Google Sans Code at its default weight.
|
|
127
|
+
- DejaVu fallbacks for mathematical notation and missing glyphs.
|
|
128
|
+
|
|
129
|
+
The package registers its bundled fonts directly with Matplotlib. No operating-system installation or cache clearing is needed. Font licenses and attribution are included under `src/plotform/assets/fonts/`.
|
|
130
|
+
|
|
131
|
+
## Colors
|
|
132
|
+
|
|
133
|
+
The default vivid categorical cycle is:
|
|
134
|
+
|
|
135
|
+
| Order | Color | Hex |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| 1 | Teal | `#008080` |
|
|
138
|
+
| 2 | Amber | `#E69F00` |
|
|
139
|
+
| 3 | Blue | `#0072B2` |
|
|
140
|
+
| 4 | Coral/vermillion | `#D55E00` |
|
|
141
|
+
| 5 | Purple | `#6A51A3` |
|
|
142
|
+
| 6 | Green | `#4C9A2A` |
|
|
143
|
+
| 7 | Rose | `#CC79A7` |
|
|
144
|
+
| 8 | Dark slate | `#4B5563` |
|
|
145
|
+
|
|
146
|
+
Extend the cycle deterministically:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
tm.apply_theme(n_colors=14)
|
|
150
|
+
colors = tm.get_palette(14)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Axes, labels, and ticks are black by default while grids remain light. For muted-gray decorations:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
tm.apply_theme(gray_decorations=True)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Three continuous colormaps are registered:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
ax.imshow(values, cmap=tm.SCALAR_CMAP) # Full-range scalar default
|
|
163
|
+
ax.imshow(values, cmap=tm.DIVERGING_CMAP) # Signed/diverging data
|
|
164
|
+
ax.imshow(values, cmap=tm.SEQUENTIAL_CMAP) # Optional pale brand-teal map
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`SCALAR_CMAP` follows navy → blue → teal → green → amber, avoiding near-white endpoints. Use fully opaque scalar markers and `depthshade=False` for mplot3d scatter plots. Use `shade=True` for `bar3d` to preserve face lighting.
|
|
168
|
+
|
|
169
|
+
## Pill legends
|
|
170
|
+
|
|
171
|
+
Solid category-color fills use adaptive white or black text, targeting at least 4.5:1 contrast. Marker and line handles are explicitly hidden so they cannot overlap labels.
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
tm.legend(ax, ncols=3)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
For auxiliary uncertainty regions, use an explicitly tinted entry:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
tm.legend(ax, styles={"95% interval": "soft"})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
For dense plots, place the legend outside or add a white group capsule:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
tm.legend(ax, outside=True)
|
|
187
|
+
tm.legend(ax, backdrop=True, backdrop_alpha=0.88)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Rounded bars and annotations
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
import numpy as np
|
|
194
|
+
|
|
195
|
+
x = np.arange(4)
|
|
196
|
+
|
|
197
|
+
# Exact semicircular cap with a square baseline.
|
|
198
|
+
tm.bar(x, [3, 5, -2, 4], radius="full", ends="free", ax=ax)
|
|
199
|
+
|
|
200
|
+
# Complete vertical capsule.
|
|
201
|
+
tm.bar(x, [3, 5, -2, 4], radius="full", ends="both", ax=ax)
|
|
202
|
+
|
|
203
|
+
# Clamp bars too short for an exact full capsule.
|
|
204
|
+
tm.bar(x, [3, 5, -2, 4], radius="fit", ends="both", ax=ax)
|
|
205
|
+
|
|
206
|
+
# Numeric circular corner radius.
|
|
207
|
+
tm.bar(x, [3, 5, -2, 4], radius=0.16, ax=ax)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Exact full mode raises a clear error when the display geometry cannot contain the requested semicircle. Use `radius="fit"` to opt into clamping instead.
|
|
211
|
+
|
|
212
|
+
Annotations use exact stadium geometry:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
tm.annotate("New", (0.15, 0.85), ax=ax, transform=ax.transAxes)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`plotform.rounded` also exposes `capsule_path`, `CapsuleBox`, and separate squircle primitives for advanced use.
|
|
219
|
+
|
|
220
|
+
[](https://github.com/Econ01/plotform/blob/main/examples/geometry_check.py)
|
|
221
|
+
|
|
222
|
+
## Capsule colorbars
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
image = ax.imshow(values, cmap=tm.SCALAR_CMAP)
|
|
226
|
+
colorbar = tm.colorbar(image, ax=ax)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The visible gradient is clipped to an exact vertical capsule in display space. Ticks and labels remain outside. Image, contour, streamplot, scatter, and surface mappables are supported. Standard rectangular `fig.colorbar()` output remains available.
|
|
230
|
+
|
|
231
|
+
## 3D plots
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
fig = plt.figure()
|
|
235
|
+
ax = fig.add_subplot(projection="3d")
|
|
236
|
+
tm.style_3d(ax)
|
|
237
|
+
ax.plot_surface(xx, yy, zz, cmap=tm.DIVERGING_CMAP, shade=True)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`style_3d()` clears panes and applies consistent grids, ticks, and viewing geometry. Pass `gray_decorations=True` for muted decorations.
|
|
241
|
+
|
|
242
|
+
## Visual showcase
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
python examples/showcase.py
|
|
246
|
+
python examples/geometry_check.py
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The showcase creates 27 default-versus-Plotform comparisons at 800 DPI, lightweight previews, selected PDF/SVG exports, and a contact sheet under `artifacts/showcase/`. The geometry script creates exact capsule reference boards under `artifacts/`.
|
|
250
|
+
|
|
251
|
+
[View the complete 27-chart contact sheet](https://github.com/Econ01/plotform/blob/main/docs/images/contact-sheet.png) or regenerate the documentation images with `python tools/make_doc_images.py`.
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
python -m pip install -e ".[dev]"
|
|
257
|
+
pytest
|
|
258
|
+
ruff check .
|
|
259
|
+
ruff format --check .
|
|
260
|
+
python -m build
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
See `CHANGELOG.md` for release history and `docs/RELEASING.md` for the GitHub, TestPyPI, and PyPI release process.
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
Code is MIT licensed. Bundled font software remains under the SIL Open Font License; see the font asset notices for details.
|
plotform-0.1.0/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Plotform
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Econ01/plotform/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/plotform/)
|
|
5
|
+
[](https://pypi.org/project/plotform/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**A minimalist Matplotlib theme and plotting toolkit with customizable palettes, capsule-shaped chart elements, and bundled fonts.**
|
|
9
|
+
|
|
10
|
+
Plotform combines a vivid, minimalist visual style with native-style helpers for pill legends, circularly rounded bars, capsule annotations, vertical capsule colorbars, and clean 3D plots. Fonts are bundled, so system font installation is not required.
|
|
11
|
+
|
|
12
|
+
[](https://github.com/Econ01/plotform/tree/main/examples)
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Python 3.10 or newer
|
|
17
|
+
- Matplotlib 3.8 or newer
|
|
18
|
+
- NumPy 1.24 or newer
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python -m pip install plotform
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For development from a source checkout:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python -m pip install -e ".[dev]"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import matplotlib.pyplot as plt
|
|
36
|
+
import plotform as tm
|
|
37
|
+
|
|
38
|
+
tm.apply_theme()
|
|
39
|
+
|
|
40
|
+
fig, ax = plt.subplots()
|
|
41
|
+
ax.plot([1, 2, 3], [2, 4, 3], label="Example")
|
|
42
|
+
ax.set(title="A minimalist chart", xlabel="Time", ylabel="Signal")
|
|
43
|
+
tm.legend(ax)
|
|
44
|
+
|
|
45
|
+
fig.savefig("chart.png", dpi=800)
|
|
46
|
+
fig.savefig("chart.pdf")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`apply_theme()` resets Matplotlib's style before applying Plotform and changes process-global settings. Call it once before creating figures. To preserve and later restore another style, use the scoped form:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
with tm.theme():
|
|
53
|
+
fig, ax = plt.subplots()
|
|
54
|
+
ax.plot([1, 2, 3], [2, 4, 3], label="Example")
|
|
55
|
+
tm.legend(ax)
|
|
56
|
+
fig.savefig("chart.png")
|
|
57
|
+
# Previous rcParams are restored here.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Plain Matplotlib, without Plotform
|
|
61
|
+
|
|
62
|
+
Scripts that must run without the package do not need a compatibility layer. Use native Matplotlib directly:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import matplotlib.pyplot as plt
|
|
66
|
+
|
|
67
|
+
fig, ax = plt.subplots()
|
|
68
|
+
ax.bar([0, 1, 2], [3, 5, 4], label="Example")
|
|
69
|
+
ax.legend()
|
|
70
|
+
fig.savefig("plot.png")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`examples/native_matplotlib.py` is a complete instructor-friendly example. Plotform is an opt-in enhancement, not a requirement for these scripts.
|
|
74
|
+
|
|
75
|
+
## Native-style helpers
|
|
76
|
+
|
|
77
|
+
| Helper | Purpose |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `tm.legend(ax)` | Solid or soft pill legends with adaptive text contrast |
|
|
80
|
+
| `tm.bar(x, height, ax=ax)` | Vertical bars with circular caps |
|
|
81
|
+
| `tm.barh(y, width, ax=ax)` | Horizontal bars with circular caps |
|
|
82
|
+
| `tm.colorbar(image, ax=ax)` | Vertical capsule colorbar with external ticks |
|
|
83
|
+
| `tm.annotate(text, xy, ax=ax)` | Exact capsule annotation |
|
|
84
|
+
| `tm.style_3d(ax)` | Minimal 3D panes, grids, ticks, and viewing angle |
|
|
85
|
+
|
|
86
|
+
`legend()` uses the current axes when `ax` is omitted. `colorbar()` uses the current figure and axes. Advanced geometry remains available through `plotform.rounded`; font utilities remain available through `plotform.fonts`.
|
|
87
|
+
|
|
88
|
+
## Fonts
|
|
89
|
+
|
|
90
|
+
- **Plotform Sans**, derived from Google Sans Flex with `ROND=100` and default weight 400.
|
|
91
|
+
- **Plotform Mono**, derived from Google Sans Code at its default weight.
|
|
92
|
+
- DejaVu fallbacks for mathematical notation and missing glyphs.
|
|
93
|
+
|
|
94
|
+
The package registers its bundled fonts directly with Matplotlib. No operating-system installation or cache clearing is needed. Font licenses and attribution are included under `src/plotform/assets/fonts/`.
|
|
95
|
+
|
|
96
|
+
## Colors
|
|
97
|
+
|
|
98
|
+
The default vivid categorical cycle is:
|
|
99
|
+
|
|
100
|
+
| Order | Color | Hex |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| 1 | Teal | `#008080` |
|
|
103
|
+
| 2 | Amber | `#E69F00` |
|
|
104
|
+
| 3 | Blue | `#0072B2` |
|
|
105
|
+
| 4 | Coral/vermillion | `#D55E00` |
|
|
106
|
+
| 5 | Purple | `#6A51A3` |
|
|
107
|
+
| 6 | Green | `#4C9A2A` |
|
|
108
|
+
| 7 | Rose | `#CC79A7` |
|
|
109
|
+
| 8 | Dark slate | `#4B5563` |
|
|
110
|
+
|
|
111
|
+
Extend the cycle deterministically:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
tm.apply_theme(n_colors=14)
|
|
115
|
+
colors = tm.get_palette(14)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Axes, labels, and ticks are black by default while grids remain light. For muted-gray decorations:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
tm.apply_theme(gray_decorations=True)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Three continuous colormaps are registered:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
ax.imshow(values, cmap=tm.SCALAR_CMAP) # Full-range scalar default
|
|
128
|
+
ax.imshow(values, cmap=tm.DIVERGING_CMAP) # Signed/diverging data
|
|
129
|
+
ax.imshow(values, cmap=tm.SEQUENTIAL_CMAP) # Optional pale brand-teal map
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`SCALAR_CMAP` follows navy → blue → teal → green → amber, avoiding near-white endpoints. Use fully opaque scalar markers and `depthshade=False` for mplot3d scatter plots. Use `shade=True` for `bar3d` to preserve face lighting.
|
|
133
|
+
|
|
134
|
+
## Pill legends
|
|
135
|
+
|
|
136
|
+
Solid category-color fills use adaptive white or black text, targeting at least 4.5:1 contrast. Marker and line handles are explicitly hidden so they cannot overlap labels.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
tm.legend(ax, ncols=3)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For auxiliary uncertainty regions, use an explicitly tinted entry:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
tm.legend(ax, styles={"95% interval": "soft"})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For dense plots, place the legend outside or add a white group capsule:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
tm.legend(ax, outside=True)
|
|
152
|
+
tm.legend(ax, backdrop=True, backdrop_alpha=0.88)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Rounded bars and annotations
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import numpy as np
|
|
159
|
+
|
|
160
|
+
x = np.arange(4)
|
|
161
|
+
|
|
162
|
+
# Exact semicircular cap with a square baseline.
|
|
163
|
+
tm.bar(x, [3, 5, -2, 4], radius="full", ends="free", ax=ax)
|
|
164
|
+
|
|
165
|
+
# Complete vertical capsule.
|
|
166
|
+
tm.bar(x, [3, 5, -2, 4], radius="full", ends="both", ax=ax)
|
|
167
|
+
|
|
168
|
+
# Clamp bars too short for an exact full capsule.
|
|
169
|
+
tm.bar(x, [3, 5, -2, 4], radius="fit", ends="both", ax=ax)
|
|
170
|
+
|
|
171
|
+
# Numeric circular corner radius.
|
|
172
|
+
tm.bar(x, [3, 5, -2, 4], radius=0.16, ax=ax)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Exact full mode raises a clear error when the display geometry cannot contain the requested semicircle. Use `radius="fit"` to opt into clamping instead.
|
|
176
|
+
|
|
177
|
+
Annotations use exact stadium geometry:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
tm.annotate("New", (0.15, 0.85), ax=ax, transform=ax.transAxes)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`plotform.rounded` also exposes `capsule_path`, `CapsuleBox`, and separate squircle primitives for advanced use.
|
|
184
|
+
|
|
185
|
+
[](https://github.com/Econ01/plotform/blob/main/examples/geometry_check.py)
|
|
186
|
+
|
|
187
|
+
## Capsule colorbars
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
image = ax.imshow(values, cmap=tm.SCALAR_CMAP)
|
|
191
|
+
colorbar = tm.colorbar(image, ax=ax)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The visible gradient is clipped to an exact vertical capsule in display space. Ticks and labels remain outside. Image, contour, streamplot, scatter, and surface mappables are supported. Standard rectangular `fig.colorbar()` output remains available.
|
|
195
|
+
|
|
196
|
+
## 3D plots
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
fig = plt.figure()
|
|
200
|
+
ax = fig.add_subplot(projection="3d")
|
|
201
|
+
tm.style_3d(ax)
|
|
202
|
+
ax.plot_surface(xx, yy, zz, cmap=tm.DIVERGING_CMAP, shade=True)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`style_3d()` clears panes and applies consistent grids, ticks, and viewing geometry. Pass `gray_decorations=True` for muted decorations.
|
|
206
|
+
|
|
207
|
+
## Visual showcase
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
python examples/showcase.py
|
|
211
|
+
python examples/geometry_check.py
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The showcase creates 27 default-versus-Plotform comparisons at 800 DPI, lightweight previews, selected PDF/SVG exports, and a contact sheet under `artifacts/showcase/`. The geometry script creates exact capsule reference boards under `artifacts/`.
|
|
215
|
+
|
|
216
|
+
[View the complete 27-chart contact sheet](https://github.com/Econ01/plotform/blob/main/docs/images/contact-sheet.png) or regenerate the documentation images with `python tools/make_doc_images.py`.
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
python -m pip install -e ".[dev]"
|
|
222
|
+
pytest
|
|
223
|
+
ruff check .
|
|
224
|
+
ruff format --check .
|
|
225
|
+
python -m build
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
See `CHANGELOG.md` for release history and `docs/RELEASING.md` for the GitHub, TestPyPI, and PyPI release process.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
Code is MIT licensed. Bundled font software remains under the SIL Open Font License; see the font asset notices for details.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Releasing Plotform
|
|
2
|
+
|
|
3
|
+
This project uses GitHub Actions and PyPI **trusted publishing** (OIDC). Do not
|
|
4
|
+
store long-lived PyPI tokens in the repository.
|
|
5
|
+
|
|
6
|
+
## 1. Validate the source
|
|
7
|
+
|
|
8
|
+
Before creating a release:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pytest
|
|
12
|
+
ruff check .
|
|
13
|
+
ruff format --check .
|
|
14
|
+
python -m build
|
|
15
|
+
python tools/check_sdist.py
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Install the wheel into a clean environment and run a small import and plotting
|
|
19
|
+
smoke test. The CI workflow must also be green.
|
|
20
|
+
|
|
21
|
+
## 2. Push the release branch
|
|
22
|
+
|
|
23
|
+
The repository remote is `https://github.com/Econ01/plotform.git`.
|
|
24
|
+
|
|
25
|
+
If using the `release/0.1.0` branch:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git push -u origin release/0.1.0
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Open a pull request into `main`, review the diff and CI checks, and merge when
|
|
32
|
+
ready. A direct push to `main` is also possible for a solo project, but the pull
|
|
33
|
+
request provides a useful release review.
|
|
34
|
+
|
|
35
|
+
## 3. Create GitHub environments
|
|
36
|
+
|
|
37
|
+
In **Settings → Environments**, create:
|
|
38
|
+
|
|
39
|
+
- `testpypi`
|
|
40
|
+
- `pypi`
|
|
41
|
+
|
|
42
|
+
For the first production release, require manual approval on the `pypi`
|
|
43
|
+
environment so publication cannot happen accidentally.
|
|
44
|
+
|
|
45
|
+
## 4. Configure TestPyPI trusted publishing
|
|
46
|
+
|
|
47
|
+
Sign in to TestPyPI and add a **pending trusted publisher** with exactly:
|
|
48
|
+
|
|
49
|
+
- PyPI project name: `plotform`
|
|
50
|
+
- Owner: `Econ01`
|
|
51
|
+
- Repository name: `plotform`
|
|
52
|
+
- Workflow name: `release.yml`
|
|
53
|
+
- Environment name: `testpypi`
|
|
54
|
+
|
|
55
|
+
If a TestPyPI project already exists, add the publisher under its publishing
|
|
56
|
+
settings instead.
|
|
57
|
+
|
|
58
|
+
## 5. Configure production PyPI trusted publishing
|
|
59
|
+
|
|
60
|
+
On production PyPI, add the same publisher identity, except:
|
|
61
|
+
|
|
62
|
+
- Environment name: `pypi`
|
|
63
|
+
|
|
64
|
+
The workflow filename and environment names must match exactly. GitHub Actions
|
|
65
|
+
uses these values when requesting an OIDC token.
|
|
66
|
+
|
|
67
|
+
## 6. Publish to TestPyPI
|
|
68
|
+
|
|
69
|
+
In the GitHub repository, open **Actions → Release → Run workflow** and run the
|
|
70
|
+
workflow on the release branch. A manual workflow run publishes only to
|
|
71
|
+
TestPyPI; production publication happens through a GitHub release.
|
|
72
|
+
|
|
73
|
+
Then create a clean environment and install:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install \
|
|
77
|
+
--index-url https://test.pypi.org/simple/ \
|
|
78
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
79
|
+
plotform
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The extra index supplies dependencies such as Matplotlib and NumPy that may not
|
|
83
|
+
be available on TestPyPI.
|
|
84
|
+
|
|
85
|
+
Verify:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import plotform as tm
|
|
89
|
+
|
|
90
|
+
assert tm.__version__ == "0.1.0"
|
|
91
|
+
tm.apply_theme()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run a small plotting example as well.
|
|
95
|
+
|
|
96
|
+
## 7. Create the production release
|
|
97
|
+
|
|
98
|
+
After TestPyPI validation:
|
|
99
|
+
|
|
100
|
+
1. Ensure `pyproject.toml` contains version `0.1.0`.
|
|
101
|
+
2. Update `CHANGELOG.md` if needed.
|
|
102
|
+
3. Create and push tag `v0.1.0`:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git tag -a v0.1.0 -m "Plotform 0.1.0"
|
|
106
|
+
git push origin v0.1.0
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
4. In GitHub, choose **Releases → Draft a new release**, select `v0.1.0`, add
|
|
110
|
+
the changelog notes, and publish.
|
|
111
|
+
5. The Release workflow checks the tag, builds the distributions, and publishes
|
|
112
|
+
to production PyPI through the `pypi` environment.
|
|
113
|
+
6. Approve the production environment if protection is enabled.
|
|
114
|
+
|
|
115
|
+
## 8. Verify the public installation
|
|
116
|
+
|
|
117
|
+
In another clean environment:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m pip install plotform
|
|
121
|
+
python -c "import plotform; print(plotform.__version__)"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Finally, add the repository description, topics, and PyPI link in GitHub's About
|
|
125
|
+
section. The README already contains CI, PyPI, Python, and license badges.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|