scistackplot 0.1.29__tar.gz → 0.1.31__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 (60) hide show
  1. {scistackplot-0.1.29 → scistackplot-0.1.31}/.gitignore +1 -0
  2. scistackplot-0.1.31/PKG-INFO +486 -0
  3. scistackplot-0.1.31/README.md +450 -0
  4. {scistackplot-0.1.29 → scistackplot-0.1.31}/pyproject.toml +2 -2
  5. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/__init__.py +181 -4
  6. scistackplot-0.1.31/src/scistackplot/aliases.py +415 -0
  7. scistackplot-0.1.31/src/scistackplot/autosize.py +557 -0
  8. scistackplot-0.1.31/src/scistackplot/capability.py +917 -0
  9. scistackplot-0.1.31/src/scistackplot/cell.py +209 -0
  10. scistackplot-0.1.31/src/scistackplot/codegen.py +2821 -0
  11. scistackplot-0.1.31/src/scistackplot/colors.py +333 -0
  12. scistackplot-0.1.31/src/scistackplot/compare.py +645 -0
  13. scistackplot-0.1.31/src/scistackplot/diffbars.py +1310 -0
  14. scistackplot-0.1.31/src/scistackplot/export.py +610 -0
  15. scistackplot-0.1.31/src/scistackplot/figsize.py +118 -0
  16. scistackplot-0.1.31/src/scistackplot/figure_file.py +128 -0
  17. scistackplot-0.1.31/src/scistackplot/groups.py +198 -0
  18. scistackplot-0.1.31/src/scistackplot/panels.py +197 -0
  19. scistackplot-0.1.31/src/scistackplot/paper.py +200 -0
  20. scistackplot-0.1.31/src/scistackplot/presets.py +495 -0
  21. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/reduce.py +1269 -240
  22. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/reducer.py +206 -116
  23. scistackplot-0.1.31/src/scistackplot/render/__init__.py +38 -0
  24. scistackplot-0.1.31/src/scistackplot/render/base.py +1064 -0
  25. scistackplot-0.1.31/src/scistackplot/render/mpl.py +1874 -0
  26. scistackplot-0.1.31/src/scistackplot/render/plotly_.py +1498 -0
  27. scistackplot-0.1.31/src/scistackplot/resolved.py +434 -0
  28. scistackplot-0.1.31/src/scistackplot/restore.py +552 -0
  29. scistackplot-0.1.31/src/scistackplot/roles.py +1348 -0
  30. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/series_stats.py +29 -1
  31. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/sources/__init__.py +2 -2
  32. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/sources/base.py +164 -13
  33. scistackplot-0.1.31/src/scistackplot/spaghetti.py +65 -0
  34. scistackplot-0.1.31/src/scistackplot/spec.py +1410 -0
  35. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/table.py +144 -5
  36. scistackplot-0.1.31/src/scistackplot/textsize.py +209 -0
  37. scistackplot-0.1.31/src/scistackplot/ticklabels.py +632 -0
  38. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/variants.py +142 -23
  39. scistackplot-0.1.31/src/scistackplot/weights.py +125 -0
  40. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/xaxis.py +14 -1
  41. scistackplot-0.1.31/src/scistackplot/ylimits.py +806 -0
  42. scistackplot-0.1.29/PKG-INFO +0 -213
  43. scistackplot-0.1.29/README.md +0 -177
  44. scistackplot-0.1.29/src/scistackplot/capability.py +0 -685
  45. scistackplot-0.1.29/src/scistackplot/codegen.py +0 -943
  46. scistackplot-0.1.29/src/scistackplot/groups.py +0 -110
  47. scistackplot-0.1.29/src/scistackplot/render/__init__.py +0 -26
  48. scistackplot-0.1.29/src/scistackplot/render/base.py +0 -262
  49. scistackplot-0.1.29/src/scistackplot/render/mpl.py +0 -458
  50. scistackplot-0.1.29/src/scistackplot/render/plotly_.py +0 -537
  51. scistackplot-0.1.29/src/scistackplot/resolved.py +0 -240
  52. scistackplot-0.1.29/src/scistackplot/roles.py +0 -431
  53. scistackplot-0.1.29/src/scistackplot/spec.py +0 -788
  54. scistackplot-0.1.29/src/scistackplot/ylimits.py +0 -452
  55. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/dedup.py +0 -0
  56. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/framesize.py +0 -0
  57. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/numeric.py +0 -0
  58. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/shape.py +0 -0
  59. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/sources/csv.py +0 -0
  60. {scistackplot-0.1.29 → scistackplot-0.1.31}/src/scistackplot/sources/frame.py +0 -0
@@ -31,3 +31,4 @@ scistack-gui/frontend/dist/
31
31
  /exports/
32
32
  *.log
33
33
  output.txt
34
+ scimatlab/tests/matlab/test-failures.txt
@@ -0,0 +1,486 @@
1
+ Metadata-Version: 2.5
2
+ Name: scistackplot
3
+ Version: 0.1.31
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.0
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
+ # scistackplot
38
+
39
+ ## Build the figure by looking at it, then keep it
40
+
41
+ `scistackplot` turns a long-format table into a figure from a small,
42
+ serializable description — a `PlotSpec`. It works standalone on a CSV or a
43
+ DataFrame with no database and no configuration, and the same `PlotSpec` is
44
+ exactly what the body of a SciDB `plot_` endpoint needs, so an interactive
45
+ exploration can be frozen into a lineage-tracked pipeline step.
46
+
47
+ ```bash
48
+ pip install scistackplot
49
+ ```
50
+
51
+ ## The idea
52
+
53
+ A plotting GUI looks like it produces pictures. It doesn't — it produces a
54
+ **specification**, and the picture is a view of it. That is what lets an
55
+ inherently visual tool live inside a reproducible pipeline:
56
+
57
+ ```python
58
+ import pandas as pd
59
+ from scistackplot import DataFrameSource, PlotSpec, Role, PlotKind, render
60
+
61
+ source = DataFrameSource(pd.read_csv("gait.csv"))
62
+ spec = PlotSpec(
63
+ measures=["StepLength"],
64
+ roles={"session": Role.GROUP, "limb": Role.GROUP, "subject": Role.COLLAPSE},
65
+ groups=["limb", "session"], # innermost first: limbs inside each session tick
66
+ color="limb",
67
+ kind=PlotKind.BOX,
68
+ )
69
+ figure = render(source, spec)
70
+ ```
71
+
72
+ ## Every factor does exactly one thing
73
+
74
+ The whole control surface is one rule: each categorical column carries exactly
75
+ one role, in one of two panes (`docs/claude/grouping-and-collapse.md`).
76
+
77
+ | Role | Meaning |
78
+ |---|---|
79
+ | `GROUP` | one **mark** per level — a bar, a box, a line. Ordered by `PlotSpec.groups`, innermost first; one layer may be the `color` |
80
+ | `FACET` | one subplot per level (arranged by `FacetOptions`) |
81
+ | `ITERATE` | a separate **figure** per level — the default for an unmentioned factor |
82
+ | `COLLAPSE` | averaged away. The **last** collapsed key is the *sample* |
83
+
84
+ Collapsed keys average away deepest first, nested and unweighted — "trial
85
+ within subject, then subject" — so each subject counts once however many
86
+ trials it has (`Aggregation(pooled=True)` is the deliberate alternative). What
87
+ remains at each mark is the sample, and **every kind draws the sample**:
88
+ bar and band draw its centre ± spread, box and violin its distribution,
89
+ scatter and strip one point per sample row, line one line per sample level.
90
+ (A spaghetti whose sample cannot be joined across its x axis, such as trials
91
+ under session ticks, draws each line through their mean.)
92
+
93
+ Which plot kinds are available follows from that assignment plus the measure's
94
+ shape, through one pure function:
95
+
96
+ ```python
97
+ from scistackplot import available_plots, default_plot, Shape
98
+
99
+ available_plots(Shape.SCALAR, {"session": Role.GROUP}) # scatter, strip, bar
100
+ available_plots(Shape.SCALAR, {"session": Role.GROUP, "trial": Role.COLLAPSE}) # + box, violin
101
+ ```
102
+
103
+ A distribution needs a sample, and a sample exists only when some factor is
104
+ collapsed. That single rule produces both defaults and availability:
105
+
106
+ | Measure shape | no sample | with a sample |
107
+ |---|---|---|
108
+ | scalar | scatter, or a bar with no error bar | box / violin / bar + CI |
109
+ | 1-D array | one line per leaf group | mean line + shaded error band |
110
+ | 2-D | heatmap | mean heatmap |
111
+
112
+ "Average over trials, then show the spread across subjects" is
113
+ `trial=COLLAPSE, subject=COLLAPSE`: trial averages within each subject
114
+ first, and subject — the last — is the sample the error bars are drawn over.
115
+
116
+ **Spaghetti** is the repeated-measures view: one marker per level of the
117
+ **first** grouping layer at each x position, joined by a line — `groups =
118
+ ["subject", "session", "Intervention"]` draws every subject's line across
119
+ sessions inside their group. It needs two grouping layers, and the same list
120
+ read as a bar plot nests subject bars inside session ticks inside Intervention
121
+ brackets. Each subject keeps one small deterministic offset at every x
122
+ position (never a random jitter — a line must end on its own markers),
123
+ decided once per figure by `scistackplot.spaghetti.series_offsets` and read by
124
+ both renderers and the generated code.
125
+
126
+ **Show sample** puts the data behind a summary on top of it. `show_sample =
127
+ ["trial"]` on that box plot cuts the collapse chain before `trial`: every
128
+ trial of every subject is drawn as a small point inside its box (a deeper
129
+ key implies the shallower ones — a trial is a trial *of* a subject), and
130
+ `["subject"]` shows one point per subject, the sample itself. Points are
131
+ joined into lines when they are repeated measures — the shown key sits above
132
+ the x axis's key in the schema, so each subject has a value at every session
133
+ — and left as points otherwise; `join_sample=True/False` overrides the rule.
134
+ The marks never change, the y axis grows to hold the points, and the exported
135
+ code draws the same overlay.
136
+
137
+ ## Compare to a reference level
138
+
139
+ Draw each level of a grouping layer relative to one of them, as a difference
140
+ or a % change. The reference sits at 0:
141
+
142
+ ```python
143
+ from scistackplot import Comparison, CompareMode
144
+
145
+ spec = replace(spec, comparison=Comparison(layer="session", level="1", mode=CompareMode.PERCENT))
146
+ ```
147
+
148
+ It is **paired** when each unit has its own reference value (a subject is
149
+ measured at every session), which is the rule "Show sample" uses to join
150
+ points. Otherwise each value is compared with the reference mark's centre.
151
+ Units with no reference, a % change from a reference <= 0, or a 1-D series
152
+ whose length differs from its reference are dropped and named in the log.
153
+ A comparison that cannot apply (a log axis, an x-y plot, a layer that no
154
+ longer groups) is inert: the raw values are drawn and the reason is
155
+ reported. The exported code and "Save data" compare the same way, and the saved value column is named after what it holds (`M, % change from session 1`). See
156
+ `docs/claude/compare-to-reference.md`.
157
+
158
+ ## Save the data behind a figure
159
+
160
+ The rows a plot is drawn from are available as a long table, so the
161
+ statistics run on exactly what the figure shows:
162
+
163
+ ```python
164
+ from scistackplot import plot_data
165
+
166
+ data = plot_data(spec, table) # the plotted sample
167
+ data.to_csv("step_length.csv", index=False)
168
+ deeper = plot_data(spec, table, depth="trial") # keep trials, average cycles only
169
+ ```
170
+
171
+ Every figure of a Separate-figures fan-out is in one frame, with the figure
172
+ keys as columns. The rows come from the same plan and the same sample step
173
+ the figure uses: filters, variants and the cell statistic are all applied
174
+ already. `data_export_options` lists the depths and the header each one
175
+ writes. A struct variable's fields (`ColName`) are written one column per
176
+ field by default; pass `fields_as_columns=False` for one row per field.
177
+ Scalar plots only; a raw 1-D or 2-D plot is refused with the reason.
178
+ See `docs/claude/plot-data-export.md`.
179
+
180
+ ## Reopen a spec an older version wrote
181
+
182
+ A spec saved last month may name settings this version has renamed or
183
+ removed. `PlotSpec.from_dict` is strict and refuses it. `restore_spec`
184
+ keeps everything that still reads and tells you what did not:
185
+
186
+ ```python
187
+ from scistackplot import reconcile, restore_spec
188
+
189
+ restored = restore_spec(stored_dict, fallback_measure="StepLength")
190
+ for note in restored.notes: # e.g. "style.widht: 'widht' is no longer a setting; ignored"
191
+ print(note.path, note.kind, note.message)
192
+
193
+ checked = reconcile(restored.spec, table) # names today's data no longer has
194
+ ```
195
+
196
+ It never migrates. A renamed setting takes its default and gets a note. It
197
+ never raises, except when there is no usable measure and no
198
+ `fallback_measure`. `reconcile` removes `show_sample` keys and a
199
+ `sample_color` that are no longer factors, because the figure would refuse
200
+ them. It reports stale roles and leaves them in place, because resolve drops
201
+ them itself. See `docs/claude/saved-plots.md`.
202
+
203
+ ## Presets: the settings without the data
204
+
205
+ `make_template` takes a spec's settings without the variable: no measure,
206
+ no variant rows, no title, y label or y limits. `apply_preset` draws another
207
+ variable with them and checks the result against that variable's data:
208
+
209
+ ```python
210
+ from scistackplot import apply_preset, make_template
211
+
212
+ template = make_template(step_length_spec) # plain JSON
213
+ applied = apply_preset(template, PlotSpec(measures=["StepWidth"]), table=width_table)
214
+ applied.spec, applied.notes # notes: roles the data lacks, a kind it cannot draw, ...
215
+ ```
216
+
217
+ `presets.FIELD_CLASSES` is the one statement of which settings travel. The
218
+ GUI's Presets section and `scistackplotdb.save_preset` store templates in
219
+ the project database. See `docs/claude/plot-presets.md`.
220
+
221
+ ## Arranging the subplots
222
+
223
+ Faceted panels flow in order by default, wrapping at `FacetOptions.wrap`. When
224
+ the arrangement matters, describe it with **rules** instead of positions:
225
+
226
+ ```python
227
+ from scistackplot import FacetOptions, MatchOp, Matcher, PlotSpec, Role
228
+
229
+ spec = PlotSpec(
230
+ measures=["RawEMG"],
231
+ roles={"ColName": Role.FACET, "subject": Role.GROUP},
232
+ color="subject",
233
+ facet=FacetOptions(
234
+ rows=[Matcher(op=MatchOp.STARTS_WITH, value="R"),
235
+ Matcher(op=MatchOp.STARTS_WITH, value="L")],
236
+ cols=[Matcher(op=MatchOp.ENDS_WITH, value="HAM"),
237
+ Matcher(op=MatchOp.ENDS_WITH, value="TA")],
238
+ ),
239
+ )
240
+ ```
241
+
242
+ Rules describe a layout rather than a hand-arrangement, so the same
243
+ `FacetOptions` applies to any variable whose panels are named the same way.
244
+ Ops are `starts_with`, `ends_with`, `contains`, `not_contains`, `equals` and
245
+ `regex`; a panel matching no rule lands in a trailing "other" row or column
246
+ rather than vanishing.
247
+
248
+ Each panel is named on its **y axis**, not by a caption above it. A caption
249
+ spends a strip of every row of the grid on text; the axis title is room the
250
+ panel was already spending, so a 4x3 grid gets that height back for the data.
251
+ The generated seaborn code says the same thing (`g.set_titles("")`), because
252
+ the export must be the figure you previewed.
253
+
254
+ ## Ordering is not cosmetic
255
+
256
+ Zero-padded IDs (`"01"`, `"02"`, … `"10"`) sort lexicographically into
257
+ 1, 10, 2 under pandas' default — visibly wrong on an axis, and wrong in a way
258
+ that looks like a data problem. `LongTable` carries each factor's real level
259
+ order; sources that know better (SciDB knows its declared `schema_key_types`)
260
+ supply it explicitly, and everything else falls back to a natural sort.
261
+
262
+ ## Rendering
263
+
264
+ Two backends translate the same reduced plot, so the interactive view and the
265
+ exported figure cannot disagree:
266
+
267
+ ```python
268
+ from scistackplot import resolve, render_matplotlib, render_plotly
269
+
270
+ resolved = resolve(spec, table) # all reduction happens here
271
+ figure = render_matplotlib(resolved[0]) # export / pipeline — a Figure
272
+ payload = render_plotly(resolved[0]) # interactive — a plotly.js dict
273
+ ```
274
+
275
+ `render_plotly` builds plain JSON and needs no plotly package.
276
+
277
+ ## Legible x labels
278
+
279
+ Crowded tick labels are fitted, not left to collide. One pure function,
280
+ `scistackplot.fit_labels`, decides for the whole figure (the most crowded
281
+ panel decides for every panel), trying the least destructive fix first:
282
+
283
+ 1. **Numbered labels** (every label is, or ends in, a number) drop the prefix
284
+ they share: `SS01 … SS40` → `01 … 40`. Names are never touched.
285
+ 2. Wrap at `_ - / . space` onto two lines.
286
+ 3. Shrink the font, never below 8pt or 70% of the tick size.
287
+ 4. Rotate 45°, then 90°.
288
+ 5. Numbered labels only: show every k-th, keeping each bracket's first and
289
+ last. Names stop at step 4 and the log WARNs that they still overlap.
290
+
291
+ The matplotlib export measures the real text; bracket rows are placed a fixed
292
+ number of points below the fitted tick labels and fitted too (shrink and wrap
293
+ only). A nested axis has **no x title**: its tick and bracket rows already name
294
+ every level. `StyleOptions.hide_legend_ticks=True` blanks the labels of a
295
+ layer that is also the colour while the legend lists it. Any of the fitted
296
+ settings can be fixed instead: `tick_rotation`, `text.x_ticks`,
297
+ `tick_every` (None = fitted). A fixed value is kept even where it overlaps, and
298
+ the fit reports that it does. The generated seaborn code replays the fit as
299
+ operations on seaborn's own labels, and saves without `bbox_inches="tight"`. See
300
+ `.claude/plan-tick-label-legibility.md`.
301
+
302
+ The exported legend fits too. At the right it may take 30% of the width
303
+ (`LEGEND_BUDGET`); past that the title wraps at " / ", the line samples
304
+ shorten and the text shrinks (never below the tick labels' floor). Still too
305
+ wide, or leaving the x labels no room, it moves below the panels in as many
306
+ columns as fit. `PlotSpec.sample_in_legend=False` (the Show sample pane's
307
+ "Show in legend") leaves the overlay's colours out of it.
308
+
309
+ ## Figure size
310
+
311
+ `StyleOptions.width` / `height` are inches and size the **saved** figure and
312
+ the generated code; the interactive preview fills whatever pane it is in.
313
+ `scistackplot.figsize` is the one vocabulary of aspect-ratio presets (`4:3`,
314
+ `16:9`, `3:2`, golden, `2:1`, `1:1`, two portrait ratios, `custom`): pick a
315
+ ratio, name a width — a journal column is 3.5 in, a double column 7.2 in — and
316
+ `height_for` gives the height. `aspect_name` runs the other way, so a reopened
317
+ spec reports the ratio it was saved with, and `render_matplotlib` logs the
318
+ size it drew at INFO. The Plot Studio panel's "Figure size" section is this
319
+ module with a dropdown on it.
320
+
321
+ `write_figure(resolved, path, dpi=200)` writes the file at exactly that size.
322
+ There's no whitespace trim: it refuses `bbox_inches`, and the renderer fits the
323
+ labels and legend inside the canvas. Anything still drawn past the edge is
324
+ measured (`canvas_overflow`) and WARNed. The file is never resized to make room.
325
+ The GUI's Save goes through it.
326
+
327
+ The preview never decides for itself. `layout_decisions(resolved, width_in=,
328
+ height_in=)` lays the figure out with matplotlib at a size and returns the tick,
329
+ bracket and legend decisions; `render_plotly(resolved, decisions=...,
330
+ fixed_size_px=...)` draws exactly those. The Plot Studio previews at the export
331
+ size (drawn at Width x Height, 1 pt = 1 px) or at the pane's size ("Fit pane").
332
+
333
+ `StyleOptions.text` (`TextSizes`) sizes each piece of text separately:
334
+ `base` (points) is matplotlib's `font.size`, and `title`,
335
+ `x_label`, `y_label`, `x_ticks`, `y_ticks`, `groups` (the bracket rows),
336
+ `legend` and `legend_title` are each either fixed or `None`, which derives
337
+ them from `base` with matplotlib's own ratios. Only `base` set draws exactly
338
+ what one font knob would. A fixed size is never shrunk by the label or legend
339
+ fit, which may still rotate, wrap or move it. `scistackplot.textsize` is the
340
+ one owner (`resolve_sizes`, `rc_params`). The export, the generated code and
341
+ the plotly preview (as px) all read it, under `rc_context` so nothing leaks
342
+ into the next figure. See `docs/claude/plot-text-and-labels.md`.
343
+
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.
354
+
355
+ The figure's **paper** (white background, black text, a black frame round every
356
+ panel, outward ticks, no grid, and the error-bar ink) has one owner,
357
+ `scistackplot.paper.PAPER`. Its values are matplotlib's defaults, written
358
+ out. The export, the generated code and the plotly preview all draw it, so
359
+ the preview shows the saved figure's page rather than plotly's grid. A
360
+ personal `matplotlibrc` cannot change an export. The Plot Studio shows the
361
+ figure untouched in light mode; dark mode recolours the ink for the screen.
362
+ See `docs/claude/preview-paper-parity.md`.
363
+
364
+ `StyleOptions.sample_weight` and `StyleOptions.line_weight` are multipliers
365
+ (default 1) on the point size AND line thickness together: the first for the
366
+ "Show sample" overlay's points and joining lines, the second for a
367
+ spaghetti's own points and lines. `scistackplot.weights` is the one owner;
368
+ matplotlib, the plotly preview and the generated code all read it.
369
+
370
+ ## What the text reads as: display aliases
371
+
372
+ A figure shows the data's own names, such as `BL`, `F` and `StepLength`.
373
+ An alias says what one reads as, and it changes only the text:
374
+
375
+ ```python
376
+ from scistackplot import Alias
377
+
378
+ spec = PlotSpec(
379
+ measures=["StepLength"],
380
+ roles={"session": Role.GROUP, "subject": Role.COLLAPSE},
381
+ groups=["session"],
382
+ aliases={
383
+ "session": Alias(name="Visit", levels={"BL": "Baseline", "POST": "After"}),
384
+ "StepLength": Alias(name="Step length (cm)"),
385
+ },
386
+ )
387
+ ```
388
+
389
+ `name` is used where the figure says what a thing is (axis and legend
390
+ titles). `levels` is used wherever a value appears: ticks, brackets,
391
+ legend entries, panel titles and figure titles.
392
+
393
+ A scidb-backed table also carries the project's aliases (`[aliases]` in
394
+ `scistack.toml`, read live through `scidb.aliases`). The plot's own entries
395
+ override them field by field, and `""` shows the raw text again.
396
+
397
+ Only the text changes. Filters, facet layout rules, the level order and
398
+ `plot_data` all keep raw levels, and so does the data the exported code
399
+ draws. The exported code writes the merged aliases in as a literal and
400
+ relabels what it drew. Two levels of one factor that would read the same are
401
+ refused (`AliasError`), because the figure could not tell them apart.
402
+ `scistackplot.aliases.DisplayText` (on `ResolvedPlot.text`) is the one owner
403
+ of how every level and name reads. See `docs/claude/plot-text-and-labels.md`.
404
+
405
+ ## What colour a mark is: pinned colours
406
+
407
+ The palette (`StyleOptions.palette`, Okabe-Ito by default) paints each level
408
+ of the colour layer. You can pin a level's colour with `PlotSpec.colors`,
409
+ and set the one colour of an uncoloured figure with `StyleOptions.mark_color`:
410
+
411
+ ```python
412
+ spec = PlotSpec(
413
+ ...,
414
+ color="session",
415
+ colors={"session": {"BL": "#0072b2", "POST": "rgb(213, 94, 0)"}},
416
+ )
417
+ ```
418
+
419
+ Colours may be `#rrggbb`, `#rgb`, `rgb(r, g, b)` or a matplotlib colour
420
+ name. `scistackplot.colors.parse_color` is the one reader, and it
421
+ canonicalises every colour to `#rrggbb`.
422
+
423
+ A scidb-backed table also carries the project's `[colors]` (read live
424
+ through `scidb.colors`). The plot's pins override them level by level.
425
+
426
+ A level you don't pin keeps its palette colour at its declared position, so
427
+ pinning one level never moves another's.
428
+
429
+ Pins also paint the "Show sample" overlay when it is coloured by the same
430
+ key. The exported code writes the merged pins in as literals.
431
+
432
+ `render.base.palette_for` stays the one owner of the colour a mark is drawn
433
+ in. See `docs/claude/plot-colors.md`.
434
+
435
+ ## Export: real code, not a call back into this library
436
+
437
+ ```python
438
+ from scistackplot import generate_plot_function
439
+
440
+ print(generate_plot_function(spec, table))
441
+ ```
442
+
443
+ ```python
444
+ def plot_steplength(df, filename):
445
+ import matplotlib.pyplot as plt
446
+ import pandas as pd
447
+ import seaborn as sns
448
+
449
+ g = sns.catplot(
450
+ data=df,
451
+ x='session',
452
+ y='StepLength',
453
+ hue='limb',
454
+ kind="box",
455
+ )
456
+ g.set_axis_labels('session', 'StepLength')
457
+ return g.figure
458
+ ```
459
+
460
+ Your pipeline gets ordinary seaborn code it can keep, edit, and read — no
461
+ runtime dependency on this package. The spec is embedded in the docstring, so
462
+ the GUI can reopen a figure you have since hand-edited.
463
+
464
+ ## Data sources
465
+
466
+ `DataSource` is a three-method protocol (`describe`, `get_table`,
467
+ `joinable_with`). `scistackplot` ships `CsvSource` and `DataFrameSource`;
468
+ [`scistackplotdb`](../scistackplotdb/README.md) ships the SciDB one. Anything
469
+ consuming the protocol — including the Plot Studio panel in the SciStack GUI —
470
+ works identically against a lone CSV and a full project database.
471
+
472
+ ## Relationship to SciDB endpoints
473
+
474
+ Recording a figure is SciDB's job and is unchanged: name a function `plot_`,
475
+ return a Figure, and `finalized=True` stores it as a queryable record with an
476
+ embedded provenance stamp. `scistackplot` supplies the body of that function;
477
+ `scistackplotdb` generates the `for_each` call around it.
478
+
479
+ See [`docs/claude/plotting-library-design.md`](../docs/claude/plotting-library-design.md).
480
+
481
+ ## Optional extras
482
+
483
+ ```bash
484
+ pip install "scistackplot[mpl]" # matplotlib + seaborn (export)
485
+ pip install "scistackplot[interactive]" # plotly Figure objects
486
+ ```