@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liam O'Dea
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @ggts-sh/skill
|
|
2
|
+
|
|
3
|
+
The ggts agent skill: `SKILL.md` plus deep-dive `references/` that teach a
|
|
4
|
+
coding agent the shared grammar of graphics — TypeScript builders, React
|
|
5
|
+
and Svelte components, PortableSpec JSON, and the `ggts check` /
|
|
6
|
+
`ggts render` feedback loop.
|
|
7
|
+
|
|
8
|
+
This package is the one published home of the skill. It versions in lock-step
|
|
9
|
+
with [`@ggts-sh/spec`](https://www.npmjs.com/package/@ggts-sh/spec),
|
|
10
|
+
[`@ggts-sh/core`](https://www.npmjs.com/package/@ggts-sh/core),
|
|
11
|
+
[`@ggts-sh/compose`](https://www.npmjs.com/package/@ggts-sh/compose),
|
|
12
|
+
[`@ggts-sh/react`](https://www.npmjs.com/package/@ggts-sh/react),
|
|
13
|
+
[`@ggts-sh/svelte`](https://www.npmjs.com/package/@ggts-sh/svelte), and
|
|
14
|
+
[`@ggts-sh/cli`](https://www.npmjs.com/package/@ggts-sh/cli): a given version
|
|
15
|
+
number describes the spec, the renderers, and this skill as of the same
|
|
16
|
+
release. Pin it like any other dependency and let dependabot (or
|
|
17
|
+
npm-check-updates) tell you when the bundled skill in your repo is stale.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install --save-dev --save-exact @ggts-sh/skill @ggts-sh/cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package root **is** the skill directory: `SKILL.md` sits next to this
|
|
26
|
+
README. Skill loaders key off the frontmatter (`name: ggts`), not the
|
|
27
|
+
directory name.
|
|
28
|
+
|
|
29
|
+
## Use
|
|
30
|
+
|
|
31
|
+
Point your agent at the skill, or copy/symlink it into your agent's skills
|
|
32
|
+
directory under the name `ggts`:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
# Claude Code
|
|
36
|
+
cp -R node_modules/@ggts-sh/skill .claude/skills/ggts
|
|
37
|
+
|
|
38
|
+
# pi
|
|
39
|
+
cp -R node_modules/@ggts-sh/skill .pi/agent/skills/ggts
|
|
40
|
+
|
|
41
|
+
# or reference it in place
|
|
42
|
+
node_modules/@ggts-sh/skill/SKILL.md
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Re-run the copy on every version bump (a two-line `postinstall` or a sync
|
|
46
|
+
script works); the dependabot PR is the signal that the skill changed.
|
|
47
|
+
|
|
48
|
+
The skill assumes the agent can also run
|
|
49
|
+
[`@ggts-sh/cli`](https://www.npmjs.com/package/@ggts-sh/cli)
|
|
50
|
+
(`ggts check` and `ggts render`) for spec validation and headless SVG rendering — install it
|
|
51
|
+
in every sandbox where an agent authors specs.
|
|
52
|
+
|
|
53
|
+
## Contents
|
|
54
|
+
|
|
55
|
+
- `SKILL.md` — target selection, shared grammar, authoring workflow.
|
|
56
|
+
- `references/react.md` and `references/svelte.md` — adapter instructions.
|
|
57
|
+
- `references/` — geoms and stats, scales and palettes, themes, interactions,
|
|
58
|
+
composition surfaces, recipes.
|
|
59
|
+
|
|
60
|
+
## Guarantees
|
|
61
|
+
|
|
62
|
+
CI checks the teaching contracts and shipped examples:
|
|
63
|
+
|
|
64
|
+
1. **Content contracts** (`scripts/skill-content/*.test.ts`,
|
|
65
|
+
`scripts/skill-package.test.ts`) — inventory completeness for every geom,
|
|
66
|
+
stat, position, theme, and color scheme; every complete JSON fence
|
|
67
|
+
normalizes and validates; pack shape and lock-step version. Packed-consumer
|
|
68
|
+
checks also compile complete TSX and Svelte examples from the installed skill.
|
|
69
|
+
2. **Trigger / disclosure contracts** (`scripts/skill-trigger.test.ts`) —
|
|
70
|
+
frontmatter description quality (the loader's selection signal), balanced
|
|
71
|
+
positive/negative trigger fixtures under `evals/trigger-cases.json`,
|
|
72
|
+
relative-link integrity, and progressive disclosure of every `references/`
|
|
73
|
+
file from `SKILL.md`.
|
|
74
|
+
3. **Held-out NL→spec evals** (`tests/evals/`, `bun run evals`) — model
|
|
75
|
+
capability on PortableSpec authoring with deterministic graders. These do
|
|
76
|
+
**not** load this skill today; agent-in-the-loop skill evals (with/without
|
|
77
|
+
skill A/B, multi-trial trigger accuracy) are tracked as follow-up work.
|
|
78
|
+
|
|
79
|
+
`evals/trigger-cases.json` is not packed to npm (`files` is only `SKILL.md` +
|
|
80
|
+
`references/`). It is the living seed for future skill-loaded agent evals.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ggts
|
|
3
|
+
description: Build data visualizations with ggts, a grammar-of-graphics charting library (ggplot2 semantics, React and Svelte 5 components, TypeScript builders, JSON specs, headless SVG rendering). Use whenever creating, editing, validating, or debugging charts, plots, graphs, scatter plots, bar charts, histograms, line charts, boxplots, density plots, violins, heatmaps, maps, faceted/small-multiple charts, or data visualization in a JavaScript/TypeScript/React/Svelte project; when code imports from "@ggts-sh/react", "@ggts-sh/svelte", "@ggts-sh/spec", or "@ggts-sh/core"; when composing GGPlot with Geom*/Scale*/Theme*/Facet*/Coord*/Guide*/Labs children; when emitting a ggts plot spec JSON; or when rendering charts server-side/headless to SVG.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ggts
|
|
7
|
+
|
|
8
|
+
ggplot2’s layered grammar for TypeScript, with headless rendering and React
|
|
9
|
+
and Svelte adapters. A chart spec carries the same grammar between surfaces.
|
|
10
|
+
|
|
11
|
+
## Choose the target
|
|
12
|
+
|
|
13
|
+
1. Follow the user’s explicit framework choice. Otherwise inspect the target
|
|
14
|
+
application’s package.json and imports. In a mixed monorepo, choose the
|
|
15
|
+
application before generating component code.
|
|
16
|
+
2. For a sandbox or an unspecified framework, author PortableSpec JSON or use
|
|
17
|
+
the TypeScript builder; run the CLI before shipping the chart.
|
|
18
|
+
3. For React, read [React](references/react.md). For Svelte, read
|
|
19
|
+
[Svelte](references/svelte.md). Keep framework callbacks and host-only
|
|
20
|
+
interactions in that adapter; PortableSpec holds the portable grammar.
|
|
21
|
+
4. In every agent sandbox, install `@ggts-sh/cli` and `@ggts-sh/skill`
|
|
22
|
+
at matching local versions and keep the lockfile. Expose
|
|
23
|
+
the installed skill to the agent, refreshing any copied skill on upgrades.
|
|
24
|
+
Run `ggts check chart.json`, apply relevant diagnostics, then
|
|
25
|
+
`ggts render chart.json > chart.svg` and inspect the output. A local npm
|
|
26
|
+
install exposes these commands through `npm exec -- ggts`.
|
|
27
|
+
|
|
28
|
+
## Registration (call these)
|
|
29
|
+
|
|
30
|
+
The `@ggts-sh/core` barrel is side-effect-free. Call `registerAll()` once
|
|
31
|
+
at startup for every agent-authored JSON, builder, `layers`, or `<GGPlot spec>`
|
|
32
|
+
path. It installs the full grammar; both `ggts render` and `ggts check` do
|
|
33
|
+
this for you. Missing opt-ins throw `not registered in this build` and name
|
|
34
|
+
the required registration. `registerBasic()` alone is insufficient for
|
|
35
|
+
specialty stats and Temporal.
|
|
36
|
+
|
|
37
|
+
Framework geom children self-register their own geom and default stat;
|
|
38
|
+
stat overrides still need the matching family. Spec-driven temporal options
|
|
39
|
+
need `installTemporal()` or `registerAll()` because there is no Temporal child.
|
|
40
|
+
Use the full default for agents. If the user requests a lean bundle, read the
|
|
41
|
+
[registration reference](references/registration.md) for the exact family,
|
|
42
|
+
color, theme, and headless entry-point rules before selecting imports.
|
|
43
|
+
|
|
44
|
+
## Mental model
|
|
45
|
+
|
|
46
|
+
**Everything that composes a plot is a layer in component composition.** Marks _and_ the
|
|
47
|
+
seven grammar families (scale, theme, coord, facet, labs, guides, legend)
|
|
48
|
+
register as `Layer` kinds via `createPlotLayer` / geom factories. Never call
|
|
49
|
+
Scale/Theme/Guide/Labs/Coord/Facet/Legend “non-layers.”
|
|
50
|
+
|
|
51
|
+
Two serializations of the same grammar:
|
|
52
|
+
|
|
53
|
+
```text fragment
|
|
54
|
+
# React / Svelte composition
|
|
55
|
+
plot children = mark layers + grammar layers
|
|
56
|
+
mark = Geom* → Layer.kind "mark"
|
|
57
|
+
grammar = Scale* | Theme* | Coord* | Facet* | Labs | Guide* | Legend
|
|
58
|
+
→ Layer.kind "scale"|"theme"|"coord"|"facet"|"labs"|"guides"|"legend"
|
|
59
|
+
|
|
60
|
+
# PortableSpec JSON (agent / headless wire format)
|
|
61
|
+
spec = data + aes + layers[] # layers[] = MARKS ONLY
|
|
62
|
+
+ scales? coord? facet? labs? theme? guides? legend?
|
|
63
|
+
one mark layer = { geom, stat?, position?, positionParams?, aes?, params?, render?, data? }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
PortableSpec puts grammar pieces in top-level keys because that is how the
|
|
67
|
+
JSON schema folds them — **not** because they are outside the layer model.
|
|
68
|
+
“Non-mark layer” / “grammar layer” is correct; “non-layer grammar component”
|
|
69
|
+
is wrong and must never appear in issues, docs, or comments.
|
|
70
|
+
|
|
71
|
+
- **data**: `{"values": [rows]}`, `{"columns": {name: [...]}}`, or
|
|
72
|
+
`{"name": "dataset"}` (resolved from `spec.datasets` or runtime data).
|
|
73
|
+
- **mark `layers[]`** draw in order — later marks on top (z-order).
|
|
74
|
+
- Geom defaults: bar → count+stack, histogram → bin+stack, col/area →
|
|
75
|
+
identity+stack, boxplot → boxplot+dodge, violin → ydensity+dodge,
|
|
76
|
+
jitter → point+jitter, freqpoly → bin, smooth → smooth, count → sum,
|
|
77
|
+
density → density, hex → bin_hex, everything else identity+identity.
|
|
78
|
+
|
|
79
|
+
## Aes: JSON specs vs component props
|
|
80
|
+
|
|
81
|
+
The one rule that differs between the two skins:
|
|
82
|
+
|
|
83
|
+
| Surface | `x: "displ"` bare string | canonical |
|
|
84
|
+
| ------------------------------------------- | ------------------------------------- | ------------------------ |
|
|
85
|
+
| JSON `PortableSpec` (incl. `spec` prop) | INVALID | `{"field": "displ"}` |
|
|
86
|
+
| React / Svelte `aes` prop (plot or `Geom*`) | valid shorthand, expands to `{field}` | same object form also OK |
|
|
87
|
+
|
|
88
|
+
Canonical channel forms: `{"field": "col"}` maps a column, `{"value": "red"}`
|
|
89
|
+
is a constant, `{"stat": "count"}` reads a stat output, `null` unsets an
|
|
90
|
+
inherited channel. Layer aes merges over plot aes per channel.
|
|
91
|
+
Channels (25): x, y, color, fill, size, linewidth, alpha, shape, linetype,
|
|
92
|
+
group, label, weight, ymin, ymax, xmin, xmax, xend, yend, width, height, z,
|
|
93
|
+
map_id, angle, radius, sample. Mapped size/linewidth/alpha/shape/linetype work
|
|
94
|
+
only on the geoms in `STYLE_AESTHETIC_GEOMS`.
|
|
95
|
+
|
|
96
|
+
## The validation contract (use it!)
|
|
97
|
+
|
|
98
|
+
`validate(spec)` = schema shape. `validate(spec, { profile })` adds data-aware
|
|
99
|
+
checks against a `DataProfile` —
|
|
100
|
+
`{"fields": [{"name": "displ", "type": "quantitative"}], "rowCount": 234}`
|
|
101
|
+
(types: quantitative | temporal | ordinal | nominal). Every error is:
|
|
102
|
+
|
|
103
|
+
```json fragment
|
|
104
|
+
{
|
|
105
|
+
"code": "unknown-field",
|
|
106
|
+
"path": "/layers/0/aes/x",
|
|
107
|
+
"message": "Unknown field \"dsipl\" (available: displ, hwy). Did you mean \"displ\"?",
|
|
108
|
+
"allowed": ["displ", "hwy"],
|
|
109
|
+
"fix": {
|
|
110
|
+
"description": "Map \"x\" to \"displ\".",
|
|
111
|
+
"example": { "field": "displ" }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Errors include `fix.example` — apply it at `path` and re-validate.**
|
|
117
|
+
`validate(spec, { lint: true })` additionally returns advisories for
|
|
118
|
+
valid-but-questionable specs (line over unordered categories, >10 discrete
|
|
119
|
+
colors, stacked negative areas, discrete×discrete scatter, transform-domain
|
|
120
|
+
mixed-sign data, fractional calendar years on a linear scale); `lintSpec(spec)`
|
|
121
|
+
is the standalone equivalent. Advisories never block; fix them when they match
|
|
122
|
+
intent. `normalize(input)` canonicalizes
|
|
123
|
+
authoring sugar into a `PortableSpec`; `isPortable`/`toPortable` check and
|
|
124
|
+
strip runtime-only fields. CLIs: `ggts render spec.json > out.svg`
|
|
125
|
+
(from `@ggts-sh/cli`; JSON-line diagnostics on stderr — exit 3 means
|
|
126
|
+
validation errors, exit 0 with stderr output means quality warnings worth
|
|
127
|
+
fixing) and `ggts-codemod [--write] src` (ships with `@ggts-sh/svelte`).
|
|
128
|
+
|
|
129
|
+
**CLI coverage for interaction:** the CLI always covers PortableSpec validation,
|
|
130
|
+
pipeline warnings/advisories, scale diagnostics (`source: "scale"`), and
|
|
131
|
+
spec-lint (`source: "spec-lint"`). Host inspect **mode** is not a PortableSpec
|
|
132
|
+
field — without intent the agent loop never sees inspect×geom bar/col guide
|
|
133
|
+
codes such as `INTERACTION_INSPECT_X_ON_COL`. When the host will enable inspect,
|
|
134
|
+
declare it:
|
|
135
|
+
|
|
136
|
+
```sh fragment
|
|
137
|
+
ggts render --inspect xy spec.json > out.svg
|
|
138
|
+
# stderr: kind advisory|warning, source "interaction", bar/col x-guide codes
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Modes match the host enum (`auto|exact|x|y|xy`). This path covers the pure
|
|
142
|
+
bar/col axis-guide collectors only (same codes as host for that slice).
|
|
143
|
+
High-cardinality discrete and runtime key/lineage/wiring diagnostics still
|
|
144
|
+
require a mounted host's `ondiagnostic`.
|
|
145
|
+
|
|
146
|
+
## Interactions (host props / children, not PortableSpec fields)
|
|
147
|
+
|
|
148
|
+
Opt-in host capabilities: `<Inspect />` for tooltips and crosshairs;
|
|
149
|
+
`select` / `zoom` on `<GGPlot>`; legend emphasis and data-changing filter via
|
|
150
|
+
`<GuideLegend channel="color" focus />` / `filter` (per aesthetic; host-only,
|
|
151
|
+
not in `guideLegend()` / PortableSpec). Row identity for selection, legend
|
|
152
|
+
focus, and linked views **defaults** to an `id` column when present, else
|
|
153
|
+
the row index (order-stable only) — ordinary charts omit custom identity.
|
|
154
|
+
Override with `identity` on `<Inspect>`, object-form `select`, or
|
|
155
|
+
`createPlotInteraction` when a non-`id` durable field or accessor is needed
|
|
156
|
+
(for example `<Inspect identity="year" />`). Plot-level `key` is deprecated
|
|
157
|
+
since 0.21. Link plots by sharing one `createPlotInteraction()` controller
|
|
158
|
+
and matching `interactionScope` channels; observe everything through
|
|
159
|
+
`oninteraction` or per-capability handlers.
|
|
160
|
+
|
|
161
|
+
**Inspect mode and hit hygiene:** prefer `mode="auto"` when library auto matches
|
|
162
|
+
the mark; pin `mode="exact"` (or leave auto) on violin, boxplot, and discrete-axis
|
|
163
|
+
error bars — never freescrolling `x`/`y`/`xy` on those band geoms; use
|
|
164
|
+
`x`/`y`/`xy` only for continuous shared-axis or free 2d hits; set
|
|
165
|
+
`inspect={false}` on decorative layers in multi-layer stories. The CLI SVG
|
|
166
|
+
loop cannot validate host inspect mode — hover/pin in a browser after edits.
|
|
167
|
+
Read [references/interactions.md](references/interactions.md) (Choosing inspect
|
|
168
|
+
mode, Multi-layer hit hygiene) before enabling Inspect or writing interactive
|
|
169
|
+
or linked-view code.
|
|
170
|
+
|
|
171
|
+
## Pointers
|
|
172
|
+
|
|
173
|
+
- JSON Schema (constrained decoding): `packages/spec/schema/v0.json` in the
|
|
174
|
+
repo, `/schema/v0.json` on the docs site, or
|
|
175
|
+
`import schema from "@ggts-sh/spec/schema/v0.json"`.
|
|
176
|
+
- Full corpus for models: `/llms-full.txt` on the docs site (all guide prose
|
|
177
|
+
plus every example with spec JSON and Svelte source); index at `/llms.txt`.
|
|
178
|
+
- Error catalog: `/guide/errors`; advisories: `/guide/advisories`;
|
|
179
|
+
lifecycle/editions: `/guide/lifecycle` (specs are stamped with the current
|
|
180
|
+
appearance edition, currently 2). Upgrading off deprecated props:
|
|
181
|
+
`/guide/upgrading`.
|
|
182
|
+
- References in this skill — read the one that matches the task:
|
|
183
|
+
[geoms-and-stats.md](references/geoms-and-stats.md) (any geom/stat/position
|
|
184
|
+
beyond the everyday set, annotations),
|
|
185
|
+
[scales-and-palettes.md](references/scales-and-palettes.md) (scale options,
|
|
186
|
+
palettes, temporal),
|
|
187
|
+
[themes.md](references/themes.md) (every product theme name, shells,
|
|
188
|
+
role overrides),
|
|
189
|
+
[composition-surfaces.md](references/composition-surfaces.md) (coords,
|
|
190
|
+
facets, guides, merge semantics),
|
|
191
|
+
[interactions.md](references/interactions.md) (tooltips, selection, linking),
|
|
192
|
+
[recipes.md](references/recipes.md) (long-tail chart recipes).
|
|
193
|
+
Registration calls live in the [registration reference](references/registration.md).
|
|
194
|
+
|
|
195
|
+
- [Grammar and recipes](references/grammar.md): choose charts, scales, and themes;
|
|
196
|
+
the same recipes work in every adapter.
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ggts-sh/skill",
|
|
3
|
+
"version": "0.43.0",
|
|
4
|
+
"description": "Agent skill for the shared grammar of graphics: TypeScript, React, Svelte, portable JSON, and the ggts check/render feedback loop. Versions with all framework adapters.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-skill",
|
|
7
|
+
"agents",
|
|
8
|
+
"charts",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"dataviz",
|
|
11
|
+
"ggplot2",
|
|
12
|
+
"grammar-of-graphics",
|
|
13
|
+
"llm",
|
|
14
|
+
"plot",
|
|
15
|
+
"react",
|
|
16
|
+
"skill",
|
|
17
|
+
"svelte",
|
|
18
|
+
"typescript",
|
|
19
|
+
"visualization"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://ggts.sh/",
|
|
22
|
+
"bugs": "https://github.com/ggts-sh/ggts/issues",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Liam O'Dea",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/ggts-sh/ggts.git",
|
|
28
|
+
"directory": "packages/skill"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"SKILL.md",
|
|
32
|
+
"references"
|
|
33
|
+
],
|
|
34
|
+
"type": "module",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"provenance": true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<!-- Source of truth: packages/svelte/src/lib/index.ts (component exports), packages/svelte/src/lib/plot-props.ts, packages/svelte/src/lib/layers/ (merge semantics, grammar-families). Theme roster lives in themes.md (asserted complete by scripts/skill-content/inventory.test.ts). -->
|
|
2
|
+
|
|
3
|
+
# Composition surfaces: coords, facets, guides, labs
|
|
4
|
+
|
|
5
|
+
Every surface here is a **grammar layer** (non-mark `Layer` kind). Each exists
|
|
6
|
+
in three equivalent forms: a key in the JSON `PortableSpec`, a removed
|
|
7
|
+
`<GGPlot>` prop (gone since 0.13.0; codemod rewrites old sources), and a
|
|
8
|
+
declaration-only child component
|
|
9
|
+
(canonical in Svelte). Child components emit no markup, register on init via
|
|
10
|
+
`createPlotLayer` / `registerPlotLayer`, unregister on destroy, and are inert
|
|
11
|
+
without a `<GGPlot>` ancestor.
|
|
12
|
+
|
|
13
|
+
**Ontology:** scale, theme, coord, facet, labs, guides, and legend **are
|
|
14
|
+
layers**. PortableSpec’s top-level `layers[]` array holds marks only; these
|
|
15
|
+
families fold into sibling keys. That is wire format, not “not a layer.”
|
|
16
|
+
|
|
17
|
+
## Themes
|
|
18
|
+
|
|
19
|
+
Full product roster (37 names), shells, and role overrides:
|
|
20
|
+
[themes.md](themes.md). Theme is a REPLACE family (last registration wins) —
|
|
21
|
+
see replace-vs-merge below.
|
|
22
|
+
|
|
23
|
+
## Coords
|
|
24
|
+
|
|
25
|
+
Components: `Coord` (escape hatch, `value: CoordSpec | "flip"`), `CoordFlip`,
|
|
26
|
+
`CoordCartesian`, `CoordTransform`, `CoordFixed`, `CoordEqual` (alias of
|
|
27
|
+
`CoordFixed`), `CoordSf`, `CoordRadial`, `CoordPolar` (ggplot2 `coord_polar`
|
|
28
|
+
alias shell — prefer `CoordRadial` for new work). `<CoordCartesian/>`
|
|
29
|
+
registers `{"type":"cartesian"}`, which `normalize()` drops when bare — use
|
|
30
|
+
it to clear an earlier coord under REPLACE. JSON forms:
|
|
31
|
+
|
|
32
|
+
```json fragment
|
|
33
|
+
"coord": {"type": "flip"}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```json fragment
|
|
37
|
+
"coord": {
|
|
38
|
+
"type": "transform",
|
|
39
|
+
"x": {"transform": "log10", "limits": [1, 1000], "reverse": false, "expand": true},
|
|
40
|
+
"y": {"transform": "sqrt"},
|
|
41
|
+
"clip": true
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```json fragment
|
|
46
|
+
"coord": {
|
|
47
|
+
"type": "radial",
|
|
48
|
+
"theta": "y",
|
|
49
|
+
"innerRadius": 0.4,
|
|
50
|
+
"expand": false
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Coord transform runs AFTER stats and scale training (scale `transform` runs
|
|
55
|
+
before stats). It preserves stat inputs; coordinate `limits` are a viewport —
|
|
56
|
+
they never remove rows or recompute statistics. `expand` (default true) adds
|
|
57
|
+
a 5% display expansion to explicit limits; `clip` (default true) clips marks
|
|
58
|
+
to the panel.
|
|
59
|
+
- Non-identity coordinate transforms require continuous, non-temporal
|
|
60
|
+
position scales on the transformed axes.
|
|
61
|
+
- `{"type": "fixed", "ratio": 1}`: ratio is physical y-unit length divided by
|
|
62
|
+
physical x-unit length (default 1). Layout fits the largest centered data
|
|
63
|
+
rectangle after chart chrome. Fixed-aspect coords reject free positional
|
|
64
|
+
facet scales (`coord-fixed-free-scales` error).
|
|
65
|
+
- `{"type": "sf"}` (`<CoordSf/>`): fixed-aspect coords for already-projected
|
|
66
|
+
map data; no CRS reprojection in v1. Accepts `ratio` like fixed.
|
|
67
|
+
- `{"type": "radial", …}` (`<CoordRadial/>`, helpers `coordRadial` /
|
|
68
|
+
`coord_radial`): polar coordinates (ggplot2 `coord_radial`). Maps one
|
|
69
|
+
aesthetic to angle and the other to radius — pie, coxcomb, polar scatter.
|
|
70
|
+
Options: `theta` (`"x"` default | `"y"` for pie), `start` / `end` (radians
|
|
71
|
+
from 12 o'clock; default full circle), `innerRadius` (0–1 fraction, donut
|
|
72
|
+
hole), `expand` (default true; false for pie/coxcomb), `clip` (default
|
|
73
|
+
off), `reverse` (`"none"` | `"theta"` | `"r"` | `"thetar"`),
|
|
74
|
+
`thetaLimits` / `rLimits`. Full-circle panels use a square data rectangle.
|
|
75
|
+
Equivalent skins: JSON ≡ `coordRadial({…})` ≡ `.coordRadial({…})` ≡
|
|
76
|
+
`<CoordRadial …/>`.
|
|
77
|
+
- `CoordPolar` / `coordPolar` / `coord_polar`: superseded ggplot2
|
|
78
|
+
`coord_polar` alias. Emits `{ type: "radial", clip: true, … }`;
|
|
79
|
+
`direction: -1` maps to `reverse: "theta"`. Prefer `<CoordRadial/>` for
|
|
80
|
+
partial arcs, inner radius, and reverse.
|
|
81
|
+
|
|
82
|
+
## Facets
|
|
83
|
+
|
|
84
|
+
Components: `Facet` (full `FacetInput` surface), `FacetWrap` (`field`,
|
|
85
|
+
`ncol?`, `scales?`, `strip?`), `FacetGrid` (`rows?`, `cols?`, `scales?`,
|
|
86
|
+
`strip?`). `field`/`rows`/`cols` accept a bare string or
|
|
87
|
+
`{ field, levels?, labels? }` for authored panel order and strip text. Wrap
|
|
88
|
+
form is mutually exclusive with rows/cols; a bare `<Facet/>` fails validate
|
|
89
|
+
with `facet-form-missing`.
|
|
90
|
+
|
|
91
|
+
```json fragment
|
|
92
|
+
"facet": {"wrap": {"field": "region"}, "ncol": 3, "scales": "free_y",
|
|
93
|
+
"strip": {"position": "top", "show": true}}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- Panels partition the data BEFORE stats and positions run — each panel
|
|
97
|
+
computes its own counts, bins, and stacks.
|
|
98
|
+
- `scales`: `"fixed"` (default — panels share both positional scales) |
|
|
99
|
+
`"free"` | `"free_x"` | `"free_y"`. Discrete color/fill assignments are
|
|
100
|
+
ALWAYS global (one legend), whatever `scales` says.
|
|
101
|
+
- `strip`: `position` `"top"` (default) | `"bottom"` | `"left"` | `"right"`
|
|
102
|
+
(left/right strips take layout space rather than overlaying the panel);
|
|
103
|
+
`show` (default true) — set false when direct labels are authored elsewhere.
|
|
104
|
+
|
|
105
|
+
## Guides, legend, labs
|
|
106
|
+
|
|
107
|
+
Guides are keyed by aesthetic channel: `x`, `y`, `color`, `fill`, `size`,
|
|
108
|
+
`linewidth`, `alpha`, `shape`, `linetype`. Components: `Guides`
|
|
109
|
+
(escape hatch, `value: GuidesSpec`), plus one shell per guide type carrying a
|
|
110
|
+
`channel` prop — `GuideAxis` (channels `x`/`y` only), `GuideLegend`,
|
|
111
|
+
`GuideColorbar`, `GuideColorsteps` (non-position channels), `GuideNone` (any
|
|
112
|
+
channel). `<GuideLegend channel="color" position="bottom"/>` assembles
|
|
113
|
+
`guides: {"color": {"type": "legend", "position": "bottom"}}`.
|
|
114
|
+
|
|
115
|
+
- Top-level `guides` override a scale-local `guide`.
|
|
116
|
+
- Shared options: `title`, `theme` (bounded size/gap overrides), `collision`;
|
|
117
|
+
non-position guides add integer `order` (placement rank), `position`
|
|
118
|
+
`"auto"|"right"|"bottom"`, `direction` `"auto"|"vertical"|"horizontal"`,
|
|
119
|
+
and `force` (show a guide suppressed by default, e.g. identity scales).
|
|
120
|
+
- Responsive rule: `auto`-positioned non-position guides move below the panel
|
|
121
|
+
when the viewport is 480px wide or less, or when a right guide would leave
|
|
122
|
+
under 320px of panel (after an estimated 80px of other chrome).
|
|
123
|
+
- `<Legend order="stable-domain"|"present-first-seen"|"sorted"/>` is the
|
|
124
|
+
plot-wide legend ENTRY-SORT enum (default `stable-domain`; ordering never
|
|
125
|
+
changes color assignments). Distinct from `<GuideLegend order={2}/>`, a
|
|
126
|
+
per-aesthetic integer placement rank — same word, unrelated concepts.
|
|
127
|
+
- `Labs` takes flat string props: `title`, `subtitle`, `caption`, `x`, `y`,
|
|
128
|
+
`color`, `fill`, `size`, `linewidth`, `alpha`, `shape`, `linetype`. The
|
|
129
|
+
spec type is re-exported from `@ggts-sh/svelte` as `LabsSpec`.
|
|
130
|
+
|
|
131
|
+
```svelte fragment
|
|
132
|
+
<GGPlot
|
|
133
|
+
data={rows}
|
|
134
|
+
aes={{ x: "cat", y: "val", fill: "group" }}
|
|
135
|
+
layers={[{ geom: "col" }]}
|
|
136
|
+
>
|
|
137
|
+
<CoordFlip />
|
|
138
|
+
<FacetWrap field="region" ncol={2} />
|
|
139
|
+
<ThemeMinimal />
|
|
140
|
+
<Labs title="Sales by category" x="Category" y="Sales" />
|
|
141
|
+
<GuideLegend channel="fill" position="bottom" />
|
|
142
|
+
<Legend order="sorted" />
|
|
143
|
+
</GGPlot>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Equivalent complete JSON spec:
|
|
147
|
+
|
|
148
|
+
```json complete
|
|
149
|
+
{
|
|
150
|
+
"data": {
|
|
151
|
+
"values": [
|
|
152
|
+
{ "cat": "a", "val": 3, "group": "g1", "region": "west" },
|
|
153
|
+
{ "cat": "b", "val": 5, "group": "g2", "region": "west" },
|
|
154
|
+
{ "cat": "a", "val": 2, "group": "g1", "region": "east" },
|
|
155
|
+
{ "cat": "b", "val": 4, "group": "g2", "region": "east" }
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"layers": [
|
|
159
|
+
{
|
|
160
|
+
"geom": "col",
|
|
161
|
+
"aes": {
|
|
162
|
+
"x": { "field": "cat" },
|
|
163
|
+
"y": { "field": "val" },
|
|
164
|
+
"fill": { "field": "group" }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"coord": { "type": "flip" },
|
|
169
|
+
"facet": { "wrap": { "field": "region" }, "ncol": 2 },
|
|
170
|
+
"theme": "minimal",
|
|
171
|
+
"labs": { "title": "Sales by category", "x": "Category", "y": "Sales" },
|
|
172
|
+
"guides": { "fill": { "type": "legend", "position": "bottom" } },
|
|
173
|
+
"legend": { "order": "sorted" }
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Replace-vs-merge semantics (child layers)
|
|
178
|
+
|
|
179
|
+
- The `spec` prop wins over everything — when set, all other props and all
|
|
180
|
+
children are ignored.
|
|
181
|
+
- Mark layers: the `layers` prop, when set, is used INSTEAD of geom children.
|
|
182
|
+
Otherwise geom children register in document order, and registration order
|
|
183
|
+
is z-order (first = bottom).
|
|
184
|
+
- Grammar (non-mark) families fold onto the builder in registration order —
|
|
185
|
+
later siblings beat earlier ones.
|
|
186
|
+
- REPLACE families — `coord`, `facet`, `theme`: the last registration fully
|
|
187
|
+
replaces earlier ones (and any matching prop). Two or more children of one
|
|
188
|
+
kind emit a `DUPLICATE_PLOT_LAYER` advisory.
|
|
189
|
+
- Scales merge per-channel; two scale children configuring the same channel
|
|
190
|
+
emit `DUPLICATE_SCALE_CHANNEL` (note British/American spellings write the
|
|
191
|
+
same channel: `<ScaleColorDiscrete/>` and `<ScaleColourContinuous/>` both
|
|
192
|
+
configure `color`).
|
|
193
|
+
- `labs`, `guides`, `legend` are keyed-MERGE families: siblings touching
|
|
194
|
+
different keys all survive; a colliding key emits `DUPLICATE_MERGE_KEY` and
|
|
195
|
+
the later child's value wins. The value at a key is replaced whole — a
|
|
196
|
+
guide child never field-merges into another guide object for that channel.
|
|
197
|
+
- All three advisory codes arrive through `ondiagnostic` with severity
|
|
198
|
+
`"advisory"`; the chart still renders.
|
|
199
|
+
- Grammar children emit no markup and are inert without a `<GGPlot>` ancestor.
|