@ggts-sh/skill 0.43.0

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.
@@ -0,0 +1,396 @@
1
+ <!-- Source of truth: packages/spec/src/schema-names.ts (scheme/shape/linetype names), packages/spec/src/capabilities.ts (SCALE_CAPABILITIES), scripts/gen-scale-children.ts (Scale* component generation), packages/spec/src/scale-*.ts helpers. Inventory tables are asserted complete by scripts/skill-content/inventory.test.ts. -->
2
+
3
+ # Scales and palettes
4
+
5
+ ## Scale families
6
+
7
+ `SCALE_CAPABILITIES` declares seven families. `scaleTypes` are the canonical
8
+ post-normalize types; authored aliases never survive `normalize` (e.g. authored
9
+ `type: "log"` becomes `{ "type": "linear", "transform": "log10" }` — trained
10
+ models never report type `log`).
11
+
12
+ | Family | Aesthetics | Canonical types | Transforms | Authored aliases |
13
+ | ------------------- | ---------------------- | --------------------------------------------- | --------------------- | ---------------- |
14
+ | position-continuous | x, y | linear | identity, log10, sqrt | log |
15
+ | position-binned | x, y | binned | identity, log10, sqrt | — |
16
+ | position-temporal | x, y | time | identity | — |
17
+ | position-discrete | x, y | band | — | — |
18
+ | color-fill | color, fill | ordinal, sequential, binned, manual, identity | identity, log10, sqrt | — |
19
+ | numeric-style | size, linewidth, alpha | sequential, ordinal, binned, manual, identity | identity | — |
20
+ | finite-style | shape, linetype | ordinal, binned, manual, identity | — | — |
21
+
22
+ **Ordering contract**: scale transforms run before stats and positions (a
23
+ log10 x reshapes what `stat: "smooth"` sees). Coordinate transforms
24
+ (`coord: {"type": "transform", ...}`) run after stats and preserve stat inputs.
25
+
26
+ ### Position scales (x/y)
27
+
28
+ Options on any position scale (`PositionScaleSpec`):
29
+
30
+ | Option | Values / default | Notes |
31
+ | ------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
32
+ | `type` | linear, binned, time, band (+ authored alias log) | Omit to infer from field type |
33
+ | `transform` | identity (default), log10 (values > 0), sqrt (values >= 0) | Only identity on time scales |
34
+ | `domain` | `[min, max]` (continuous/time) or full category list (band) | Pins the scale; out-of-domain data drops with a warning |
35
+ | `oob` | censor (default) or squish | Applied to explicit source limits, before stats |
36
+ | `nice` | boolean, default true | Rounds inferred domain to tick-friendly bounds; ignored with `domain` |
37
+ | `zero` | boolean | Bars/cols/areas force true on the measure axis; set false to override |
38
+ | `expand` | `{ mult, add }` | Default `{mult: 0.05, add: 0}` non-temporal; time axes default to zero |
39
+ | `breaks` | numbers, or ISO strings on time scales | Explicit ticks; on binned scales these are bin boundaries, at most 65 (64 bins) |
40
+ | `minorBreaks` | numbers | Minor gridlines; time scales use `dateMinorBreaks` instead |
41
+ | `reverse` | boolean, default false | Flips output direction |
42
+ | `labels` | format string | Time: strftime-style; numeric: `",d"`, `".1f"`, `".0%"`, `"~s"` |
43
+ | `naValue` | number or null (default) | Substitute for missing positional values, after OOB, before transform |
44
+ | `guide` | GuideSpec | Axis styling; band axes accept BandAxisGuideSpec |
45
+
46
+ Helper functions additionally accept `limits` as authoring sugar for `domain`
47
+ (supplying both throws). `scaleXReverse`/`scaleYReverse` omit the `transform`
48
+ and `reverse` options by type. Binned integer ids stay private; guides and
49
+ events use semantic values.
50
+
51
+ ```json fragment
52
+ "scales": { "x": { "type": "linear", "transform": "log10", "limits": [1, 1000] } }
53
+ ```
54
+
55
+ ### Color and fill scales
56
+
57
+ Five types, each gating its own option set (`ColorScaleSpec`; forbidden
58
+ options fail schema validation):
59
+
60
+ - **ordinal** — discrete categories. `scheme` (categorical name, or a
61
+ sequential name for discrete sampling), `range` (explicit hex list),
62
+ `domain`, `domainMode` (`grow` default preserves assignments across filters;
63
+ `data` rebuilds), `reverse`, `onExhaust`. No transform, breaks, oob, labels.
64
+ - **sequential** — continuous ramp. `scheme` or `range` (>= 2 colors
65
+ interpolated), `transform`, `domain` `[min, max]`, `breaks` (colorbar
66
+ reference ticks), `oob` (`censor` default, `squish`, or `wrap` for
67
+ periodic data). Cyclic `*O` schemes require `wrap` (the default when
68
+ omitted) and an explicit domain period. Guide plan: `colorbar`.
69
+ - **binned** — color steps. Same shape as sequential but `breaks` are ordered
70
+ boundaries (at most 65). Guide plan: `colorsteps`.
71
+ - **manual** — explicit mapping. `range` is required and must supply one color
72
+ per `domain` value (mismatched lengths are the `color-manual-domain-range`
73
+ error). No scheme, breaks, reverse, oob, labels, onExhaust.
74
+ - **identity** — data cells are the colors. Only `naValue`, `unknownValue`,
75
+ and `guide` are allowed. Each source value is validated as `#rgb`/`#rrggbb`;
76
+ invalid cells map to `unknownValue` with a bounded warning. The guide is
77
+ suppressed by default — force it with `"guide": {"type": "legend", "force": true}`.
78
+
79
+ `naValue` colors null/missing cells; `unknownValue` colors invalid,
80
+ out-of-domain, or unmapped cells. Both default to `#999999`. Bounded warnings
81
+ count how many cells fell back. Guide plans are `discrete` (ordinal, manual,
82
+ identity), `colorbar` (sequential), or `colorsteps` (binned); identity and
83
+ single-entry manual guides need `force: true` to appear. When `type` is
84
+ omitted, a named `scheme` selects its family (categorical → ordinal,
85
+ sequential name → sequential).
86
+
87
+ **Headless color registration:** `@ggts-sh/core/render` and `registerBasic()`
88
+ install every color kind. Lean `@ggts-sh/core/headless` does not. Call
89
+ `registerDefaultOrdinalColor()` for the built-in palette or an explicit
90
+ `range`. A named categorical scheme (`observable10`, `Dark2`, …) needs
91
+ `registerOrdinalColor()`. A named sequential or diverging scheme (`viridis`,
92
+ `Blues`, Crameri `batlow`, …) needs `registerSequentialColor()` — a
93
+ sequential name with no `type` infers sequential. Explicit
94
+ `type: "ordinal"` still needs `registerOrdinalColor()`. Other kinds:
95
+ `registerBinnedColor()`, `registerManualColor()`, `registerIdentityColor()`.
96
+ A named scheme on the default-only register throws
97
+ `not registered in this build`. Full inventory: SKILL.md Registration.
98
+
99
+ ### Style scales
100
+
101
+ **Headless style registration:** mapped size / linewidth / alpha need
102
+ `registerNumericStyle()`; mapped shape / linetype need
103
+ `registerFiniteStyle()`. `@ggts-sh/core/render` and `registerBasic()`
104
+ install both. Categorical x/y axes also need `registerBandGuide()`.
105
+
106
+ - **numeric-style** (size, linewidth, alpha): sequential, ordinal, binned,
107
+ manual, identity — numeric output ranges instead of colors, identity
108
+ transform only. `sizeUnit` applies to size only (alpha's schema drops it so
109
+ it cannot no-op silently). Manual domain/range length mismatch is
110
+ `style-manual-domain-range`. Extra size helpers: `scaleSizeArea` /
111
+ `scale_size_area` (area-true scaling, ggplot2's recommended default),
112
+ `scaleSizeBinnedArea` / `scale_size_binned_area`, and `scaleRadius` /
113
+ `scale_radius`. Bare ggplot2 names `scale_size`, `scale_linewidth`,
114
+ `scale_alpha` alias the continuous helpers.
115
+ - **finite-style** (shape, linetype): ordinal, binned, manual, identity over a
116
+ closed symbol set (see the tables below) — no transform. Manual ranges and
117
+ identity values must be members of the audited name lists.
118
+
119
+ ## The Scale* component system
120
+
121
+ 98 generated `.svelte` shells plus 28 alias exports plus the hand-written
122
+ `<Scale>` escape hatch = 127 `Scale*` components exported from
123
+ `@ggts-sh/svelte`. Shells are generated by `bun run scale:children:gen`
124
+ (scripts/gen-scale-children.ts; `--check` is the drift gate). Do not edit them
125
+ by hand.
126
+
127
+ Which variants exist per channel (aliases in parentheses are extra export
128
+ names for the same component):
129
+
130
+ | Variant | X, Y | Color, Fill | Size | Linewidth | Alpha | Shape | Linetype |
131
+ | -------------------------------- | ---- | ----------- | ---------------------- | --------------------------- | ----------------------- | ----------------------- | -------------------------- |
132
+ | Continuous | yes | yes | yes | yes | yes | — | — |
133
+ | Discrete | yes | yes | yes (ScaleSizeOrdinal) | yes (ScaleLinewidthOrdinal) | yes (ScaleAlphaOrdinal) | yes (ScaleShapeOrdinal) | yes (ScaleLinetypeOrdinal) |
134
+ | Binned | yes | yes | yes | yes | yes | yes | yes |
135
+ | Log10, Sqrt | yes | yes | — | — | — | — | — |
136
+ | Reverse | yes | — | — | — | — | — | — |
137
+ | Date, Datetime | yes | yes | yes | yes | yes | — | — |
138
+ | Time | yes | — | — | — | — | — | — |
139
+ | Manual, Identity | — | yes | yes | yes | yes | yes | yes |
140
+ | Ordinal | — | yes | (alias) | (alias) | (alias) | (alias) | (alias) |
141
+ | Brewer, Distiller, Fermenter | — | yes | — | — | — | — | — |
142
+ | Steps, Steps2, Stepsn | — | yes | — | — | — | — | — |
143
+ | Gradient, Gradient2, Gradientn | — | yes | — | — | — | — | — |
144
+ | Hue, Grey | — | yes | — | — | — | — | — |
145
+ | ViridisC, ViridisD, ViridisB | — | yes | — | — | — | — | — |
146
+ | Area, BinnedArea (+ ScaleRadius) | — | — | yes | — | — | — | — |
147
+
148
+ Every `ScaleColor*` component also exports a British `ScaleColour*` alias (24
149
+ aliases; same file, same binding). `ScaleColorOrdinal` is a distinct component
150
+ (explicit ordinal family), unlike the style-channel Ordinal names which are
151
+ re-exports of the Discrete shells.
152
+
153
+ **Function-twin pattern.** Every shell wraps one camelCase helper from
154
+ `@ggts-sh/spec`; the component's props are that helper's options object
155
+ flattened (undefined props are stripped). Each camelCase helper has a
156
+ binding-identical ggplot2 snake_case twin, and every Color helper has a
157
+ binding-identical Colour twin: `scaleXLog10` is `scale_x_log10`;
158
+ `scaleColourBrewer` is `scaleColorBrewer` is `scale_color_brewer` is
159
+ `scale_colour_brewer`. Brewer/Distiller/Fermenter and Viridis helpers accept
160
+ ggplot2's `palette`/`option` (maps to `scheme`) and `direction` (`-1` sets
161
+ `reverse: true`).
162
+
163
+ ```svelte fragment
164
+ <GGPlot
165
+ data={rows}
166
+ aes={{ x: "day", y: "kwh", color: "site" }}
167
+ layers={[{ geom: "line" }]}
168
+ >
169
+ <ScaleXDate dateBreaks="1 month" dateLabels="%b %Y" />
170
+ <ScaleColorBrewer palette="Dark2" />
171
+ </GGPlot>
172
+ ```
173
+
174
+ **Escape hatch.** `<Scale value={scalesFragment} />` registers a raw `Scales`
175
+ fragment — use it for computed scales or variable references. It is
176
+ byte-identity-preserving: `value` is not routed through any helper.
177
+
178
+ ## Palettes
179
+
180
+ ### Categorical schemes (47)
181
+
182
+ | Scheme | Description |
183
+ | --------------------- | ------------------------------------------------------------------ |
184
+ | observable10 | Default: 10-hue Observable 10 palette |
185
+ | ipsum | hrbrthemes ipsum palette, published source order |
186
+ | flexoki | Flexoki light-background qualitative palette |
187
+ | colorblind | ggthemes 8-color colorblind-safe palette |
188
+ | stata | ggthemes Stata s2color scheme (15 colors, the default) |
189
+ | economist | ggthemes Economist blues/greens (scale_colour/fill_economist) |
190
+ | solarized | ggthemes Solarized accents, blue first (scale_*_solarized) |
191
+ | few | ggthemes Few "Medium" — the default `scale_colour_few` palette |
192
+ | few_light | ggthemes Few "Light" — for filled areas (`scale_fill_few` default) |
193
+ | few_dark | ggthemes Few "Dark" — for thin lines and highlighting |
194
+ | fivethirtyeight | ggthemes FiveThirtyEight three-color line palette (blue/red/green) |
195
+ | ptol | ggthemes Paul Tol qualitative 12 (full-capacity order) |
196
+ | canva | ggthemes Canva "Fresh and bright" 4 (the `scale_*_canva` default) |
197
+ | wsj | ggthemes WSJ colors6 — the default `scale_*_wsj` palette |
198
+ | wsj_rgby | ggthemes WSJ red/green/blue/yellow |
199
+ | wsj_red_green | ggthemes WSJ green/red good–bad pair |
200
+ | wsj_black_green | ggthemes WSJ black-to-green sentiment scale |
201
+ | wsj_dem_rep | ggthemes WSJ Democrat/Republican/Undecided |
202
+ | tableau20 | ggthemes Tableau 20 (classic paired set) |
203
+ | tableau_colorblind | ggthemes Tableau Color Blind 10 |
204
+ | tableau_seattle_grays | ggthemes Tableau Seattle Grays (5) |
205
+ | tableau_jewel_bright | ggthemes Tableau Jewel Bright (9) |
206
+ | tableau_hue_circle | ggthemes Tableau Hue Circle (19) |
207
+ | pander | ggthemes pander colorblind/printer-friendly 8 (Okabe-Ito order) |
208
+ | Dark2 | ColorBrewer qualitative, dark tones |
209
+ | hue | Evenly spaced HSL hues — the ggplot2-shaped `scale_*_hue` default |
210
+ | actonS | Crameri categorical 10-class prefix (unordered) |
211
+ | bamakoS | Crameri categorical 10-class prefix (unordered) |
212
+ | batlowS | Crameri categorical 10-class prefix (unordered) |
213
+ | batlowWS | Crameri categorical 10-class prefix (unordered) |
214
+ | batlowKS | Crameri categorical 10-class prefix (unordered) |
215
+ | bilbaoS | Crameri categorical 10-class prefix (unordered) |
216
+ | budaS | Crameri categorical 10-class prefix (unordered) |
217
+ | davosS | Crameri categorical 10-class prefix (unordered) |
218
+ | devonS | Crameri categorical 10-class prefix (unordered) |
219
+ | glasgowS | Crameri categorical 10-class prefix (unordered) |
220
+ | grayCS | Crameri categorical 10-class prefix (unordered) |
221
+ | hawaiiS | Crameri categorical 10-class prefix (unordered) |
222
+ | imolaS | Crameri categorical 10-class prefix (unordered) |
223
+ | lajollaS | Crameri categorical 10-class prefix (unordered) |
224
+ | lapazS | Crameri categorical 10-class prefix (unordered) |
225
+ | lipariS | Crameri categorical 10-class prefix (unordered) |
226
+ | naviaS | Crameri categorical 10-class prefix (unordered) |
227
+ | nuukS | Crameri categorical 10-class prefix (unordered) |
228
+ | osloS | Crameri categorical 10-class prefix (unordered) |
229
+ | tokyoS | Crameri categorical 10-class prefix (unordered) |
230
+ | turkuS | Crameri categorical 10-class prefix (unordered) |
231
+
232
+ There is no registered `"grey"` / `"gray"` scheme name. Use
233
+ `<ScaleColorGrey />` / `scaleColorGrey()` (or `start`/`end`) — those bake an
234
+ explicit greyscale `range`. Non-cyclic sequential scheme names are also legal
235
+ on ordinal scales (discrete sampling along the ramp — what `scale_*_viridis_d`
236
+ does). Cyclic `*O` names are sequential only.
237
+
238
+ **Palette exhaustion** (`onExhaust`, ordinal scales only): `cycle` (default)
239
+ restarts the palette and emits the bounded `palette-exhausted` warning once;
240
+ `error` throws the `palette-exhausted` pipeline error instead.
241
+
242
+ ### Sequential schemes (60)
243
+
244
+ | Scheme | Description |
245
+ | -------- | ------------------------------------------------------ |
246
+ | viridis | Default sequential: perceptually uniform purple→yellow |
247
+ | magma | Perceptually uniform black→red→pale yellow |
248
+ | plasma | Perceptually uniform blue→magenta→yellow |
249
+ | inferno | Perceptually uniform black→orange→yellow |
250
+ | cividis | Colorblind-optimized blue→yellow |
251
+ | turbo | Google turbo rainbow (high contrast, not uniform) |
252
+ | Blues | ColorBrewer single-hue light→dark blue |
253
+ | Greens | ColorBrewer single-hue light→dark green |
254
+ | Reds | ColorBrewer single-hue light→dark red |
255
+ | Oranges | ColorBrewer single-hue light→dark orange |
256
+ | Purples | ColorBrewer single-hue light→dark purple |
257
+ | Greys | ColorBrewer single-hue light→dark grey |
258
+ | YlOrRd | ColorBrewer multi-hue yellow→orange→red |
259
+ | YlGnBu | ColorBrewer multi-hue yellow→green→blue |
260
+ | BuPu | ColorBrewer multi-hue blue→purple |
261
+ | RdYlBu | ColorBrewer diverging red→yellow→blue |
262
+ | RdBu | ColorBrewer diverging red→white→blue |
263
+ | BrBG | ColorBrewer diverging brown→teal |
264
+ | Spectral | ColorBrewer diverging rainbow |
265
+ | PuOr | ColorBrewer diverging purple→orange |
266
+ | acton | Crameri sequential (scientific, CVD-friendly) |
267
+ | bamako | Crameri sequential (scientific, CVD-friendly) |
268
+ | batlow | Crameri sequential flagship (#UseBatlow) |
269
+ | batlowW | Crameri sequential batlow light end |
270
+ | batlowK | Crameri sequential batlow dark end |
271
+ | bilbao | Crameri sequential (scientific, CVD-friendly) |
272
+ | buda | Crameri sequential (scientific, CVD-friendly) |
273
+ | davos | Crameri sequential (scientific, CVD-friendly) |
274
+ | devon | Crameri sequential (scientific, CVD-friendly) |
275
+ | glasgow | Crameri sequential (scientific, CVD-friendly) |
276
+ | grayC | Crameri sequential greyscale |
277
+ | hawaii | Crameri sequential (scientific, CVD-friendly) |
278
+ | imola | Crameri sequential (scientific, CVD-friendly) |
279
+ | lajolla | Crameri sequential (scientific, CVD-friendly) |
280
+ | lapaz | Crameri sequential (scientific, CVD-friendly) |
281
+ | lipari | Crameri sequential (scientific, CVD-friendly) |
282
+ | navia | Crameri sequential (scientific, CVD-friendly) |
283
+ | naviaW | Crameri sequential navia light end |
284
+ | nuuk | Crameri sequential (scientific, CVD-friendly) |
285
+ | oslo | Crameri sequential (scientific, CVD-friendly) |
286
+ | tokyo | Crameri sequential (scientific, CVD-friendly) |
287
+ | turku | Crameri sequential (scientific, CVD-friendly) |
288
+ | bam | Crameri diverging (scientific, CVD-friendly) |
289
+ | berlin | Crameri diverging (scientific, CVD-friendly) |
290
+ | broc | Crameri diverging (scientific, CVD-friendly) |
291
+ | cork | Crameri diverging (scientific, CVD-friendly) |
292
+ | lisbon | Crameri diverging (scientific, CVD-friendly) |
293
+ | managua | Crameri diverging (scientific, CVD-friendly) |
294
+ | roma | Crameri diverging (scientific, CVD-friendly) |
295
+ | tofino | Crameri diverging (scientific, CVD-friendly) |
296
+ | vanimo | Crameri diverging (scientific, CVD-friendly) |
297
+ | vik | Crameri diverging (scientific, CVD-friendly) |
298
+ | bukavu | Crameri multi-sequential (scientific, CVD-friendly) |
299
+ | fes | Crameri multi-sequential (scientific, CVD-friendly) |
300
+ | oleron | Crameri multi-sequential (scientific, CVD-friendly) |
301
+ | bamO | Crameri cyclic — periodic data only; wrap OOB |
302
+ | brocO | Crameri cyclic — periodic data only; wrap OOB |
303
+ | corkO | Crameri cyclic — periodic data only; wrap OOB |
304
+ | romaO | Crameri cyclic — periodic data only; wrap OOB |
305
+ | vikO | Crameri cyclic — periodic data only; wrap OOB |
306
+
307
+ ### Finite symbol sets (default assignment order)
308
+
309
+ | Point shapes (7) | Linetypes (6) |
310
+ | ---------------- | ------------- |
311
+ | circle | solid |
312
+ | triangle | dashed |
313
+ | square | dotted |
314
+ | diamond | dotdash |
315
+ | plus | longdash |
316
+ | cross | twodash |
317
+ | circle-open | |
318
+
319
+ ## Temporal scales
320
+
321
+ **Registration:** Temporal scale children (`<ScaleXDate>`, `<ScaleYDatetime>`,
322
+ `<ScaleColorDate>`, …) call `installTemporal()` on import. Spec-driven
323
+ `spec` / `layers` charts have no child: call `installTemporal()` or
324
+ `registerAll()` when they set `type: "time"`, a named parser, timezone, date
325
+ interval, or full Temporal guides. ISO strings with no explicit temporal
326
+ options keep the lean UTC path.
327
+
328
+ **Auto-inference.** ISO dates and date-times, four-digit-year strings,
329
+ year-months, month-years, and year-quarters infer a time scale after bounded
330
+ sampling plus whole-column validation. Ambiguous ordered dates (`01/02/2026`)
331
+ stay discrete — set `"parse": "dmy"` or `"parse": "mdy"`. For year-like
332
+ identifiers that are labels, not instants, force `{"type": "band"}`. Never
333
+ preprocess dates into row indexes. Explicit `linear`/`binned` (including
334
+ authored `log`) disables temporal inference, so numeric strings stay
335
+ quantitative. Explicit ordinal color/fill keeps temporal-looking labels as
336
+ separate groups; sequential temporal color/fill parses domains and uses
337
+ calendar legend labels.
338
+
339
+ **Time-scale options** (position and sequential/binned color scales):
340
+
341
+ | Option | Values / default |
342
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
343
+ | `parse` | Named parser (`"iso"`, `"year"`, `"ym"`, `"my"`, `"yq"`, `"ymd"`, `"ydm"`, `"mdy"`, `"myd"`, `"dmy"`, `"dym"`, and their `_hm`/`_hms` variants), exact `{ "format": "..." }`, or `{ "epoch": "seconds" \| "milliseconds" }` |
344
+ | `parseFailure` | `error` (default) stops with bounded evidence; `censor` drops invalid values to missing with a warning. Censoring requires an explicit parser |
345
+ | `temporalKind` | `date` (calendar dates), `datetime` (instants), `time` (position only: time of day) |
346
+ | `timezone` | IANA zone for timezone-less input; default `UTC` |
347
+ | `disambiguation` | DST gap/fold policy: `reject` (default), `earlier`, `later`, `compatible` |
348
+ | `dateBreaks`, `dateMinorBreaks` | `"<step> <unit>"`, e.g. `"2 weeks"`; units millisecond…year, step 1–1,000,000 |
349
+ | `dateLabels` | Strict tokens: `%Y %y %m %b %B %d %e %a %A %H %I %M %S %L %p %q %z %Z %%` |
350
+ | `locale` | BCP 47 tag for labels; default `en-US` |
351
+ | `weekStart` | Weekday for week boundaries; default `monday` |
352
+
353
+ **Precedence**: explicit `breaks` outrank `dateBreaks`; `dateLabels` outranks
354
+ `labels`. Authored labels are preserved with a diagnostic — never silently
355
+ rotated, thinned, or truncated. Automatic temporal labels use measured panel
356
+ extent and calendar boundaries; inspect `model.guidePlans` for the selected
357
+ interval and labels.
358
+
359
+ **Authoring surfaces.** The `gg()` builder has `.scaleXDate()`,
360
+ `.scaleXDatetime()`, `.scaleXTime()` (and Y forms), each with component twins
361
+ (`<ScaleXDate/>` …) and function twins (`scaleXDate`/`scale_x_date` …).
362
+ Date helpers serialize mapped authoring `Date` cells as calendar dates;
363
+ datetime helpers preserve instants. `scaleXTime`/`scale_x_time` is time-of-day
364
+ only: portable numbers are **seconds since midnight** (mapped to epoch ms on
365
+ 1970-01-01Z), `Date` cells use the UTC clock portion, and default labels are
366
+ `%H:%M:%S`.
367
+
368
+ **Parser helpers** (from `@ggts-sh/spec`): `ymd`, `ydm`, `mdy`, `myd`, `dmy`,
369
+ `dym` (each also as `_hm`/`_hms`, e.g. `dmy_hms`), `ym`, `my`, `yq`,
370
+ `parseTemporalFormat(value, format)`, `fromEpochSeconds`,
371
+ `fromEpochMilliseconds`. Each accepts one value or an array, returns `Date`(s),
372
+ and throws `TemporalParseError` on failure — use them to convert columns at
373
+ authoring time instead of hand-rolled parsing.
374
+
375
+ ```json complete
376
+ {
377
+ "data": {
378
+ "values": [
379
+ { "day": "2026-01-05", "kwh": 12.1, "site": "north" },
380
+ { "day": "2026-02-02", "kwh": 14.9, "site": "north" },
381
+ { "day": "2026-01-05", "kwh": 13.4, "site": "south" },
382
+ { "day": "2026-02-02", "kwh": 16.0, "site": "south" }
383
+ ]
384
+ },
385
+ "aes": {
386
+ "x": { "field": "day" },
387
+ "y": { "field": "kwh" },
388
+ "color": { "field": "site" }
389
+ },
390
+ "layers": [{ "geom": "line" }],
391
+ "scales": {
392
+ "x": { "type": "time", "dateBreaks": "1 month", "dateLabels": "%b %Y" },
393
+ "color": { "type": "ordinal", "scheme": "Dark2" }
394
+ }
395
+ }
396
+ ```
@@ -0,0 +1,79 @@
1
+ # Svelte 5
2
+
3
+ Use this reference for a Svelte target. Shared grammar and validation live in
4
+ [the skill](../SKILL.md). For browser interactions, read
5
+ [interaction rules](interactions.md) before adding Inspect, selection, zoom, or linked plots.
6
+
7
+ ## Svelte composition — children are canonical
8
+
9
+ ```svelte complete
10
+ <script lang="ts">
11
+ import {
12
+ GeomPoint,
13
+ GeomSmooth,
14
+ GGPlot,
15
+ Labs,
16
+ ScaleColorDiscrete,
17
+ ThemeMinimal,
18
+ } from "@ggts-sh/svelte";
19
+
20
+ const cars = [
21
+ { displ: 1.8, hwy: 29, class: "compact" },
22
+ { displ: 2.0, hwy: 31, class: "compact" },
23
+ { displ: 3.5, hwy: 26, class: "midsize" },
24
+ { displ: 5.3, hwy: 20, class: "suv" },
25
+ { displ: 5.7, hwy: 17, class: "suv" },
26
+ { displ: 6.2, hwy: 16, class: "suv" },
27
+ ];
28
+ </script>
29
+
30
+ <GGPlot data={cars} aes={{ x: "displ", y: "hwy", color: "class" }} height={400}>
31
+ <GeomSmooth method="loess" se={false} />
32
+ <GeomPoint size={3} alpha={0.85} />
33
+ <ScaleColorDiscrete scheme="observable10" />
34
+ <ThemeMinimal />
35
+ <Labs
36
+ title="Bigger engines, thirstier cars"
37
+ x="Displacement (l)"
38
+ y="Highway mpg"
39
+ color="Class"
40
+ />
41
+ </GGPlot>
42
+ ```
43
+
44
+ Convention (ggplot2 thinking order): mark layers first, then scales / coords /
45
+ facets, then theme / guides / labs, then host-only `<Inspect>` last. Grammar
46
+ **layers** (theme/scale/coord/facet/guides/labs/legend) render no markup and
47
+ register declaratively as non-mark `Layer` kinds; mark-layer registration
48
+ order is z-order (points above smooth here). Interleave does not change the
49
+ assembled PortableSpec beyond mark z-order and last-wins folds within a
50
+ family. Every geom takes aesthetics through one `aes` object prop
51
+ (bare-string shorthand allowed) and constant style params as direct props
52
+ (`size={3}`); structural props are `data`, `stat`, `position`,
53
+ `positionParams`, `render`.
54
+
55
+ `<GGPlot>` props: `spec`, `data`, `aes`, `layers`, `width`
56
+ (number | "container"), `height`, `a11y`, `ariaLabel`, the interaction props
57
+ (`select`, `zoom`, `tool`, `interaction`, `interactionScope`; prefer
58
+ `<Inspect>` for inspection and `<GuideLegend channel focus>` /
59
+ `<GuideLegend channel filter>` for legend interaction — do not put
60
+ `inspect` / `legendFocus` / `legendFilter` on `<GGPlot>` in new code), the
61
+ `on*` handlers, and `children`. Plot-level `key` is **deprecated** since
62
+ 0.21 — prefer `identity` on `<Inspect>`, object-form `select`, or
63
+ `createPlotInteraction` (default: `id` column when present, else row index).
64
+ Instance methods: `resetScales()`, `setZoom()`.
65
+
66
+ Precedence: `spec` wins over everything else. For mark layers, an explicit
67
+ `layers` prop wins over geom children — use it for dynamic layer lists (a keyed
68
+ `{#each}` reorder does not preserve z-order). For grammar families, children
69
+ win. Coord/facet/theme REPLACE (last wins); scales merge per channel;
70
+ labs/guides/legend merge per key. Duplicates emit `DUPLICATE_PLOT_LAYER` /
71
+ `DUPLICATE_SCALE_CHANNEL` / `DUPLICATE_MERGE_KEY` advisories — full semantics
72
+ in
73
+ [references/composition-surfaces.md](composition-surfaces.md).
74
+
75
+ **Removed in 0.13.0** (deprecated since 0.11.0): the seven `<GGPlot>` grammar
76
+ props `facet`, `coord`, `scales`, `guides`, `legend`, `theme`, `labs`. Compose
77
+ them as children instead; `spec`, `data`, `aes`, and `layers` stay first-class.
78
+ Migrate old sources with `npm exec --package=@ggts-sh/svelte -- ggts-codemod --write src` (dry-run without
79
+ `--write`).
@@ -0,0 +1,96 @@
1
+ <!-- Source of truth: packages/spec/src/schema-names.ts (THEME_NAMES, THEME_NAME_ALIASES), packages/core/src/theme-builtins.ts (token maps), packages/svelte/src/lib (Theme* shells). Inventory table is asserted complete by scripts/skill-content/inventory.test.ts. -->
2
+
3
+ # Themes
4
+
5
+ JSON form: `"theme": "minimal"` (a registered name) or a theme object —
6
+ optional `"name"` base plus role overrides, e.g.
7
+ `{"name": "dark", "ink": "#eee"}`. `grey` and `gray` are registered aliases of
8
+ `ggplot2` (same token map). Theme is a REPLACE grammar family on `<GGPlot>`
9
+ (last child wins) — merge rules live in
10
+ [composition-surfaces.md](composition-surfaces.md).
11
+
12
+ ## Product themes (32)
13
+
14
+ | Name | Look |
15
+ | ----------------------- | -------------------------------------------------------------------------- |
16
+ | default | quiet hrbrthemes-style base: real typography, hairline grid, no axis frame |
17
+ | light | light grid, thin panel border, x/y ticks |
18
+ | dark | dark paper and panel, light ink |
19
+ | minimal | light grid only, no ticks or border |
20
+ | ggplot2 | classic grey panel, white grid |
21
+ | classic | no grid, black axis lines and ticks |
22
+ | bw | white panel, grey grid, black rectangular border (print/B&W) |
23
+ | hrbr | same token map as default |
24
+ | few | no grid, thin panel border (Stephen Few) |
25
+ | clean | dashed y grid only, axis lines and ticks |
26
+ | fivethirtyeight | grey paper and panel, white grid, blue accent |
27
+ | economist | pale blue paper, white grid, x ticks, red accent |
28
+ | tufte | monochrome ink, no grid |
29
+ | linedraw | black-on-white line art: hairline black grid, black border |
30
+ | void | no axes, grid, or panel chrome; marks and legends remain |
31
+ | stata | Stata s2color: bluish-gray plot region, white panel, y-grid |
32
+ | stata_s1color | Stata s1color: white panel with black border, light y-grid |
33
+ | solarized | Solarized light: cream panel, muted base1 chrome, blue accent |
34
+ | solarizeddark | Solarized dark: deep teal panel, muted base01 chrome, blue accent |
35
+ | economist_white | Economist Graphic Detail: white panel, gray grid, light-gray paper |
36
+ | solarized_2 | Solarized grey-style variant: base2 panel, base3 grid, no frame |
37
+ | solarized_2dark | solarized_2 on dark base tones |
38
+ | wsj | Wall Street Journal: brown paper, dotted black y-grid, x line + ticks |
39
+ | hc | Highcharts default: y-only #D8D8D8 grid on white, no border |
40
+ | hcdark | Highcharts darkunica: #2a2a2b paper, #707073 y-grid |
41
+ | pander | pander: dashed grey grid and ticks, bold title on white |
42
+ | base | base R: black frame and ticks, no grid, bold title |
43
+ | igray | inverse gray: white panel, gray90 surround and grid |
44
+ | map | every axis/panel/grid element blank — marks only, for maps |
45
+ | solid | nothing but marks — every non-geom element removed |
46
+ | grey (alias of ggplot2) | UK theme_grey |
47
+ | gray (alias of ggplot2) | US theme_gray |
48
+
49
+ The internal `test` theme exists for snapshots only — not a product surface;
50
+ do not document or recommend it.
51
+
52
+ **Headless named themes:** `@ggts-sh/core/headless` resolves only `default`
53
+ and `void`. A named catalog theme (`dark`, `minimal`, `economist`, …) on that
54
+ entry throws. Use `@ggts-sh/core` or `@ggts-sh/core/render` (or pass the
55
+ full editions table). This is not a `register*()` call. Full inventory:
56
+ SKILL.md Registration.
57
+
58
+ ## Svelte shells and overrides
59
+
60
+ One named shell per product theme — `ThemeDefault`, `ThemeLight`,
61
+ `ThemeDark`, `ThemeMinimal`, `ThemeGgplot2`, `ThemeClassic`, `ThemeBw`,
62
+ `ThemeHrbr`, `ThemeFew`, `ThemeClean`, `ThemeFivethirtyeight`,
63
+ `ThemeEconomist`, `ThemeTufte`, `ThemeLinedraw`, `ThemeVoid`, `ThemeStata`,
64
+ `ThemeStatas1color`, `ThemeSolarized`, `ThemeSolarizeddark`,
65
+ `ThemeEconomistwhite`, `ThemeSolarized2`, `ThemeSolarized2dark`, `ThemeWsj`,
66
+ `ThemeHc`, `ThemeHcdark`, `ThemePander`, `ThemeBase`, `ThemeIgray`,
67
+ `ThemeMap`, `ThemeSolid`, `ThemeGrey`, `ThemeGray`. Escape hatch
68
+ `<Theme name={dynamicName} />` for reactive names.
69
+
70
+ Every shell and `<Theme>` also accepts role-override props (`ink`, `paper`,
71
+ `accent`, `grid`, `panel`, `axisText`, `axisLine`, `tickColor`,
72
+ `panelBorder`, tooltip/selection/focus roles, …): `<ThemeDark ink="#eee" />`.
73
+
74
+ ```svelte fragment
75
+ <GGPlot data={rows} aes={{ x: "displ", y: "hwy" }} layers={[{ geom: "point" }]}>
76
+ <ThemeEconomist />
77
+ </GGPlot>
78
+ ```
79
+
80
+ ```json complete
81
+ {
82
+ "data": {
83
+ "values": [
84
+ { "displ": 1.8, "hwy": 29 },
85
+ { "displ": 3.5, "hwy": 26 }
86
+ ]
87
+ },
88
+ "layers": [
89
+ {
90
+ "geom": "point",
91
+ "aes": { "x": { "field": "displ" }, "y": { "field": "hwy" } }
92
+ }
93
+ ],
94
+ "theme": "economist"
95
+ }
96
+ ```