@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.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/SKILL.md +196 -0
- package/package.json +39 -0
- package/references/composition-surfaces.md +199 -0
- package/references/geoms-and-stats.md +276 -0
- package/references/grammar.md +111 -0
- package/references/interactions.md +312 -0
- package/references/react.md +101 -0
- package/references/recipes.md +441 -0
- package/references/registration.md +72 -0
- package/references/scales-and-palettes.md +396 -0
- package/references/svelte.md +79 -0
- package/references/themes.md +96 -0
|
@@ -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).
|