plotlet 0.1.3__tar.gz → 0.3.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.
- {plotlet-0.1.3/src/plotlet.egg-info → plotlet-0.3.0}/PKG-INFO +9 -7
- {plotlet-0.1.3 → plotlet-0.3.0}/README.md +8 -6
- {plotlet-0.1.3 → plotlet-0.3.0}/pyproject.toml +1 -1
- plotlet-0.3.0/src/plotlet/__init__.py +33 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/builtin_artists.py +102 -0
- plotlet-0.3.0/src/plotlet/chart.py +563 -0
- plotlet-0.3.0/src/plotlet/core.py +971 -0
- plotlet-0.3.0/src/plotlet/layout.py +835 -0
- plotlet-0.3.0/src/plotlet/layout_diagram.py +235 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/legend.py +35 -25
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/registry.py +8 -1
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/spec.json +2 -2
- {plotlet-0.1.3 → plotlet-0.3.0/src/plotlet.egg-info}/PKG-INFO +9 -7
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet.egg-info/SOURCES.txt +4 -2
- {plotlet-0.1.3 → plotlet-0.3.0}/tests/test_chart.py +56 -3
- plotlet-0.3.0/tests/test_layout_diagram.py +93 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/tests/test_legend.py +32 -20
- plotlet-0.3.0/tests/test_subplots.py +365 -0
- plotlet-0.3.0/tests/test_units.py +208 -0
- plotlet-0.1.3/src/plotlet/__init__.py +0 -29
- plotlet-0.1.3/src/plotlet/chart.py +0 -337
- plotlet-0.1.3/src/plotlet/core.py +0 -581
- plotlet-0.1.3/src/plotlet/layout.py +0 -530
- plotlet-0.1.3/tests/test_old.py +0 -112
- plotlet-0.1.3/tests/test_subplots.py +0 -306
- {plotlet-0.1.3 → plotlet-0.3.0}/LICENSE +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/setup.cfg +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/_cm_data.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/_png.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/_spec.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/artists.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/colormaps.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/colors.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/font.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/fonts/DejaVuSans.ttf +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet/scales.py +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet.egg-info/dependency_links.txt +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet.egg-info/requires.txt +0 -0
- {plotlet-0.1.3 → plotlet-0.3.0}/src/plotlet.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotlet
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Small, hackable Python library that emits matplotlib-style SVG plots.
|
|
5
5
|
Author: gitbamboo42
|
|
6
6
|
License-Expression: MIT
|
|
@@ -62,9 +62,10 @@ pip install plotlet
|
|
|
62
62
|
- **Lightweight.** `fonttools` for font handling. numpy / pandas / polars inputs work transparently if you have them.
|
|
63
63
|
- **Static SVG output.** No interactivity, no animation. Same script → byte-identical SVG.
|
|
64
64
|
- **Cross-machine reproducible.** Bundled DejaVu Sans + text-as-paths means rendering is identical on Linux, macOS, Windows, headless CI.
|
|
65
|
-
- **Jupyter-native.** `
|
|
65
|
+
- **Jupyter-native.** `Chart._repr_html_` auto-renders the last expression in a cell.
|
|
66
66
|
- **Tiny output.** Each plot is ~50 KB SVG, self-contained.
|
|
67
|
-
- **Compositional.** Multi-panel layouts via `|`, `/`, `pt.grid`; share scales with `share_x
|
|
67
|
+
- **Compositional.** Multi-panel layouts via `|`, `/`, `pt.grid`; share scales with `(a | b).share_x()` or `pt.grid(..., share_x="col")`; layout-level legend with `pt.legend()` covering both discrete swatches and continuous gradients (the colorbar).
|
|
68
|
+
- **AI-readable.** Every figure ships `data-plotlet-*` attributes describing plot type, axes, scales, ranges, and series labels — readable in one XML parse, no glyph-path OCR. Schema: [docs/AI_ATTRS.md](docs/AI_ATTRS.md).
|
|
68
69
|
|
|
69
70
|
## API
|
|
70
71
|
|
|
@@ -74,7 +75,7 @@ pip install plotlet
|
|
|
74
75
|
|
|
75
76
|
Pass at construction (`pt.chart(data, title=..., grid=True, ...)`) or as chained setters (`c.title(...)`, etc.):
|
|
76
77
|
|
|
77
|
-
`title`, `xlabel`, `ylabel`, `xlim=(a, b)`, `ylim=(a, b)`, `xscale="linear"|"log"|"category"` (chained: `c.xscale("category", order=[...], padding=0)`), `yscale=...`, `grid=True/False`, `legend=True/False`, `
|
|
78
|
+
`title`, `xlabel`, `ylabel`, `xlim=(a, b)`, `ylim=(a, b)`, `xscale="linear"|"log"|"category"` (chained: `c.xscale("category", order=[...], padding=0)`), `yscale=...`, `grid=True/False`, `legend=True/False`, `data_width`, `data_height` (the data region — preferred), or `canvas_width`, `canvas_height` (the full SVG canvas — mutually exclusive with the data form). Sizes accept bare pixels (`400`) or unit-suffixed strings (`"4in"`, `"10cm"`, `"100mm"`, `"72pt"`).
|
|
78
79
|
|
|
79
80
|
String-valued data on either axis (`scatter(["a","b","c"], ...)`, `bar`, …) auto-switches to a categorical scale, alphabetical by default. `padding=0` makes category bands contiguous (heatmap-track look).
|
|
80
81
|
|
|
@@ -110,10 +111,11 @@ pt.grid([[a, b], # 2-D grid; cells may be `None`
|
|
|
110
111
|
[c, d]])
|
|
111
112
|
|
|
112
113
|
# Share x or y across panels — collapses the gap between them
|
|
113
|
-
# and
|
|
114
|
+
# and unions data ranges; the first leaf in reading order anchors
|
|
115
|
+
# the scale, others are aspect-scaled to match.
|
|
114
116
|
top = pt.chart()
|
|
115
|
-
main = pt.chart(
|
|
116
|
-
top / main
|
|
117
|
+
main = pt.chart()
|
|
118
|
+
(top / main).share_x() # vertically stacked, x-axis joined
|
|
117
119
|
|
|
118
120
|
# Layout-level legend (covers colorbar and discrete swatches in
|
|
119
121
|
# one constructor — geometry follows from the source's color mapping).
|
|
@@ -33,9 +33,10 @@ pip install plotlet
|
|
|
33
33
|
- **Lightweight.** `fonttools` for font handling. numpy / pandas / polars inputs work transparently if you have them.
|
|
34
34
|
- **Static SVG output.** No interactivity, no animation. Same script → byte-identical SVG.
|
|
35
35
|
- **Cross-machine reproducible.** Bundled DejaVu Sans + text-as-paths means rendering is identical on Linux, macOS, Windows, headless CI.
|
|
36
|
-
- **Jupyter-native.** `
|
|
36
|
+
- **Jupyter-native.** `Chart._repr_html_` auto-renders the last expression in a cell.
|
|
37
37
|
- **Tiny output.** Each plot is ~50 KB SVG, self-contained.
|
|
38
|
-
- **Compositional.** Multi-panel layouts via `|`, `/`, `pt.grid`; share scales with `share_x
|
|
38
|
+
- **Compositional.** Multi-panel layouts via `|`, `/`, `pt.grid`; share scales with `(a | b).share_x()` or `pt.grid(..., share_x="col")`; layout-level legend with `pt.legend()` covering both discrete swatches and continuous gradients (the colorbar).
|
|
39
|
+
- **AI-readable.** Every figure ships `data-plotlet-*` attributes describing plot type, axes, scales, ranges, and series labels — readable in one XML parse, no glyph-path OCR. Schema: [docs/AI_ATTRS.md](docs/AI_ATTRS.md).
|
|
39
40
|
|
|
40
41
|
## API
|
|
41
42
|
|
|
@@ -45,7 +46,7 @@ pip install plotlet
|
|
|
45
46
|
|
|
46
47
|
Pass at construction (`pt.chart(data, title=..., grid=True, ...)`) or as chained setters (`c.title(...)`, etc.):
|
|
47
48
|
|
|
48
|
-
`title`, `xlabel`, `ylabel`, `xlim=(a, b)`, `ylim=(a, b)`, `xscale="linear"|"log"|"category"` (chained: `c.xscale("category", order=[...], padding=0)`), `yscale=...`, `grid=True/False`, `legend=True/False`, `
|
|
49
|
+
`title`, `xlabel`, `ylabel`, `xlim=(a, b)`, `ylim=(a, b)`, `xscale="linear"|"log"|"category"` (chained: `c.xscale("category", order=[...], padding=0)`), `yscale=...`, `grid=True/False`, `legend=True/False`, `data_width`, `data_height` (the data region — preferred), or `canvas_width`, `canvas_height` (the full SVG canvas — mutually exclusive with the data form). Sizes accept bare pixels (`400`) or unit-suffixed strings (`"4in"`, `"10cm"`, `"100mm"`, `"72pt"`).
|
|
49
50
|
|
|
50
51
|
String-valued data on either axis (`scatter(["a","b","c"], ...)`, `bar`, …) auto-switches to a categorical scale, alphabetical by default. `padding=0` makes category bands contiguous (heatmap-track look).
|
|
51
52
|
|
|
@@ -81,10 +82,11 @@ pt.grid([[a, b], # 2-D grid; cells may be `None`
|
|
|
81
82
|
[c, d]])
|
|
82
83
|
|
|
83
84
|
# Share x or y across panels — collapses the gap between them
|
|
84
|
-
# and
|
|
85
|
+
# and unions data ranges; the first leaf in reading order anchors
|
|
86
|
+
# the scale, others are aspect-scaled to match.
|
|
85
87
|
top = pt.chart()
|
|
86
|
-
main = pt.chart(
|
|
87
|
-
top / main
|
|
88
|
+
main = pt.chart()
|
|
89
|
+
(top / main).share_x() # vertically stacked, x-axis joined
|
|
88
90
|
|
|
89
91
|
# Layout-level legend (covers colorbar and discrete swatches in
|
|
90
92
|
# one constructor — geometry follows from the source's color mapping).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""plotlet — pure-Python SVG renderer, matplotlib-flavored.
|
|
2
|
+
|
|
3
|
+
import plotlet as pt
|
|
4
|
+
c = pt.chart(df, title="...", xlabel="x", ylabel="y", legend=True, grid=True)
|
|
5
|
+
c.line(x="time", y="value", hue="series")
|
|
6
|
+
c # auto-renders in Jupyter
|
|
7
|
+
|
|
8
|
+
For non-tabular use, the chained form works directly on the same Chart:
|
|
9
|
+
|
|
10
|
+
c = pt.chart()
|
|
11
|
+
c.plot([1, 2, 3], [1, 4, 9], label="squares")
|
|
12
|
+
c.title("Hello").legend().grid(True)
|
|
13
|
+
c
|
|
14
|
+
"""
|
|
15
|
+
from ._spec import SPEC
|
|
16
|
+
from .colors import TAB10, colors
|
|
17
|
+
from .colormaps import colormap, list_colormaps
|
|
18
|
+
from .chart import Chart, chart
|
|
19
|
+
from .layout import grid
|
|
20
|
+
from .legend import legend
|
|
21
|
+
from .registry import ArtistSpec, add_artist
|
|
22
|
+
from .layout_diagram import layout_diagram
|
|
23
|
+
|
|
24
|
+
__all__ = ["chart", "Chart", "SPEC", "TAB10", "colors",
|
|
25
|
+
"colormap", "list_colormaps", "grid", "legend",
|
|
26
|
+
"ArtistSpec", "add_artist", "layout_diagram"]
|
|
27
|
+
|
|
28
|
+
# Single source of truth: pyproject.toml. importlib.metadata reads it at
|
|
29
|
+
# runtime from the installed package metadata (works for `pip install` and
|
|
30
|
+
# `pip install -e .` alike). Migrating to setuptools_scm later doesn't
|
|
31
|
+
# touch this code — it just changes how pyproject.toml's version is set.
|
|
32
|
+
from importlib.metadata import version as _pkg_version
|
|
33
|
+
__version__ = _pkg_version("plotlet")
|
|
@@ -79,6 +79,98 @@ def _refspan_legend_swatch(a, ctx, x0, y_mid):
|
|
|
79
79
|
return _rect_swatch(a, x0, y_mid, ctx.defaults["refspan_alpha"])
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
# --- AI-readable structural attrs + payloads (0.3.0) ------------------------
|
|
83
|
+
# Each helper computes type-specific attrs from the recorded artist dict.
|
|
84
|
+
# The wrapper in core._wrap_artist supplies the common attrs (type, index,
|
|
85
|
+
# label, color); these add the per-artist fields. Returned dict keys map
|
|
86
|
+
# 1:1 to `data-plotlet-<key>` attribute names.
|
|
87
|
+
|
|
88
|
+
def _xy_minmax(xs, ys):
|
|
89
|
+
"""Min/max for an x/y series, ignoring NaN. Returns dict ready to merge
|
|
90
|
+
into a data_attrs result."""
|
|
91
|
+
fxs = [x for x in xs if isinstance(x, (int, float)) and x == x]
|
|
92
|
+
fys = [y for y in ys if isinstance(y, (int, float)) and y == y]
|
|
93
|
+
out = {}
|
|
94
|
+
if fxs:
|
|
95
|
+
out["x-min"] = min(fxs)
|
|
96
|
+
out["x-max"] = max(fxs)
|
|
97
|
+
if fys:
|
|
98
|
+
out["y-min"] = min(fys)
|
|
99
|
+
out["y-max"] = max(fys)
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _plot_data_attrs(a):
|
|
104
|
+
out = {"n": len(a["xs"])}
|
|
105
|
+
out.update(_xy_minmax(a["xs"], a["ys"]))
|
|
106
|
+
if a["opts"].get("linestyle"):
|
|
107
|
+
out["linestyle"] = a["opts"]["linestyle"]
|
|
108
|
+
if a["opts"].get("marker"):
|
|
109
|
+
out["marker"] = a["opts"]["marker"]
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _scatter_data_attrs(a):
|
|
114
|
+
out = {"n": len(a["xs"])}
|
|
115
|
+
out.update(_xy_minmax(a["xs"], a["ys"]))
|
|
116
|
+
out["marker"] = a["opts"].get("marker", "o")
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _bar_data_attrs(a):
|
|
121
|
+
fvals = [v for v in a["vals"] if isinstance(v, (int, float)) and v == v]
|
|
122
|
+
out = {"n": len(a["cats"])}
|
|
123
|
+
if fvals:
|
|
124
|
+
out["y-min"] = min(fvals)
|
|
125
|
+
out["y-max"] = max(fvals)
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _hist_data_attrs(a):
|
|
130
|
+
raw = a["data"]
|
|
131
|
+
out = {"n": len(raw), "bins": len(a.get("_bins", [])) or a["opts"].get("bins", 10)}
|
|
132
|
+
bins = a.get("_bins") or []
|
|
133
|
+
if bins:
|
|
134
|
+
out["x-min"] = bins[0]["x0"]
|
|
135
|
+
out["x-max"] = bins[-1]["x1"]
|
|
136
|
+
out["count-max"] = max(b["count"] for b in bins)
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _fill_between_data_attrs(a):
|
|
141
|
+
ys_all = list(a["y1"]) + list(a["y2"])
|
|
142
|
+
out = {"n": len(a["xs"])}
|
|
143
|
+
out.update(_xy_minmax(a["xs"], ys_all))
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _axhline_data_attrs(a): return {"y": a["y"]}
|
|
148
|
+
def _axvline_data_attrs(a): return {"x": a["x"]}
|
|
149
|
+
def _axhspan_data_attrs(a): return {"ymin": a["ymin"], "ymax": a["ymax"]}
|
|
150
|
+
def _axvspan_data_attrs(a): return {"xmin": a["xmin"], "xmax": a["xmax"]}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _imshow_data_attrs(a):
|
|
154
|
+
out = {
|
|
155
|
+
"rows": a["_nrows"],
|
|
156
|
+
"cols": a["_ncols"],
|
|
157
|
+
"vmin": a["_vmin"],
|
|
158
|
+
"vmax": a["_vmax"],
|
|
159
|
+
"cmap": a["opts"].get("cmap", _D["default_cmap"]),
|
|
160
|
+
# imshow is always raster (PNG-embedded above the rect threshold,
|
|
161
|
+
# individual <rect>s below). The flag is here so AI tools know
|
|
162
|
+
# which decoding strategy they're looking at.
|
|
163
|
+
"data-encoding": "png-embedded" if (a["_nrows"] * a["_ncols"]
|
|
164
|
+
> _D["imshow_max_rects"]) else "rects",
|
|
165
|
+
}
|
|
166
|
+
extent = a["opts"].get("extent")
|
|
167
|
+
if extent is not None:
|
|
168
|
+
out["extent"] = ",".join(repr(float(v)) for v in extent)
|
|
169
|
+
return out
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
82
174
|
# --- plot (line) ------------------------------------------------------------
|
|
83
175
|
|
|
84
176
|
add_artist(ArtistSpec(
|
|
@@ -89,6 +181,7 @@ add_artist(ArtistSpec(
|
|
|
89
181
|
ydomain=_ys_of,
|
|
90
182
|
draw=lambda a, ctx: _artist_plot(a, ctx.x_scale, ctx.y_scale, ctx.color),
|
|
91
183
|
legend_swatch=_plot_legend_swatch,
|
|
184
|
+
data_attrs=_plot_data_attrs,
|
|
92
185
|
))
|
|
93
186
|
|
|
94
187
|
|
|
@@ -102,6 +195,7 @@ add_artist(ArtistSpec(
|
|
|
102
195
|
ydomain=_ys_of,
|
|
103
196
|
draw=lambda a, ctx: _artist_scatter(a, ctx.x_scale, ctx.y_scale, ctx.color),
|
|
104
197
|
legend_swatch=_scatter_legend_swatch,
|
|
198
|
+
data_attrs=_scatter_data_attrs,
|
|
105
199
|
))
|
|
106
200
|
|
|
107
201
|
|
|
@@ -117,6 +211,7 @@ add_artist(ArtistSpec(
|
|
|
117
211
|
ydomain=_vals_of,
|
|
118
212
|
draw=lambda a, ctx: _artist_bar(a, ctx.x_scale, ctx.y_scale, ctx.color),
|
|
119
213
|
legend_swatch=_bar_legend_swatch,
|
|
214
|
+
data_attrs=_bar_data_attrs,
|
|
120
215
|
))
|
|
121
216
|
|
|
122
217
|
|
|
@@ -129,6 +224,7 @@ add_artist(ArtistSpec(
|
|
|
129
224
|
ydomain=_bin_ys,
|
|
130
225
|
draw=lambda a, ctx: _artist_hist(a, ctx.x_scale, ctx.y_scale, ctx.ih, ctx.color),
|
|
131
226
|
legend_swatch=_bar_legend_swatch,
|
|
227
|
+
data_attrs=_hist_data_attrs,
|
|
132
228
|
))
|
|
133
229
|
|
|
134
230
|
|
|
@@ -145,6 +241,7 @@ add_artist(ArtistSpec(
|
|
|
145
241
|
ydomain=_y1y2_of,
|
|
146
242
|
draw=lambda a, ctx: _artist_fill_between(a, ctx.x_scale, ctx.y_scale, ctx.color),
|
|
147
243
|
legend_swatch=_plot_legend_swatch,
|
|
244
|
+
data_attrs=_fill_between_data_attrs,
|
|
148
245
|
))
|
|
149
246
|
|
|
150
247
|
|
|
@@ -160,6 +257,7 @@ add_artist(ArtistSpec(
|
|
|
160
257
|
uses_color_cycle=False,
|
|
161
258
|
default_color=_D["refline_color"],
|
|
162
259
|
legend_swatch=_refline_legend_swatch,
|
|
260
|
+
data_attrs=_axhline_data_attrs,
|
|
163
261
|
))
|
|
164
262
|
|
|
165
263
|
add_artist(ArtistSpec(
|
|
@@ -171,6 +269,7 @@ add_artist(ArtistSpec(
|
|
|
171
269
|
uses_color_cycle=False,
|
|
172
270
|
default_color=_D["refline_color"],
|
|
173
271
|
legend_swatch=_refline_legend_swatch,
|
|
272
|
+
data_attrs=_axvline_data_attrs,
|
|
174
273
|
))
|
|
175
274
|
|
|
176
275
|
add_artist(ArtistSpec(
|
|
@@ -182,6 +281,7 @@ add_artist(ArtistSpec(
|
|
|
182
281
|
uses_color_cycle=False,
|
|
183
282
|
default_color=_D["refspan_color"],
|
|
184
283
|
legend_swatch=_refspan_legend_swatch,
|
|
284
|
+
data_attrs=_axhspan_data_attrs,
|
|
185
285
|
))
|
|
186
286
|
|
|
187
287
|
add_artist(ArtistSpec(
|
|
@@ -193,6 +293,7 @@ add_artist(ArtistSpec(
|
|
|
193
293
|
uses_color_cycle=False,
|
|
194
294
|
default_color=_D["refspan_color"],
|
|
195
295
|
legend_swatch=_refspan_legend_swatch,
|
|
296
|
+
data_attrs=_axvspan_data_attrs,
|
|
196
297
|
))
|
|
197
298
|
|
|
198
299
|
|
|
@@ -251,4 +352,5 @@ add_artist(ArtistSpec(
|
|
|
251
352
|
draw=lambda a, ctx: _artist_imshow(a, ctx.x_scale, ctx.y_scale, None),
|
|
252
353
|
legend_gradient=_imshow_legend_gradient,
|
|
253
354
|
uses_color_cycle=False,
|
|
355
|
+
data_attrs=_imshow_data_attrs,
|
|
254
356
|
))
|