@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,441 @@
1
+ <!-- Recipes adapted from examples/<category>/<name>/ where noted; hand-authored recipes are validated by scripts/skill-content/fences.test.ts (normalize + validate on every json complete fence). -->
2
+
3
+ # Long-tail recipes
4
+
5
+ Each recipe pairs a validated JSON `PortableSpec` with its Svelte-children twin.
6
+ Inline data is cut to a few rows — swap in yours. Plot-level `aes` inherits into
7
+ every layer; a layer `aes` merges over it, and `null` unsets a channel.
8
+
9
+ ## Error bars from raw values (stat summary)
10
+
11
+ Raw observations per group; the summary stat computes mean ± se, no preaggregation.
12
+ <!-- adapted from examples/errorbar/mean-se/ -->
13
+
14
+ ```json complete
15
+ {
16
+ "data": {
17
+ "values": [
18
+ { "drug": "A", "sleep": 0.7 },
19
+ { "drug": "A", "sleep": 1.6 },
20
+ { "drug": "A", "sleep": 0.2 },
21
+ { "drug": "B", "sleep": 1.9 },
22
+ { "drug": "B", "sleep": 3.4 },
23
+ { "drug": "B", "sleep": 2.5 }
24
+ ]
25
+ },
26
+ "aes": { "x": { "field": "drug" }, "y": { "field": "sleep" } },
27
+ "layers": [
28
+ {
29
+ "geom": "point",
30
+ "position": "jitter",
31
+ "positionParams": { "width": 0.1, "height": 0, "seed": 7 },
32
+ "params": { "alpha": 0.5 }
33
+ },
34
+ { "geom": "errorbar", "stat": "summary", "params": { "width": 0.3 } }
35
+ ]
36
+ }
37
+ ```
38
+
39
+ ```svelte fragment
40
+ <GGPlot data={rows} aes={{ x: "drug", y: "sleep" }}>
41
+ <GeomPoint
42
+ position="jitter"
43
+ positionParams={{ width: 0.1, height: 0, seed: 7 }}
44
+ alpha={0.5}
45
+ />
46
+ <GeomErrorbar stat="summary" width={0.3} />
47
+ </GGPlot>
48
+ ```
49
+
50
+ The `stat="summary"` override needs one call at app startup:
51
+ `import { registerSummary } from "@ggts-sh/svelte"; registerSummary();`
52
+ (or `registerAll()`; the JSON spec twin needs `registerAll()` regardless).
53
+
54
+ ## Value labels on columns
55
+
56
+ `dy`/`dx` are PIXEL offsets (negative dy = up); `position: "nudge"` + `positionParams` offsets in DATA units instead.
57
+ <!-- adapted from examples/text/labels/ -->
58
+
59
+ ```json complete
60
+ {
61
+ "data": {
62
+ "values": [
63
+ { "cat": "a", "n": 4 },
64
+ { "cat": "b", "n": 7 },
65
+ { "cat": "c", "n": 5 }
66
+ ]
67
+ },
68
+ "aes": { "x": { "field": "cat" }, "y": { "field": "n" } },
69
+ "layers": [
70
+ { "geom": "col" },
71
+ {
72
+ "geom": "text",
73
+ "aes": { "label": { "field": "n" } },
74
+ "params": { "dy": -8 }
75
+ }
76
+ ]
77
+ }
78
+ ```
79
+
80
+ ```svelte fragment
81
+ <GGPlot data={rows} aes={{ x: "cat", y: "n" }}>
82
+ <GeomCol />
83
+ <GeomText aes={{ label: "n" }} dy={-8} />
84
+ </GGPlot>
85
+ ```
86
+
87
+ ## Big scatter on canvas
88
+
89
+ `"render": "canvas"` forces the canvas path; layers over `CANVAS_AUTO_THRESHOLD` (2000) marks switch on their own. Plot-level `"a11y": "force-svg"` overrides for assistive tech.
90
+
91
+ ```json complete
92
+ {
93
+ "data": {
94
+ "values": [
95
+ { "x": 1, "y": 2 },
96
+ { "x": 2, "y": 3 },
97
+ { "x": 3, "y": 2.5 },
98
+ { "x": 4, "y": 4 }
99
+ ]
100
+ },
101
+ "layers": [
102
+ {
103
+ "geom": "point",
104
+ "render": "canvas",
105
+ "aes": { "x": { "field": "x" }, "y": { "field": "y" } },
106
+ "params": { "size": 1.2, "alpha": 0.4 }
107
+ }
108
+ ],
109
+ "a11y": "force-svg"
110
+ }
111
+ ```
112
+
113
+ ```svelte fragment
114
+ <GGPlot data={rows} aes={{ x: "x", y: "y" }} a11y="force-svg">
115
+ <GeomPoint render="canvas" size={1.2} alpha={0.4} />
116
+ </GGPlot>
117
+ ```
118
+
119
+ ## Log10 axis scatter
120
+
121
+ Positive data only; the transform runs before stats. Authored `"type": "log"` canonicalizes to this form.
122
+ <!-- adapted from examples/point/log-scale/ -->
123
+
124
+ ```json complete
125
+ {
126
+ "data": {
127
+ "values": [
128
+ { "density": 10, "rate": 3 },
129
+ { "density": 100, "rate": 9 },
130
+ { "density": 1000, "rate": 30 },
131
+ { "density": 5000, "rate": 55 }
132
+ ]
133
+ },
134
+ "layers": [
135
+ {
136
+ "geom": "point",
137
+ "aes": { "x": { "field": "density" }, "y": { "field": "rate" } }
138
+ }
139
+ ],
140
+ "scales": { "x": { "type": "linear", "transform": "log10" } }
141
+ }
142
+ ```
143
+
144
+ ```svelte fragment
145
+ <GGPlot data={rows} aes={{ x: "density", y: "rate" }}>
146
+ <GeomPoint />
147
+ <ScaleXLog10 />
148
+ </GGPlot>
149
+ ```
150
+
151
+ ## Violin with jittered points
152
+
153
+ Distribution shape per group; needs several y values per group for the density. Seeded jitter is deterministic.
154
+ <!-- adapted from examples/boxplot/violin/ -->
155
+
156
+ ```json complete
157
+ {
158
+ "data": {
159
+ "values": [
160
+ { "run": "a", "v": 1 },
161
+ { "run": "a", "v": 2 },
162
+ { "run": "a", "v": 2.4 },
163
+ { "run": "a", "v": 3.1 },
164
+ { "run": "a", "v": 1.7 },
165
+ { "run": "b", "v": 4 },
166
+ { "run": "b", "v": 5.2 },
167
+ { "run": "b", "v": 4.4 },
168
+ { "run": "b", "v": 6 },
169
+ { "run": "b", "v": 5.1 }
170
+ ]
171
+ },
172
+ "aes": { "x": { "field": "run" }, "y": { "field": "v" } },
173
+ "layers": [
174
+ { "geom": "violin", "params": { "trim": true, "alpha": 0.7 } },
175
+ {
176
+ "geom": "point",
177
+ "position": "jitter",
178
+ "positionParams": { "width": 0.08, "height": 0, "seed": 3 },
179
+ "params": { "size": 2 }
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ ```svelte fragment
186
+ <GGPlot data={rows} aes={{ x: "run", y: "v" }}>
187
+ <GeomViolin trim alpha={0.7} />
188
+ <GeomPoint
189
+ position="jitter"
190
+ positionParams={{ width: 0.08, height: 0, seed: 3 }}
191
+ size={2}
192
+ />
193
+ </GGPlot>
194
+ ```
195
+
196
+ ## Tile heatmap (discrete x/y, continuous fill)
197
+
198
+ One tile per (x, y) pair; a sequential fill scale carries the value.
199
+ <!-- adapted from examples/tile/heatmap/ -->
200
+
201
+ ```json complete
202
+ {
203
+ "data": {
204
+ "values": [
205
+ { "day": "Mon", "hour": "am", "n": 3 },
206
+ { "day": "Mon", "hour": "pm", "n": 8 },
207
+ { "day": "Tue", "hour": "am", "n": 5 },
208
+ { "day": "Tue", "hour": "pm", "n": 12 }
209
+ ]
210
+ },
211
+ "layers": [
212
+ {
213
+ "geom": "tile",
214
+ "aes": {
215
+ "x": { "field": "day" },
216
+ "y": { "field": "hour" },
217
+ "fill": { "field": "n" }
218
+ }
219
+ }
220
+ ],
221
+ "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }
222
+ }
223
+ ```
224
+
225
+ ```svelte fragment
226
+ <GGPlot data={rows} aes={{ x: "day", y: "hour", fill: "n" }}>
227
+ <GeomTile />
228
+ <ScaleFillContinuous scheme="viridis" />
229
+ </GGPlot>
230
+ ```
231
+
232
+ ## Hex density heatmap
233
+
234
+ Overplotted x/y pairs binned into hexagons; fill defaults to the stat's `count`. `bin_2d` is the rectangular twin.
235
+ <!-- adapted from examples/hex/basic/ -->
236
+
237
+ ```json complete
238
+ {
239
+ "data": {
240
+ "values": [
241
+ { "x": 1, "y": 1 },
242
+ { "x": 1.1, "y": 1.2 },
243
+ { "x": 0.9, "y": 0.8 },
244
+ { "x": 3, "y": 3 },
245
+ { "x": 3.2, "y": 2.9 },
246
+ { "x": 2.8, "y": 3.1 }
247
+ ]
248
+ },
249
+ "layers": [
250
+ {
251
+ "geom": "hex",
252
+ "aes": { "x": { "field": "x" }, "y": { "field": "y" } },
253
+ "params": { "bins": 6 }
254
+ }
255
+ ]
256
+ }
257
+ ```
258
+
259
+ ```svelte fragment
260
+ <GGPlot data={rows} aes={{ x: "x", y: "y" }}>
261
+ <GeomHex bins={6} />
262
+ </GGPlot>
263
+ ```
264
+
265
+ ## Step chart (ECDF)
266
+
267
+ Stairs through precomputed points; `direction: "hv"` is the ECDF convention. `step` takes stat `identity` only, so compute F̂(x) = i/n yourself.
268
+ <!-- adapted from examples/step/ecdf/ -->
269
+
270
+ ```json complete
271
+ {
272
+ "data": {
273
+ "values": [
274
+ { "x": 1.2, "y": 0.25 },
275
+ { "x": 2, "y": 0.5 },
276
+ { "x": 3.1, "y": 0.75 },
277
+ { "x": 4.5, "y": 1 }
278
+ ]
279
+ },
280
+ "layers": [
281
+ {
282
+ "geom": "step",
283
+ "aes": { "x": { "field": "x" }, "y": { "field": "y" } },
284
+ "params": { "direction": "hv" }
285
+ }
286
+ ]
287
+ }
288
+ ```
289
+
290
+ ```svelte fragment
291
+ <GGPlot data={rows} aes={{ x: "x", y: "y" }}>
292
+ <GeomStep direction="hv" />
293
+ </GGPlot>
294
+ ```
295
+
296
+ ## Ribbon band with a center line
297
+
298
+ Map ymin/ymax at plot level for the band; the line layer adds its own y.
299
+ <!-- adapted from examples/ribbon/bounds/ -->
300
+
301
+ ```json complete
302
+ {
303
+ "data": {
304
+ "values": [
305
+ { "t": 1, "lo": 2, "hi": 5, "mid": 3.4 },
306
+ { "t": 2, "lo": 3, "hi": 6, "mid": 4.5 },
307
+ { "t": 3, "lo": 2.5, "hi": 7, "mid": 4.8 },
308
+ { "t": 4, "lo": 4, "hi": 8, "mid": 6 }
309
+ ]
310
+ },
311
+ "aes": {
312
+ "x": { "field": "t" },
313
+ "ymin": { "field": "lo" },
314
+ "ymax": { "field": "hi" }
315
+ },
316
+ "layers": [
317
+ { "geom": "ribbon", "params": { "alpha": 0.35 } },
318
+ {
319
+ "geom": "line",
320
+ "aes": { "y": { "field": "mid" } },
321
+ "params": { "linewidth": 1.5 }
322
+ }
323
+ ]
324
+ }
325
+ ```
326
+
327
+ ```svelte fragment
328
+ <GGPlot data={rows} aes={{ x: "t", ymin: "lo", ymax: "hi" }}>
329
+ <GeomRibbon alpha={0.35} />
330
+ <GeomLine aes={{ y: "mid" }} linewidth={1.5} />
331
+ </GGPlot>
332
+ ```
333
+
334
+ ## Horizontal boxplot (coord flip)
335
+
336
+ Map the category to x and the value to y as usual, then flip — long labels get room.
337
+ <!-- hand-authored; validated by skill-content test -->
338
+
339
+ ```json complete
340
+ {
341
+ "data": {
342
+ "values": [
343
+ { "g": "a", "v": 1 },
344
+ { "g": "a", "v": 2 },
345
+ { "g": "a", "v": 3 },
346
+ { "g": "a", "v": 2.2 },
347
+ { "g": "b", "v": 4 },
348
+ { "g": "b", "v": 5 },
349
+ { "g": "b", "v": 6 },
350
+ { "g": "b", "v": 4.8 }
351
+ ]
352
+ },
353
+ "layers": [
354
+ {
355
+ "geom": "boxplot",
356
+ "aes": { "x": { "field": "g" }, "y": { "field": "v" } }
357
+ }
358
+ ],
359
+ "coord": { "type": "flip" }
360
+ }
361
+ ```
362
+
363
+ ```svelte fragment
364
+ <GGPlot data={rows} aes={{ x: "g", y: "v" }}>
365
+ <GeomBoxplot />
366
+ <CoordFlip />
367
+ </GGPlot>
368
+ ```
369
+
370
+ ## Smooth trend with per-layer aes override
371
+
372
+ Both layers inherit x/y; only the point layer maps size. A layer channel set to `null` would drop an inherited one instead.
373
+ <!-- adapted from examples/smooth/loess-scatter/ -->
374
+
375
+ ```json complete
376
+ {
377
+ "data": {
378
+ "values": [
379
+ { "year": 1, "angle": 10, "w": 1 },
380
+ { "year": 2, "angle": 14, "w": 3 },
381
+ { "year": 3, "angle": 13, "w": 2 },
382
+ { "year": 4, "angle": 18, "w": 4 },
383
+ { "year": 5, "angle": 21, "w": 2 },
384
+ { "year": 6, "angle": 20, "w": 5 }
385
+ ]
386
+ },
387
+ "aes": { "x": { "field": "year" }, "y": { "field": "angle" } },
388
+ "layers": [
389
+ { "geom": "smooth", "params": { "method": "loess", "span": 0.9 } },
390
+ {
391
+ "geom": "point",
392
+ "aes": { "size": { "field": "w" } },
393
+ "params": { "alpha": 0.85 }
394
+ }
395
+ ]
396
+ }
397
+ ```
398
+
399
+ ```svelte fragment
400
+ <GGPlot data={rows} aes={{ x: "year", y: "angle" }}>
401
+ <GeomSmooth method="loess" span={0.9} />
402
+ <GeomPoint aes={{ size: "w" }} alpha={0.85} />
403
+ </GGPlot>
404
+ ```
405
+
406
+ ## Map polygons (geom sf)
407
+
408
+ Rows carry a `geometry` column of GeoJSON Geometry JSON strings, already projected; map no x/y — fill carries the value.
409
+ <!-- adapted from examples/sf/basic/ -->
410
+
411
+ ```json complete
412
+ {
413
+ "data": {
414
+ "values": [
415
+ {
416
+ "region": "A",
417
+ "rate": 12,
418
+ "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[2,0],[1,2],[0,0]]]}"
419
+ },
420
+ {
421
+ "region": "B",
422
+ "rate": 28,
423
+ "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[2.2,0],[4.2,0],[3.2,2],[2.2,0]]]}"
424
+ }
425
+ ]
426
+ },
427
+ "layers": [
428
+ {
429
+ "geom": "sf",
430
+ "aes": { "fill": { "field": "rate" } },
431
+ "params": { "linewidth": 0.8 }
432
+ }
433
+ ]
434
+ }
435
+ ```
436
+
437
+ ```svelte fragment
438
+ <GGPlot data={regions} aes={{ fill: "rate" }}>
439
+ <GeomSf linewidth={0.8} />
440
+ </GGPlot>
441
+ ```
@@ -0,0 +1,72 @@
1
+ # Registration reference
2
+
3
+ ## Registration (call these)
4
+
5
+ The `@ggts-sh/core` barrel is side-effect-free. A missing opt-in throws
6
+ `not registered in this build` and names the fix. **Default for agents:**
7
+ call `registerAll()` once at startup for every spec-driven, `layers`,
8
+ `<GGPlot spec>`, `runPipeline`, or `renderToSVGString` path. Do not invent
9
+ a lean register list unless the user asked for a small bundle.
10
+
11
+ `ggts render` already registers the full grammar. Svelte **children**
12
+ self-register only the piece they are.
13
+
14
+ | Surface | What is already registered | What you must still call |
15
+ | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `<Geom*>` child | that geom + its **default** stat | `register<Family>()` for a `stat="…"` override (from `@ggts-sh/svelte`) — e.g. `<GeomErrorbar stat="summary" />` → `registerSummary()` |
17
+ | `<ScaleXDate>` / `<ScaleYDatetime>` / other Temporal scale children | `installTemporal()` | nothing |
18
+ | `<GGPlot>` construction | `registerBasic()` + `installCandidates()` (identity geoms/stats, every color/style kind, band-axis planner, host hit-testing) | `registerAll()` for specialty geoms/stats; `installTemporal()` for spec/`layers` temporal charts that have no Temporal child |
19
+ | `@ggts-sh/core/render` import | same basic tier as `registerBasic()` | specialty families + Temporal |
20
+ | `@ggts-sh/core/temporal` import | Temporal polyfill + guides | the rest of the grammar |
21
+ | CLI `ggts render` | full grammar | nothing |
22
+
23
+ **Spec-driven Temporal:** ISO strings with no explicit temporal options keep
24
+ the lean UTC path. Charts driven through `spec` or `layers` have no Temporal
25
+ child. Call `installTemporal()` (from `@ggts-sh/svelte` or `@ggts-sh/core`)
26
+ or `registerAll()` when those charts set `type: "time"`, a named parser,
27
+ timezone, date interval, or full Temporal guide planning.
28
+
29
+ **Lean headless** (`@ggts-sh/core/headless` + `@ggts-sh/core/headless/register`)
30
+ is opt-in per family. One forgotten call fails at render:
31
+
32
+ - Basic geoms: `registerBasicPoints()`, `registerBasicLines()`,
33
+ `registerBasicAreas()`, `registerBasicBars()`, `registerBasicRects()`,
34
+ `registerBasicGlyphs()`, `registerBasicSegments()`.
35
+ - Specialty geoms/stats (same names from `@ggts-sh/core` /
36
+ `@ggts-sh/svelte`): `registerAbline()`, `registerAlign()`, `registerBin()`,
37
+ `registerBin2d()`, `registerBoxplot()`, `registerConnect()`,
38
+ `registerContour()`, `registerCrossbar()`, `registerCurve()`,
39
+ `registerDensity()`, `registerDensity2d()`, `registerDensity2dFilled()`,
40
+ `registerDotplot()`, `registerEcdf()`, `registerEllipse()`,
41
+ `registerErrorbar()`, `registerFunction()`, `registerHex()`,
42
+ `registerLinerange()`, `registerManual()`, `registerMap()`,
43
+ `registerPointrange()`, `registerPolygon()`, `registerQq()`,
44
+ `registerQqLine()`, `registerQuantile()`, `registerRaster()`,
45
+ `registerRug()`, `registerSf()`, `registerSfLabel()`, `registerSfText()`,
46
+ `registerSmooth()`, `registerSpoke()`, `registerSummary()`,
47
+ `registerSummaryBin()`, `registerSummaryRolling()`, `registerTile()`,
48
+ `registerUnique()`, `registerViolin()`.
49
+ - Color: `registerDefaultOrdinalColor()` for the built-in palette or an
50
+ explicit `range`. A named **categorical** scheme (`observable10`,
51
+ `colorblind`, `Dark2`, …) needs `registerOrdinalColor()`. A named
52
+ **sequential or diverging** scheme (`viridis`, `magma`, `Blues`, `RdBu`,
53
+ Crameri `batlow`, cyclic `romaO`, …) needs `registerSequentialColor()`
54
+ — a sequential name with no `type` infers sequential, so
55
+ `registerOrdinalColor()` is not enough. Cyclic `*O` names are sequential
56
+ only. Explicit `type: "ordinal"` (including discrete sampling of a
57
+ non-cyclic sequential name) still needs `registerOrdinalColor()`. Other kinds:
58
+ `registerBinnedColor()`, `registerManualColor()`,
59
+ `registerIdentityColor()`.
60
+ - Style: `registerNumericStyle()` (size / linewidth / alpha),
61
+ `registerFiniteStyle()` (shape / linetype).
62
+ - Band axes: `registerBandGuide()` for categorical x/y.
63
+ - Legends: `registerDiscreteLegend()` / `registerContinuousLegend()` (the
64
+ matching color register also pulls these).
65
+ - Named themes: `@ggts-sh/core/headless` resolves only `default` and `void`.
66
+ `dark`, `minimal`, and the rest need `@ggts-sh/core` or
67
+ `@ggts-sh/core/render` — not a `register*()` call.
68
+
69
+ `registerAll()` covers every stat frame, every geom batch, every color kind
70
+ (with catalogs), every style kind, the band-axis planner, Temporal, and
71
+ interaction candidates. `registerBasic()` covers identity charts only; it
72
+ does not install Temporal or specialty geoms/stats.