scistackplot 0.1.33__tar.gz → 0.1.34__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.
Files changed (50) hide show
  1. scistackplot-0.1.33/README.md → scistackplot-0.1.34/PKG-INFO +90 -10
  2. scistackplot-0.1.33/PKG-INFO → scistackplot-0.1.34/README.md +51 -46
  3. {scistackplot-0.1.33 → scistackplot-0.1.34}/pyproject.toml +7 -0
  4. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/__init__.py +41 -2
  5. scistackplot-0.1.34/src/scistackplot/autosize.py +465 -0
  6. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/capability.py +2 -2
  7. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/codegen.py +33 -22
  8. scistackplot-0.1.34/src/scistackplot/custom.py +1023 -0
  9. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/figure_file.py +49 -1
  10. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/presets.py +49 -3
  11. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/render/base.py +34 -0
  12. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/render/mpl.py +239 -21
  13. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/render/plotly_.py +48 -0
  14. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/resolved.py +7 -0
  15. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/roles.py +29 -0
  16. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/spec.py +144 -21
  17. scistackplot-0.1.34/src/scistackplot/textsize.py +309 -0
  18. scistackplot-0.1.33/src/scistackplot/autosize.py +0 -557
  19. scistackplot-0.1.33/src/scistackplot/textsize.py +0 -209
  20. {scistackplot-0.1.33 → scistackplot-0.1.34}/.gitignore +0 -0
  21. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/aliases.py +0 -0
  22. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/cell.py +0 -0
  23. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/colors.py +0 -0
  24. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/compare.py +0 -0
  25. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/dedup.py +0 -0
  26. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/diffbars.py +0 -0
  27. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/export.py +0 -0
  28. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/figsize.py +0 -0
  29. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/framesize.py +0 -0
  30. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/groups.py +0 -0
  31. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/numeric.py +0 -0
  32. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/panels.py +0 -0
  33. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/paper.py +0 -0
  34. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/reduce.py +0 -0
  35. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/reducer.py +0 -0
  36. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/render/__init__.py +0 -0
  37. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/restore.py +0 -0
  38. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/series_stats.py +0 -0
  39. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/shape.py +0 -0
  40. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/sources/__init__.py +0 -0
  41. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/sources/base.py +0 -0
  42. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/sources/csv.py +0 -0
  43. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/sources/frame.py +0 -0
  44. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/spaghetti.py +0 -0
  45. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/table.py +0 -0
  46. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/ticklabels.py +0 -0
  47. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/variants.py +0 -0
  48. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/weights.py +0 -0
  49. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/xaxis.py +0 -0
  50. {scistackplot-0.1.33 → scistackplot-0.1.34}/src/scistackplot/ylimits.py +0 -0
@@ -1,3 +1,42 @@
1
+ Metadata-Version: 2.5
2
+ Name: scistackplot
3
+ Version: 0.1.34
4
+ Summary: Spec-driven plotting for long-format scientific data — standalone, GUI-friendly
5
+ Author: SciStack Contributors
6
+ License-Expression: MIT
7
+ Keywords: data-science,matplotlib,plotly,plotting,visualization
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Visualization
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: numpy>=1.21
21
+ Requires-Dist: pandas>=1.5
22
+ Requires-Dist: scistacklog==0.1.34
23
+ Provides-Extra: dev
24
+ Requires-Dist: matplotlib>=3.6; extra == 'dev'
25
+ Requires-Dist: plotly>=5.0; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: seaborn>=0.13; extra == 'dev'
29
+ Requires-Dist: tomli-w>=1.0; extra == 'dev'
30
+ Provides-Extra: interactive
31
+ Requires-Dist: plotly>=5.0; extra == 'interactive'
32
+ Provides-Extra: mpl
33
+ Requires-Dist: matplotlib>=3.6; extra == 'mpl'
34
+ Requires-Dist: seaborn>=0.13; extra == 'mpl'
35
+ Provides-Extra: plotly-export
36
+ Requires-Dist: kaleido>=1.0; extra == 'plotly-export'
37
+ Requires-Dist: plotly>=6.1; extra == 'plotly-export'
38
+ Description-Content-Type: text/markdown
39
+
1
40
  # scistackplot
2
41
 
3
42
  ## Build the figure by looking at it, then keep it
@@ -305,16 +344,20 @@ one owner (`resolve_sizes`, `rc_params`). The export, the generated code and
305
344
  the plotly preview (as px) all read it, under `rc_context` so nothing leaks
306
345
  into the next figure. See `docs/claude/plot-text-and-labels.md`.
307
346
 
308
- **Automatic size (the default).** With `base` unset (`None`), `scistackplot.autosize`
309
- makes each unfixed element as large as the figure lays it out cleanly, inside
310
- the band of `TextSizes.target`: `"print"` (8–12 pt, the default) or `"slide"`
311
- (14–28 pt), in points at the saved size. It searches per element on real
312
- matplotlib layouts. A rotated or shrunk x tick limits only `x_ticks`, a legend
313
- moved below limits only `legend`, and a plot area under 55 % of the canvas
314
- limits all of them. The chosen sizes ride on `ResolvedPlot.auto_text`, and
315
- every renderer reads them through `textsize.sizes_for`.
316
- `settle_text_size(resolved)` runs the search by hand. A number in `base`
317
- turns it off.
347
+ **Automatic size (the default).** `base` is ONE reference size; every unfixed
348
+ element is drawn at reference × its ratio (`DEFAULT_TEXT_RATIOS`: title 1.44,
349
+ axis and legend titles 1.2, the rest 1.0, overridable per element in
350
+ `TextSizes.ratios`), clamped into that element's floor/ceiling (the band of
351
+ `TextSizes.target` × the ratio — `"print"` 8–12 pt, `"slide"` 14–28 pt — or
352
+ `TextSizes.floors` / `ceilings` in pt). With `base` unset (`None`),
353
+ `scistackplot.autosize` searches the largest reference whose real matplotlib
354
+ layout breaks no rule: no text into text (tick fit, numeric tick collisions,
355
+ neighbouring panels, brackets, legend), nothing off the canvas, at least 3
356
+ tick labels per numeric axis, no y title taller than its panel. One failing
357
+ rule steps all the text down together, so the proportions hold. The result
358
+ rides on `ResolvedPlot.auto_text`, and every renderer reads it through
359
+ `textsize.sizes_for`. `settle_text_size(resolved)` runs the search by hand. A
360
+ number in `base` fixes the reference (unclamped).
318
361
 
319
362
  The figure's **paper** (white background, black text, a black frame round every
320
363
  panel, outward ticks, no grid, and the error-bar ink) has one owner,
@@ -396,6 +439,43 @@ key. The exported code writes the merged pins in as literals.
396
439
  `render.base.palette_for` stays the one owner of the colour a mark is drawn
397
440
  in. See `docs/claude/plot-colors.md`.
398
441
 
442
+ ## Your own plot function
443
+
444
+ A `plot_` function of your project can draw the figure instead of a built-in
445
+ kind. Plot Studio opens on it from the `plot_` node, and the node's edges bind
446
+ its inputs. The same works from code:
447
+
448
+ ```python
449
+ from dataclasses import replace
450
+
451
+ from scistackplot import CustomCall, CustomPlot, PlotKind, resolve_custom, write_figure
452
+
453
+ def plot_steps(steps, mass, ax, context):
454
+ for _, row in steps.iterrows():
455
+ ax.plot([context.x(row)], [row["StepLength"]], "o", color=context.color_of(row["session"]))
456
+
457
+ call = CustomCall(fn=plot_steps, inputs={"steps": steps_table, "mass": mass_table},
458
+ variables={"steps": "StepLength", "mass": "Mass"})
459
+ spec = replace(spec, kind=PlotKind.CUSTOM, custom=CustomPlot(function="plot_steps"))
460
+ for figure in resolve_custom(spec, call):
461
+ write_figure(figure, f"{figure.figure_label}.png")
462
+ ```
463
+
464
+ **With an `ax` parameter**, the function draws one panel per call, and
465
+ everything else is scistackplot's: panels, ticks, legend, text, size and
466
+ colours. `context.x(...)` places a mark on the tick its group levels name.
467
+ **Without `ax`**, it returns a whole matplotlib or plotly figure; only the
468
+ data settings and the size apply.
469
+
470
+ **What the function receives:**
471
+ - Each input is its own table: rows selected by the figure's and panel's
472
+ levels, with the inner collapsed keys averaged away.
473
+ - An input at a coarser level is passed whole to every call.
474
+ - With `as_table` off, an input arrives as the bare value instead of a table.
475
+
476
+ Writing a plotly figure needs `pip install "scistackplot[plotly-export]"`.
477
+ See `docs/claude/custom-plot-kinds.md`.
478
+
399
479
  ## Export: real code, not a call back into this library
400
480
 
401
481
  ```python
@@ -1,39 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: scistackplot
3
- Version: 0.1.33
4
- Summary: Spec-driven plotting for long-format scientific data — standalone, GUI-friendly
5
- Author: SciStack Contributors
6
- License-Expression: MIT
7
- Keywords: data-science,matplotlib,plotly,plotting,visualization
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Intended Audience :: Science/Research
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Topic :: Scientific/Engineering :: Visualization
18
- Classifier: Typing :: Typed
19
- Requires-Python: >=3.10
20
- Requires-Dist: numpy>=1.21
21
- Requires-Dist: pandas>=1.5
22
- Requires-Dist: scistacklog==0.1.33
23
- Provides-Extra: dev
24
- Requires-Dist: matplotlib>=3.6; extra == 'dev'
25
- Requires-Dist: plotly>=5.0; extra == 'dev'
26
- Requires-Dist: pytest-cov>=4.0; extra == 'dev'
27
- Requires-Dist: pytest>=7.0; extra == 'dev'
28
- Requires-Dist: seaborn>=0.13; extra == 'dev'
29
- Requires-Dist: tomli-w>=1.0; extra == 'dev'
30
- Provides-Extra: interactive
31
- Requires-Dist: plotly>=5.0; extra == 'interactive'
32
- Provides-Extra: mpl
33
- Requires-Dist: matplotlib>=3.6; extra == 'mpl'
34
- Requires-Dist: seaborn>=0.13; extra == 'mpl'
35
- Description-Content-Type: text/markdown
36
-
37
1
  # scistackplot
38
2
 
39
3
  ## Build the figure by looking at it, then keep it
@@ -341,16 +305,20 @@ one owner (`resolve_sizes`, `rc_params`). The export, the generated code and
341
305
  the plotly preview (as px) all read it, under `rc_context` so nothing leaks
342
306
  into the next figure. See `docs/claude/plot-text-and-labels.md`.
343
307
 
344
- **Automatic size (the default).** With `base` unset (`None`), `scistackplot.autosize`
345
- makes each unfixed element as large as the figure lays it out cleanly, inside
346
- the band of `TextSizes.target`: `"print"` (8–12 pt, the default) or `"slide"`
347
- (14–28 pt), in points at the saved size. It searches per element on real
348
- matplotlib layouts. A rotated or shrunk x tick limits only `x_ticks`, a legend
349
- moved below limits only `legend`, and a plot area under 55 % of the canvas
350
- limits all of them. The chosen sizes ride on `ResolvedPlot.auto_text`, and
351
- every renderer reads them through `textsize.sizes_for`.
352
- `settle_text_size(resolved)` runs the search by hand. A number in `base`
353
- turns it off.
308
+ **Automatic size (the default).** `base` is ONE reference size; every unfixed
309
+ element is drawn at reference × its ratio (`DEFAULT_TEXT_RATIOS`: title 1.44,
310
+ axis and legend titles 1.2, the rest 1.0, overridable per element in
311
+ `TextSizes.ratios`), clamped into that element's floor/ceiling (the band of
312
+ `TextSizes.target` × the ratio — `"print"` 8–12 pt, `"slide"` 14–28 pt — or
313
+ `TextSizes.floors` / `ceilings` in pt). With `base` unset (`None`),
314
+ `scistackplot.autosize` searches the largest reference whose real matplotlib
315
+ layout breaks no rule: no text into text (tick fit, numeric tick collisions,
316
+ neighbouring panels, brackets, legend), nothing off the canvas, at least 3
317
+ tick labels per numeric axis, no y title taller than its panel. One failing
318
+ rule steps all the text down together, so the proportions hold. The result
319
+ rides on `ResolvedPlot.auto_text`, and every renderer reads it through
320
+ `textsize.sizes_for`. `settle_text_size(resolved)` runs the search by hand. A
321
+ number in `base` fixes the reference (unclamped).
354
322
 
355
323
  The figure's **paper** (white background, black text, a black frame round every
356
324
  panel, outward ticks, no grid, and the error-bar ink) has one owner,
@@ -432,6 +400,43 @@ key. The exported code writes the merged pins in as literals.
432
400
  `render.base.palette_for` stays the one owner of the colour a mark is drawn
433
401
  in. See `docs/claude/plot-colors.md`.
434
402
 
403
+ ## Your own plot function
404
+
405
+ A `plot_` function of your project can draw the figure instead of a built-in
406
+ kind. Plot Studio opens on it from the `plot_` node, and the node's edges bind
407
+ its inputs. The same works from code:
408
+
409
+ ```python
410
+ from dataclasses import replace
411
+
412
+ from scistackplot import CustomCall, CustomPlot, PlotKind, resolve_custom, write_figure
413
+
414
+ def plot_steps(steps, mass, ax, context):
415
+ for _, row in steps.iterrows():
416
+ ax.plot([context.x(row)], [row["StepLength"]], "o", color=context.color_of(row["session"]))
417
+
418
+ call = CustomCall(fn=plot_steps, inputs={"steps": steps_table, "mass": mass_table},
419
+ variables={"steps": "StepLength", "mass": "Mass"})
420
+ spec = replace(spec, kind=PlotKind.CUSTOM, custom=CustomPlot(function="plot_steps"))
421
+ for figure in resolve_custom(spec, call):
422
+ write_figure(figure, f"{figure.figure_label}.png")
423
+ ```
424
+
425
+ **With an `ax` parameter**, the function draws one panel per call, and
426
+ everything else is scistackplot's: panels, ticks, legend, text, size and
427
+ colours. `context.x(...)` places a mark on the tick its group levels name.
428
+ **Without `ax`**, it returns a whole matplotlib or plotly figure; only the
429
+ data settings and the size apply.
430
+
431
+ **What the function receives:**
432
+ - Each input is its own table: rows selected by the figure's and panel's
433
+ levels, with the inner collapsed keys averaged away.
434
+ - An input at a coarser level is passed whole to every call.
435
+ - With `as_table` off, an input arrives as the bare value instead of a table.
436
+
437
+ Writing a plotly figure needs `pip install "scistackplot[plotly-export]"`.
438
+ See `docs/claude/custom-plot-kinds.md`.
439
+
435
440
  ## Export: real code, not a call back into this library
436
441
 
437
442
  ```python
@@ -58,6 +58,13 @@ mpl = [
58
58
  interactive = [
59
59
  "plotly>=5.0",
60
60
  ]
61
+ # Writing a plotly figure a custom plot_ function returned to a file
62
+ # (scistackplot.custom.write_plotly). kaleido 1.x also needs Chrome, which
63
+ # `plotly_get_chrome` installs.
64
+ plotly-export = [
65
+ "plotly>=6.1",
66
+ "kaleido>=1.0",
67
+ ]
61
68
  dev = [
62
69
  "pytest>=7.0",
63
70
  "pytest-cov>=4.0",
@@ -97,6 +97,18 @@ from .roles import (
97
97
  from .restore import NoteKind, RestoreError, RestoreNote, Restored, reconcile, restore_spec
98
98
  from .presets import FieldClass, apply_preset, make_template, shape_warning
99
99
  from .compare import CompareMode, Comparison
100
+ from .custom import (
101
+ CustomCall,
102
+ CustomFunctionError,
103
+ CustomSignature,
104
+ PlotContext,
105
+ custom_capabilities,
106
+ custom_requirement,
107
+ inspect_function,
108
+ preview_payload as custom_preview,
109
+ resolve_custom,
110
+ resolve_custom_one,
111
+ )
100
112
  from .diffbars import DifferenceBar, bars_for_panel, slot_endpoints
101
113
  from .panels import PanelOverride, override_for
102
114
  from .shape import Shape, classify_column, classify_value, is_plottable
@@ -112,6 +124,8 @@ from .spec import (
112
124
  LocationFilter,
113
125
  MatchOp,
114
126
  Matcher,
127
+ BUILTIN_KINDS,
128
+ CustomPlot,
115
129
  PlotKind,
116
130
  PlotSpec,
117
131
  Role,
@@ -120,14 +134,23 @@ from .spec import (
120
134
  Statistic,
121
135
  Alias,
122
136
  StyleOptions,
137
+ TEXT_ELEMENTS,
123
138
  TEXT_TARGETS,
124
139
  TextSizes,
125
140
  VariantSet,
126
141
  YAxis,
127
142
  grid_shape_for,
128
143
  )
129
- from .textsize import ResolvedSizes, rc_params, resolve_sizes, sizes_for
130
- from .autosize import AutoTextSize, BANDS, settle as settle_text_size
144
+ from .textsize import (
145
+ BANDS,
146
+ DEFAULT_TEXT_RATIOS,
147
+ ResolvedSizes,
148
+ drawn_sizes,
149
+ rc_params,
150
+ resolve_sizes,
151
+ sizes_for,
152
+ )
153
+ from .autosize import AutoTextSize, settle as settle_text_size
131
154
  from .aliases import AliasError, DisplayText, display_text
132
155
  from .colors import ColorError, MarkColors, mark_colors, parse_color
133
156
  from .figsize import (
@@ -185,6 +208,8 @@ __all__ = [
185
208
  "PlotSpec",
186
209
  "Role",
187
210
  "PlotKind",
211
+ "BUILTIN_KINDS",
212
+ "CustomPlot",
188
213
  "Statistic",
189
214
  "ErrorBand",
190
215
  "Aggregation",
@@ -209,6 +234,17 @@ __all__ = [
209
234
  # difference bars (.claude/plan-difference-bars.md)
210
235
  "CompareMode",
211
236
  "Comparison",
237
+ # custom plot kinds: a user plot_ function draws the figure
238
+ "CustomCall",
239
+ "CustomFunctionError",
240
+ "CustomSignature",
241
+ "PlotContext",
242
+ "custom_capabilities",
243
+ "custom_requirement",
244
+ "inspect_function",
245
+ "custom_preview",
246
+ "resolve_custom",
247
+ "resolve_custom_one",
212
248
  "DifferenceBar",
213
249
  "bars_for_panel",
214
250
  "slot_endpoints",
@@ -232,9 +268,12 @@ __all__ = [
232
268
  "ResolvedSizes",
233
269
  "resolve_sizes",
234
270
  "sizes_for",
271
+ "drawn_sizes",
272
+ "DEFAULT_TEXT_RATIOS",
235
273
  "rc_params",
236
274
  # automatic text size (autosize)
237
275
  "TEXT_TARGETS",
276
+ "TEXT_ELEMENTS",
238
277
  "BANDS",
239
278
  "AutoTextSize",
240
279
  "settle_text_size",