@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,276 @@
1
+ <!-- Source of truth: packages/spec/src/schema-catalog.ts (KNOWN_GEOMS, KNOWN_STATS, KNOWN_POSITIONS, GEOM_DEFAULTS), packages/spec/src/schema-declarations.ts (per-geom position unions, params), packages/spec/src/geom-params.ts (GEOM_PARAM_KEYS), scripts/gen-geom-children.ts (Geom* shell generation), packages/svelte/src/lib/geoms/. Inventory tables are asserted complete by scripts/skill-content/inventory.test.ts. -->
2
+
3
+ # Geoms, stats, and positions
4
+
5
+ A JSON layer is `{ "geom": ..., "stat"?, "position"?, "positionParams"?, "aes"?, "data"?, "params"?, "render"? }`.
6
+ Every geom is also a Svelte component (`<GeomPoint/>` inside `<GGPlot>`); the component takes
7
+ `data`, `aes`, `stat`, `position`, `positionParams`, `render` props, and its constant style
8
+ params (the layer's `params` keys) as DIRECT props. Shells are generated by
9
+ `bun run geom:children:gen` (scripts/gen-geom-children.ts); `createGeomLayer` whitelists
10
+ params from `GEOM_PARAM_KEYS` — do not hand-edit `packages/svelte/src/lib/geoms/Geom*.svelte`:
11
+
12
+ ```svelte fragment
13
+ <GeomPoint
14
+ aes={{ x: "displ", y: "hwy" }}
15
+ size={3}
16
+ alpha={0.6}
17
+ position="jitter"
18
+ positionParams={{ seed: 7 }}
19
+ />
20
+ ```
21
+
22
+ Omit `stat`/`position` to get the geom's defaults (normalize fills them in). Each layer
23
+ declaration allows only its own stat and position values — anything else is a schema error.
24
+
25
+ Component layer z-order equals registration order (decision 0001). Static markup, `{#if}`
26
+ membership, and unkeyed `{#each}` keep declaration order; keyed `{#each}` reorder or
27
+ re-mount appends re-registered layers at the END — use the props-first API
28
+ (`layers={[...]}`) for dynamic layer composition. Components emit no markup and are inert
29
+ outside a `<GGPlot>` ancestor.
30
+
31
+ ## Geom inventory
32
+
33
+ All 49 geoms. "Default" is stat+position from GEOM_DEFAULTS. "Channels" lists required
34
+ channels (all geoms also consume color/fill/alpha and group where meaningful; strokes take
35
+ linewidth/linetype, points take size/shape). "Positions" is the layer's full allowed set.
36
+
37
+ | geom | component | default (stat + position) | channels | key params | positions |
38
+ | ------------------- | ------------------- | ---------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------- |
39
+ | `point` | GeomPoint | identity + identity | x, y | alpha, size, shape; summary_bin/manual add bins, binwidth, fun, funMin, funMax; summary_rolling adds window | identity, jitter, nudge |
40
+ | `line` | GeomLine | identity + identity | x, y (ecdf/bin: x only) | linewidth, alpha, curve, connection, bins, binwidth, fun; summary_rolling adds window | identity |
41
+ | `path` | GeomPath | identity + identity | x, y (data order, not x-sorted) | linewidth, curve, connection; ellipse: level, type, segments | identity |
42
+ | `step` | GeomStep | identity + identity | x, y | direction ("hv" default, "vh", "mid") | identity |
43
+ | `col` | GeomCol | identity + stack | x (discrete), y (height) | width, alpha | stack, fill, dodge, identity |
44
+ | `bar` | GeomBar | count + stack | x only (y computed) | width, alpha; stat bin adds bins, binwidth | stack, fill, dodge, identity |
45
+ | `histogram` | GeomHistogram | bin + stack | x (continuous; y computed) | bins (default 30), binwidth, boundary, center, closed | stack, fill, dodge, identity |
46
+ | `freqpoly` | GeomFreqpoly | bin + identity | x (continuous; y computed) | bins, binwidth, boundary, center, closed, linewidth | identity |
47
+ | `area` | GeomArea | identity + stack | x, y | alpha; stat align for mismatched x samples | stack, fill, dodge, identity |
48
+ | `ribbon` | GeomRibbon | identity + identity | x+ymin+ymax OR y+xmin+xmax | alpha, outline, orientation | identity |
49
+ | `rule` | GeomRule | identity + identity | none (annotation) or exactly ONE of x / y | xintercept, yintercept, linewidth | identity |
50
+ | `hline` | GeomHline | identity + identity | none, or aes.y (data-driven) | yintercept, linewidth | identity |
51
+ | `vline` | GeomVline | identity + identity | none, or aes.x (data-driven) | xintercept, linewidth | identity |
52
+ | `abline` | GeomAbline | identity + identity | none (annotation only) | slope, intercept, linewidth | identity |
53
+ | `text` | GeomText | identity + identity | x, y, label | size, anchor, dx, dy (px offsets) | identity, nudge |
54
+ | `label` | GeomLabel | identity + identity | x, y, label | size, anchor, dx, dy, padding, radius, linewidth | identity, nudge |
55
+ | `smooth` | GeomSmooth | smooth + identity | x, y (quantitative) | method, se, level, span, degree, n | identity |
56
+ | `quantile` | GeomQuantile | quantile + identity | x, y (quantitative) | quantiles (default [0.25, 0.5, 0.75]), n | identity |
57
+ | `boxplot` | GeomBoxplot | boxplot + dodge | x (discrete), y (quantitative) | width, coef, outlierSize, linewidth | dodge, identity |
58
+ | `violin` | GeomViolin | ydensity + dodge | x (discrete), y (continuous) | bw, adjust, trim, scale, width | dodge, identity |
59
+ | `density` | GeomDensity | density + identity | x (continuous; y computed) | bw, adjust, n, cut | identity |
60
+ | `errorbar` | GeomErrorbar | identity + identity | x, ymin, ymax (identity); x, y (summary/summary_bin) | width, fun, funMin, funMax, bins, binwidth | identity |
61
+ | `linerange` | GeomLinerange | identity + identity | x, ymin, ymax (identity); x, y (summary) | none | identity |
62
+ | `pointrange` | GeomPointrange | identity + identity | x, y, ymin, ymax (identity); x, y (summary) | size, shape, linewidth, fun | identity |
63
+ | `crossbar` | GeomCrossbar | identity + identity | x, y, ymin, ymax | width, fatten, fun, funMin, funMax | identity |
64
+ | `rect` | GeomRect | identity + identity | xmin, xmax, ymin, ymax | linewidth, alpha | identity |
65
+ | `tile` | GeomTile | identity + identity | x, y (cell centers); width/height via aes or params | width, height, linewidth | identity |
66
+ | `raster` | GeomRaster | identity + identity | x, y (regular grid), fill | hjust, vjust, interpolate | identity |
67
+ | `bin_2d` | GeomBin2d | bin_2d + identity | x, y (continuous; fill defaults to after-stat count) | bins, binwidth, drop | identity |
68
+ | `hex` | GeomHex | bin_hex + identity | x, y (continuous; fill defaults to after-stat count) | bins, drop | identity |
69
+ | `segment` | GeomSegment | identity + identity | x, y, xend, yend | linewidth, lineend | identity |
70
+ | `curve` | GeomCurve | identity + identity | x, y, xend, yend | curvature, angle, ncp, lineend | identity |
71
+ | `spoke` | GeomSpoke | identity + identity | x, y; angle (radians) + radius via aes or params | angle, radius, linewidth | identity |
72
+ | `count` | GeomCount | sum + identity | x, y (size defaults to after-stat n) | alpha, size, shape | identity, jitter, nudge |
73
+ | `jitter` | GeomJitter | identity + jitter | x, y (alias; normalize → point + position jitter) | alpha, size, shape | jitter |
74
+ | `function` | GeomFunction | function + identity | none (y computed from params.fun) | fun, n, xlim, args | identity |
75
+ | `polygon` | GeomPolygon | identity + identity | x, y (vertices in data order, auto-closed) | alpha, linewidth | identity |
76
+ | `contour` | GeomContour | contour + identity | x, y, z (regular complete grid) | bins (default 10), binwidth, breaks | identity |
77
+ | `density_2d` | GeomDensity2d | density_2d + identity | x, y (continuous) | h, adjust, n, bins, binwidth, breaks | identity |
78
+ | `density_2d_filled` | GeomDensity2dFilled | density_2d_filled + identity | x, y (continuous; fill defaults to after-stat level) | h, adjust, n, bins, breaks | identity |
79
+ | `dotplot` | GeomDotplot | bindot + identity | x (continuous; y is computed stackpos) | bins, binwidth, stackdir, stackratio, dotsize | identity |
80
+ | `rug` | GeomRug | identity + identity | x and/or y (match params.sides) | sides (default "bl"), length | identity |
81
+ | `map` | GeomMap | identity + identity | map_id (joined to map data) | map (required DataRef), mapId | identity |
82
+ | `sf` | GeomSf | sf + identity | none (GeoJSON geometry column) | geometry (default "geometry"), size, linewidth | identity |
83
+ | `sf_text` | GeomSfText | sf_coordinates + identity | label (+ geometry column; no x/y) | geometry, size, anchor, dx, dy | identity |
84
+ | `sf_label` | GeomSfLabel | sf_coordinates + identity | label (+ geometry column; no x/y) | geometry, padding, radius, dx, dy | identity |
85
+ | `blank` | GeomBlank | identity + identity | any mapped channel (trains scales, draws nothing) | none | identity |
86
+ | `qq` | GeomQq | qq + identity | sample | size, shape | identity |
87
+ | `qq_line` | GeomQqLine | qq_line + identity | sample | linewidth | identity |
88
+
89
+ Alias geoms (rewritten by `normalize`): `histogram` → bar + stat bin; `freqpoly` → line +
90
+ stat bin; `jitter` → point + position jitter; `hline`/`vline` → rule. Annotation-form
91
+ rule/hline/vline (fixed intercepts in params) inherit NO plot aes. Any geom absent from
92
+ GEOM_DEFAULTS notes above defaults to identity + identity.
93
+
94
+ ## Stat inventory
95
+
96
+ There are NO `Stat*` components — `stat` is a prop on geom components and a field on JSON
97
+ layers. Stats either compute new columns (read them with the after-stat aes form
98
+ `{"stat": "count"}`) or transform rows in place.
99
+
100
+ **Computed-y rule:** when the stat computes y (bar/histogram/freqpoly, line + stat bin or
101
+ ecdf, density, function, dotplot/bindot), `aes.y` must NOT map a field — that is the
102
+ `computed-y-mapped` error. Leave y unmapped (the default after-stat mapping applies), map
103
+ `{"stat": "..."}` explicitly, or unset an inherited y with `null`:
104
+
105
+ ```json fragment
106
+ {
107
+ "geom": "histogram",
108
+ "aes": { "x": { "field": "hwy" }, "y": { "stat": "density" } },
109
+ "params": { "bins": 20 }
110
+ }
111
+ ```
112
+
113
+ **After-stat color/fill:** `{"stat": ...}` on color or fill resolves ONLY for the stats
114
+ that publish color columns — `bin_2d` and `bin_hex` (count, density, ncount, ndensity)
115
+ and `density_2d` / `density_2d_filled` (level, density). On every other stat an
116
+ after-stat color mapping is dropped; those geoms already default fill to the right
117
+ after-stat column, so usually map nothing at all.
118
+
119
+ | stat | computed columns | typical geoms | key params |
120
+ | ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
121
+ | `identity` | none (rows drawn as-is) | most geoms (default) | — |
122
+ | `unique` | none (dedupes rows on mapped aesthetics, first wins) | point, line, path, col, text, rect, rule, segment, ribbon, area, errorbar | — |
123
+ | `manual` | none (one row per group via a named aggregate) | point, line, path | fun (required: first, last, mean, median, min, max, sum) |
124
+ | `connect` | none (expands successive points into connection vertices) | path (ggplot2 default), line | connection (hv, vh, mid, linear) |
125
+ | `align` | none (interpolates each group onto the union of x values) | area, line | — |
126
+ | `count` | count | bar | — |
127
+ | `bin` | count, density, ncount, ndensity | histogram, bar, freqpoly, line | bins (default 30), binwidth, boundary, center, closed |
128
+ | `bin_hex` | count, density, ncount, ndensity | hex | bins, drop |
129
+ | `bin_2d` | count, density, ncount, ndensity | bin_2d | bins, binwidth, drop |
130
+ | `smooth` | y, ymin, ymax, se | smooth | method, se, level, span, degree, n |
131
+ | `quantile` | y (one line per quantile) | quantile | quantiles, n |
132
+ | `boxplot` | ymin, lower, middle, upper, ymax | boxplot | coef |
133
+ | `density` | density, count, scaled, ndensity | density | bw, adjust, n, cut |
134
+ | `summary` | y, ymin, ymax | errorbar, linerange, pointrange, crossbar | fun, funMin, funMax (default mean ± se) |
135
+ | `summary_bin` | y, ymin, ymax (per x bin) | point, line, errorbar | fun + bins, binwidth, boundary, center, closed |
136
+ | `summary_rolling` | y (per centered x window) | point, line | window (required, x units), fun (mean/median/sum/min/max; default mean) |
137
+ | `sum` | n, prop (aggregates coincident x, y) | count, point | — |
138
+ | `ydensity` | density, count, scaled, violinwidth, y | violin | bw, adjust, n, trim, scale |
139
+ | `function` | y (evaluates a portable named fun) | function | fun, n, xlim, args |
140
+ | `ecdf` | ecdf (y defaults to it) | line (use params.curve "step-hv") | — |
141
+ | `contour` | level | contour | bins, binwidth, breaks |
142
+ | `density_2d` | level, density (isolines; also after-stat color) | density_2d | h, adjust, n, bins, breaks |
143
+ | `density_2d_filled` | level, density (filled bands) | density_2d_filled | h, adjust, n, bins, breaks |
144
+ | `bindot` | stackpos (the computed y; count is NOT a valid y here) | dotplot | bins, binwidth, stackdir, stackratio |
145
+ | `ellipse` | none (one closed normal-ellipse ring per group) | path | level, type, segments |
146
+ | `sf` | none (renders the GeoJSON geometry column) | sf | geometry |
147
+ | `sf_coordinates` | none (computes x/y anchors from geometry) | sf_text, sf_label | geometry |
148
+ | `qq` | sample, theoretical | qq | — |
149
+ | `qq_line` | sample, theoretical | qq_line | — |
150
+
151
+ ### Stat usage patterns
152
+
153
+ Non-default stats go on the layer; their params share the layer's `params` object.
154
+
155
+ **Component-form registration:** a `<Geom*>` child self-registers only that
156
+ geom and its DEFAULT stat. Spec-driven surfaces — JSON spec, `layers` prop,
157
+ `runPipeline`, `renderToSVGString` — register nothing per layer: call
158
+ `registerAll()` once instead (or the matching `register<Family>()`). Any
159
+ `stat="…"` override below needs the matching family register call once at
160
+ app startup, imported from `@ggts-sh/svelte`:
161
+ `stat="summary"` → `registerSummary()`, `stat="summary_bin"` →
162
+ `registerSummaryBin()`, `stat="summary_rolling"` → `registerSummaryRolling()`, `stat="ecdf"` → `registerEcdf()`, `stat="manual"` →
163
+ `registerManual()`, `stat="unique"` → `registerUnique()`, `stat="connect"` →
164
+ `registerConnect()`, `stat="align"` → `registerAlign()`, `stat="ellipse"` →
165
+ `registerEllipse()`. (One `registerAll()` covers all of them.) Missing
166
+ registration fails loudly: 'Stat "…" is not registered in this build.',
167
+ naming the fix. Lean headless geom families
168
+ (`registerBasicPoints()`, `registerBasicLines()`, …) live on
169
+ `@ggts-sh/core/headless/register`. Full inventory: SKILL.md Registration.
170
+
171
+ ```json fragment
172
+ {
173
+ "geom": "errorbar",
174
+ "stat": "summary",
175
+ "aes": { "x": { "field": "group" }, "y": { "field": "value" } }
176
+ }
177
+ ```
178
+
179
+ ```json fragment
180
+ {
181
+ "geom": "line",
182
+ "stat": "ecdf",
183
+ "aes": { "x": { "field": "value" } },
184
+ "params": { "curve": "step-hv" }
185
+ }
186
+ ```
187
+
188
+ ```json fragment
189
+ {
190
+ "geom": "line",
191
+ "stat": "manual",
192
+ "aes": { "x": { "field": "month" }, "y": { "field": "value" } },
193
+ "params": { "fun": "mean" }
194
+ }
195
+ ```
196
+
197
+ ```json fragment
198
+ { "geom": "function", "params": { "fun": "sin", "n": 200, "xlim": [0, 6.28] } }
199
+ ```
200
+
201
+ ```json fragment
202
+ {
203
+ "geom": "point",
204
+ "stat": "summary_bin",
205
+ "aes": { "x": { "field": "carat" }, "y": { "field": "price" } },
206
+ "params": { "bins": 20, "fun": "median" }
207
+ }
208
+ ```
209
+
210
+ - `summary` / `summary_bin` default to mean ± se; override with `fun`, `funMin`, `funMax`
211
+ (first, last, mean, median, min, max, sum).
212
+ - `align` fixes continuous-x stack/fill when groups sample different x values (area, line).
213
+ - `connect` (path/line) expands point pairs into hv / vh / mid / linear connectors.
214
+ - `unique` dedupes rows on the mapped aesthetic combination, first occurrence wins.
215
+ - `ellipse` on path draws one closed normal-data ellipse per group (params.level default 0.95).
216
+
217
+ ## Position inventory
218
+
219
+ All 6 positions. Each layer declaration scopes which it accepts (see the geom table); a
220
+ disallowed position is a schema error, not a silent fallback.
221
+
222
+ | position | effect | positionParams | allowed on |
223
+ | ---------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ------------------------------------------ |
224
+ | `identity` | leave positions unchanged | — | every geom |
225
+ | `stack` | pile grouped values (positive up, negative down) | — | bar, col, histogram, area |
226
+ | `fill` | stack, then rescale to proportions of 1 | — | bar, col, histogram, area |
227
+ | `dodge` | place groups side by side within each x band | — (no width param) | bar, col, histogram, area, boxplot, violin |
228
+ | `jitter` | seeded random offsets — ALWAYS deterministic | width, height (data units on continuous axes, band-step fractions on discrete), seed (default 42) | point, count, jitter |
229
+ | `nudge` | fixed offset in data units / band-step fractions | x, y (default 0) | point, count, text, label |
230
+
231
+ `positionParams` exists only on layers that allow jitter or nudge (point, count, jitter,
232
+ text, label). Jitter defaults width/height to 40% of the data resolution; pass `seed` to
233
+ vary the (reproducible) layout. Stack, fill, and dodge take no parameters.
234
+
235
+ ## Annotations
236
+
237
+ There is no `Annotate` API — annotate with ordinary geom layers:
238
+
239
+ - **Reference lines:** `rule` with `params.yintercept` / `params.xintercept` and NO aes
240
+ (annotation form; inherits nothing). `hline` (params.yintercept) and `vline`
241
+ (params.xintercept) are aliases that normalize to rule. Data-driven rules map exactly
242
+ ONE of aes.x / aes.y (both mapped is the `rule-both-axes` error).
243
+ - **Sloped line:** `abline` with `params.slope` and `params.intercept` (annotation only —
244
+ no data-mapped slope).
245
+ - **Text:** `text` / `label` with layer-local inline data. `params.dx`/`params.dy` are PX
246
+ offsets; `position: "nudge"` + `positionParams.x`/`y` offset in DATA units.
247
+ - **Arrows and callout paths:** `segment` (straight; x, y, xend, yend) or `curve` (same
248
+ channels plus curvature/angle/ncp).
249
+ - **Radial marks:** `spoke` (x, y plus angle in radians and radius from aes or params).
250
+
251
+ ```json complete
252
+ {
253
+ "data": {
254
+ "values": [
255
+ { "x": 1, "y": 3 },
256
+ { "x": 2, "y": 5 },
257
+ { "x": 3, "y": 4 }
258
+ ]
259
+ },
260
+ "aes": { "x": { "field": "x" }, "y": { "field": "y" } },
261
+ "layers": [
262
+ { "geom": "point" },
263
+ { "geom": "rule", "params": { "yintercept": 4 } },
264
+ {
265
+ "geom": "text",
266
+ "data": { "values": [{ "x": 2, "y": 5, "note": "peak" }] },
267
+ "aes": {
268
+ "x": { "field": "x" },
269
+ "y": { "field": "y" },
270
+ "label": { "field": "note" }
271
+ },
272
+ "params": { "dy": -8 }
273
+ }
274
+ ]
275
+ }
276
+ ```
@@ -0,0 +1,111 @@
1
+ # Grammar and chart recipes
2
+
3
+ Read this before choosing geoms, scales, themes, or a chart recipe.
4
+
5
+ ## Which geom for which data
6
+
7
+ | x type | y type | extra | recommend |
8
+ | ---------------- | ------------------------------------- | ------------------ | ------------------------------------------------------------ |
9
+ | quantitative | quantitative | ≤ ~2k rows | `point` (+ `smooth` layer for trend) |
10
+ | quantitative | quantitative | many rows | `point` with `"render": "canvas"`, or `hex`/`bin_2d` density |
11
+ | temporal | quantitative | — | `line` (multi-series: map `color` to the series field) |
12
+ | nominal/ordinal | quantitative (pre-aggregated) | — | `col`; many/long labels → add a `flip` coord |
13
+ | nominal/ordinal | (none — count rows) | — | `bar` (count stat; do NOT map y) |
14
+ | quantitative | (none — distribution) | — | `histogram` (or `density` for smooth overlay) |
15
+ | nominal | quantitative (distribution per group) | — | `boxplot` (or `violin` for shape) |
16
+ | nominal | nominal | — | `point` + `"position": "jitter"`, or counts via `count` |
17
+ | quantitative | quantitative | uncertainty bounds | `errorbar` (map ymin/ymax) or `smooth` (se ribbon) |
18
+ | any of the above | + one more nominal field | few values | same geom + facet wrap on that field |
19
+
20
+ Only the everyday geoms appear above. All 49 geoms (violin, hex, contour, qq,
21
+ step, segment, sf/maps, text/label annotation, …), all 28 stats with their
22
+ computed columns, and the position rules live in
23
+ [references/geoms-and-stats.md](geoms-and-stats.md) — read it
24
+ whenever the user wants a geom, stat, or annotation not shown here.
25
+
26
+ Two rules worth keeping in working memory:
27
+
28
+ - **Positions are scoped per geom** (a disallowed position is a schema error):
29
+ stack/fill only on bar, col, histogram, area; dodge on those plus boxplot
30
+ and violin; jitter on point, count, jitter; nudge on point, count, text,
31
+ label; identity everywhere.
32
+ - **A stat that computes y forbids mapping `aes.y` to a field**
33
+ (bar/histogram/density/…) — the `computed-y-mapped` error. Read stat outputs
34
+ with `{"stat": "count"}` aes.
35
+
36
+ ## Scales, palettes, themes
37
+
38
+ - x/y families: `{"type": "linear"|"binned"|"time"|"band"}` with
39
+ `"transform": "identity"|"log10"|"sqrt"`, `domain`/`limits`,
40
+ `oob: "censor"|"squish"`, `expand`, `nice`, breaks, `reverse`. Authored
41
+ `type:"log"` canonicalizes to linear+log10. Scale transforms run before
42
+ stats and positions; coord transforms run after stats.
43
+ - color/fill families: `ordinal`, `sequential`, `binned`, `manual`,
44
+ `identity`. 107 named schemes — 47 categorical (`observable10`, `colorblind`,
45
+ `Dark2`, Crameri `batlowS`…) and 60 sequential/diverging (`viridis`, `magma`,
46
+ `Blues`, `RdBu`, Crameri `batlow`, cyclic `romaO`…).
47
+ Size/linewidth/alpha and shape/linetype have their own scale families.
48
+ - Three equivalent skins:
49
+ JSON `"scales": {"x": {"type": "linear", "transform": "log10"}}` ≡ helper
50
+ functions `scaleXLog10()` / `scale_x_log10()` (binding-identical camelCase,
51
+ snake_case, and Colour spellings, from `@ggts-sh/spec`) ≡ components
52
+ `<ScaleXLog10/>`. The `gg()` builder chains the same names:
53
+ `gg(rows, aes({ x: "flipper", y: "mass" })).geomPoint({ alpha: 0.7 }).scaleXLog10().spec()`.
54
+ - Temporal: ISO dates/date-times, four-digit-year strings, year-months, and
55
+ year-quarters infer time automatically. Ambiguous ordered dates need
56
+ `"parse": "dmy"` or `"mdy"`; force `{"type": "band"}` for year-like
57
+ identifiers; never preprocess dates into indexes. Spec-driven time scales
58
+ also need `installTemporal()` or `registerAll()` — see Registration.
59
+ - Themes: 32 names (`default`, `light`, `dark`, `minimal`, `ggplot2`,
60
+ `classic`, `bw`, `hrbr`, `few`, `clean`, `fivethirtyeight`, `economist`,
61
+ `tufte`, `linedraw`, `void`, `stata`, `stata_s1color`, `solarized`,
62
+ `solarizeddark`, `economist_white`, `solarized_2`, `solarized_2dark`,
63
+ `wsj`, `hc`, `hcdark`, `pander`, `base`, `igray`, `map`, `solid`, plus
64
+ `grey`/`gray` aliasing `ggplot2`) as `<ThemeTufte/>`-style children or
65
+ `"theme": "tufte"` in JSON. Looks, shells, and role overrides:
66
+ [references/themes.md](themes.md).
67
+
68
+ Full option surfaces — every scale option, the `Scale*` component matrix, all
69
+ scheme tables, the whole temporal/parser system:
70
+ [references/scales-and-palettes.md](scales-and-palettes.md).
71
+ Theme roster and shells:
72
+ [references/themes.md](themes.md). Coords, facets, guides, legend
73
+ order, and `Labs`:
74
+ [references/composition-surfaces.md](composition-surfaces.md).
75
+
76
+ ## Recipes (spec JSON — the everyday twelve)
77
+
78
+ All specs assume inline `"data": {"values": [...]}` or a named dataset.
79
+
80
+ 1. **Scatter** — `{"layers":[{"geom":"point","aes":{"x":{"field":"displ"},"y":{"field":"hwy"}}}]}`; color by category: add `"color":{"field":"class"}`.
81
+ 2. **Scatter + trend** — `{"aes":{"x":{"field":"x"},"y":{"field":"y"}},"layers":[{"geom":"point"},{"geom":"smooth","params":{"method":"loess"}}]}` (plot-level aes inherits into both layers).
82
+ 3. **Line (time series)** — `{"layers":[{"geom":"line","aes":{"x":{"field":"date"},"y":{"field":"value"}}}]}`; multi-series: map `"color":{"field":"series"}`.
83
+ 4. **Column chart (pre-computed heights)** — `{"layers":[{"geom":"col","aes":{"x":{"field":"category"},"y":{"field":"amount"}}}]}`
84
+ 5. **Bar chart (count rows)** — `{"layers":[{"geom":"bar","aes":{"x":{"field":"category"}}}]}` — never map y on bar.
85
+ 6. **Horizontal bars** — recipe 4 or 5 + `"coord":{"type":"flip"}`.
86
+ 7. **Stacked / dodged / proportion bars** — recipe 5 + `"fill":{"field":"subgroup"}` in aes (stack is the default); `"position":"dodge"` for side-by-side, `"position":"fill"` for 100% stacked.
87
+ 8. **Histogram** — `{"layers":[{"geom":"histogram","aes":{"x":{"field":"measure"}},"params":{"bins":30}}]}` (or `"binwidth"`; never both `center` and `boundary`).
88
+ 9. **Boxplot by category** — `{"layers":[{"geom":"boxplot","aes":{"x":{"field":"group"},"y":{"field":"value"}}}]}` (x must be discrete).
89
+ 10. **Facets (small multiples)** — any recipe + `"facet":{"wrap":{"field":"panel"},"ncol":3}` (add `"scales":"free_y"` for per-panel y).
90
+ 11. **Reference line annotation** — add layer `{"geom":"rule","params":{"yintercept":0}}`.
91
+ 12. **Finishing** — `"labs":{"title":...,"x":...,"y":...}`, `"width"`/`"height"` in px, `"theme":"minimal"`.
92
+
93
+ The same charts as Svelte children, twice over:
94
+
95
+ ```svelte fragment
96
+ <GGPlot data={sales} aes={{ x: "quarter", y: "amount", fill: "region" }}>
97
+ <GeomCol position="dodge" />
98
+ </GGPlot>
99
+ ```
100
+
101
+ ```svelte fragment
102
+ <GGPlot data={measurements} aes={{ x: "value" }}>
103
+ <GeomHistogram bins={20} />
104
+ <FacetWrap field="site" ncol={2} />
105
+ </GGPlot>
106
+ ```
107
+
108
+ Long-tail recipes — errorbar, value labels, canvas big-scatter, log axis,
109
+ violin, tile heatmap, hex density, ECDF step, ribbon, flipped boxplot,
110
+ per-layer aes override, sf/map — each as validated JSON plus a Svelte twin:
111
+ [references/recipes.md](recipes.md).