# ggsvelte — full docs corpus for language models Generated from the docs guide sources and the examples manifest (one source, three uses). Each example shows its canonical PortableSpec JSON (what an agent should emit) and the equivalent Svelte component usage. ## Current release facts - Package version: 0.37.0 - Defaults edition: 2 - Registered chart themes (33): default, light, dark, minimal, ggplot2, classic, bw, hrbr, few, clean, fivethirtyeight, economist, tufte, linedraw, void, stata, stata_s1color, solarized, solarizeddark, economist_white, solarized_2, solarized_2dark, wsj, hc, hcdark, pander, base, igray, map, solid, grey, gray, test --- # Getting started ggsvelte is ggplot2's layered grammar for Svelte 5. A plot is data + an aesthetic mapping + one or more layers, and every plot normalizes to a PortableSpec: strict JSON, no functions, no closures. That JSON is the surface to generate, validate, and correct against. ## Install ```sh complete bun add @ggsvelte/svelte # or: npm install @ggsvelte/svelte # or: pnpm add @ggsvelte/svelte ``` `@ggsvelte/spec` (schema, validate, builder) and `@ggsvelte/core` (pipeline, headless render) are dependencies of the Svelte package. Install them directly for spec-only or headless work. The `ggsvelte-render` CLI is its own package — install `@ggsvelte/cli` in every sandbox where an agent authors specs, so validation errors and chart-quality warnings surface before a chart ships. The agent skill is also its own package: `@ggsvelte/skill` (`SKILL.md` + `references/` at the package root) — install it and copy/symlink `node_modules/@ggsvelte/skill` into the agent's skills directory as `ggsvelte/` (or point the agent at `node_modules/@ggsvelte/skill/SKILL.md` directly). Bundled teaching data lives at `@ggsvelte/svelte/data`. ## A complete Svelte file `src/routes/+page.svelte`: ```svelte complete ``` Omitted width follows the container; default height is 400px. No chart CSS is required. During server rendering the plot uses a deterministic 832 x 400 fallback, then measures the real container after hydration; inside `display: none` or a zero-width track it stays not-ready until the container has positive width. ## The PortableSpec contract The same chart as JSON. This is the canonical form — the Svelte component and the TypeScript builder both normalize to it. ```json fragment { "data": { "name": "kyotoSakura" }, "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" } }, "layers": [ { "geom": "rect", "data": { "values": [ { "epoch": "Medieval warm period", "year": 950, "until": 1250, "top": "03-18", "bottom": "05-10" }, { "epoch": "Little Ice Age", "year": 1300, "until": 1850, "top": "03-18", "bottom": "05-10" }, { "epoch": "Industrial era", "year": 1850, "until": 2026, "top": "03-18", "bottom": "05-10" } ] }, "aes": { "x": null, "y": null, "xmin": { "field": "year" }, "xmax": { "field": "until" }, "ymin": { "field": "top" }, "ymax": { "field": "bottom" }, "fill": { "field": "epoch" } }, "params": { "alpha": 0.55 }, "inspect": false }, { "geom": "text", "data": { "values": [ { "epoch": "Medieval warm period", "midYear": 1100, "nameDate": "03-14" }, { "epoch": "Little Ice Age", "midYear": 1575, "nameDate": "03-14" }, { "epoch": "Industrial era", "midYear": 1938, "nameDate": "03-14" } ] }, "aes": { "x": { "field": "midYear" }, "y": { "field": "nameDate" }, "label": { "field": "epoch" }, "color": { "value": "#6b7075" } }, "params": { "size": 11 }, "inspect": false }, { "geom": "rule", "aes": { "color": { "value": "#b7c1cd" }, "linetype": { "value": "dotted" } }, "params": { "yintercept": "04-05", "linewidth": 0.75 }, "inspect": false }, { "geom": "rule", "aes": { "color": { "value": "#b7c1cd" }, "linetype": { "value": "dotted" } }, "params": { "yintercept": "04-25", "linewidth": 0.75 }, "inspect": false }, { "geom": "point", "aes": { "color": { "value": "#4a5568" } }, "params": { "alpha": 0.55, "size": 1.4 } }, { "geom": "rule", "aes": { "color": { "value": "#6b7075" } }, "params": { "yintercept": "04-15", "linewidth": 1 }, "inspect": false }, { "geom": "text", "data": { "values": [ { "year": 812, "bloomDate": "04-15", "label": "median" } ] }, "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "label": { "field": "label" }, "color": { "value": "#6b7075" } }, "params": { "size": 9, "anchor": "start", "dy": 22 }, "inspect": false }, { "geom": "line", "stat": "summary_rolling", "aes": { "color": { "value": "#262626" } }, "params": { "fun": "median", "window": 30, "curve": "linear", "linewidth": 1.8 } }, { "geom": "point", "data": { "values": [ { "year": 1323, "bloomDate": "05-04" } ] }, "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#2c5282" } }, "params": { "shape": "circle-open", "size": 3.5 } }, { "geom": "point", "data": { "values": [ { "year": 1409, "bloomDate": "03-27" } ] }, "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#c53030" } }, "params": { "shape": "circle-open", "size": 3.5 } }, { "geom": "point", "data": { "values": [ { "year": 2023, "bloomDate": "03-25" } ] }, "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#c53030" } }, "params": { "size": 3 } }, { "geom": "segment", "data": { "values": [ { "year": 1323, "bloomDate": "05-04", "label": "1323 · May 4, latest on record", "labelYear": 1305, "labelDate": "05-07" }, { "year": 1409, "bloomDate": "03-27", "label": "1409 · March 27, earliest for six centuries", "labelYear": 1400, "labelDate": "03-22" }, { "year": 2023, "bloomDate": "03-25", "label": "2023 · March 25, earliest in 1,200 years", "labelYear": 2014, "labelDate": "03-20" } ] }, "aes": { "x": { "field": "labelYear" }, "y": { "field": "labelDate" }, "xend": { "field": "year" }, "yend": { "field": "bloomDate" }, "color": { "value": "#b3452f" } }, "params": { "linewidth": 0.7, "alpha": 0.9 } }, { "geom": "text", "data": { "values": [ { "year": 1323, "bloomDate": "05-04", "label": "1323 · May 4, latest on record", "labelYear": 1305, "labelDate": "05-07" }, { "year": 1409, "bloomDate": "03-27", "label": "1409 · March 27, earliest for six centuries", "labelYear": 1400, "labelDate": "03-22" }, { "year": 2023, "bloomDate": "03-25", "label": "2023 · March 25, earliest in 1,200 years", "labelYear": 2014, "labelDate": "03-20" } ] }, "aes": { "x": { "field": "labelYear" }, "y": { "field": "labelDate" }, "label": { "field": "label" }, "color": { "value": "#b3452f" } }, "params": { "size": 11, "anchor": "end", "dx": -4 } } ], "scales": { "x": { "type": "linear", "labels": "d", "domain": [ 800, 2030 ] }, "y": { "type": "time", "temporalKind": "monthDay", "reverse": true, "breaks": [ "04-05", "04-15", "04-25" ], "dateLabels": "%b %e", "domain": [ "05-10", "03-10" ] }, "fill": { "type": "manual", "domain": [ "Medieval warm period", "Little Ice Age", "Industrial era" ], "range": [ "#f5edc4", "#dce8f2", "#f3dcda" ] } }, "guides": { "fill": { "type": "none" } }, "labs": { "x": "Year", "y": "Bloom date (earlier ↑)" }, "theme": "tufte" } ``` Rules that matter when generating specs: - Channels are objects, never bare strings: `{"field": "year"}` maps a column, `{"value": "#777777"}` sets a constant, `null` unsets a channel inherited from the plot-level `aes`. - Data has three forms. `{"values": [...]}` inlines rows; `{"columns": {...}}` is the columnar form; `{"name": "..."}` refers to a `datasets` entry. Inline `values` for data small enough to read, `datasets` + `columns` for anything large or shared between layers. Never truncate rows silently — say so, or point at the full source. - `layers` is ordered bottom to top and must hold at least one layer. A layer may carry its own `data`, which then replaces the plot's for that layer. - Stats are declarative. `{"geom": "smooth", "params": {"method": "loess"}}` fits in the pipeline; do not precompute a trend column and pass it off as raw data. The full machine-readable contract is /schema/v0.json. ## The validate loop `validate(spec)` checks schema shape; `validate(spec, { profile })` adds data-aware checks without shipping data; `{ lint: true }` also returns advisories for valid-but-questionable specs. Every error carries a stable `code`, a JSON `path` into the spec, a `message`, and a `fix` naming the change to make. That is the correction loop: emit, validate, apply the fix at the path, re-emit. Do not guess, and do not fall back to a different chart — the fix says what is wrong. ```ts fragment import { validate } from "@ggsvelte/spec"; const result = validate(spec); if (!result.ok) { for (const error of result.errors) { console.error(error.code, error.path, error.fix); } } ``` The complete error catalog, with the fix for each code, is at /guide/errors; advisories are at /guide/advisories. ## Headless rendering No browser, no DOM. `renderToSVGString` is pure: ```ts fragment import { registerAll, renderToSVGString } from "@ggsvelte/core"; // Headless/spec-driven rendering opts into the full grammar explicitly (#1420). registerAll(); const svg = renderToSVGString(spec, { width: 900, height: 360 }); ``` The installed CLI writes SVG to stdout and JSON Lines diagnostics to stderr, with exit classes documented at /reference/cli: ```sh fragment ggsvelte-render spec.json > chart.svg 2> diagnostics.jsonl ``` ## Building specs in TypeScript The fluent builder produces the same PortableSpec, with types: ```ts fragment import { aes, gg } from "@ggsvelte/svelte"; import { kyotoSakura } from "@ggsvelte/svelte/data"; const spec = gg(kyotoSakura, aes({ x: "year", y: "bloomDate" })) .geomPoint() .geomLine({ stat: "summary_rolling", fun: "median", window: 30, curve: "linear", }) .spec(); ``` ## Bundled data `@ggsvelte/svelte/data` exports seven cited teaching tables (each also served as JSON under the same name on the docs site): - `kyotoSakura` — 838 peak cherry-blossom dates for Kyoto, 812-2026 CE (`year`, `bloomDate`, `bloomDoy`). Time series. Data copyright Yasuyuki Aono; cite `KYOTO_SAKURA_CITATION`. - `palmerPenguins` — 333 complete Palmer Archipelago penguin measurements (`species`, `island`, bill/flipper/mass, `sex`, `year`, stable `id`). Distribution and categorical groups. CC0; cite `PALMER_PENGUINS_CITATION`. - `mpg` — 234 EPA fuel-economy rows for 38 popular models, 1999/2008 (`manufacturer`, `model`, `displ`, `class`, `drv`, `cty`, `hwy`, …). Categorical comparison. Cite `MPG_CITATION`. - `chocolateBars` — 2,530 Flavors of Cacao bar reviews (`cocoaPercent`, `rating`, company location, bean origin). Dense scatter and heatmaps. Via TidyTuesday 2022-01-18; cite `CHOCOLATE_BARS_CITATION`. - `coffeeRatings` — 1,338 Coffee Quality Institute cupping lots (`totalCupPoints`, aroma/flavor, origin, processing). Distributions and continuous scatter. Via TidyTuesday 2020-07-07; cite `COFFEE_RATINGS_CITATION`. - `beerProduction` — 36 US national beer-production totals by package type, 2008–2019 (`year`, `package`, `barrelsMillions`). Dodged multi-series bars. Via TidyTuesday 2020-03-31; cite `BEER_PRODUCTION_CITATION`. - `fastfoodMenu` — 515 US fast-food entrée nutrition rows (`restaurant`, `calories`, fat/protein/sodium). Categorical scatter and jitter. Via TidyTuesday 2018-09-04; cite `FASTFOOD_MENU_CITATION`. ## Grammar vocabulary - [Geoms](https://ggsvelte.sh/reference/geoms) — every mark, defaults, stats, positions, and params - [Guides and legends](https://ggsvelte.sh/reference/guides) — GuideLegend, colorbar, colorsteps, axis, none - [Labs](https://ggsvelte.sh/reference/labs) — title, subtitle, caption, axis/legend titles - [Axes and ticks](https://ggsvelte.sh/reference/axes) — GuideAxis, breaks/labels, collision, grids - [Labels](https://ggsvelte.sh/reference/labels) — chrome vs ticks vs GeomText/GeomLabel/SF labels - [Statistics and positions](https://ggsvelte.sh/guide/statistics-positions) — stats, jitter, stacking - [Scales](https://ggsvelte.sh/reference/scales) — every Scale* component (position, color, style) - [Scales and guides](https://ggsvelte.sh/guide/scales-guides) — continuous, discrete, manual, temporal - [Facets and coordinates](https://ggsvelte.sh/guide/facets-coordinates) — small multiples, flip, fixed aspect - [Chart themes](https://ggsvelte.sh/themes) and [palettes](https://ggsvelte.sh/palettes) — paper/ink chrome and data color - [Themes reference](https://ggsvelte.sh/reference/themes) and [palettes reference](https://ggsvelte.sh/reference/palettes) — props, tokens, scheme → scale helpers - [Interactions](https://ggsvelte.sh/guide/interactions) — inspect, pin, selection, zoom, linked views - [Production](https://ggsvelte.sh/guide/production) — sizing, SVG/canvas, SSR, export, support matrix - [Lifecycle](https://ggsvelte.sh/guide/lifecycle) — what is stable and what is not --- # Statistics and positions Stats derive marks from mapped rows. Positions control how derived marks share coordinate space. The full list of statistical transforms lives in the [stat reference](https://ggsvelte.sh/reference/stats): after_stat columns and which geoms accept each value. Open a specific stat, for example [count](https://ggsvelte.sh/reference/stats/count) or [smooth](https://ggsvelte.sh/reference/stats/smooth). Position adjustments are listed in the [position reference](https://ggsvelte.sh/reference/positions): stack, fill, dodge, jitter, nudge, and identity, with `positionParams` for jitter and nudge. ## Statistical summaries ```svelte fragment ``` [Loess example](https://ggsvelte.sh/examples/smooth/loess-scatter): smoother and confidence ribbon on source points. Histogram, density, boxplot, and errorbar use the same derive-then-render path. For discrete x, `stat: "summary"` collapses each group to one summary (default mean ± se). For continuous x, use `stat: "summary_bin"` instead. ## Binned y summaries (`summary_bin`) `stat: "summary_bin"` (ggplot2 `stat_summary_bin`) bins continuous `x` with the same break rules as `stat_bin`, then summarizes `y` in each non-empty (group × bin). Default fun is mean ± se. Available on **point**, **line**, and **errorbar**. Empty bins are omitted (unlike count bins). ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y" })) .geomPoint({ alpha: 0.35 }) .geomErrorbar({ stat: "summary_bin", binwidth: 1, boundary: 0 }) .geomLine({ stat: "summary_bin", binwidth: 1, boundary: 0 }) .spec(); ``` Bin knobs match histogram / freqpoly: `bins`, `binwidth`, `boundary`, `center`, `closed`. Summary knobs: `fun`, `funMin`, `funMax`. [Binned mean ± se](https://ggsvelte.sh/examples/errorbar/summary-bin): raw points with per-bin errorbars and a summary line. ## Quantile regression lines Linear quantile regression (ggplot2 `geom_quantile` / `stat_quantile`): fit `y ~ x` at each conditional quantile τ and draw one line per τ (default 0.25 / 0.5 / 0.75). v1 is linear only — no rqss, no weights. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y" })) .geomPoint() .geomQuantile({ quantiles: [0.1, 0.5, 0.9] }) .spec(); ``` [Quantile lines](https://ggsvelte.sh/examples/point/quantile-lines): scatter with three RQ lines. ## Contour isolines Contour isolines (ggplot2 `geom_contour` / `stat_contour`) draw open path polylines of constant `z` over a **regular** continuous `x` × `y` grid. Levels come from `params.breaks`, else `binwidth`, else `bins` evenly spaced from min(z)..max(z) inclusive (default 10). v1 is open polylines only — no `contour_filled`, no irregular triangulation, no default color-by-level. ```svelte fragment ``` ```ts fragment gg(grid, aes({ x: "x", y: "y", z: "z" })) .geomContour({ breaks: [0.25, 0.5, 0.75] }) .spec(); ``` Incomplete grid cells (missing/NaN corners) are skipped; groups without a usable grid or levels are dropped with a warning. after_stat `level` is carried for tooltips. [Contour isolines](https://ggsvelte.sh/examples/contour/basic): nested levels of a radial peak. ## 2D density isolines Bivariate KDE isolines (ggplot2 `geom_density_2d` / `stat_density_2d`) estimate a product Gaussian density over continuous `x` and `y`, then draw open path polylines of constant density. Bandwidth follows MASS `bandwidth.nrd` then kde2d's h/4 scaling (or `params.h` as one number or `[hx, hy]`). Grid `params.n`×`n` (default 100) spans a 5%-expanded data range. Levels use the same breaks / binwidth / bins rules as contour. Weights deferred. ```svelte fragment ``` ```ts fragment gg(scatter, aes({ x: "x", y: "y" })) .geomPoint({ alpha: 0.5 }) .geomDensity2d({ bins: 5, n: 40 }) .spec(); ``` Groups with fewer than two finite points are dropped with a warning. after_stat `level` and `density` are carried for tooltips. [2D density isolines](https://ggsvelte.sh/examples/density/kde-2d): scatter under nested KDE contours. ## 2D density filled bands `geom_density_2d_filled` / `stat_density_2d_filled` reuses the same KDE grid and draws **closed** isoline rings as filled polygons (ggplot2 `geom_density_2d_filled`). Open rings are dropped with `density-2d-filled-open-dropped`. Fill defaults to after_stat `level`. ```svelte fragment ``` ```ts fragment gg(scatter, aes({ x: "x", y: "y" })) .geomPoint({ alpha: 0.45 }) .geomDensity2dFilled({ bins: 5, n: 40 }) .spec(); ``` True isobands between consecutive levels and weights are deferred. [2D density filled bands](https://ggsvelte.sh/examples/density/kde-2d-filled): scatter under closed KDE rings colored by level. ## Dotplot (histodot) Histodot stacked dots (ggplot2 `geom_dotplot` / `stat_bindot`): continuous `x` is binned with the same break rules as `stat_bin`, then **one point per observation** is stacked in each bin. y is after_stat `stackpos` only (not count). Diameter tracks binwidth in x pixels (`dotsize` multiplier; `size` for an absolute px override). `stackdir`: `up` | `down` | `center` | `centerwhole`; `stackratio` scales vertical spacing (default 1). ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "v" })) .geomDotplot({ binwidth: 0.5, boundary: 0 }) .spec(); ``` v1 is histodot only — no Wilkinson `dotdensity`, no `binaxis = "y"`, no weights. Mapping aes.y fails loud (`computed-y-mapped`). [Dotplot histodot](https://ggsvelte.sh/examples/dotplot/histodot): stacked points in fixed bins. ## Simple features (`geom_sf`) `geom_sf` draws already-projected GeoJSON **Geometry** values stored as JSON **strings** in a data column (default `geometry`; override with `params.geometry`). Point/MultiPoint → points; LineString/MultiLineString → open paths; Polygon/MultiPolygon → closed fills. Multipart geometries expand to multiple marks. **Interior rings** are even-odd **holes** (SVG `fill-rule="evenodd"`, canvas, and hit-testing). `GeometryCollection` is flattened to leaf Point/Line/Polygon families (recursive, nesting depth capped). Mixed families in one layer still error (split layers). Default stat is public **`stat_sf`** (ggplot2 `stat_sf`): geometry expand runs on the normal non-identity frame path. Layer JSON stamps `stat: "sf"` (not `identity`). No CRS / `coord_sf` yet — coordinates are treated as already projected. ```svelte fragment ``` ```ts fragment gg(regions, aes({ fill: "rate" })).geomSf().spec(); // layer.stat === "sf"; geometry column holds JSON.stringify({ type: "Polygon", ... }) ``` [SF polygons](https://ggsvelte.sh/examples/sf/basic): three triangles filled by a rate field. [GeometryCollection expand](https://ggsvelte.sh/examples/sf/geometry-collection): one GC cell renders as two polygon parts. ### SF text labels (`geom_sf_text`) `geom_sf_text` (ggplot2 `geom_sf_text`) defaults to `stat_sf_coordinates`: one representative point per geometry part, then draws `aes.label` there. Point coordinates pass through; LineString uses the vertex mean; Polygon uses the exterior-ring shoelace centroid. **MultiPoint / MultiLineString / MultiPolygon emit one label per part** (feature aesthetics duplicated onto each part). **GeometryCollection** expands to leaves first, then the same per-part rule applies (one label per leaf part). Requires `aes.label` (no `aes.x`/`aes.y`). **Migration (multi-part labels):** earlier releases labeled only the first Multi* component. Callers that relied on a single first-component label will now see one label per part — filter geometries or aggregate labels if you need the old single-label behavior. ```svelte fragment ``` ```ts fragment gg(regions, aes({ fill: "rate", label: "region" })) .geomSf({ alpha: 0.55 }) .geomSfText({ size: 14 }) .spec(); ``` [SF region labels](https://ggsvelte.sh/examples/sf/labels): filled polygons with names at centroids. ### SF boxed labels (`geom_sf_label`) `geom_sf_label` is the boxed sibling of `geom_sf_text`: same `stat_sf_coordinates` placement, plus a measured rounded rect behind the text. `color` is ink + box stroke; `fill` is the box background (theme paper by default). Params include `padding`, `radius`, `linewidth`, and text `size`/`anchor`/`dx`/`dy`. ```svelte fragment ``` ```ts fragment gg(regions, aes({ fill: "rate", label: "region" })) .geomSf({ alpha: 0.45 }) .geomSfLabel({ padding: 3, radius: 2, size: 13 }) .spec(); ``` [SF boxed labels](https://ggsvelte.sh/examples/sf/boxed-labels): names on paper-backed label boxes. ## Ellipse confidence rings Bivariate normal confidence ellipses (ggplot2 `stat_ellipse`, type `norm` only) on **path** layers: per group, estimate mean and sample covariance, scale by √χ²₂(level), and sample the perimeter (`segments`, default 51) plus a closing duplicate for a closed ring. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y", color: "g" })) .geomPoint() .geomPath({ stat: "ellipse", level: 0.95 }) .spec(); ``` Path-only in v1 (not polygon). Groups with fewer than two finite points or zero variance are dropped with a warning. Rejected on other geoms. [Ellipse confidence rings](https://ggsvelte.sh/examples/path/ellipse-rings): scatter under 95% rings per series. ## Frequency polygon Frequency polygon (ggplot2 `geom_freqpoly`) bins continuous `x` and draws a line through bin centers (y defaults to count). Canonical form is `line` + `stat: "bin"` + position identity — not a separate mark type: ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "v", color: "g" })).geomFreqpoly({ bins: 30 }).spec(); // → { geom: "line", stat: "bin", position: "identity", y: { stat: "count" } } ``` [Frequency polygon](https://ggsvelte.sh/examples/freqpoly/basic): Michelson light-speed runs as a line through bin centers (companion to the histogram specimen). ## Unique (first-wins aesthetic dedupe) `stat: "unique"` drops duplicate rows on the combination of mapped aesthetic fields before drawing — first occurrence wins, panel-local (ggplot2 `stat_unique`). Available on identity-capable geoms (point, line, path, text, col, area, rect, ribbon, rule, segment, errorbar). ```svelte fragment ``` [stat unique overplotting](https://ggsvelte.sh/examples/point/stat-unique): stacked identical `(x, y, series)` triples collapse to one mark. ## Blank (scale training without marks) `geom: "blank"` (ggplot2 `geom_blank`) contributes mapped aesthetics to **scale training and layout only** — no paint, no hit targets. Use it to expand domains, force axes open for sparse marks, or reserve layout without drawing. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y" })) .geomPoint() .geomBlank({ aes: aes({ x: "x2", y: "y2" }) }) // expands domains only .spec(); ``` No channels are required. Mapped style channels (color, size, …) train their scales without drawing marks. Surfaces: `.geomBlank()`, ``. [Blank domain expand](https://ggsvelte.sh/examples/blank/domain-expand): co-layer blank rows stretch axes past the plotted points. [Blank axes only](https://ggsvelte.sh/examples/blank/axes-only): axes and scales with no marks. ## Convenience geoms (jitter, hline, vline) Name aliases that normalize to existing marks — no new paint paths: | Sugar | Normalizes to | |-------|----------------| | `jitter` | `point` + `position: "jitter"` | | `hline` | `rule` (horizontal) | | `vline` | `rule` (vertical) | `geomJitter` / `` accept flat `width` / `height` / `seed` and assemble them into `positionParams` at the builder/component boundary. `hline` / `vline` annotation intercepts (`yintercept` / `xintercept`) suppress plot-aes inheritance (ggplot2 `inherit.aes = FALSE`). Data-driven forms drop the orthogonal axis so the one-axis rule contract holds. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y" })) .geomJitter({ width: 0.2, height: 0.2 }) .geomHline({ yintercept: 0 }) .geomVline({ xintercept: 10 }) .spec(); ``` [Jitter sugar](https://ggsvelte.sh/examples/jitter/basic): overplotted points with position jitter. [Hline threshold](https://ggsvelte.sh/examples/hline/threshold) and [Vline cutoff](https://ggsvelte.sh/examples/vline/cutoff): annotation intercepts as rule aliases. ## Manual (portable named per-group transforms) `stat: "manual"` (ggplot2 `stat_manual`, portable v1) applies a **named** per-group transform — no JS callbacks (PortableSpec only). Required `params.fun`: | fun | Behavior | |-----|----------| | `first` / `last` | Keep one source row per aesthetic group | | `mean` / `median` / `min` / `max` / `sum` | One synthetic row; x and y aggregated independently | Surfaces: **point**, **line**, **path**. Missing `fun` fails with `manual-fun-required`; unknown names are schema `invalid-enum-value`. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y", color: "g" })) .geomPoint({ stat: "manual", fun: "mean" }) .spec(); ``` [stat manual mean centroids](https://ggsvelte.sh/examples/point/stat-manual-mean): identity scatter under large mean points per series. ## Align (shared continuous-x grid for stack) `stat: "align"` (ggplot2 `stat_align`) is for continuous-x `area` / `line` when series sample different x values. It unions finite x across groups, linearly interpolates each series onto that shared grid, and sets y to 0 outside a group's observed x range so `position: "stack"` / `"fill"` can compose without jagged seams. ```ts fragment gg(data, aes({ x: "t", y: "v", fill: "series" })) .geomArea({ stat: "align", position: "stack" }) .spec(); ``` ```svelte fragment ``` Available on **area** and **line** only (not point or shared identity-only geoms). Outside a group's x span y is 0 (stack-friendly). Discrete x joins the default grouping interaction (ggplot2 parity), so a band-x area/line with a discrete series field derives one group per (category, series) cell — every ribbon degenerates and a `group-single-observation` warning fires. Map `aes.group` to the series field to join categories into ribbons. Stacked **area** rescues sparse groups on its own: when a group's continuous x samples skip an interior grid point (a shape that would render as a floating band chorded over the stack below), the default identity stat auto-applies this align transform and emits a `stack-align-applied` advisory. The rescue stands down when the x scale may train discrete, or when a group repeats an x value (identity stacking sums repeats; align keeps the last). Pre-fill the data to control every cell exactly. ## Connect (named path joins) `stat: "connect"` (ggplot2 `stat_connect`) expands successive finite points into intermediate vertices so stepped joins are real path geometry — not only a stroke curve flag. `params.connection`: `hv` (default), `vh`, `mid`, `linear`. On **path** expansion is in data order; on **line** points are sorted by x first, and geometry skips a second x-sort so tied-x elbows stay intact. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y" })) .geomPath({ stat: "connect", connection: "hv" }) .spec(); ``` [Connect hv path](https://ggsvelte.sh/examples/path/connect-hv): three data points expand to a horizontal-then-vertical polyline. ## Curve connectors Curved connectors (ggplot2 `geom_curve`): one quadratic Bezier per row from `(x,y)` to `(xend,yend)`, tessellated in **panel px** so curvature is not aspect-skewed. Params: `curvature` (default 0.5), `angle` (degrees, default 90), `ncp` (control-point density). Same required channels as segment; `lineend` maps to SVG stroke-linecap (default butt). ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y", xend: "xend", yend: "yend" })) .geomCurve({ curvature: 0.5, angle: 90, ncp: 5 }) .spec(); ``` Intentional subset: quadratic approximation, not full grid xspline. [Curve connectors](https://ggsvelte.sh/examples/curve/connectors): Darwin maize pairs as arcs. ## Spoke (origin + angle + radius) `geom: "spoke"` (ggplot2 `geom_spoke`) draws one finite segment per row from `(x, y)` in direction `angle` (radians; 0 = +x, π/2 = +y) with length `radius` in **data units**. Endpoints are `xend = x + radius·cos(angle)`, `yend = y + radius·sin(angle)`, then the same position transform as x/y. Tips train domains; paint reuses segment strokes. `angle` and `radius` come from aes and/or constant `params`. Continuous x and y required. ```svelte fragment ``` ```ts fragment gg(data, aes({ x: "x", y: "y", angle: "theta", radius: "r" })) .geomSpoke({ linewidth: 1.5, lineend: "round" }) .spec(); // constants: .geomSpoke({ angle: 0, radius: 1 }) ``` [Spoke vector field](https://ggsvelte.sh/examples/spoke/vector-field): synthetic 5×5 field with mapped angle and radius. ## Map (fortified choropleth) `geom: "map"` (ggplot2 `geom_map`) joins a **fortified map table** to value rows. Map coordinates come from `long`+`lat` or `x`+`y`; the join key is `aes.map_id` on the value table matched to `params.mapId` on the map (default `"region"`, then `"id"`). Optional map `group` splits multipoly rings. Missing regions drop with a `map-region-missing` warning. ```svelte fragment ``` ```ts fragment gg(rates, aes({ map_id: "region", fill: "rate" })) .geomMap({ map: { values: fortified }, mapId: "region" }) .spec(); ``` Intentional subset: no network map fetches, no sf/CRS, no public `geom_polygon` (map ships the closed-path renderer only). [Map choropleth](https://ggsvelte.sh/examples/map/choropleth): three toy regions filled by rate. ## Positions Stack sums, dodge side-by-side groups, fill normalizes each stack to one, jitter separates overlaps with a deterministic seed. [Bar examples](https://ggsvelte.sh/examples?category=bar). --- # Scales and guides A scale translates semantic data values into a visual position or color. A position transform runs before statistics; an axis explains the trained scale without changing its meaning. ## Continuous position scales Numeric x and y fields use a continuous linear scale by default. Non-temporal continuous scales reserve 5% multiplicative display expansion at both ends. Expansion affects only display training, never filtering or statistics. Restore flush bounds with `expand: { mult: 0, add: 0 }`. Use the closed `identity`, `log10`, and `sqrt` transforms. The scale family stays `linear`: GuidePlan and RenderModel report `type/scaleType: "linear"` plus the transform. Authored `type: "log"` remains an accepted alias and canonicalizes to `{ type: "linear", transform: "log10" }`. ```json fragment { "scales": { "x": { "type": "linear", "transform": "log10" }, "y": { "type": "linear", "transform": "sqrt", "reverse": true } } } ``` Builder helpers and their ggplot2 aliases produce the same canonical spec: ```ts fragment import { scaleXLog10, scaleYSqrt, scale_x_log10, scaleColorGradient, scale_color_gradient2, scale_fill_gradientn, } from "@ggsvelte/spec"; const camel = scaleXLog10({ domain: [1, 10_000] }); const alias = scale_x_log10({ limits: [1, 10_000] }); const root = scaleYSqrt({ reverse: true }); // Continuous colour gradients (#826): map to sequential scales with explicit range. const twoStop = scaleColorGradient({ low: "#132B43", high: "#56B1F7" }); const diverging = scale_color_gradient2({ low: "#B2182B", mid: "#F7F7F7", high: "#2166AC" }); const nStop = scale_fill_gradientn({ colours: ["#440154", "#21918c", "#fde725"] }); ``` Svelte shells: ``, ``, `` (and fill / colour aliases). gradientn requires ≥2 hex stops via `colours` / `colors` / `values`. See [gradient colour example](https://ggsvelte.sh/examples/point/gradient-continuous). The Svelte surface accepts the same JSON and re-exports the same helpers: ```svelte fragment ``` The smooth receives transformed x and y. This is intentionally different from a post-stat coordinate transform: scale transformation can change a fit, histogram, density estimate, summary, or boxplot. ## Limits, missing values, and OOB policy `domain` and helper `limits` pin an unexpanded interval in semantic source units. Supplying both to a helper is an error. The default `oob: "censor"` replaces out-of-limit values with missing before stats; `oob: "squish"` clamps them to the nearest limit first. `naValue` replaces missing/censored positions before transform-domain validation. Log10 requires positive values and sqrt requires non-negative values. Recovery is explicit: filter or repair the data, select identity, widen limits, or choose the intended OOB policy. See [scale-transform-domain](https://ggsvelte.sh/guide/errors#scale-transform-domain), [scale-oob-censored](https://ggsvelte.sh/guide/errors#scale-oob-censored), and [scale-oob-squished](https://ggsvelte.sh/guide/errors#scale-oob-squished). ## Binned positions A binned scale assigns quantitative values to bounded transformed-space bins while preserving source values for tooltips and events: ```svelte fragment ``` The runtime keeps integer bin identities private for count/stack/fill/dodge. Geometry, jitter, guides, and synthesized candidates use transformed centers and semantic inverse values. Explicit or automatic bins are right-closed with an inclusive lowest edge and are capped at 64. ## Breaks and labels `breaks` and `minorBreaks` are bounded semantic source values. Major breaks win when a major and minor coincide. Explicit breaks outside the trained domain are omitted with [scale-break-outside-domain](https://ggsvelte.sh/guide/errors#scale-break-outside-domain). Temporal `dateMinorBreaks` outranks generic `minorBreaks`. `reverse` changes the pixel direction but not semantic tick order. `nice` controls numeric domain rounding. Guides retain complete semantic values and apply the forward transform exactly once. For the full split between scale formats, axis guides, and plot titles, see [Axes and ticks](https://ggsvelte.sh/reference/axes) and [Labs](https://ggsvelte.sh/reference/labs). ## Categorical color Use a named categorical scheme when color identifies groups: ```svelte fragment ``` Stable assignments preserve category identity as rows filter or reorder. See registered schemes and capacities on [Color palettes](https://ggsvelte.sh/palettes). Palette exhaustion is `onExhaust: "cycle"` (default, warn once) or `"error"` — diagnostics at [palette-exhausted](https://ggsvelte.sh/guide/errors#palette-exhausted) and [palette-exhausted — warning](https://ggsvelte.sh/guide/errors#palette-exhausted-warning). ggplot2-shaped discrete helpers (portable named schemes, not bake-only): ```svelte fragment ``` ```ts fragment import { scaleColorHue, scaleColorGrey, scaleColorOrdinal } from "@ggsvelte/spec"; scaleColorHue(); // { color: { type: "ordinal", scheme: "hue" } } scaleColorGrey(); // bakes a 10-stop greyscale range (US gray is a binding-identical alias) scaleColorOrdinal({ scheme: "colorblind" }); // alias of scaleColorDiscrete // Custom h/c/l (hue) or start/end (grey) bake a fixed 10-stop range instead. ``` [Hue discrete colour](https://ggsvelte.sh/examples/point/hue-discrete): even-hue groups via `scale_color_hue`. There is no registered `"grey"` / `"gray"` scheme — use `scaleColorGrey()` / `` for greyscale discrete color. ## Continuous, binned, manual, and identity color Quantitative color/fill defaults to a continuous viridis colorbar. Named viridis-family constructors match ggplot2 `scale_*_viridis_{c,d,b}` (`option` selects `viridis`/`magma`/`plasma`/`inferno`/`cividis`/`turbo`; `direction: -1` reverses). Discrete viridis samples evenly across the ramp. The `identity`, `log10`, and `sqrt` transforms run before color-domain training; they do not change position statistics. Explicit reference `breaks` stay in semantic source units. ```ts fragment import { scaleColorLog10, scaleColorViridisD, scaleFillViridisC, } from "@ggsvelte/spec"; const color = scaleColorLog10({ domain: [1, 1000] }); const fill = scaleFillViridisC({ option: "plasma" }); const groups = scaleColorViridisD({ option: "viridis" }); ``` Binned color/fill uses deterministic `[lower, upper)` intervals with the final upper edge included. At most 65 boundaries (64 steps) are portable. A colorsteps guide exposes every boundary, label, swatch, and inclusivity rule: ```svelte fragment ``` Manual scales pair each domain value with exactly one color and never recycle unknown values. Identity scales validate source `#rgb`/`#rrggbb` values and show no guide by default. `naValue` handles missing values; `unknownValue` handles invalid, unmapped, or censored values. Multi-aesthetic helpers expand the same identity or manual config across channels (British `colour` aliases `color`): ```ts fragment import { scaleContinuousIdentity, scaleDiscreteManual, scaleType, } from "@ggsvelte/spec"; const linked = scaleDiscreteManual({ aesthetics: ["colour", "fill"], values: ["#4477aa", "#ee6677"], domain: ["control", "treated"], }); const rawSize = scaleContinuousIdentity({ aesthetics: ["size", "alpha"] }); // Agent default: scaleType({ aesthetic: "color", dataKind: "nominal" }) → "ordinal" ``` ```ts fragment const manual = scaleColorManual({ domain: ["control", "treated"], values: ["#4477aa", "#ee6677"], unknownValue: "#999999", }); const identity = scaleFillIdentity({ naValue: "#cccccc" }); ``` Color and colour spellings are binding-identical exports, including `scaleColorBinned`, `scaleColourBinned`, `scale_color_binned`, and `scale_colour_binned`. Fill exports use the same families. Date/datetime helpers reuse the strict parser registry and semantic epoch representation: `scaleColorDate`, `scaleColorDatetime`, `scaleFillDate`, and `scaleFillDatetime`. Open [binned color](https://ggsvelte.sh/examples/color/binned) for colorsteps, and continuous color scales via `scaleColorContinuous` / `scale_color_continuous`. ## Size, linewidth, alpha, shape, and linetype The remaining visual channels use the same stable scale contract. Quantitative `size`, `linewidth`, and `alpha` default to sequential scales; categorical values default to ordinal scales. Size interpolation is perceptually linear in symbol area. Alpha is bounded to 0–1, while size and linewidth must stay positive. ```ts fragment import { scaleSizeContinuous, scaleLinewidthBinned, scaleAlphaDate, scaleShapeManual, scaleLinetypeDiscrete, } from "@ggsvelte/spec"; const scales = { ...scaleSizeContinuous({ range: [2, 10] }), ...scaleLinewidthBinned({ breaks: [0, 10, 20, 50] }), ...scaleShapeManual({ domain: ["control", "treated"], values: ["circle", "triangle"], }), ...scaleLinetypeDiscrete(), }; ``` Shape and linetype are finite perceptual sets. Continuous values therefore require an explicit `binned` scale; they are never silently interpolated. Manual scales require one output per domain value, and exhaustion errors by default unless `onExhaust: "cycle"` is explicitly selected. Identity scales validate literal outputs and suppress guides. Discrete and binned style mappings participate in grouping; continuous numeric styles do not. Mapped values survive stats, positions, SVG/Canvas rendering, server rendering, inspection, legend focus/filtering, and hit testing. Literal constants remain unscaled unless authored as `{ value, scale: true }`. Missing and invalid values use distinct `naValue` and `unknownValue` outputs. Date/datetime helpers reuse the strict parser and timezone semantics used by position and color scales. The five style channels each have their own scale families in the [scales reference](https://ggsvelte.sh/reference/scales): [size](https://ggsvelte.sh/reference/scales/size_continuous), [alpha](https://ggsvelte.sh/reference/scales/alpha_continuous), [shape](https://ggsvelte.sh/reference/scales/shape_ordinal), [linetype](https://ggsvelte.sh/reference/scales/linetype_discrete), and [linewidth](https://ggsvelte.sh/reference/scales/linewidth_continuous). ## Responsive guide presentation Guide appearance is downstream of scale training. The full list of guide shells and helpers lives in the [guides reference](https://ggsvelte.sh/reference/guides): [GuideLegend](https://ggsvelte.sh/reference/guides/legend), [GuideColorbar](https://ggsvelte.sh/reference/guides/colorbar), [GuideColorsteps](https://ggsvelte.sh/reference/guides/colorsteps), [GuideAxis](https://ggsvelte.sh/reference/guides/axis), and [GuideNone](https://ggsvelte.sh/reference/guides/none). Author top-level `guides`, a scale-local `guide`, or fluent `.guides()` with `guideAxis`, `guideLegend`, `guideColorbar`, `guideColorsteps`, and `guideNone`. Top-level entries win over scale-local entries. Axis shells, collision, and band label layout are catalogued under [Axes and ticks](https://ggsvelte.sh/reference/axes); plot titles stay on [Labs](https://ggsvelte.sh/reference/labs). ```ts fragment import { guideAxis, guideColorsteps } from "@ggsvelte/spec"; const guides = { x: guideAxis({ title: "Hour", showTicks: false }), color: guideColorsteps({ position: "bottom", direction: "horizontal" }), }; ``` In Svelte that object is a `` child: ```svelte fragment ``` Automatic legends stay right only when the viewport is wider than 480px and at least 320px of readable panel remains; otherwise they move below. Bottom keys wrap without shrinking type and bottom ramps are horizontal. Discrete guides merge only across exact semantic and presentation identities. Exact raw-value entries stay interactive after merging; numeric ticks and bins do not become filter targets. Identity/manual guides with fewer than two entries remain hidden unless `force: true` is explicit. ## Date and time axes Declare a time scale for ISO 8601 values and let the scale choose UTC calendar ticks. Time axes preserve temporal parsing and expansion behavior and always use the identity position transform. Pin breaks or labels only when the audience needs a fixed convention. The [time-axis example](https://ggsvelte.sh/examples/line/time-axis) is the runnable contract. --- # Facets and coordinates Facets partition rows into panels before panel stats. Coordinates present trained scales (flip, etc.) without rewriting aesthetic mappings. ## Facet a comparison ```svelte fragment ``` [facet wrap](https://ggsvelte.sh/examples/facet/wrap), [free-y](https://ggsvelte.sh/examples/facet/wrap-free-y). ## Coordinates Prefer `coord flip` for horizontal bars over swapping x/y semantics. [Horizontal bar](https://ggsvelte.sh/examples/bar/horizontal). ## Scale transforms versus coordinate transforms A scale transform changes the values consumed by statistics and positions. A coordinate transform leaves those computations alone and projects the final geometry: ```ts fragment // The linear fit consumes log10(exposure). gg(rows, aes({ x: "exposure", y: "response" })) .geomSmooth({ method: "lm" }) .scaleXLog10(); // The fit consumes exposure; only its rendered geometry is curved. gg(rows, aes({ x: "exposure", y: "response" })) .geomSmooth({ method: "lm" }) .coordTransform({ x: "log10" }); ``` The portable JSON form is strict and callback-free: ```json complete { "type": "transform", "x": { "transform": "log10", "limits": [1, 1000], "reverse": false, "expand": false }, "clip": true } ``` Use `coordTransform` or its identical ggplot2-style alias `coord_transform`. In Svelte it is a `` child, which takes the same options (`` is the escape hatch for a coordinate computed elsewhere): ```svelte fragment ``` Coordinate limits create a post-stat viewport: they do not censor rows or recompute a fit. Coordinate inversion runs before scale inversion, so tooltip, interval, and brush-zoom values remain semantic. Nonlinear lines, smooths, areas, smooth confidence bands, and segments use bounded adaptive tessellation; synthetic render vertices never become inspectable data. Set `clip: false` only for intentional panel overflow. ## Preserve physical data-unit ratios Use `coordFixed()` when equal data units must have equal physical lengths. The layout reserves titles, axes, and responsive guides first, then centers the largest exact-ratio data rectangle in the remaining allocation. Panel fill, grids, marks, clipping, axes, and facet strips occupy only that rectangle; letterbox gutters use the theme paper role by default. ```ts fragment gg(rows, aes({ x: "x", y: "y" })) .geomLine() .coordFixed({ ratio: 1 }); ``` `ratio` is physical y-unit length divided by physical x-unit length. The camelCase helper, builder `.coordFixed()`, `coord_fixed`, `coordEqual`, and `coord_equal` all emit the same strict JSON. Free positional facet scales are rejected with `coord-fixed-free-scales`; use fixed facet scales when panels must share one physical comparison. On an unusually constrained container the ratio is never stretched: minor furniture is removed, the SVG declares `data-gg-layout="degraded"`, and authors receive one `coord-fixed-degraded` warning. See the runnable [fixed-aspect example](https://ggsvelte.sh/examples/point/fixed-aspect). Non-identity coordinate transforms reject band and temporal axes with `coord-transform-continuous` or `coord-transform-temporal`. Domains that cross log10/sqrt boundaries fail with `coord-transform-domain` and exact recovery guidance. --- # Dates without preprocessing ggsvelte infers strict ISO dates/date-times, four-digit year strings, year-months, month-years, year-quarters, and runtime `Date` values from data. Classification inspects at most the first and last 32 non-null values; after it selects one parser family, every non-null value must validate. A partially valid column never becomes partially temporal. ## Let the default work `"1835"`, `"1900"`, and `"2026"` are spaced as calendar years, not as three equally spaced categories. Numeric `1835` stays quantitative. ```svelte fragment ``` ## Inspect the choice Read `model.scaleDecisions` in `onrender` for field, parser, precision, bounded evidence, validated count, trained domain, ambiguity, and a portable override. Exceptional or advisory choices also appear in `model.scaleDiagnostics` as stable problem/cause/fix records. The responsive axis decisions live in `model.guidePlans`: each drawn panel axis reports its calendar interval, visible and complete labels, major/minor tier, locale, timezone, overlap state, and stable ID. `ScaleDecision.guidePlanIds` links inference to those panel plans without copying facet-specific arrays. ## Responsive calendar labels Automatic temporal axes score calendar-aligned candidates from milliseconds to centuries against the actual panel extent and measured label widths. They prefer 3–7 major labels, but no-overlap wins. Month, quarter, year, week, and day stepping follows civil boundaries rather than average milliseconds. The planner runs inside the existing two layout passes and may move only to a coarser interval during pass B. Default date labels keep UTC calendar meaning. Datetime labels use the configured IANA timezone. The deterministic defaults are `en-US`, UTC, and Monday week starts. Visible labels may suppress repeated context; every SVG major tick keeps a complete standalone label in its ``. Use exact portable controls when the default is not the editorial choice: ```ts fragment const spec = gg(rows, aes({ x: "when", y: "value" })) .geomLine() .scaleXDatetime({ dateBreaks: "2 weeks", dateMinorBreaks: "1 day", dateLabels: "%e %b", locale: "en-GB", timezone: "Europe/London", weekStart: "monday", }) .spec(); ``` Canonical JSON uses the same fields. Interval strings are a positive integer plus `millisecond`, `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, or `year` (singular or plural). Explicit `breaks` outrank `dateBreaks`; `dateLabels` outranks the older soft-fallback `labels` field. Authored breaks and labels are never silently thinned, rotated, or truncated. If they cannot fit, the render keeps them and emits a structured scale diagnostic with a coarser-interval or wider-layout fix. ## Override one choice Ambiguous values such as `03/04/2024` stay discrete. Pick the intended order: ```ts fragment const spec = gg(rows, aes({ x: "when", y: "value" })) .geomLine() .scaleXDate({ parse: "dmy" }) .spec(); ``` Canonical JSON uses `scales: { x: { type: "time", parse: "dmy" } }`. The closed parser names are generated from the runtime registry: `iso`, `year`, `ym`, `my`, `yq`, `md`, `ymd`, `ydm`, `mdy`, `myd`, `dmy`, `dym`, `ymd_hm`, `ymd_hms`, `ydm_hm`, `ydm_hms`, `mdy_hm`, `mdy_hms`, `myd_hm`, `myd_hms`, `dmy_hm`, `dmy_hms`, `dym_hm`, `dym_hms`. Exact bounded formats and epoch seconds/milliseconds are object parser forms. Timezone-less values mean UTC; IANA zones use Temporal with explicit DST disambiguation. If four-digit strings are identifiers, force categories with `.scaleXDiscrete()`, `scale_x_discrete()`, or `scales: { x: { type: "band" } }`. ## PortableSpec boundary PortableSpec remains strict JSON: no `Date`, callback, or regular expression. The checked capability ledger records the temporal family as `implemented`; docs, helper tests, and agent checks consume that ledger. Builder and Svelte authoring may contain runtime Dates; they canonicalize to ISO before validation. The standalone `ymd`, `mdy`, `dmy`, related order and timestamp helpers, exact-format parser, and epoch helpers return authoring Dates. --- # Interactions Static by default. Opt in with `<Inspect>`, `select`, `zoom`, and GuideLegend `focus` / `filter` (or the legacy GGPlot `inspect` prop, and deprecated plot props `legendFocus` / `legendFilter`). With more than one draw tool, an accessible tool rail keeps gestures from competing. Without a controller, state is private to one chart and callbacks report changes. Pass `createPlotInteraction()` when plots, controls, or tables share semantic state (required, stable semantic scope via `interactionScope`). Examples: [inspect](https://ggsvelte.sh/examples/interactions/inspection), [interval/zoom](https://ggsvelte.sh/examples/interactions/interval-selection), [linked views](https://ggsvelte.sh/examples/interaction/linked-views), [legend focus](https://ggsvelte.sh/examples/interaction/legend-focus), [legend filter](https://ggsvelte.sh/examples/interaction/legend-filter), [facet intervals](https://ggsvelte.sh/examples/interaction/facet-intervals). Contracts: [interaction reference](https://ggsvelte.sh/guide/interaction-reference). ## Inspection `<Inspect />` enables the default HTML tooltip, semantic crosshair, keyboard traversal, and click-or-Enter pinning (same as the legacy `inspect={true}` prop on `<GGPlot>`). Configure it when the chart has a natural comparison axis: ```svelte fragment <script lang="ts"> import { GeomLine, GeomPoint, GGPlot, Inspect } from "@ggsvelte/svelte"; </script> <GGPlot {data} aes={{ x: "date", y: "value", color: "series" }} oninspect={(event) => console.log(event)} > <GeomLine /> <GeomPoint /> <Inspect mode="x" pin maxDistance={24} /> </GGPlot> ``` The modes are `auto`, `exact`, `x`, `y`, and `xy`. `auto` resolves to a concrete mode before an event is emitted. `x` and `y` return one representative per semantic series at the focused axis value; `exact` and `xy` return the focused datum. `maxDistance` is measured in CSS pixels: the dominant axis for `x` or `y`, Euclidean distance for `xy`, and geometry containment plus tolerance for `exact`. Rect marks (`geom_col` / `geom_bar`) never draw a point ring; default hover is tooltip-only. Pass `muteSiblings` on `<Inspect>` to mute non-focused bars via the interaction mask. For custom HTML, pass a Svelte 5 snippet on `content`. Informational content is the default; choose `contentMode="interactive"` only when the pinned tooltip contains controls that need focus. ```svelte fragment {#snippet details(inspection)} <strong>{inspection.focus.row?.name}</strong> <span>{inspection.members.length} series at this value</span> {/snippet} <GGPlot {data} aes={{ x: "date", y: "value" }}> <Inspect mode="x" content={details} /> </GGPlot> ``` ## Point and interval selection Point selection is durable identity, not a renderer index. Identity defaults to an `id` column or row index; override with `identity` on Select / Inspect for a non-`id` natural key: ```svelte fragment <GGPlot select={{ type: "point", multiple: true, identity: "id" }} onselect={(event) => { if (event.mode === "point") selectedKeys = event.keys; }} /> ``` Use interval selection for brushing. The callback receives both the selected domain and normalized plot-pixel rectangle, plus semantic keys and a lineage count for aggregate marks. ```svelte fragment <GGPlot select={{ type: "interval", mode: "xy", persistent: true }} onselect={(event) => { if (event.mode !== "point" && event.phase === "end") { selectedDomain = event.domain; } }} /> ``` Faceted intervals use stable field-and-value panel identities rather than panel indices. Choose a preset for the relationship between panels: - `independent` (default) replaces the interval in only the origin panel. - `union` keeps independently drawn panel intervals and combines their keys. - `cross-panel` projects one semantic domain through every compatible panel. `cross-panel` intersects the interval with each panel's domain when facet scales are free; a disjoint panel selects nothing instead of clamping to an unrelated edge. Panel identity survives row reordering and temporary absence. See the [runnable facet example](https://ggsvelte.sh/examples/interaction/facet-intervals). ## Shared controlled state `createPlotInteraction<Key>()` owns selection, emphasis, and continuous zoom domains outside any chart. Give linked consumers the same controller and a required, stable semantic scope via `interactionScope`. A transition is published once by its origin; passive charts render the new snapshot without emitting the callback again. Controlled plots never infer channel names: add an `x` and/or `y` scope whenever controlled zoom uses that channel. ```svelte fragment <script lang="ts"> import { createPlotInteraction } from "@ggsvelte/svelte"; const interaction = createPlotInteraction<string>({ identity: "id" }); const scope = { keys: "penguin-id", x: "flipper-mm", y: "mass-g" } as const; const selected = $derived(interaction.selected(scope)); </script> <GGPlot {data} select={{ type: "point", multiple: true }} {interaction} interactionScope={scope} /> <GGPlot {data} select={{ type: "point", multiple: true }} {interaction} interactionScope={scope} /> <button onclick={() => interaction.setSelection(["gentoo-1"], { scope })}> Select Gentoo 1 </button> ``` Use `setSelection`, `toggleSelection`, and `clearSelection` for durable keys. `setEmphasis` is presentation-only: linked charts update their highlight overlay without retraining scales or rerunning the render pipeline. Matching `x` and `y` scope names share numeric zoom domains. When application data is replaced, call `reconcileKeys(validKeys, { scope })` explicitly; a chart never guesses whether a temporary subset should erase another view's selection. Durable facet intervals use their own optional `interactionScope.intervals` namespace (falling back to `keys`). Read them with `intervals(scope)`, write one with `setInterval`, clear one panel with `clearInterval`, or clear the scope with `clearIntervals`. Interval state is semantic data-space state, not pixels or renderer indices. ## Legend focus `<GuideLegend channel="color" focus />` adds real HTML controls over that aesthetic's discrete legend. Hover and DOM focus preview one chart without mutating shared state. Click, touch, Enter, or Space commits the matching stable row keys; the active entry or Escape clears them. Arrow keys traverse entries in rendered legend order, with Home and End moving to the boundaries. `focus={{ preview: false }}` keeps committed activation but disables transient previews. Continuous ramps remain static. A stable `key` is required: encoded legend values are reported as values, never used as controller keys. Focused and muted marks share one semantic mask across SVG and canvas, and the mask does not retrain scales, recompute statistics, change layout, or reassign colors. Author discrete legend appearance with [GuideLegend](https://ggsvelte.sh/reference/guides/legend); see the full [guides reference](https://ggsvelte.sh/reference/guides) and the [runnable three-view example](https://ggsvelte.sh/examples/interaction/legend-focus). The plot prop `legendFocus` is deprecated since 0.19.0. ## Legend filtering GuideLegend `focus` is presentation emphasis only — it does not change data. `<GuideLegend channel="color" filter />` adds Show-group checkboxes on that aesthetic's discrete legend and filters rows before facets, stats, scales, layout, and render. Hidden groups stay in the legend catalog and keep the same categorical color when shown again. Use `filter={{ mode: "exclude", multiple: true }}` for the default independent checkboxes. `mode: "include"` stores the shown values instead; `multiple: false` makes a toggle isolate one group. `onlegendfilter` reports the raw typed values and field in a `LegendFilterClause`. Reset legend filters restores the data pipeline; Clear legend focus only removes presentation emphasis. The plot prop `legendFilter` is deprecated since 0.19.0. See the [stable-color example](https://ggsvelte.sh/examples/interaction/legend-filter). ## Brush zoom `zoom={true}` enables two-dimensional brush zoom. Set `zoom={{ mode: "x" }}` or `zoom={{ mode: "y" }}` for a single axis. The tool rail separates Zoom area from Select area when both are enabled. A completed zoom emits explicit domains; Reset zoom or double-click emits a clear event. Faceted interval selection is supported, but faceted brush zoom remains disabled with `INTERACTION_INTERVAL_FACET_UNSUPPORTED`; use a linked detail view when each facet needs a zoomed inspection surface. ```svelte fragment <GGPlot zoom={{ mode: "xy" }} onzoom={(event) => console.log(event.domains)} /> ``` ## Precise bounds without dragging After an interval selection or zoom is committed, the tool rail exposes Edit x or y bounds alongside its drag controls. The inline HTML form stages edits: typing does not rerun the chart, Apply commits once, Cancel or Escape discards the draft, and validation focuses the first invalid field. This provides a keyboard and assistive-technology path to the same semantic result as brushing. - Linear and reversed scales accept ascending data-space numbers. Reversal is presentation only, so do not enter screen order. - Log scales accept positive ascending numbers. - Time scales accept ISO 8601 dates or date-times with `Z` or an explicit offset; events store Unix milliseconds. - Band scales use two native selects and include both endpoint categories. Recovery actions are deliberately separate: Clear panel selection removes one facet interval, Clear all selections removes interval state, Reset zoom restores natural domains, and Reset legend filters restores excluded rows. None of these controls silently performs another reset. ## Event reference All events carry `type`, `phase`, and `source` (`pointer`, `keyboard`, `touch`, or `programmatic`). Use the focused callback for one capability or `oninteraction` for the discriminated union of every event. ### `oninspect(event: PlotInspection)` - A change is `{ type: "inspect", phase: "change", state, source, mode, panelId, focus, members }`. - `state` is `transient` or `pinned`; `members` is always non-empty and `focus` is the member under direct inspection. - `x` and `y` changes also carry the original logical `axisValue` and its formatted `axisLabel`. - Dismissal is the small event `{ type: "inspect", phase: "clear", source }`. Each `PlotDatum` has `key`, source `row` when one exists, aggregate `sourceKeys` and `lineageCount`, `layerIndex`, `panelId`, mapped `fields`, and a plot-pixel `anchor`. Keyless or synthetic marks expose `key: null`; internal renderer indices never leak into callbacks. ### `onselect(event: PlotSelection)` - Point selection emits `{ type: "select", phase: "end" | "clear", mode: "point", keys, source }`. - Interval selection emits `start`, `change`, `end`, and `clear` phases with `mode`, `panelId`, `domain`, `pixels`, `keys`, `lineageCount`, and `source`. ### `onzoom(event: ZoomEvent)` - Zoom completion is `{ type: "zoom", phase: "end", source, domains }`. - Reset is `{ type: "zoom", phase: "clear", source, domains: null }`. ### `onlegendfocus(event: LegendFocusEvent)` - Preview and commit emit `{ type: "legend-focus", phase: "change", state, source, scale, value, label, keys }`. - `state` is `transient` or `committed`. `value` is the raw encoded domain value while `keys` are distinct stable source-row identities. - Dismissal emits `{ type: "legend-focus", phase: "clear", source }`. ### `onlegendfilter(event: LegendFilterEvent)` - A change emits `{ type: "legend-filter", phase: "change", source, clause }`. - `clause` names the color or fill scale, source field, typed values, and include or exclude mode. Reset emits `phase: "clear"` and `clause: null`. - Filtering is data-changing and intentionally separate from the presentation-only `onlegendfocus` event. `oninteraction(event: PlotInteractionEvent)` receives the same objects. It does not wrap or duplicate them. A linked chart that consumes shared state should not re-emit the origin chart's event. ## Keyboard and accessibility defaults Name charts with `ariaLabel` (subject or takeaway — not generic image alt). Focus the plot, then use arrow keys or brackets to traverse data. Enter or Space pins inspection, activates point selection, or sets the two corners of an area, depending on the active tool. Escape dismisses the current interaction. Keyboard inspection updates a polite live region with a concise axis, count, and pin summary; complete pinned content remains ordinary labelled and navigable DOM. Canvas marks keep SVG axes/legends and the accessible description path. ## Identity and diagnostics Ordinary charts omit custom identity: the engine uses an `id` column when present, otherwise the row index (order-stable only). For a non-`id` natural key or accessor, set `identity` on `<Inspect>`, object-form `select`, or `createPlotInteraction({ identity })` — not plot-level `key` (deprecated). Keys must be non-null unique `PropertyKey` values and stable across updates. Invalid or duplicate keys emit structured diagnostics through `ondiagnostic`. Stable identity lets pinned inspection and point selection follow a datum when data is updated. --- # Production ## Responsive sizing Omit width: GGPlot observes its container. Positive-width block, no chart CSS. Omitted height: 400px default. Collapsed parent, hidden tab, or zero-width track → not-ready until ResizeObserver reports positive width. Do not paper over that with a fake fixed width. [Troubleshooting](https://ggsvelte.sh/guide/errors#quickstart-troubleshooting). SSR uses an 832×400 deterministic fallback and stays not-ready in HTML until hydration measures the real container. Reserve layout space to avoid CLS. ## Rendering Renderer follows mark density and interaction needs. Axes, legends, labels, and a11y chrome stay semantic regardless of SVG vs canvas. SVG: DOM marks. Canvas: dense strata. Auto: switches above the published threshold (`CANVAS_AUTO_THRESHOLD`) and emits `canvas-auto`. Force with layer `"render": "canvas"` or `render="canvas"`; axes, legends, and a11y chrome stay SVG. Inspection and selection use the model-owned candidate store, not DOM hit tests. Stable keys keep identity across SVG/canvas; renderer indices never appear in public callbacks. Measure with repo fixtures before forcing canvas globally. For a real-data scatter surface, see [scatter color](https://ggsvelte.sh/examples/point/scatter-color). ## Server and export Three paths, one PortableSpec: Svelte SSR, pure `renderToSVGString`, CLI. ```ts fragment import { registerAll, renderToSVGString } from "@ggsvelte/core"; // Headless full-grammar rendering (#1420): explicit opt-in. registerAll(); const svg = renderToSVGString(spec, { width: 640, height: 400 }); ``` ```sh fragment # npm install -g @ggsvelte/cli ggsvelte-render spec.json > chart.svg ``` SVG on stdout; JSON Lines diagnostics on stderr — the agent feedback loop. [CLI reference](https://ggsvelte.sh/reference/cli). ## Compatibility Every release is tested as an installed package: clean install, strict type-check, client build, server render, pure Node render, and the `ggsvelte-render` CLI. - Node.js `>=22` (22 and 24 in CI; 26 nightly) - Svelte `^5.33.1` (tested floor 5.33.1, current 5.56.5) - npm bundled with Node, pnpm 11.13.0, Bun 1.3.14 - Chromium, Firefox, and WebKit (Playwright 1.61.1) - Ubuntu and Windows in CI; macOS nightly Exact machine-checked rows live in [support-matrix.json](https://github.com/ljodea/ggsvelte/blob/main/support-matrix.json). Bun is the contributor toolchain only; consumers can use any installer above. --- # Interaction reference Searchable interaction contract. Chart-local state and callbacks by default. `createPlotInteraction()` for shared semantic state across plots and UI. ## Static default No capture layer, tooltip, selection, or zoom until a capability is enabled. Page scroll is not hijacked by unused tools. ## Capability props ### `inspect` Prefer the declaration child `<Inspect />` (or `<Inspect mode="x" pin />`, etc.). Options match the legacy GGPlot prop: `mode`, `pin`, `maxDistance`, `content`, `contentMode`, `muteSiblings`. Empty `<Inspect />` equals `inspect={true}`. The GGPlot `inspect` prop still works. The heading id stays `inspect` for stable deep links. ### Point selection `select={{ type: "point", multiple: true }}` stores stable semantic keys. Identity defaults to an `id` column or row index; override with `select={{ type: "point", identity: "…" }}` or `<Inspect identity="…" />`. ### Interval selection `select={{ type: "interval", mode: "x" | "y" | "xy", persistent: true }}` enables an explicit Select area tool and emits domain and pixel bounds. In facets, add `preset: "independent" | "union" | "cross-panel"` to replace one panel, combine panel selections, or project one domain through compatible panels. ### `zoom` `zoom={{ mode: "x" | "y" | "xy" }}` enables the explicit Zoom area tool. Reset zoom and double-click return to the natural domains. ### `legendFocus` Prefer `<GuideLegend channel="color" focus />` (boolean or `{ preview?: boolean }`) for discrete legend preview and committed focus on that aesthetic. Host-only — never a PortableSpec / `guideLegend()` field. Use `focus={{ preview: false }}` to disable hover/focus preview while retaining click, touch, Enter, Space, Escape, and arrow-key controls. Requires stable row `key` values; continuous ramps stay static. The plot prop `legendFocus={true}` is deprecated since 0.19.0 (removed in 0.20.0) and still enables focus plot-wide during the dual-read window — see [Legend focus on GuideLegend](https://ggsvelte.sh/guide/upgrading#legend-focus-on-guidelegend). ### `legendFilter` Prefer `<GuideLegend channel="color" filter />` (boolean or `{ mode?: "exclude" | "include", multiple?: boolean }`) for data-changing checkboxes on that aesthetic's discrete legend. Host-only — never a PortableSpec / `guideLegend()` field. It changes the rows supplied to facets, statistics, scales, and rendering while preserving the full legend catalog and categorical color identity. Receive typed clauses through `onlegendfilter`. Independent of presentation-only GuideLegend `focus`. The plot prop `legendFilter={true}` is deprecated since 0.19.0 (removed in 0.20.0) and still enables filter plot-wide during the dual-read window — see [Legend filter on GuideLegend](https://ggsvelte.sh/guide/upgrading#legend-filter-on-guidelegend). ## Controlled tool `tool` and `ontoolchange` control the active Inspect, Select area, or Zoom area mode. Keep the value in Svelte state when application controls and the plot tool rail must stay synchronized: ```svelte fragment <script lang="ts"> import { GGPlot, Inspect, type InteractionTool } from "@ggsvelte/svelte"; let activeTool = $state<InteractionTool>("inspect"); </script> <GGPlot select={{ type: "interval" }} tool={activeTool} ontoolchange={(next) => (activeTool = next)} > <Inspect /> </GGPlot> ``` A controlled unavailable tool requests a change and emits a diagnostic; it does not silently arm a different drag behavior. The active tool remains local to one chart; shared controllers coordinate data semantics, not UI modes. ## Shared controller `createPlotInteraction<Key>({ onchange? })` returns a reactive `PlotInteractionController<Key>`. Pass it through the `interaction` prop and name the semantic channels with the required `interactionScope={{ keys, x?, y? }}`; controlled plots never fall back to a generic scope or infer x/y channel names from encodings. Controlled zoom requires an explicit scope for every active channel (x, y, or both). - Reads: `selected(scope)`, `emphasized(scope)`, `intervals(scope)`, `isSelected(key, scope)`, `zoom(scope)`, `snapshot`, and `revision`. - Selection: `setSelection`, `toggleSelection`, and `clearSelection`. - Lightweight presentation: `setEmphasis` and `clearEmphasis`. - Facet intervals: `setInterval`, `clearInterval`, and `clearIntervals`. - Domains: `setZoom` and `resetZoom` for finite numeric x/y pairs. - Data replacement: `reconcileKeys(validKeys, { scope })` explicitly removes selected or emphasized keys that no longer exist. Scopes are application-level names. Reuse a key scope only where keys mean the same thing, and reuse x/y scopes only where their data domains are compatible. Every mutation returns one immutable transition or `null` for a no-op. Passive consumers never republish controller state, preventing linked-view feedback loops. Do not mutate the controller inside its synchronous `onchange` callback; schedule a later Svelte application update instead. See the [linked views example](https://ggsvelte.sh/examples/interaction/linked-views). ## Identity `key` is a field name or accessor returning a unique stable `PropertyKey`. Public events expose semantic keys, aggregate `sourceKeys`, and `lineageCount`, never renderer indices. ## Events ### `oninspect` Receives `PlotInspection`: `change` with transient or pinned focus and members, or `clear`. ### `onselect` Receives `PlotSelection`. Point selection emits `end` and `clear`. Interval selection emits `start`, `change`, `end`, and `clear`. ### `onzoom` Receives `ZoomEvent`: `end` with explicit domains or `clear` with null domains. ### `onlegendfocus` Receives `LegendFocusEvent`: a transient or committed `change` carrying the raw encoded value, formatted label, scale channel, and stable row keys, or a small `clear` event. The same object is included in `oninteraction`. ### `onlegendfilter` Receives `LegendFilterEvent`: a `change` with one typed `LegendFilterClause`, or `clear` with `clause: null`. Legend filtering changes pipeline input and is not folded into the presentation interaction union. ### `oninteraction` Receives the same discriminated `PlotInteractionEvent` union emitted by the focused callbacks. Narrow on `type` and `phase`. ### `ondiagnostic` Receives structured `PlotDiagnostic` objects (`InteractionDiagnostic` or `DeprecationDiagnostic`) with `severity`, `code`, `message`, `prop`, `suggestions`, and `docUrl`. Deprecation advisories also carry `since` and `removeIn`. ```svelte fragment <GGPlot ondiagnostic={(diagnostic) => console.warn(diagnostic.code, diagnostic.message, diagnostic.suggestions)} /> ``` Every event has a `source`: `pointer`, `keyboard`, `touch`, or `programmatic`. ## Diagnostics ### `INTERACTION_INTERVAL_FACET_UNSUPPORTED` Brush zoom currently requires one unfaceted panel. - Prop: `zoom` - Severity: `warning` - Try: Remove the facet; Use faceted interval selection; Zoom a linked detail view - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-interval-facet-unsupported](https://ggsvelte.sh/guide/interaction-reference#interaction-interval-facet-unsupported) ### `INTERACTION_INVALID_MAX_DISTANCE` inspect.maxDistance must be a finite non-negative CSS-pixel distance. - Prop: `inspect.maxDistance` - Severity: `error` - Try: Use a finite number greater than or equal to zero - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-max-distance](https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-max-distance) ### `INTERACTION_POINT_REQUIRES_KEY` Durable point selection requires resolved row identity. - Prop: `identity` - Severity: `warning` - Try: Ordinary charts omit identity — defaults to an id column or row index; Override with <Inspect identity="id" /> or select={{ type: "point", identity: "id" }} - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-point-requires-key](https://ggsvelte.sh/guide/interaction-reference#interaction-point-requires-key) ### `INTERACTION_INTERVAL_PRESET_REQUIRES_KEY` Coordinated interval presets (union, cross-panel) require resolved row identity; without it they combine no rows. - Prop: `identity` - Severity: `warning` - Try: Ordinary charts omit identity — defaults to an id column or row index; Override with select={{ type: "interval", identity: "id", … }} or <Inspect identity="id" /> - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-interval-preset-requires-key](https://ggsvelte.sh/guide/interaction-reference#interaction-interval-preset-requires-key) ### `INTERACTION_INVALID_KEY` A key accessor returned null, undefined, or a non-PropertyKey value. - Prop: `identity` - Severity: `error` - Try: Return a stable string, number, or symbol for every row - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-key](https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-key) ### `INTERACTION_DUPLICATE_KEY` The key accessor returned a duplicate value; durable interaction is disabled for that value. - Prop: `identity` - Severity: `error` - Try: Use a field that uniquely identifies each source row - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-duplicate-key](https://ggsvelte.sh/guide/interaction-reference#interaction-duplicate-key) ### `INTERACTION_UNSTABLE_KEY` The key accessor returned a different value for the same source row. - Prop: `identity` - Severity: `error` - Try: Return an immutable field that uniquely identifies each row - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-unstable-key](https://ggsvelte.sh/guide/interaction-reference#interaction-unstable-key) ### `INTERACTION_MISSING_LINEAGE` A synthetic or aggregate mark did not expose source-row lineage. - Prop: `layers` - Severity: `warning` - Try: Use a stat that preserves source-row lineage - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-missing-lineage](https://ggsvelte.sh/guide/interaction-reference#interaction-missing-lineage) ### `INTERACTION_LEGEND_REQUIRES_KEY` Legend focus requires resolved row identity so encoded legend values never become identities. - Prop: `identity` - Severity: `warning` - Try: Ordinary charts omit identity — defaults to an id column or row index; Override with <Inspect identity="id" /> or createPlotInteraction({ identity: "id" }) - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-legend-requires-key](https://ggsvelte.sh/guide/interaction-reference#interaction-legend-requires-key) ### `INTERACTION_LEGEND_DISCRETE_ONLY` Legend focus currently applies to discrete color and fill legends; continuous ramps remain static. - Prop: `focus` - Severity: `advisory` - Try: Enable focus on a discrete guide: <GuideLegend channel="color" focus />; Use a discrete color or fill mapping; Keep the continuous ramp static - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-legend-discrete-only](https://ggsvelte.sh/guide/interaction-reference#interaction-legend-discrete-only) ### `INTERACTION_INTERVAL_SCALE_UNSUPPORTED` Interval domains and brush zoom require continuous linear, log, or time scales. - Prop: `scales` - Severity: `warning` - Try: Use a continuous positional scale; Use point inspection for band data - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-interval-scale-unsupported](https://ggsvelte.sh/guide/interaction-reference#interaction-interval-scale-unsupported) ### `INTERACTION_TOOL_UNAVAILABLE` The requested interaction tool is unavailable for the enabled capabilities. - Prop: `tool` - Severity: `warning` - Try: Enable the matching capability; Choose an available interaction tool - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-tool-unavailable](https://ggsvelte.sh/guide/interaction-reference#interaction-tool-unavailable) ### `INTERACTION_SCOPE_WITHOUT_CONTROLLER` interactionScope is ignored without an interaction controller; chart-local scope is derived from key and aes. - Prop: `interactionScope` - Severity: `advisory` - Try: Pass interaction={createPlotInteraction()} to control this plot; Remove interactionScope from uncontrolled plots - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-scope-without-controller](https://ggsvelte.sh/guide/interaction-reference#interaction-scope-without-controller) ### `INTERACTION_HANDLER_WITHOUT_CAPABILITY` An interaction handler is set but its capability prop is not enabled, so the handler never fires. - Prop: `oninspect / onselect / onzoom / onlegendfocus / onlegendfilter` - Severity: `advisory` - Try: Enable the matching capability prop (for example select for onselect); Remove the unused handler - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-handler-without-capability](https://ggsvelte.sh/guide/interaction-reference#interaction-handler-without-capability) ### `INTERACTION_INSPECT_X_ON_COL` inspect.mode x/xy draws a crosshair on the x (band) axis through column marks; columns already encode x as a filled band, so the guide cuts the bar body and rarely adds information. Under coord_flip the guide is horizontal but still tracks the band. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomCol; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-col](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-col) ### `INTERACTION_INSPECT_X_ON_BAR` inspect.mode x/xy draws a crosshair through bar marks; bars are filled regions on the band axis, so the guide cuts the bar body and rarely adds information. Under coord_flip the guide orientation swaps with the axes but still fights the marks. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomBar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-bar](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-bar) ### `INTERACTION_INSPECT_X_BISECTS_COL_LABELS` inspect.mode x/xy draws a crosshair through GeomCol marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read (including under coord_flip, when the guide is horizontal). - Prop: `inspect.mode` - Severity: `warning` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") when columns have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-col-labels](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-col-labels) ### `INTERACTION_INSPECT_X_BISECTS_BAR_LABELS` inspect.mode x/xy draws a crosshair through GeomBar marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read (including under coord_flip). - Prop: `inspect.mode` - Severity: `warning` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") when bars have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-bar-labels](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-bar-labels) ### `INTERACTION_INSPECT_AXIS_ON_VIOLIN` inspect.mode draws an axis guide through violin marks; violins sit on a discrete band, so freescrolling x/y/xy guides cut the density body and often leave the band tooltip row blank. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomViolin; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-violin](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-violin) ### `INTERACTION_INSPECT_AXIS_ON_BOXPLOT` inspect.mode draws an axis guide through boxplot marks; boxes sit on a discrete band, so freescrolling x/y/xy guides cut the box body and rarely add information. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomBoxplot; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-boxplot](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-boxplot) ### `INTERACTION_INSPECT_AXIS_ON_ERRORBAR` inspect.mode draws an axis guide through errorbar marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomErrorbar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-errorbar](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-errorbar) ### `INTERACTION_INSPECT_AXIS_ON_LINERANGE` inspect.mode draws an axis guide through linerange marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomLinerange; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-linerange](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-linerange) ### `INTERACTION_INSPECT_AXIS_ON_POINTRANGE` inspect.mode draws an axis guide through pointrange marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomPointrange; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-pointrange](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-pointrange) ### `INTERACTION_INSPECT_AXIS_ON_CROSSBAR` inspect.mode draws an axis guide through crossbar marks; even when hits pin to a category, freescrolling x/y/xy guides rarely add information beyond exact mark focus. - Prop: `inspect.mode` - Severity: `advisory` - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomCrossbar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-crossbar](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-axis-on-crossbar) ### `INTERACTION_INSPECT_HIGH_CARDINALITY_DISCRETE` Inspect is enabled with a high-cardinality discrete color/fill domain; the default tooltip shows the focused series, the largest contributors at that x (or y), a stack total, and an overflow line — not every series. - Prop: `inspect` - Severity: `advisory` - Try: Prep top-n data before plotting if only the largest series matter; Pass a custom content snippet on <Inspect content={…} /> for a full multi-series listing; Pin the tooltip to scroll the full group when every series must be readable - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-high-cardinality-discrete](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-high-cardinality-discrete) ### `INTERACTION_INSPECT_IDENTITY_DROPPED` An <Inspect> child replaced the inspect prop whole (REPLACE), so the prop's `identity` no longer applies; rows fall back to an id column or row index. - Prop: `Inspect` - Severity: `advisory` - Try: Move identity onto the <Inspect> child; Drop identity from the inspect prop if the default row identity is intended - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-identity-dropped](https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-identity-dropped) ### `INTERACTION_DUPLICATE_INSPECT_CAPABILITY` Multiple <Inspect> children are registered; only the last one's options apply (REPLACE). - Prop: `Inspect` - Severity: `advisory` - Try: Keep a single <Inspect> child; Merge options onto one <Inspect> instead of stacking siblings - More: [https://ggsvelte.sh/guide/interaction-reference#interaction-duplicate-inspect-capability](https://ggsvelte.sh/guide/interaction-reference#interaction-duplicate-inspect-capability) ## Accessibility The plot surface is named and keyboard focusable when interaction is enabled. Arrow keys or brackets traverse data; Enter or Space pins or commits the active tool; Escape dismisses. A polite live region announces concise state while pinned HTML remains labelled, navigable DOM. Area tools remain explicit so ordinary page scrolling is available until a user chooses a drag mode. Committed interval and zoom state exposes precise Edit-bounds buttons in the tool rail. Their inline form uses labelled native inputs, stages drafts until Apply, validates log/time/category constraints, restores trigger focus after Apply or Cancel, and supports Escape. Linear and reversed domains use ascending data values; time uses ISO 8601 text; band intervals use inclusive native selects. Clear panel selection, Clear all selections, Reset zoom, and Reset legend filters remain separate operations. --- # Errors reference Diagnostics are generated from the catalogs used by validation, rendering, interaction, and the CLI. Identity is the pair `(source, code)`: a bare code can intentionally exist in more than one source with a different consequence. ## Quickstart troubleshooting - **Collapsed or zero-width container:** the responsive plot remains `data-gg-ready="false"` until ResizeObserver reports a positive width. Give the parent a real grid/flex track width; no fixed chart width is needed. - **SSR and hydration:** omitted width server-renders at 832 × 400, stays not-ready on the server, then measures its real container after hydration. - **Unexpected height:** omitted height is 400px unless the spec supplies one. - **TypeScript or linked-package mismatch:** install one compatible `@ggsvelte/svelte` version and let it resolve matching core/spec packages; remove stale lockfile overrides that mix versions. - **CLI input failure:** run `ggsvelte-render --help`; keep SVG stdout separate from JSON Lines stderr while correcting the reported input. ## Validation errors (@ggsvelte/spec) Each entry answers what failed, why, how to recover safely, and whether output was blocked or degraded. ### `invalid-spec-root` **Code + severity:** `invalid-spec-root` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** The spec is not a JSON object. **Fix:** Provide an object with at least one layer: {"layers": [{"geom": "point", ...}]}. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-spec-root](https://ggsvelte.sh/guide/errors#invalid-spec-root) ### `missing-layers` **Code + severity:** `missing-layers` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** `layers` is missing or not an array. **Fix:** Add a layers array with at least one layer object. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#missing-layers](https://ggsvelte.sh/guide/errors#missing-layers) ### `empty-layers` **Code + severity:** `empty-layers` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** `layers` is present but empty (a plot needs at least one layer). **Fix:** Add a layer, e.g. {"geom": "point"}. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#empty-layers](https://ggsvelte.sh/guide/errors#empty-layers) ### `invalid-layer` **Code + severity:** `invalid-layer` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A layer is not an object. **Fix:** Replace the entry with a layer object carrying a "geom". **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-layer](https://ggsvelte.sh/guide/errors#invalid-layer) ### `missing-geom` **Code + severity:** `missing-geom` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A layer has no "geom" discriminator. **Fix:** Add a geom name to the layer, e.g. {"geom": "point"}. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#missing-geom](https://ggsvelte.sh/guide/errors#missing-geom) ### `unknown-geom` **Code + severity:** `unknown-geom` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A layer's "geom" is not a known geom name. **Fix:** Use one of the allowed geoms (the error lists them, with a did-you-mean). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#unknown-geom](https://ggsvelte.sh/guide/errors#unknown-geom) ### `invalid-channel-value` **Code + severity:** `invalid-channel-value` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A channel value is not one of the canonical forms {field}/{value}/{stat}/null (bare strings are builder-only shorthand). **Fix:** Wrap field mappings as {"field": "column_name"} and constants as {"value": ...}. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-channel-value](https://ggsvelte.sh/guide/errors#invalid-channel-value) ### `unexpected-property` **Code + severity:** `unexpected-property` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** An object carries a property the schema does not allow. **Fix:** Remove or rename the property (the error suggests the closest allowed name). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#unexpected-property](https://ggsvelte.sh/guide/errors#unexpected-property) ### `missing-property` **Code + severity:** `missing-property` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A required property is missing. **Fix:** Add the named property. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#missing-property](https://ggsvelte.sh/guide/errors#missing-property) ### `invalid-enum-value` **Code + severity:** `invalid-enum-value` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A value is outside its declared enum/literal set. **Fix:** Use one of the allowed values (the error lists them, with a did-you-mean). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-enum-value](https://ggsvelte.sh/guide/errors#invalid-enum-value) ### `value-out-of-range` **Code + severity:** `value-out-of-range` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A numeric value is outside its declared bounds. **Fix:** Set the value inside the bounds stated by the error message. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#value-out-of-range](https://ggsvelte.sh/guide/errors#value-out-of-range) ### `invalid-data` **Code + severity:** `invalid-data` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** `data` (or a dataset entry) matches none of the data forms. **Fix:** Use {"values": [...rows]}, {"columns": {...arrays}}, or {"name": "dataset"}. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-data](https://ggsvelte.sh/guide/errors#invalid-data) ### `invalid-type` **Code + severity:** `invalid-type` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** Fallback: a value has the wrong JSON type. **Fix:** Give the property the JSON type the message names. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-type](https://ggsvelte.sh/guide/errors#invalid-type) ### `scale-scheme-type` **Code + severity:** `scale-scheme-type` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A named color scheme is incompatible with the configured color scale type. **Fix:** Use a categorical scheme with "ordinal" (or a sequential-family scheme like "viridis" for discrete viridis), or "viridis"/hex stops with "sequential". **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-scheme-type](https://ggsvelte.sh/guide/errors#scale-scheme-type) ### `scale-range-color` **Code + severity:** `scale-range-color` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A custom color range contains a color outside the supported hex syntax. **Fix:** Replace each custom color with #rgb or #rrggbb syntax. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-range-color](https://ggsvelte.sh/guide/errors#scale-range-color) ### `scale-type-transform-conflict` **Code + severity:** `scale-type-transform-conflict` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A scale declares an incompatible type/family with its transform (temporal/discrete/manual/identity with a non-identity transform, or type: "log" with a non-log10 transform). **Fix:** Use identity for temporal/discrete/manual/identity scales; for base-10 log use type: "linear" with transform: "log10"; or choose a quantitative family that admits the transform. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-type-transform-conflict](https://ggsvelte.sh/guide/errors#scale-type-transform-conflict) ### `color-manual-domain-range` **Code + severity:** `color-manual-domain-range` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A manual color/fill scale has a different number of domain values and range colors. **Fix:** Provide exactly one range color for every domain value. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#color-manual-domain-range](https://ggsvelte.sh/guide/errors#color-manual-domain-range) ### `scale-binned-breaks` **Code + severity:** `scale-binned-breaks` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A binned style scale's authored breaks are missing, non-finite after parsing, duplicated, or not strictly increasing. **Fix:** Provide 2+ strictly increasing boundaries (numeric, or temporal strings that parse under the scale's parser). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-binned-breaks](https://ggsvelte.sh/guide/errors#scale-binned-breaks) ### `scale-binned-domain` **Code + severity:** `scale-binned-domain` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A binned style scale's explicit domain does not match the first and last authored breaks. **Fix:** Set domain to the first and last break values, or omit domain and let breaks define it. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-binned-domain](https://ggsvelte.sh/guide/errors#scale-binned-domain) ### `guide-aesthetic-incompatible` **Code + severity:** `guide-aesthetic-incompatible` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** A guide variant is incompatible with its aesthetic or trained scale family. **Fix:** Use axis for x/y, legend for discrete/style scales, colorbar for sequential color, or colorsteps for binned color. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#guide-aesthetic-incompatible](https://ggsvelte.sh/guide/errors#guide-aesthetic-incompatible) ### `coord-fixed-free-scales` **Code + severity:** `coord-fixed-free-scales` · error **What failed:** Spec validation rejected the value at its reported path (schema shape). **Why:** Fixed-aspect coordinates cannot represent free positional facet scales truthfully. **Fix:** Use facet.scales = "fixed", or remove coord_fixed / coord_sf. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#coord-fixed-free-scales](https://ggsvelte.sh/guide/errors#coord-fixed-free-scales) ### `missing-required-channel` **Code + severity:** `missing-required-channel` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A geom is missing a required aesthetic channel (x, y, label, ...). **Fix:** Map the named channel to a data field in the layer's aes or the plot-level aes. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#missing-required-channel](https://ggsvelte.sh/guide/errors#missing-required-channel) ### `rule-form-ambiguous` **Code + severity:** `rule-form-ambiguous` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A rule layer mixes the annotation form (params.xintercept/yintercept) with mapped aes.x/aes.y. **Fix:** Use fixed intercepts OR a data mapping, never both (unset the other with null). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#rule-form-ambiguous](https://ggsvelte.sh/guide/errors#rule-form-ambiguous) ### `rule-form-missing` **Code + severity:** `rule-form-missing` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A rule layer has neither intercept params nor a mapped aes.x/aes.y — nothing to draw. **Fix:** Set params.yintercept/xintercept (annotation) or map aes.x/aes.y (data-driven). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#rule-form-missing](https://ggsvelte.sh/guide/errors#rule-form-missing) ### `rule-both-axes` **Code + severity:** `rule-both-axes` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A data-driven rule layer maps BOTH aes.x and aes.y (pick one direction). **Fix:** Keep one direction (vertical: map x; horizontal: map y) and unset the other with null. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#rule-both-axes](https://ggsvelte.sh/guide/errors#rule-both-axes) ### `computed-y-mapped` **Code + severity:** `computed-y-mapped` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A layer whose stat computes y (count, bin, density) maps aes.y to a data field. **Fix:** Unset y with null — or, for pre-computed bar heights, switch the layer to geom "col". **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#computed-y-mapped](https://ggsvelte.sh/guide/errors#computed-y-mapped) ### `bin-center-and-boundary` **Code + severity:** `bin-center-and-boundary` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A bin-stat layer sets BOTH params.center and params.boundary. **Fix:** Keep one bin-grid alignment parameter and remove the other. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#bin-center-and-boundary](https://ggsvelte.sh/guide/errors#bin-center-and-boundary) ### `facet-form-ambiguous` **Code + severity:** `facet-form-ambiguous` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A facet sets BOTH the wrap form and the rows/cols grid form. **Fix:** Keep facet.wrap (and drop rows/cols), or keep rows/cols (and drop wrap). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#facet-form-ambiguous](https://ggsvelte.sh/guide/errors#facet-form-ambiguous) ### `facet-form-missing` **Code + severity:** `facet-form-missing` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A facet sets neither wrap nor rows/cols — nothing to partition by. **Fix:** Set facet.wrap (wrap form) or facet.rows/facet.cols (grid form). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#facet-form-missing](https://ggsvelte.sh/guide/errors#facet-form-missing) ### `facet-ncol-without-wrap` **Code + severity:** `facet-ncol-without-wrap` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** facet.ncol only applies to the wrap form. **Fix:** Remove ncol, or switch to the wrap form. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#facet-ncol-without-wrap](https://ggsvelte.sh/guide/errors#facet-ncol-without-wrap) ### `unsupported-geom-aesthetic` **Code + severity:** `unsupported-geom-aesthetic` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A mapped style aesthetic is not consumed by the selected geom. **Fix:** Remove the mapping or move it to one of the compatible geoms listed in the error. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#unsupported-geom-aesthetic](https://ggsvelte.sh/guide/errors#unsupported-geom-aesthetic) ### `ribbon-orientation-ambiguous` **Code + severity:** `ribbon-orientation-ambiguous` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A ribbon layer maps both x-orientation (x+ymin+ymax) and y-orientation (y+xmin+xmax) contracts without params.orientation. **Fix:** Set params.orientation to "x" or "y", or map only one complete interval contract. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#ribbon-orientation-ambiguous](https://ggsvelte.sh/guide/errors#ribbon-orientation-ambiguous) ### `paint-stops-unordered` **Code + severity:** `paint-stops-unordered` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A gradient paint's color stops are not in non-decreasing offset order. **Fix:** Sort stops by offset ascending (each offset between 0 and 1 inclusive). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#paint-stops-unordered](https://ggsvelte.sh/guide/errors#paint-stops-unordered) ### `paint-scale-conflict` **Code + severity:** `paint-scale-conflict` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** Within-mark fillPaint/strokePaint cannot combine with a data-mapped fill/color scale channel. **Fix:** Remove the data-mapped fill/color aesthetic, or remove the paint and keep the scale. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#paint-scale-conflict](https://ggsvelte.sh/guide/errors#paint-scale-conflict) ### `unknown-field` **Code + severity:** `unknown-field` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A channel maps a field that does not exist in the data. **Fix:** Map the channel to one of the available fields (the error lists them, with a did-you-mean). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#unknown-field](https://ggsvelte.sh/guide/errors#unknown-field) **Minimal illustration — copy only the relevant fragment:** ```json fragment copy { "aes": { "x": { "field": "weight" }, "y": { "field": "economy" } } } ``` ### `all-null-column` **Code + severity:** `all-null-column` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A mapped column contains only null values. **Fix:** Map the channel to a column with actual values, or fix the data. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#all-null-column](https://ggsvelte.sh/guide/errors#all-null-column) ### `scale-type-mismatch` **Code + severity:** `scale-type-mismatch` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A configured scale type is incompatible with the mapped field's type. **Fix:** Change the scale type to match the field (band for categories, time for temporal), or map a compatible field. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#scale-type-mismatch](https://ggsvelte.sh/guide/errors#scale-type-mismatch) ### `channel-type-mismatch` **Code + severity:** `channel-type-mismatch` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A mapped field's type is incompatible with the layer's geom/stat (e.g. a nominal x on smooth/bin/density, a continuous x on boxplot). **Fix:** Map a field of the type the geom/stat needs, or switch to a geom that fits the field (the message suggests one). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#channel-type-mismatch](https://ggsvelte.sh/guide/errors#channel-type-mismatch) ### `unknown-stat-column` **Code + severity:** `unknown-stat-column` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A { stat } channel names a column the layer's stat does not generate. **Fix:** Use one of the columns the stat generates (the error lists them), or change the layer's stat. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#unknown-stat-column](https://ggsvelte.sh/guide/errors#unknown-stat-column) ### `manual-fun-required` **Code + severity:** `manual-fun-required` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A layer uses stat manual without params.fun. **Fix:** Set params.fun to one of first|last|mean|median|min|max|sum (portable named registry; #814). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#manual-fun-required](https://ggsvelte.sh/guide/errors#manual-fun-required) ### `summary-rolling-window-required` **Code + severity:** `summary-rolling-window-required` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A layer uses stat summary_rolling without params.window. **Fix:** Set params.window to the rolling-window width in x data units (greater than 0). **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#summary-rolling-window-required](https://ggsvelte.sh/guide/errors#summary-rolling-window-required) ### `summary-fun-unsupported` **Code + severity:** `summary-fun-unsupported` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** A summary stat uses params.fun first|last, which only stat manual implements. **Fix:** Use mean, median, or sum (min/max via funMin/funMax where supported); first|last belong to stat manual. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#summary-fun-unsupported](https://ggsvelte.sh/guide/errors#summary-fun-unsupported) ### `invalid-data-profile` **Code + severity:** `invalid-data-profile` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** The provided DataProfile is malformed. **Fix:** Provide { fields: [{ name, type: "quantitative"|"temporal"|"ordinal"|"nominal" }], rowCount? }. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#invalid-data-profile](https://ggsvelte.sh/guide/errors#invalid-data-profile) ### `validation-limit` **Code + severity:** `validation-limit` · error **What failed:** Spec validation rejected the value at its reported path (grammar or data-aware check). **Why:** Validation input exceeded a documented limit (rows/bytes/depth/diagnostics). **Fix:** Validate with a DataProfile instead of huge inline data, or raise the limit via options.limits. **Consequence (blocked):** Validation failed; no chart should be rendered from this spec. **Stable link:** [/guide/errors#validation-limit](https://ggsvelte.sh/guide/errors#validation-limit) ## Render-time errors (@ggsvelte/core) Each entry answers what failed, why, how to recover safely, and whether output was blocked or degraded. ### `guide-aesthetic-incompatible` — pipeline **Code + severity:** `guide-aesthetic-incompatible` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A guide variant is incompatible with its aesthetic or trained scale family. **Fix:** Use axis for x/y, legend for discrete/style scales, colorbar for sequential color, or colorsteps for binned color. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#guide-aesthetic-incompatible-pipeline](https://ggsvelte.sh/guide/errors#guide-aesthetic-incompatible-pipeline) ### `guide-layout-overflow` **Code + severity:** `guide-layout-overflow` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A guide configured with collision: error cannot fit its authored label. **Fix:** Use collision: "ellipsis" for long labels; increase the chart size, reduce categories, or suppress an oversized guide. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#guide-layout-overflow](https://ggsvelte.sh/guide/errors#guide-layout-overflow) ### `no-data` **Code + severity:** `no-data` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A layer has neither plot-level data nor its own layer.data. **Fix:** Set spec.data and/or layer.data ({values}, {columns}, or {name}), or pass named data via RunOptions.data. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#no-data](https://ggsvelte.sh/guide/errors#no-data) ### `dataset-collision` **Code + severity:** `dataset-collision` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A dataset name is defined in both spec.datasets and RunOptions.data. **Fix:** Rename one of them, or pass allowOverride: true to let the runtime data win. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#dataset-collision](https://ggsvelte.sh/guide/errors#dataset-collision) ### `unknown-dataset` **Code + severity:** `unknown-dataset` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A {"name": ...} data ref names a dataset that is not defined anywhere. **Fix:** Define it in spec.datasets or RunOptions.data (the message lists the available names). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unknown-dataset](https://ggsvelte.sh/guide/errors#unknown-dataset) ### `unknown-field` — pipeline **Code + severity:** `unknown-field` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A channel maps a field that does not exist in the data. **Fix:** Map the channel to one of the available fields (the error lists them, with a did-you-mean). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unknown-field-pipeline](https://ggsvelte.sh/guide/errors#unknown-field-pipeline) ### `all-null-column` — pipeline **Code + severity:** `all-null-column` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A mapped column contains only null values. **Fix:** Map the channel to a column with actual values, or fix the data. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#all-null-column-pipeline](https://ggsvelte.sh/guide/errors#all-null-column-pipeline) ### `missing-channel` **Code + severity:** `missing-channel` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A geom is missing a required aesthetic channel at render time. **Fix:** Map the named channel in the layer's aes or the plot-level aes. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#missing-channel](https://ggsvelte.sh/guide/errors#missing-channel) ### `unknown-stat-column` — pipeline **Code + severity:** `unknown-stat-column` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A { stat } channel names a column the layer's stat does not generate. **Fix:** Use one of the columns the stat generates (the error lists them), or change the layer's stat. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unknown-stat-column-pipeline](https://ggsvelte.sh/guide/errors#unknown-stat-column-pipeline) ### `channel-type-mismatch` — pipeline **Code + severity:** `channel-type-mismatch` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A mapped field's type is incompatible with the layer's geom/stat (e.g. a nominal x on smooth/bin/density, a continuous x on boxplot). **Fix:** Map a field of the type the geom/stat needs, or switch to a geom that fits the field (the message suggests one). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#channel-type-mismatch-pipeline](https://ggsvelte.sh/guide/errors#channel-type-mismatch-pipeline) ### `computed-y-mapped` — pipeline **Code + severity:** `computed-y-mapped` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A layer whose stat computes y (count, bin, density) maps aes.y to a data field. **Fix:** Unset y with null — or, for pre-computed bar heights, switch the layer to geom "col". **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#computed-y-mapped-pipeline](https://ggsvelte.sh/guide/errors#computed-y-mapped-pipeline) ### `bin-center-and-boundary` — pipeline **Code + severity:** `bin-center-and-boundary` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A bin-stat layer sets BOTH params.center and params.boundary. **Fix:** Keep one bin-grid alignment parameter and remove the other. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#bin-center-and-boundary-pipeline](https://ggsvelte.sh/guide/errors#bin-center-and-boundary-pipeline) ### `rule-form-ambiguous` — pipeline **Code + severity:** `rule-form-ambiguous` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A rule layer mixes the annotation form (params.xintercept/yintercept) with mapped aes.x/aes.y. **Fix:** Use fixed intercepts OR a data mapping, never both (unset the other with null). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#rule-form-ambiguous-pipeline](https://ggsvelte.sh/guide/errors#rule-form-ambiguous-pipeline) ### `rule-form-missing` — pipeline **Code + severity:** `rule-form-missing` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A rule layer has neither intercept params nor a mapped aes.x/aes.y — nothing to draw. **Fix:** Set params.yintercept/xintercept (annotation) or map aes.x/aes.y (data-driven). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#rule-form-missing-pipeline](https://ggsvelte.sh/guide/errors#rule-form-missing-pipeline) ### `rule-both-axes` — pipeline **Code + severity:** `rule-both-axes` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A data-driven rule layer maps BOTH aes.x and aes.y (pick one direction). **Fix:** Keep one direction (vertical: map x; horizontal: map y) and unset the other with null. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#rule-both-axes-pipeline](https://ggsvelte.sh/guide/errors#rule-both-axes-pipeline) ### `ribbon-orientation-ambiguous` — pipeline **Code + severity:** `ribbon-orientation-ambiguous` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A ribbon layer maps both x-orientation (x+ymin+ymax) and y-orientation (y+xmin+xmax) contracts without params.orientation. **Fix:** Set params.orientation to "x" or "y", or map only one complete interval contract. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#ribbon-orientation-ambiguous-pipeline](https://ggsvelte.sh/guide/errors#ribbon-orientation-ambiguous-pipeline) ### `ribbon-inverted-bounds` **Code + severity:** `ribbon-inverted-bounds` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A ribbon layer has one or more rows where the lower bound exceeds the upper bound. **Fix:** Swap or correct ymin/ymax (or xmin/xmax) so lower ≤ upper on every finite row. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#ribbon-inverted-bounds](https://ggsvelte.sh/guide/errors#ribbon-inverted-bounds) ### `facet-form-ambiguous` — pipeline **Code + severity:** `facet-form-ambiguous` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A facet sets BOTH the wrap form and the rows/cols grid form. **Fix:** Keep facet.wrap (and drop rows/cols), or keep rows/cols (and drop wrap). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#facet-form-ambiguous-pipeline](https://ggsvelte.sh/guide/errors#facet-form-ambiguous-pipeline) ### `facet-form-missing` — pipeline **Code + severity:** `facet-form-missing` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A facet sets neither wrap nor rows/cols — nothing to partition by. **Fix:** Set facet.wrap (wrap form) or facet.rows/facet.cols (grid form). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#facet-form-missing-pipeline](https://ggsvelte.sh/guide/errors#facet-form-missing-pipeline) ### `invalid-scale-domain` **Code + severity:** `invalid-scale-domain` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** An explicit scale domain is malformed for its scale type. **Fix:** Provide a two-element [min, max] for continuous scales (values of the field's type). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-scale-domain](https://ggsvelte.sh/guide/errors#invalid-scale-domain) ### `invalid-scale-breaks` **Code + severity:** `invalid-scale-breaks` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** One or more explicit scale breaks do not match the scale's parser. **Fix:** Use numeric breaks for numeric scales or values matching the temporal parse option. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-scale-breaks](https://ggsvelte.sh/guide/errors#invalid-scale-breaks) ### `invalid-temporal-labels` **Code + severity:** `invalid-temporal-labels` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal dateLabels string uses an unsupported or incomplete token. **Fix:** Use only the documented closed dateLabels token grammar. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-temporal-labels](https://ggsvelte.sh/guide/errors#invalid-temporal-labels) ### `invalid-temporal-locale` **Code + severity:** `invalid-temporal-locale` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal scale locale is not a valid supported BCP 47 locale. **Fix:** Use a canonical BCP 47 locale such as en-US, en-GB, fr-FR, or ja-JP. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-temporal-locale](https://ggsvelte.sh/guide/errors#invalid-temporal-locale) ### `temporal-parse-failed` **Code + severity:** `temporal-parse-failed` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** An explicit temporal parser rejected one or more source values. **Fix:** Correct the rejected values, choose the matching parser, or explicitly opt into censoring. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#temporal-parse-failed](https://ggsvelte.sh/guide/errors#temporal-parse-failed) ### `temporal-break-limit` **Code + severity:** `temporal-break-limit` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** An explicit temporal interval would generate more bounded ticks than allowed. **Fix:** Choose a coarser calendar interval. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#temporal-break-limit](https://ggsvelte.sh/guide/errors#temporal-break-limit) ### `temporal-break-progression` **Code + severity:** `temporal-break-progression` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Calendar interval progression failed to advance monotonically. **Fix:** Choose another timezone, disambiguation policy, or interval and report the failing case. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#temporal-break-progression](https://ggsvelte.sh/guide/errors#temporal-break-progression) ### `invalid-scale-transform` **Code + severity:** `invalid-scale-transform` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** The scale transform registry was asked for an unknown transform key. **Fix:** Use a supported transform (identity, log10, sqrt). This indicates malformed runtime input. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-scale-transform](https://ggsvelte.sh/guide/errors#invalid-scale-transform) ### `scale-transform-domain` **Code + severity:** `scale-transform-domain` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** An explicit scale domain falls outside the transform's valid range (log10 <= 0, sqrt < 0). **Fix:** Restrict the domain to the transform's valid range, or use the identity transform. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#scale-transform-domain](https://ggsvelte.sh/guide/errors#scale-transform-domain) ### `scale-type-transform-conflict` — pipeline **Code + severity:** `scale-type-transform-conflict` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A scale declares an incompatible type/family with its transform (temporal/discrete/manual/identity with a non-identity transform, or type: "log" with a non-log10 transform). **Fix:** Use identity for temporal/discrete/manual/identity scales; for base-10 log use type: "linear" with transform: "log10"; or choose a quantitative family that admits the transform. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#scale-type-transform-conflict-pipeline](https://ggsvelte.sh/guide/errors#scale-type-transform-conflict-pipeline) ### `scale-zero-invalid-for-transform` **Code + severity:** `scale-zero-invalid-for-transform` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** zero: true was requested under a transform with no valid image for semantic zero (log10). **Fix:** Remove zero: true; log10 positions use the transformed-space origin (semantic 1), never log10(0). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#scale-zero-invalid-for-transform](https://ggsvelte.sh/guide/errors#scale-zero-invalid-for-transform) ### `coord-transform-domain` **Code + severity:** `coord-transform-domain` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A post-stat coordinate transform cannot project its trained or explicit viewport domain. **Fix:** Choose coordinate limits inside the transform domain, or use the identity coordinate transform. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#coord-transform-domain](https://ggsvelte.sh/guide/errors#coord-transform-domain) ### `coord-transform-temporal` **Code + severity:** `coord-transform-temporal` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A non-identity coordinate transform was requested for a temporal axis. **Fix:** Keep temporal coordinates on identity; use identity coordinate limits/reverse for a viewport. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#coord-transform-temporal](https://ggsvelte.sh/guide/errors#coord-transform-temporal) ### `coord-transform-continuous` **Code + severity:** `coord-transform-continuous` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A quantitative coordinate transform or numeric limits were requested for a band axis. **Fix:** Use identity coordinates for categories, or configure a continuous quantitative scale. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#coord-transform-continuous](https://ggsvelte.sh/guide/errors#coord-transform-continuous) ### `coord-fixed-free-scales` — pipeline **Code + severity:** `coord-fixed-free-scales` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Fixed-aspect coordinates cannot represent free positional facet scales truthfully. **Fix:** Use facet.scales = "fixed", or remove coord_fixed / coord_sf. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#coord-fixed-free-scales-pipeline](https://ggsvelte.sh/guide/errors#coord-fixed-free-scales-pipeline) ### `coord-fixed-invalid-aspect` **Code + severity:** `coord-fixed-invalid-aspect` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A fixed-aspect target or fitted data rectangle is non-finite or non-positive after chrome is allocated. **Fix:** Use a moderate finite ratio and non-degenerate positional domains, or enlarge the plot allocation. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#coord-fixed-invalid-aspect](https://ggsvelte.sh/guide/errors#coord-fixed-invalid-aspect) ### `binned-scale-requires-continuous` **Code + severity:** `binned-scale-requires-continuous` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A type: "binned" scale is bound to a discrete or temporal field. **Fix:** Map a quantitative field, or use type: "band"/"time" instead of "binned". **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#binned-scale-requires-continuous](https://ggsvelte.sh/guide/errors#binned-scale-requires-continuous) ### `binned-scale-break-limit` **Code + severity:** `binned-scale-break-limit` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A binned scale's automatic or explicit breaks would exceed MAX_BINNED_BREAKS (64). **Fix:** Supply fewer explicit breaks, or widen them so automatic binning stays under the limit. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#binned-scale-break-limit](https://ggsvelte.sh/guide/errors#binned-scale-break-limit) ### `palette-exhausted` **Code + severity:** `palette-exhausted` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A discrete color scale with onExhaust: "error" ran out of palette entries (the default "cycle" only warns). **Fix:** Provide a larger range, set an explicit domain, or accept cycling by removing onExhaust. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#palette-exhausted](https://ggsvelte.sh/guide/errors#palette-exhausted) ### `color-temporal-parse` **Code + severity:** `color-temporal-parse` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal color/fill scale could not parse the complete mapped column. **Fix:** Set the exact parse order, correct the rejected values, or explicitly choose parseFailure: censor. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-temporal-parse](https://ggsvelte.sh/guide/errors#color-temporal-parse) ### `color-temporal-kind` **Code + severity:** `color-temporal-kind` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal color/fill scale requested date or datetime precision that the data does not have. **Fix:** Use the matching date/datetime helper or correct the source precision. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-temporal-kind](https://ggsvelte.sh/guide/errors#color-temporal-kind) ### `color-manual-domain-range` — pipeline **Code + severity:** `color-manual-domain-range` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A manual color/fill scale has a different number of domain values and range colors. **Fix:** Provide exactly one range color for every domain value. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-manual-domain-range-pipeline](https://ggsvelte.sh/guide/errors#color-manual-domain-range-pipeline) ### `color-binned-breaks` **Code + severity:** `color-binned-breaks` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Binned color/fill boundaries are invalid, unparseable, duplicated, or unordered. **Fix:** Provide 2–65 strictly increasing boundaries valid for the parser and transform. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-binned-breaks](https://ggsvelte.sh/guide/errors#color-binned-breaks) ### `color-binned-empty` **Code + severity:** `color-binned-empty` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A binned color/fill scale has no values inside its parser/transform domain. **Fix:** Correct the mapped values, parser, or transform, or provide a valid explicit domain. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-binned-empty](https://ggsvelte.sh/guide/errors#color-binned-empty) ### `color-binned-domain` **Code + severity:** `color-binned-domain` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A binned color/fill domain is degenerate or invalid for its transform. **Fix:** Provide two distinct domain endpoints valid for identity, log10, or sqrt. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-binned-domain](https://ggsvelte.sh/guide/errors#color-binned-domain) ### `color-domain-invalid` **Code + severity:** `color-domain-invalid` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A sequential color/fill domain does not contain exactly two parseable values. **Fix:** Provide a two-value semantic domain matching the mapped field and parser. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-domain-invalid](https://ggsvelte.sh/guide/errors#color-domain-invalid) ### `color-transform-empty` **Code + severity:** `color-transform-empty` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Every mapped color/fill value is invalid for the requested transform. **Fix:** Correct the mapped data or choose a transform whose domain contains the values. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-transform-empty](https://ggsvelte.sh/guide/errors#color-transform-empty) ### `color-domain-transform` **Code + severity:** `color-domain-transform` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A sequential color/fill domain is invalid for its requested transform. **Fix:** Use positive endpoints for log10, non-negative endpoints for sqrt, or identity. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#color-domain-transform](https://ggsvelte.sh/guide/errors#color-domain-transform) ### `unsupported-aesthetic-scale` **Code + severity:** `unsupported-aesthetic-scale` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A finite shape/linetype aesthetic was configured as a continuous scale. **Fix:** Use a binned scale for quantitative values or an ordinal scale for categories. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unsupported-aesthetic-scale](https://ggsvelte.sh/guide/errors#unsupported-aesthetic-scale) ### `unsupported-geom-aesthetic` — pipeline **Code + severity:** `unsupported-geom-aesthetic` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A mapped style aesthetic is not consumed by the selected geom. **Fix:** Remove the mapping or move it to one of the compatible geoms listed in the error. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unsupported-geom-aesthetic-pipeline](https://ggsvelte.sh/guide/errors#unsupported-geom-aesthetic-pipeline) ### `unsupported-annotation-style` **Code + severity:** `unsupported-annotation-style` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A fixed-intercept annotation rule maps a style to a field or after-stat column, but it has no data rows to map. **Fix:** Use a constant style value (optionally { value, scale: true }) on the annotation rule. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unsupported-annotation-style](https://ggsvelte.sh/guide/errors#unsupported-annotation-style) ### `tile-nonpositive-size` **Code + severity:** `tile-nonpositive-size` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A tile layer has a non-positive or non-finite width/height. **Fix:** Map a positive width/height or set params.width / params.height to a positive number. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#tile-nonpositive-size](https://ggsvelte.sh/guide/errors#tile-nonpositive-size) ### `raster-duplicate-cells` **Code + severity:** `raster-duplicate-cells` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A raster layer has duplicate (x, y) coordinates. **Fix:** Aggregate to one value per cell, or use geom "tile" for overlapping cells. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#raster-duplicate-cells](https://ggsvelte.sh/guide/errors#raster-duplicate-cells) ### `unsupported-param` **Code + severity:** `unsupported-param` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A layer param value is not supported by this runtime. **Fix:** Use a documented supported value for the param (see the error message). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unsupported-param](https://ggsvelte.sh/guide/errors#unsupported-param) ### `invalid-aesthetic-constant` **Code + severity:** `invalid-aesthetic-constant` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A literal style constant is outside the aesthetic's supported output domain. **Fix:** Use a positive size/linewidth, alpha in [0,1], or a documented shape/linetype name. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#invalid-aesthetic-constant](https://ggsvelte.sh/guide/errors#invalid-aesthetic-constant) ### `style-temporal-parse` **Code + severity:** `style-temporal-parse` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal numeric style scale could not parse the complete mapped column. **Fix:** Set the exact parser, correct the rejected values, or explicitly choose censoring. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-temporal-parse](https://ggsvelte.sh/guide/errors#style-temporal-parse) ### `style-temporal-kind` **Code + severity:** `style-temporal-kind` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A temporal numeric style scale requested the wrong date/datetime precision. **Fix:** Use the matching date/datetime helper or correct the source precision. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-temporal-kind](https://ggsvelte.sh/guide/errors#style-temporal-kind) ### `style-manual-domain-range` **Code + severity:** `style-manual-domain-range` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A manual style scale has different domain and range lengths. **Fix:** Provide exactly one output style for every domain value. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-manual-domain-range](https://ggsvelte.sh/guide/errors#style-manual-domain-range) ### `style-palette-exhausted` **Code + severity:** `style-palette-exhausted` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A finite style scale needs more distinguishable outputs than its range provides. **Fix:** Provide a larger range, reduce categories/bins, or deliberately opt into cycling. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-palette-exhausted](https://ggsvelte.sh/guide/errors#style-palette-exhausted) ### `style-domain-empty` **Code + severity:** `style-domain-empty` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** No finite values can train the requested numeric or binned style scale. **Fix:** Correct the mapped values or provide a valid explicit domain. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-domain-empty](https://ggsvelte.sh/guide/errors#style-domain-empty) ### `style-domain-invalid` **Code + severity:** `style-domain-invalid` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** An explicit style domain is malformed or contradicts its binned boundaries. **Fix:** Provide two finite semantic endpoints matching the first and last boundaries. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-domain-invalid](https://ggsvelte.sh/guide/errors#style-domain-invalid) ### `style-range-invalid` **Code + severity:** `style-range-invalid` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A numeric sequential/binned style range has fewer than two endpoints. **Fix:** Provide at least two valid output values in the aesthetic's supported bounds. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-range-invalid](https://ggsvelte.sh/guide/errors#style-range-invalid) ### `style-binned-breaks` **Code + severity:** `style-binned-breaks` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Binned style boundaries are missing, non-finite, duplicated, or unordered. **Fix:** Provide 2–65 strictly increasing numeric boundaries. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#style-binned-breaks](https://ggsvelte.sh/guide/errors#style-binned-breaks) ### `stat-channel-unsupported` **Code + severity:** `stat-channel-unsupported` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A { stat } style mapping names an output the selected stat does not publish. **Fix:** Use a generated output listed for that stat or map the original field instead. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#stat-channel-unsupported](https://ggsvelte.sh/guide/errors#stat-channel-unsupported) ### `manual-fun-required` — pipeline **Code + severity:** `manual-fun-required` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A layer uses stat manual without params.fun. **Fix:** Set params.fun to one of first|last|mean|median|min|max|sum (portable named registry; #814). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#manual-fun-required-pipeline](https://ggsvelte.sh/guide/errors#manual-fun-required-pipeline) ### `manual-fun-unknown` **Code + severity:** `manual-fun-unknown` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A layer uses stat manual with an unregistered params.fun (defense for unvalidated specs). **Fix:** Use a registered name: first, last, mean, median, min, max, sum. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#manual-fun-unknown](https://ggsvelte.sh/guide/errors#manual-fun-unknown) ### `unknown-theme` **Code + severity:** `unknown-theme` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** spec.theme names a theme that is not registered. **Fix:** Use a registered name (default, light, dark, minimal) or a theme object. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#unknown-theme](https://ggsvelte.sh/guide/errors#unknown-theme) ### `renderer-failure` **Code + severity:** `renderer-failure` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** The SVG renderer threw while drawing a scene (never blank output — failure policy). **Fix:** This is a ggsvelte bug; the message carries the underlying error. Please report it. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#renderer-failure](https://ggsvelte.sh/guide/errors#renderer-failure) ### `max-marks-exceeded` **Code + severity:** `max-marks-exceeded` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** renderToSVGString refused to render more marks than its maxMarks safety limit. **Fix:** Raise options.maxMarks deliberately, reduce the data, or render interactively (canvas). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#max-marks-exceeded](https://ggsvelte.sh/guide/errors#max-marks-exceeded) ### `sf-geometry-missing` **Code + severity:** `sf-geometry-missing` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** geom_sf data is missing the geometry column. **Fix:** Provide a column of GeoJSON Geometry JSON strings (default field "geometry", or params.geometry). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#sf-geometry-missing](https://ggsvelte.sh/guide/errors#sf-geometry-missing) ### `sf-geometry-invalid` **Code + severity:** `sf-geometry-invalid` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** A geom_sf geometry cell could not be parsed as drawable GeoJSON Geometry. **Fix:** Use valid GeoJSON Geometry JSON strings with finite coordinates. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#sf-geometry-invalid](https://ggsvelte.sh/guide/errors#sf-geometry-invalid) ### `sf-geometry-unsupported` **Code + severity:** `sf-geometry-unsupported` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** geom_sf received a GeoJSON type outside the v1 point/line/polygon families. **Fix:** Use Point/MultiPoint, LineString/MultiLineString, Polygon/MultiPolygon, or GeometryCollection of those families (no CRS). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#sf-geometry-unsupported](https://ggsvelte.sh/guide/errors#sf-geometry-unsupported) ### `sf-geometry-mixed` **Code + severity:** `sf-geometry-mixed` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** One geom_sf layer mixes geometry families (point vs line vs polygon). **Fix:** Split mixed types into separate geom_sf layers (v1 is single-family per layer). **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#sf-geometry-mixed](https://ggsvelte.sh/guide/errors#sf-geometry-mixed) ### `map-data-required` **Code + severity:** `map-data-required` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** geom_map was used without params.map. **Fix:** Pass params.map as { values }, { columns }, or { name } against spec.datasets. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#map-data-required](https://ggsvelte.sh/guide/errors#map-data-required) ### `map-coords-missing` **Code + severity:** `map-coords-missing` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Map data is missing long/lat or x/y coordinate columns. **Fix:** Provide "long"+"lat" or "x"+"y" columns in the fortified map table. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#map-coords-missing](https://ggsvelte.sh/guide/errors#map-coords-missing) ### `map-id-column-missing` **Code + severity:** `map-id-column-missing` · error **What failed:** The render pipeline stopped at the path reported by PipelineError. **Why:** Map data is missing a region join column. **Fix:** Set params.mapId or include a "region" / "id" column in the map table. **Consequence (blocked):** Rendering stopped; ggsvelte does not substitute blank or partial output. **Stable link:** [/guide/errors#map-id-column-missing](https://ggsvelte.sh/guide/errors#map-id-column-missing) ## Render warnings Each entry answers what failed, why, how to recover safely, and whether output was blocked or degraded. ### `facet-levels-missing` **Code + severity:** `facet-levels-missing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** An explicit facet levels list includes values absent from the data; empty panels are kept. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#facet-levels-missing](https://ggsvelte.sh/guide/errors#facet-levels-missing) ### `facet-levels-unknown` **Code + severity:** `facet-levels-unknown` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Data values for a facet field were omitted from the closed levels list and are excluded from every panel. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#facet-levels-unknown](https://ggsvelte.sh/guide/errors#facet-levels-unknown) ### `empty-data` **Code + severity:** `empty-data` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** The data has no rows; the frame and axes render as a placeholder. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#empty-data](https://ggsvelte.sh/guide/errors#empty-data) ### `empty-layer` **Code + severity:** `empty-layer` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A layer produced no drawable marks after stats/positions; it was skipped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#empty-layer](https://ggsvelte.sh/guide/errors#empty-layer) ### `empty-domain` **Code + severity:** `empty-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A positional scale found no finite values; a placeholder domain is used. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#empty-domain](https://ggsvelte.sh/guide/errors#empty-domain) ### `removed-missing` **Code + severity:** `removed-missing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Rows with missing/non-finite values in required channels were dropped (count in message). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#removed-missing](https://ggsvelte.sh/guide/errors#removed-missing) ### `raster-irregular-spacing` **Code + severity:** `raster-irregular-spacing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Raster cell centers are not on a regular grid; the minimum spacing is used (prefer geom tile). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#raster-irregular-spacing](https://ggsvelte.sh/guide/errors#raster-irregular-spacing) ### `scale-transform-domain` — warning **Code + severity:** `scale-transform-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A pre-stat transform dropped values outside its domain (log10 <= 0, sqrt < 0); count in message. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#scale-transform-domain-warning](https://ggsvelte.sh/guide/errors#scale-transform-domain-warning) ### `scale-oob-censored` **Code + severity:** `scale-oob-censored` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Values outside explicit source limits were censored to missing before stats. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#scale-oob-censored](https://ggsvelte.sh/guide/errors#scale-oob-censored) ### `scale-oob-squished` **Code + severity:** `scale-oob-squished` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Values outside explicit source limits were squished to the nearest limit before stats. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#scale-oob-squished](https://ggsvelte.sh/guide/errors#scale-oob-squished) ### `scale-break-outside-domain` **Code + severity:** `scale-break-outside-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** One or more explicit continuous breaks were outside the trained display domain and omitted. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#scale-break-outside-domain](https://ggsvelte.sh/guide/errors#scale-break-outside-domain) ### `sequential-discrete-field` **Code + severity:** `sequential-discrete-field` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A sequential color scale is fed a discrete field; unparseable values render unknown. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#sequential-discrete-field](https://ggsvelte.sh/guide/errors#sequential-discrete-field) ### `color-temporal-censored` **Code + severity:** `color-temporal-censored` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A temporal color/fill parser censored invalid source values by explicit author request. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#color-temporal-censored](https://ggsvelte.sh/guide/errors#color-temporal-censored) ### `color-transform-invalid` **Code + severity:** `color-transform-invalid` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Some color/fill values are outside the requested transform domain and render unknown. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#color-transform-invalid](https://ggsvelte.sh/guide/errors#color-transform-invalid) ### `color-na-values` **Code + severity:** `color-na-values` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Missing color/fill values render with the configured NA color (count in message). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#color-na-values](https://ggsvelte.sh/guide/errors#color-na-values) ### `color-unknown-values` **Code + severity:** `color-unknown-values` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Invalid, unmapped, transformed, or censored color/fill values render with the configured unknown color (count in message). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#color-unknown-values](https://ggsvelte.sh/guide/errors#color-unknown-values) ### `style-temporal-censored` **Code + severity:** `style-temporal-censored` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A temporal numeric style parser censored invalid source values by explicit request. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-temporal-censored](https://ggsvelte.sh/guide/errors#style-temporal-censored) ### `style-na-values` **Code + severity:** `style-na-values` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Missing mapped style values use the configured NA output. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-na-values](https://ggsvelte.sh/guide/errors#style-na-values) ### `style-unknown-values` **Code + severity:** `style-unknown-values` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Invalid or out-of-domain mapped style values use the configured unknown output. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-unknown-values](https://ggsvelte.sh/guide/errors#style-unknown-values) ### `style-palette-exhausted` — warning **Code + severity:** `style-palette-exhausted` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A finite style range cycled after explicit author opt-in. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-palette-exhausted-warning](https://ggsvelte.sh/guide/errors#style-palette-exhausted-warning) ### `style-fingerprint-mismatch` **Code + severity:** `style-fingerprint-mismatch` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Restored style state used a different output range; assignments start fresh. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-fingerprint-mismatch](https://ggsvelte.sh/guide/errors#style-fingerprint-mismatch) ### `style-version-mismatch` **Code + severity:** `style-version-mismatch` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Restored style state has an unknown schema version; assignments start fresh. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-version-mismatch](https://ggsvelte.sh/guide/errors#style-version-mismatch) ### `style-out-of-domain` **Code + severity:** `style-out-of-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Values outside an explicit style domain use the unknown output. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#style-out-of-domain](https://ggsvelte.sh/guide/errors#style-out-of-domain) ### `invalid-label-format` **Code + severity:** `invalid-label-format` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A labels format string was not recognized; the default format is used. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#invalid-label-format](https://ggsvelte.sh/guide/errors#invalid-label-format) ### `unknown-edition` **Code + severity:** `unknown-edition` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** The spec targets a defaults edition this build does not know; the latest known edition's defaults are used. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#unknown-edition](https://ggsvelte.sh/guide/errors#unknown-edition) ### `color-on-fill-geom` **Code + severity:** `color-on-fill-geom` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** The color channel is mapped on a fill-styled geom (bar/col/area); fill is what varies. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#color-on-fill-geom](https://ggsvelte.sh/guide/errors#color-on-fill-geom) ### `weight-unsupported` **Code + severity:** `weight-unsupported` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** aes.weight is mapped on a stat that does not consume weights. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#weight-unsupported](https://ggsvelte.sh/guide/errors#weight-unsupported) ### `density-group-dropped` **Code + severity:** `density-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A density group had too few finite values and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#density-group-dropped](https://ggsvelte.sh/guide/errors#density-group-dropped) ### `contour-group-dropped` **Code + severity:** `contour-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A contour group lacked a usable 2D grid or levels and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#contour-group-dropped](https://ggsvelte.sh/guide/errors#contour-group-dropped) ### `density-2d-group-dropped` **Code + severity:** `density-2d-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A density_2d group had too few points or produced no contours and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#density-2d-group-dropped](https://ggsvelte.sh/guide/errors#density-2d-group-dropped) ### `density-2d-filled-open-dropped` **Code + severity:** `density-2d-filled-open-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** density_2d_filled dropped open isoline rings; v1 fills closed rings only. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#density-2d-filled-open-dropped](https://ggsvelte.sh/guide/errors#density-2d-filled-open-dropped) ### `sf-coordinates-dropped` **Code + severity:** `sf-coordinates-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** stat_sf_coordinates dropped features with no finite representative point. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#sf-coordinates-dropped](https://ggsvelte.sh/guide/errors#sf-coordinates-dropped) ### `map-region-missing` **Code + severity:** `map-region-missing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** One or more value rows had no matching map region and were dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#map-region-missing](https://ggsvelte.sh/guide/errors#map-region-missing) ### `group-single-observation` **Code + severity:** `group-single-observation` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Connected path/area marks derived one observation per group (often a discrete x joining default grouping), so each stroke or ribbon degenerates; map aes.group to join rows into series. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#group-single-observation](https://ggsvelte.sh/guide/errors#group-single-observation) ### `stack-align-skipped` **Code + severity:** `stack-align-skipped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Sparse stacked-area groups were left unaligned because the shared-grid expansion exceeds the auto-rescue budget; bands with interior gaps may render as floating polygons. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#stack-align-skipped](https://ggsvelte.sh/guide/errors#stack-align-skipped) ### `smooth-group-dropped` **Code + severity:** `smooth-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A smooth group had too few points for the fit and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#smooth-group-dropped](https://ggsvelte.sh/guide/errors#smooth-group-dropped) ### `quantile-empty` **Code + severity:** `quantile-empty` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** No valid quantiles in (0,1) after filtering; nothing drawn. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#quantile-empty](https://ggsvelte.sh/guide/errors#quantile-empty) ### `quantile-group-dropped` **Code + severity:** `quantile-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A quantile group was too small or had constant x and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#quantile-group-dropped](https://ggsvelte.sh/guide/errors#quantile-group-dropped) ### `manual-group-dropped` **Code + severity:** `manual-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A manual-stat group had no finite x or y under an aggregate fun and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#manual-group-dropped](https://ggsvelte.sh/guide/errors#manual-group-dropped) ### `ellipse-group-dropped` **Code + severity:** `ellipse-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** An ellipse group had fewer than two finite (x,y) points or zero variance and was dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#ellipse-group-dropped](https://ggsvelte.sh/guide/errors#ellipse-group-dropped) ### `stat-channel-unsupported` — warning **Code + severity:** `stat-channel-unsupported` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A color/fill after-stat mapping names an output the selected stat does not publish; the mapping is ignored (style channels throw the same code as an error). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#stat-channel-unsupported-warning](https://ggsvelte.sh/guide/errors#stat-channel-unsupported-warning) ### `palette-exhausted` — warning **Code + severity:** `palette-exhausted` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A discrete color scale ran out of palette entries and cycled (the default onExhaust). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#palette-exhausted-warning](https://ggsvelte.sh/guide/errors#palette-exhausted-warning) ### `fingerprint-mismatch` **Code + severity:** `fingerprint-mismatch` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Restored scale state was trained on a different palette; assignments start fresh. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#fingerprint-mismatch](https://ggsvelte.sh/guide/errors#fingerprint-mismatch) ### `version-mismatch` **Code + severity:** `version-mismatch` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Restored scale state has an unknown schema version; assignments start fresh. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#version-mismatch](https://ggsvelte.sh/guide/errors#version-mismatch) ### `out-of-domain` **Code + severity:** `out-of-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Values outside an explicit scale domain render the unknown color (deduplicated). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#out-of-domain](https://ggsvelte.sh/guide/errors#out-of-domain) ### `temporal-values-censored` **Code + severity:** `temporal-values-censored` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** An explicit temporal parser censored invalid source values by author request. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#temporal-values-censored](https://ggsvelte.sh/guide/errors#temporal-values-censored) ### `guide-auto-bottom` **Code + severity:** `guide-auto-bottom` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** An auto-positioned guide moved below the panel to preserve readable width. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#guide-auto-bottom](https://ggsvelte.sh/guide/errors#guide-auto-bottom) ### `unused-scale-option` **Code + severity:** `unused-scale-option` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A lower-precedence scale option was ignored in favor of an explicit winner. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#unused-scale-option](https://ggsvelte.sh/guide/errors#unused-scale-option) ### `temporal-label-overlap` **Code + severity:** `temporal-label-overlap` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Authored or exhausted automatic temporal labels overlap at the available extent. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#temporal-label-overlap](https://ggsvelte.sh/guide/errors#temporal-label-overlap) ### `temporal-label-margin-overflow` **Code + severity:** `temporal-label-margin-overflow` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A complete temporal label exceeds the bounded axis margin. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#temporal-label-margin-overflow](https://ggsvelte.sh/guide/errors#temporal-label-margin-overflow) ### `temporal-break-outside-domain` **Code + severity:** `temporal-break-outside-domain` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** One or more explicit temporal breaks were outside the trained domain and omitted. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#temporal-break-outside-domain](https://ggsvelte.sh/guide/errors#temporal-break-outside-domain) ### `band-label-overlap` **Code + severity:** `band-label-overlap` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Categorical (band) axis labels overlap even after wrapping and rotation. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#band-label-overlap](https://ggsvelte.sh/guide/errors#band-label-overlap) ### `band-label-margin-overflow` **Code + severity:** `band-label-margin-overflow` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A band label exceeds a bounded axis margin — single-line end truncation, forced-wrap height/side overflow, or a rotated label past the bottom/side cap. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#band-label-margin-overflow](https://ggsvelte.sh/guide/errors#band-label-margin-overflow) ### `coord-tessellation-cap` **Code + severity:** `coord-tessellation-cap` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Adaptive coordinate tessellation reached a deterministic depth/vertex cap and retained a bounded approximation. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#coord-tessellation-cap](https://ggsvelte.sh/guide/errors#coord-tessellation-cap) ### `coord-invalid-geometry` **Code + severity:** `coord-invalid-geometry` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** Path vertices outside a coordinate transform's domain were removed without bridging the remaining finite runs. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#coord-invalid-geometry](https://ggsvelte.sh/guide/errors#coord-invalid-geometry) ### `coord-fixed-degraded` **Code + severity:** `coord-fixed-degraded` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** A fixed-aspect data rectangle is below the documented readable minimum; the ratio remains exact and minor furniture is removed. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#coord-fixed-degraded](https://ggsvelte.sh/guide/errors#coord-fixed-degraded) ### `abline-scale-unsupported` **Code + severity:** `abline-scale-unsupported` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom_abline needs continuous x and y scales to clip y = intercept + slope · x; the layer is skipped on band axes. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#abline-scale-unsupported](https://ggsvelte.sh/guide/errors#abline-scale-unsupported) ### `function-domain-missing` **Code + severity:** `function-domain-missing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom/stat function could not resolve an x evaluation domain (no xlim, mapped x, or peer layer domain). **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#function-domain-missing](https://ggsvelte.sh/guide/errors#function-domain-missing) ### `function-fun-missing` **Code + severity:** `function-fun-missing` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom/stat function is missing the required params.fun registry function name. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#function-fun-missing](https://ggsvelte.sh/guide/errors#function-fun-missing) ### `function-fun-unknown` **Code + severity:** `function-fun-unknown` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom/stat function named an unknown params.fun registry function. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#function-fun-unknown](https://ggsvelte.sh/guide/errors#function-fun-unknown) ### `hex-band-scale` **Code + severity:** `hex-band-scale` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom_hex needs continuous x and y scales for hexagonal binning; the layer is skipped on band axes. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#hex-band-scale](https://ggsvelte.sh/guide/errors#hex-band-scale) ### `hex-missing-size` **Code + severity:** `hex-missing-size` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom_hex is missing per-cell width/height from bin_hex (identity or incomplete frame); the layer is skipped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#hex-missing-size](https://ggsvelte.sh/guide/errors#hex-missing-size) ### `ydensity-group-dropped` **Code + severity:** `ydensity-group-dropped` · warning **What failed:** A render step could not use every requested row, value, or default exactly as supplied. **Why:** geom_violin needs at least two data points per group to estimate a density; smaller groups are dropped. **Fix:** Inspect the warning message for its path and count, then correct the named data, scale, or option. **Consequence (degraded):** The chart rendered, but the warning identifies dropped, cycled, or fallback output. **Stable link:** [/guide/errors#ydensity-group-dropped](https://ggsvelte.sh/guide/errors#ydensity-group-dropped) ## Interaction diagnostics (@ggsvelte/svelte) Each entry answers what failed, why, how to recover safely, and whether output was blocked or degraded. ### `INTERACTION_INTERVAL_FACET_UNSUPPORTED` **Code + severity:** `INTERACTION_INTERVAL_FACET_UNSUPPORTED` · warning **What failed:** The zoom interaction contract could not be applied as requested. **Why:** Brush zoom currently requires one unfaceted panel. **Fix:** Remove the facet; Use faceted interval selection; Zoom a linked detail view **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-interval-facet-unsupported](https://ggsvelte.sh/guide/errors#interaction-interval-facet-unsupported) ### `INTERACTION_INVALID_MAX_DISTANCE` **Code + severity:** `INTERACTION_INVALID_MAX_DISTANCE` · error **What failed:** The inspect.maxDistance interaction contract could not be applied as requested. **Why:** inspect.maxDistance must be a finite non-negative CSS-pixel distance. **Fix:** Use a finite number greater than or equal to zero **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-invalid-max-distance](https://ggsvelte.sh/guide/errors#interaction-invalid-max-distance) ### `INTERACTION_POINT_REQUIRES_KEY` **Code + severity:** `INTERACTION_POINT_REQUIRES_KEY` · warning **What failed:** The identity interaction contract could not be applied as requested. **Why:** Durable point selection requires resolved row identity. **Fix:** Ordinary charts omit identity — defaults to an id column or row index; Override with <Inspect identity="id" /> or select={{ type: "point", identity: "id" }} **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-point-requires-key](https://ggsvelte.sh/guide/errors#interaction-point-requires-key) ### `INTERACTION_INTERVAL_PRESET_REQUIRES_KEY` **Code + severity:** `INTERACTION_INTERVAL_PRESET_REQUIRES_KEY` · warning **What failed:** The identity interaction contract could not be applied as requested. **Why:** Coordinated interval presets (union, cross-panel) require resolved row identity; without it they combine no rows. **Fix:** Ordinary charts omit identity — defaults to an id column or row index; Override with select={{ type: "interval", identity: "id", … }} or <Inspect identity="id" /> **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-interval-preset-requires-key](https://ggsvelte.sh/guide/errors#interaction-interval-preset-requires-key) ### `INTERACTION_INVALID_KEY` **Code + severity:** `INTERACTION_INVALID_KEY` · error **What failed:** The identity interaction contract could not be applied as requested. **Why:** A key accessor returned null, undefined, or a non-PropertyKey value. **Fix:** Return a stable string, number, or symbol for every row **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-invalid-key](https://ggsvelte.sh/guide/errors#interaction-invalid-key) ### `INTERACTION_DUPLICATE_KEY` **Code + severity:** `INTERACTION_DUPLICATE_KEY` · error **What failed:** The identity interaction contract could not be applied as requested. **Why:** The key accessor returned a duplicate value; durable interaction is disabled for that value. **Fix:** Use a field that uniquely identifies each source row **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-duplicate-key](https://ggsvelte.sh/guide/errors#interaction-duplicate-key) ### `INTERACTION_UNSTABLE_KEY` **Code + severity:** `INTERACTION_UNSTABLE_KEY` · error **What failed:** The identity interaction contract could not be applied as requested. **Why:** The key accessor returned a different value for the same source row. **Fix:** Return an immutable field that uniquely identifies each row **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-unstable-key](https://ggsvelte.sh/guide/errors#interaction-unstable-key) ### `INTERACTION_MISSING_LINEAGE` **Code + severity:** `INTERACTION_MISSING_LINEAGE` · warning **What failed:** The layers interaction contract could not be applied as requested. **Why:** A synthetic or aggregate mark did not expose source-row lineage. **Fix:** Use a stat that preserves source-row lineage **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-missing-lineage](https://ggsvelte.sh/guide/errors#interaction-missing-lineage) ### `INTERACTION_LEGEND_REQUIRES_KEY` **Code + severity:** `INTERACTION_LEGEND_REQUIRES_KEY` · warning **What failed:** The identity interaction contract could not be applied as requested. **Why:** Legend focus requires resolved row identity so encoded legend values never become identities. **Fix:** Ordinary charts omit identity — defaults to an id column or row index; Override with <Inspect identity="id" /> or createPlotInteraction({ identity: "id" }) **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-legend-requires-key](https://ggsvelte.sh/guide/errors#interaction-legend-requires-key) ### `INTERACTION_LEGEND_DISCRETE_ONLY` **Code + severity:** `INTERACTION_LEGEND_DISCRETE_ONLY` · advisory **What failed:** The focus interaction contract could not be applied as requested. **Why:** Legend focus currently applies to discrete color and fill legends; continuous ramps remain static. **Fix:** Enable focus on a discrete guide: <GuideLegend channel="color" focus />; Use a discrete color or fill mapping; Keep the continuous ramp static **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-legend-discrete-only](https://ggsvelte.sh/guide/errors#interaction-legend-discrete-only) ### `INTERACTION_INTERVAL_SCALE_UNSUPPORTED` **Code + severity:** `INTERACTION_INTERVAL_SCALE_UNSUPPORTED` · warning **What failed:** The scales interaction contract could not be applied as requested. **Why:** Interval domains and brush zoom require continuous linear, log, or time scales. **Fix:** Use a continuous positional scale; Use point inspection for band data **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-interval-scale-unsupported](https://ggsvelte.sh/guide/errors#interaction-interval-scale-unsupported) ### `INTERACTION_TOOL_UNAVAILABLE` **Code + severity:** `INTERACTION_TOOL_UNAVAILABLE` · warning **What failed:** The tool interaction contract could not be applied as requested. **Why:** The requested interaction tool is unavailable for the enabled capabilities. **Fix:** Enable the matching capability; Choose an available interaction tool **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-tool-unavailable](https://ggsvelte.sh/guide/errors#interaction-tool-unavailable) **Minimal illustration — copy only the relevant fragment:** ```svelte fragment copy <GGPlot data={rows} aes={{ x: "weight", y: "economy" }} tool="inspect" > <Inspect /> </GGPlot> ``` ### `INTERACTION_SCOPE_WITHOUT_CONTROLLER` **Code + severity:** `INTERACTION_SCOPE_WITHOUT_CONTROLLER` · advisory **What failed:** The interactionScope interaction contract could not be applied as requested. **Why:** interactionScope is ignored without an interaction controller; chart-local scope is derived from key and aes. **Fix:** Pass interaction={createPlotInteraction()} to control this plot; Remove interactionScope from uncontrolled plots **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-scope-without-controller](https://ggsvelte.sh/guide/errors#interaction-scope-without-controller) ### `INTERACTION_HANDLER_WITHOUT_CAPABILITY` **Code + severity:** `INTERACTION_HANDLER_WITHOUT_CAPABILITY` · advisory **What failed:** The oninspect / onselect / onzoom / onlegendfocus / onlegendfilter interaction contract could not be applied as requested. **Why:** An interaction handler is set but its capability prop is not enabled, so the handler never fires. **Fix:** Enable the matching capability prop (for example select for onselect); Remove the unused handler **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-handler-without-capability](https://ggsvelte.sh/guide/errors#interaction-handler-without-capability) ### `INTERACTION_INSPECT_X_ON_COL` **Code + severity:** `INTERACTION_INSPECT_X_ON_COL` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode x/xy draws a crosshair on the x (band) axis through column marks; columns already encode x as a filled band, so the guide cuts the bar body and rarely adds information. Under coord_flip the guide is horizontal but still tracks the band. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomCol; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-x-on-col](https://ggsvelte.sh/guide/errors#interaction-inspect-x-on-col) **Minimal illustration — copy only the relevant fragment:** ```svelte fragment copy <GGPlot data={rows} aes={{ x: "group", y: "amount" }}> <GeomCol /> <Inspect mode="exact" /> </GGPlot> ``` ### `INTERACTION_INSPECT_X_ON_BAR` **Code + severity:** `INTERACTION_INSPECT_X_ON_BAR` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode x/xy draws a crosshair through bar marks; bars are filled regions on the band axis, so the guide cuts the bar body and rarely adds information. Under coord_flip the guide orientation swaps with the axes but still fights the marks. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomBar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-x-on-bar](https://ggsvelte.sh/guide/errors#interaction-inspect-x-on-bar) ### `INTERACTION_INSPECT_X_BISECTS_COL_LABELS` **Code + severity:** `INTERACTION_INSPECT_X_BISECTS_COL_LABELS` · warning **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode x/xy draws a crosshair through GeomCol marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read (including under coord_flip, when the guide is horizontal). **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") when columns have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-inspect-x-bisects-col-labels](https://ggsvelte.sh/guide/errors#interaction-inspect-x-bisects-col-labels) **Minimal illustration — copy only the relevant fragment:** ```svelte fragment copy <GGPlot data={rows} aes={{ x: "group", y: "amount" }}> <GeomCol /> <GeomText aes={{ label: "label" }} dy={-8} /> <Inspect mode="exact" /> </GGPlot> ``` ### `INTERACTION_INSPECT_X_BISECTS_BAR_LABELS` **Code + severity:** `INTERACTION_INSPECT_X_BISECTS_BAR_LABELS` · warning **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode x/xy draws a crosshair through GeomBar marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read (including under coord_flip). **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") when bars have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels **Consequence (degraded):** The chart remains rendered, but the affected interaction is disabled or reduced. **Stable link:** [/guide/errors#interaction-inspect-x-bisects-bar-labels](https://ggsvelte.sh/guide/errors#interaction-inspect-x-bisects-bar-labels) ### `INTERACTION_INSPECT_AXIS_ON_VIOLIN` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_VIOLIN` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through violin marks; violins sit on a discrete band, so freescrolling x/y/xy guides cut the density body and often leave the band tooltip row blank. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomViolin; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-violin](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-violin) **Minimal illustration — copy only the relevant fragment:** ```svelte fragment copy <GGPlot data={rows} aes={{ x: "run", y: "value" }}> <GeomViolin /> <Inspect mode="exact" /> </GGPlot> ``` ### `INTERACTION_INSPECT_AXIS_ON_BOXPLOT` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_BOXPLOT` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through boxplot marks; boxes sit on a discrete band, so freescrolling x/y/xy guides cut the box body and rarely add information. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomBoxplot; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-boxplot](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-boxplot) ### `INTERACTION_INSPECT_AXIS_ON_ERRORBAR` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_ERRORBAR` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through errorbar marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomErrorbar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-errorbar](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-errorbar) ### `INTERACTION_INSPECT_AXIS_ON_LINERANGE` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_LINERANGE` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through linerange marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomLinerange; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-linerange](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-linerange) ### `INTERACTION_INSPECT_AXIS_ON_POINTRANGE` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_POINTRANGE` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through pointrange marks; interval geoms on a discrete band are better inspected on the mark itself than via a freescrolling guide. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomPointrange; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-pointrange](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-pointrange) ### `INTERACTION_INSPECT_AXIS_ON_CROSSBAR` **Code + severity:** `INTERACTION_INSPECT_AXIS_ON_CROSSBAR` · advisory **What failed:** The inspect.mode interaction contract could not be applied as requested. **Why:** inspect.mode draws an axis guide through crossbar marks; even when hits pin to a category, freescrolling x/y/xy guides rarely add information beyond exact mark focus. **Fix:** Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomCrossbar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-axis-on-crossbar](https://ggsvelte.sh/guide/errors#interaction-inspect-axis-on-crossbar) ### `INTERACTION_INSPECT_HIGH_CARDINALITY_DISCRETE` **Code + severity:** `INTERACTION_INSPECT_HIGH_CARDINALITY_DISCRETE` · advisory **What failed:** The inspect interaction contract could not be applied as requested. **Why:** Inspect is enabled with a high-cardinality discrete color/fill domain; the default tooltip shows the focused series, the largest contributors at that x (or y), a stack total, and an overflow line — not every series. **Fix:** Prep top-n data before plotting if only the largest series matter; Pass a custom content snippet on <Inspect content={…} /> for a full multi-series listing; Pin the tooltip to scroll the full group when every series must be readable **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-high-cardinality-discrete](https://ggsvelte.sh/guide/errors#interaction-inspect-high-cardinality-discrete) ### `INTERACTION_INSPECT_IDENTITY_DROPPED` **Code + severity:** `INTERACTION_INSPECT_IDENTITY_DROPPED` · advisory **What failed:** The Inspect interaction contract could not be applied as requested. **Why:** An <Inspect> child replaced the inspect prop whole (REPLACE), so the prop's `identity` no longer applies; rows fall back to an id column or row index. **Fix:** Move identity onto the <Inspect> child; Drop identity from the inspect prop if the default row identity is intended **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-inspect-identity-dropped](https://ggsvelte.sh/guide/errors#interaction-inspect-identity-dropped) ### `INTERACTION_DUPLICATE_INSPECT_CAPABILITY` **Code + severity:** `INTERACTION_DUPLICATE_INSPECT_CAPABILITY` · advisory **What failed:** The Inspect interaction contract could not be applied as requested. **Why:** Multiple <Inspect> children are registered; only the last one's options apply (REPLACE). **Fix:** Keep a single <Inspect> child; Merge options onto one <Inspect> instead of stacking siblings **Consequence (advisory):** The chart remains available; review the ignored or inferred interaction behavior. **Stable link:** [/guide/errors#interaction-duplicate-inspect-capability](https://ggsvelte.sh/guide/errors#interaction-duplicate-inspect-capability) **Minimal illustration — copy only the relevant fragment:** ```svelte fragment copy <GGPlot data={rows} aes={{ x: "x", y: "y" }}> <GeomPoint /> <Inspect mode="xy" /> </GGPlot> ``` ## CLI diagnostics (ggsvelte-render) SVG is written only to stdout; JSON Lines diagnostics are written only to stderr. Exit 1 means rendering failed, exit 2 means usage/input failed, and exit 3 means spec validation failed. ### `usage` **Code + severity:** `usage` · error **What failed:** ggsvelte-render stopped with exit 2 and wrote this JSON Line to stderr. **Why:** Bad flags or arguments (exit 2). --help shows usage. **Fix:** Run ggsvelte-render --help, then remove unsupported or conflicting arguments. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#usage](https://ggsvelte.sh/guide/errors#usage) ### `unreadable-input` **Code + severity:** `unreadable-input` · error **What failed:** ggsvelte-render stopped with exit 2 and wrote this JSON Line to stderr. **Why:** The spec/data file (or stdin) could not be read (exit 2). **Fix:** Check the file path and permissions, or pipe readable JSON on stdin. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#unreadable-input](https://ggsvelte.sh/guide/errors#unreadable-input) ### `invalid-json` **Code + severity:** `invalid-json` · error **What failed:** ggsvelte-render stopped with exit 2 and wrote this JSON Line to stderr. **Why:** The spec or data file is not valid JSON (exit 2). **Fix:** Correct the JSON syntax before running the command again. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#invalid-json](https://ggsvelte.sh/guide/errors#invalid-json) **Minimal illustration — copy only the relevant fragment:** ```sh fragment copy printf '%s\n' '{"data":{"values":[{"x":1,"y":2}]},"layers":[{"geom":"point","aes":{"x":{"field":"x"},"y":{"field":"y"}}}]}' \ | ggsvelte-render > chart.svg ``` ### `invalid-data-file` **Code + severity:** `invalid-data-file` · error **What failed:** ggsvelte-render stopped with exit 2 and wrote this JSON Line to stderr. **Why:** --data must be a JSON object mapping dataset names to inline data (exit 2). **Fix:** Make --data point to a JSON object whose keys are dataset names. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#invalid-data-file](https://ggsvelte.sh/guide/errors#invalid-data-file) ### `max-marks-exceeded` — cli **Code + severity:** `max-marks-exceeded` · error **What failed:** ggsvelte-render stopped with exit 1 and wrote this JSON Line to stderr. **Why:** The plot renders more marks than --max-marks allows (exit 1). **Fix:** Reduce the data or deliberately raise --max-marks after checking output cost. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#max-marks-exceeded-cli](https://ggsvelte.sh/guide/errors#max-marks-exceeded-cli) ### `internal` **Code + severity:** `internal` · error **What failed:** ggsvelte-render stopped with exit 1 and wrote this JSON Line to stderr. **Why:** An unexpected internal error (exit 1). Please report it. **Fix:** Capture stderr and report the reproducible command, versions, and input shape. **Consequence (blocked):** No successful SVG is available on stdout for this invocation. **Stable link:** [/guide/errors#internal](https://ggsvelte.sh/guide/errors#internal) --- # Advisories Advisories never block a render. Two distinct kinds, two sources: ## Spec-lint advisories (@ggsvelte/spec lintSpec) Valid-but-questionable specs (Hadley: "we can produce many plots that don't make sense, yet are grammatically valid"). Run `lintSpec(spec, { profile? })` directly, pass `{ lint: true }` to `validate()`, or read the CLI's stderr advisories (source "spec-lint"). Data-dependent rules skip silently without evidence. ### `line-over-nominal-x` (needs: data-or-profile) A line layer connects points across a nominal (unordered) x field. ### `many-discrete-colors` (needs: data) A discrete color/fill field has more than 10 distinct values. ### `stacked-area-negative` (needs: data) A stacked area layer's y field contains negative values. ### `discrete-discrete-scatter` (needs: data-or-profile) A point layer maps discrete fields on BOTH x and y. ### `transform-domain-data` (needs: data) A transform scale (log10/sqrt) is configured over data that mixes in-domain and out-of-domain values. ### `fractional-calendar-years` (needs: data) A position channel holds year-like numbers with month fractions (year + month/12) on a linear scale. ## Pipeline heuristic advisories (@ggsvelte/core) Every heuristic decision the pipeline takes is disclosed as `{ code, path, chosen, howToOverride }` on `RenderModel.advisories` — agents see the guess and can correct it. ### `scale-type-inferred` A positional scale's type was inferred from the mapped data. ### `zero-forced` Bars/areas forced the measure axis to include zero. ### `scale-baseline-transformed-origin` A bar/col/area/histogram/density measure axis under transform: log10 baselines at the transformed-space origin (semantic 1), since log10 has no semantic-zero image. ### `bar-x-discretized` A numeric x on a count-stat bar layer was treated as discrete categories. ### `bin-default-bins` The bin stat used its default bin count; set params.binwidth to control it. ### `smooth-method-inferred` The smooth stat chose its method (lm vs loess) from the group size. ### `jitter-seeded` The jitter position used its default deterministic seed. ### `stack-align-applied` A stacked area layer auto-aligned its groups onto the shared x grid; groups sampled different x, and a raw stack would render floating bands. ### `palette-inferred` A color scale used the edition's default palette/ramp. ### `canvas-auto` A high-count layer auto-switched to the canvas backend (a11y/copy-SVG tradeoff disclosed). ### `temporal-year-inferred` A four-digit string field was inferred as calendar years; a discrete override is available. ### `temporal-inference-ambiguous` A date-like field remained discrete because multiple date orders matched. ### `temporal-inference-invalid` A date-like field remained discrete because whole-column validation failed. ### `band-labels-wrapped` Long categorical x labels were wrapped onto multiple lines to avoid collisions; pin with scales.x.guide.mode or coordFlip(). ### `band-labels-rotated` Long categorical x labels were rotated to avoid collisions; pin with scales.x.guide.mode/angle, or coordFlip() for horizontal rows. ### `percent-labels-out-of-range` A continuous scale uses a percent labels format (…%) but its domain is far outside proportion space — ticks/tooltips would print values like 87300%. --- # Lifecycle & editions ## Lifecycle tags Every public export carries a lifecycle tag (generated into `lifecycle.json` from the source annotations — regenerate with `bun run lifecycle:gen`): - **experimental** — may change or disappear in any 0.x release. The default for APIs not explicitly promoted. - **stable-intent** — committed enough that changes pay the migration tax. Covers (1) the agent core path (PortableSpec, normalize, validate, renderToSVGString, GGPlot and their direct result contracts) and (2) the recommended Svelte composition children (`Geom*` shells and the theme/scale/coord/facet/labs/guides/legend grammar children). Not frozen pre-1.0, but changes here are treated as breaking: they get a changeset, a migration note, and a deprecation window where feasible. Registry and factory helpers stay experimental. - **stable** — committed API under semver (none in v0.1). - **superseded** — keeps working but stops being recommended; docs point to the replacement. Protects agent-generated code from silent breakage. ## Defaults editions `normalize()` stamps `edition: 2` onto every spec that doesn't carry one, freezing which generation of DEFAULT aesthetics (theme role tokens, categorical palette, sequential ramp) the spec was authored against. When a future edition ships better defaults, stamped specs keep their original look — old charts never reshuffle. Explicit settings (`theme`, `scales.*.range`, `scales.*.scheme`) always win over edition defaults, and unknown editions degrade to the latest known with an `unknown-edition` warning. ## @ggsvelte/spec ### experimental (925) `A11yMode` (type), `ALIAS_GEOMS`, `AblineLayer` (type), `AblineLayerInput` (type), `AblineLayerSchema`, `AblineParams` (type), `Aes` (type), `AesInput` (type), `AesSchema`, `AliasGeomName` (type), `AlphaScaleSpec` (type), `AreaLayer` (type), `AreaLayerInput` (type), `AreaLayerSchema`, `AreaParams` (type), `AuthoringCellValue` (type), `AuthoringColumns` (type), `AuthoringDataRef` (type), `AuthoringRows` (type), `AxisGuideOptions` (type), `AxisGuideSpec` (type), `BarLayer` (type), `BarLayerInput` (type), `BarLayerSchema`, `BarParams` (type), `Bin2dLayer` (type), `Bin2dLayerInput` (type), `Bin2dLayerSchema`, `Bin2dParams` (type), `BinnedColorScaleOptions` (type), `BinnedFiniteStyleScaleOptions` (type), `BlankLayer` (type), `BlankLayerInput` (type), `BlankParams` (type), `BoxplotLayer` (type), `BoxplotLayerInput` (type), `BoxplotLayerSchema`, `BoxplotParams` (type), `CATEGORICAL_SCHEME_NAMES`, `CHANNELS`, `COLOR_SCHEME_NAMES`, `COORD_REFERENCE`, `CURRENT_EDITION`, `CellValue` (type), `ChannelFn` (type), `ChannelInput` (type), `ChannelName` (type), `ChannelValue` (type), `ChannelValueSchema`, `ColLayer` (type), `ColLayerInput` (type), `ColLayerSchema`, `ColParams` (type), `ColorBrewerScaleOptions` (type), `ColorDistillerScaleOptions` (type), `ColorFermenterScaleOptions` (type), `ColorScaleOptions` (type), `ColorScaleSpec` (type), `ColorStop` (type), `ColorStopInput` (type), `ColorbarGuideOptions` (type), `ColorbarGuideSpec` (type), `ColorstepsGuideOptions` (type), `ColorstepsGuideSpec` (type), `ContinuousPositionScaleOptions` (type), `ContourLayer` (type), `ContourLayerInput` (type), `ContourParams` (type), `CoordFixedOptions` (type), `CoordFixedSpec` (type), `CoordFixedSpecSchema`, `CoordParamDoc` (type), `CoordPolarOptions` (type), `CoordRadialOptions` (type), `CoordRadialReverse` (type), `CoordRadialSpec` (type), `CoordRadialSpecSchema`, `CoordRadialTheta` (type), `CoordReferenceEntry` (type), `CoordSfOptions` (type), `CoordSfSpec` (type), `CoordSfSpecSchema`, `CoordSpec` (type), `CoordSpecSchema`, `CoordTransformAxisOptions` (type), `CoordTransformAxisSpec` (type), `CoordTransformAxisSpecSchema`, `CoordTransformName` (type), `CoordTransformOptions` (type), `CoordTransformSpec` (type), `CoordTransformSpecSchema`, `CoordTypeName` (type), `CountLayer` (type), `CountLayerInput` (type), `CountLayerSchema`, `CrossbarLayer` (type), `CrossbarLayerInput` (type), `CrossbarLayerSchema`, `CrossbarParams` (type), `CurveLayer` (type), `CurveLayerInput` (type), `CurveLayerSchema`, `CurveParams` (type), `DEFAULT_VALIDATE_LIMITS`, `DataColumns` (type), `DataInput` (type), `DataName` (type), `DataProfile` (type), `DataProfileField` (type), `DataRef` (type), `DataRefSchema`, `DataValues` (type), `Density2dFilledLayer` (type), `Density2dFilledLayerInput` (type), `Density2dLayer` (type), `Density2dLayerInput` (type), `Density2dParams` (type), `DensityLayer` (type), `DensityLayerInput` (type), `DensityLayerSchema`, `DensityParams` (type), `DiscreteColorScaleOptions` (type), `DiscreteFiniteStyleScaleOptions` (type), `DiscreteNumericStyleScaleOptions` (type), `DiscretePositionScaleOptions` (type), `DotplotLayer` (type), `DotplotLayerInput` (type), `DotplotLayerSchema`, `DotplotParams` (type), `ERROR_CATALOG`, `ERROR_CODES`, `ErrorCatalogEntry` (type), `ErrorbarLayer` (type), `ErrorbarLayerInput` (type), `ErrorbarLayerSchema`, `ErrorbarParams` (type), `FacetFieldInput` (type), `FacetFieldRef` (type), `FacetFieldRefSchema`, `FacetInput` (type), `FacetScales` (type), `FacetSpec` (type), `FacetSpecSchema`, `FacetStripSpec` (type), `FacetStripSpecSchema`, `FiniteStyleScaleOptions` (type), `FreqpolyLayer` (type), `FreqpolyLayerInput` (type), `FreqpolyLayerSchema`, `FunctionArgs` (type), `FunctionLayer` (type), `FunctionLayerInput` (type), `FunctionLayerSchema`, `FunctionParams` (type), `FunctionRegistryName` (type), `GEOM_ALIASES`, `GEOM_DEFAULTS`, `GEOM_PARAM_KEYS`, `GEOM_REFERENCE`, `GGBuilder`, `GREY_PALETTE_10`, `GUIDE_CHANNELS`, `GUIDE_REFERENCE`, `GeomAblineOptions` (type), `GeomAreaOptions` (type), `GeomBarOptions` (type), `GeomBin2dOptions` (type), `GeomBlankOptions` (type), `GeomBoxplotOptions` (type), `GeomColOptions` (type), `GeomContourOptions` (type), `GeomCountOptions` (type), `GeomCrossbarOptions` (type), `GeomCurveOptions` (type), `GeomDensity2dFilledOptions` (type), `GeomDensity2dOptions` (type), `GeomDensityOptions` (type), `GeomDotplotOptions` (type), `GeomErrorbarOptions` (type), `GeomFreqpolyOptions` (type), `GeomFunctionOptions` (type), `GeomHexOptions` (type), `GeomHistogramOptions` (type), `GeomHlineOptions` (type), `GeomJitterOptions` (type), `GeomLabelOptions` (type), `GeomLineOptions` (type), `GeomLinerangeOptions` (type), `GeomMapOptions` (type), `GeomName` (type), `GeomParamDoc` (type), `GeomPathOptions` (type), `GeomPointOptions` (type), `GeomPointrangeOptions` (type), `GeomPolygonOptions` (type), `GeomQqLineOptions` (type), `GeomQqOptions` (type), `GeomQuantileOptions` (type), `GeomRasterOptions` (type), `GeomRectOptions` (type), `GeomReferenceEntry` (type), `GeomRugOptions` (type), `GeomRuleOptions` (type), `GeomSegmentOptions` (type), `GeomSfLabelOptions` (type), `GeomSfOptions` (type), `GeomSfTextOptions` (type), `GeomSmoothOptions` (type), `GeomSpokeOptions` (type), `GeomStepOptions` (type), `GeomTextOptions` (type), `GeomTileOptions` (type), `GeomViolinOptions` (type), `GeomVlineOptions` (type), `GlowOptions` (type), `GlowSpec` (type), `Gradient2ScaleOptions` (type), `GradientPaint` (type), `GradientScaleOptions` (type), `GradientnScaleOptions` (type), `GreyScaleOptions` (type), `GuideChannelName` (type), `GuideParamDoc` (type), `GuideReferenceEntry` (type), `GuideSpec` (type), `GuideThemeSpec` (type), `GuideTypeName` (type), `GuidesSpec` (type), `HUE_PALETTE_10`, `HexLayer` (type), `HexLayerInput` (type), `HexLayerSchema`, `HexParams` (type), `HistogramLayer` (type), `HistogramLayerInput` (type), `HistogramLayerSchema`, `HlineLayer` (type), `HlineLayerInput` (type), `HlineLayerSchema`, `HlineParams` (type), `HueScaleOptions` (type), `IdentityColorScaleOptions` (type), `IdentityFiniteStyleScaleOptions` (type), `IdentityNumericStyleScaleOptions` (type), `InlineData` (type), `JSONValue` (type), `JitterLayer` (type), `JitterLayerInput` (type), `JitterLayerSchema`, `KNOWN_COORD_TYPES`, `KNOWN_GEOMS`, `KNOWN_GUIDE_TYPES`, `KNOWN_POSITIONS`, `KNOWN_STATS`, `LINETYPE_NAMES`, `LINT_CATALOG`, `LabelLayer` (type), `LabelLayerInput` (type), `LabelLayerSchema`, `LabelParams` (type), `Labs` (type), `LayerInput` (type), `LayerSpec` (type), `LayerSpecSchema`, `LegendGuideOptions` (type), `LegendGuideSpec` (type), `LegendSpec` (type), `LineLayer` (type), `LineLayerInput` (type), `LineLayerSchema`, `LineParams` (type), `LinearGradientPaint` (type), `LinearPaintOptions` (type), `LinerangeLayer` (type), `LinerangeLayerInput` (type), `LinerangeLayerSchema`, `LinerangeParams` (type), `LinetypeName` (type), `LinetypeScaleSpec` (type), `LintAdvisoryCode` (type), `LintCatalogEntry` (type), `LossyResult` (type), `MAX_BINNED_BREAKS`, `MAX_GLOW_RADIUS`, `MAX_PAINT_STOPS`, `MAX_TEMPORAL_CANDIDATES`, `MAX_TEMPORAL_MAJOR_TICKS`, `MAX_TEMPORAL_MINOR_TICKS`, `MIN_TEMPORAL_LABEL_GAP_PX`, `MONTH_DAY_REFERENCE_YEAR`, `ManualColorScaleOptions` (type), `ManualFiniteStyleScaleOptions` (type), `ManualNumericStyleScaleOptions` (type), `MapLayer` (type), `MapLayerInput` (type), `MapParams` (type), `MultiIdentityScaleOptions` (type), `MultiManualScaleOptions` (type), `MultiScaleAesthetic` (type), `MultiScaleChannel` (type), `NoneGuideSpec` (type), `NormalizedGeomName` (type), `NormalizedLayerSpec` (type), `NormalizedSpec` (type), `NumericStyleScaleOptions` (type), `OrdinalColorScaleOptions` (type), `PIPELINE_ERROR_CATALOG`, `POINT_SHAPE_NAMES`, `POSITION_REFERENCE`, `PaintSpace` (type), `ParsedTemporalColumn` (type), `PathLayer` (type), `PathLayerInput` (type), `PathLayerSchema`, `PathParams` (type), `PipelineErrorCatalogEntry` (type), `PipelineErrorCode` (type), `PlotSpecSchema`, `PointLayer` (type), `PointLayerInput` (type), `PointLayerSchema`, `PointParams` (type), `PointPosition` (type), `PointShapeName` (type), `PointrangeLayer` (type), `PointrangeLayerInput` (type), `PointrangeLayerSchema`, `PointrangeParams` (type), `PolygonLayer` (type), `PolygonLayerInput` (type), `PolygonLayerSchema`, `PolygonParams` (type), `PortabilityIssue` (type), `PositionName` (type), `PositionParamDoc` (type), `PositionParams` (type), `PositionReferenceEntry` (type), `PositionScaleSpec` (type), `PositiveStyleScaleSpec` (type), `ProfileFieldType` (type), `QqLayer` (type), `QqLayerInput` (type), `QqLayerSchema`, `QqLineLayer` (type), `QqLineLayerInput` (type), `QqLineLayerSchema`, `QqLineParams` (type), `QqParams` (type), `QuantileLayer` (type), `QuantileLayerInput` (type), `QuantileParams` (type), `RadialGradientPaint` (type), `RadialPaintOptions` (type), `RasterLayer` (type), `RasterLayerInput` (type), `RasterLayerSchema`, `RasterParams` (type), `RecommendedScaleType` (type), `RectLayer` (type), `RectLayerInput` (type), `RectLayerSchema`, `RectParams` (type), `RenderBackend` (type), `RibbonLayer` (type), `RibbonLayerInput` (type), `RibbonLayerSchema`, `RibbonParams` (type), `RugLayer` (type), `RugLayerInput` (type), `RugLayerSchema`, `RugParams` (type), `RuleLayer` (type), `RuleLayerInput` (type), `RuleLayerSchema`, `RuleParams` (type), `RuntimeAblineLayer` (type), `RuntimeAes` (type), `RuntimeAreaLayer` (type), `RuntimeBarLayer` (type), `RuntimeBin2dLayer` (type), `RuntimeBlankLayer` (type), `RuntimeBoxplotLayer` (type), `RuntimeChannelValue` (type), `RuntimeColLayer` (type), `RuntimeContourLayer` (type), `RuntimeCountLayer` (type), `RuntimeCrossbarLayer` (type), `RuntimeCurveLayer` (type), `RuntimeDensity2dFilledLayer` (type), `RuntimeDensity2dLayer` (type), `RuntimeDensityLayer` (type), `RuntimeDotplotLayer` (type), `RuntimeErrorbarLayer` (type), `RuntimeFreqpolyLayer` (type), `RuntimeFunctionLayer` (type), `RuntimeHexLayer` (type), `RuntimeHistogramLayer` (type), `RuntimeHlineLayer` (type), `RuntimeJitterLayer` (type), `RuntimeLabelLayer` (type), `RuntimeLayerSpec` (type), `RuntimeLineLayer` (type), `RuntimeLinerangeLayer` (type), `RuntimeMapLayer` (type), `RuntimePathLayer` (type), `RuntimePointLayer` (type), `RuntimePointrangeLayer` (type), `RuntimePolygonLayer` (type), `RuntimeQqLayer` (type), `RuntimeQqLineLayer` (type), `RuntimeQuantileLayer` (type), `RuntimeRasterLayer` (type), `RuntimeRectLayer` (type), `RuntimeRibbonLayer` (type), `RuntimeRugLayer` (type), `RuntimeRuleLayer` (type), `RuntimeSegmentLayer` (type), `RuntimeSfLabelLayer` (type), `RuntimeSfLayer` (type), `RuntimeSfTextLayer` (type), `RuntimeSmoothLayer` (type), `RuntimeSpec` (type), `RuntimeSpokeLayer` (type), `RuntimeStepLayer` (type), `RuntimeTextLayer` (type), `RuntimeTileLayer` (type), `RuntimeViolinLayer` (type), `RuntimeVlineLayer` (type), `SCALE_CAPABILITIES`, `SCALE_FAMILY_LABELS`, `SCALE_REFERENCE`, `SCHEMA_VERSION`, `SEQUENTIAL_SCHEME_NAMES`, `SHARED_LAYER_PROPS`, `STAT_COLUMNS`, `STAT_REFERENCE`, `STYLE_AESTHETIC_GEOMS`, `STYLE_ORDINAL_SCALE_HELPERS`, `ScaleAesthetic` (type), `ScaleCapability` (type), `ScaleExpansion` (type), `ScaleFamily` (type), `ScaleParamDoc` (type), `ScaleReferenceEntry` (type), `ScaleTypeAesthetic` (type), `Scales` (type), `ScalesSchema`, `SegmentLayer` (type), `SegmentLayerInput` (type), `SegmentLayerSchema`, `SegmentParams` (type), `SequentialColorScaleOptions` (type), `SequentialStyleScaleOptions` (type), `SfLabelLayer` (type), `SfLabelLayerInput` (type), `SfLabelParams` (type), `SfLayer` (type), `SfLayerInput` (type), `SfParams` (type), `SfTextLayer` (type), `SfTextLayerInput` (type), `SfTextParams` (type), `ShapeScaleSpec` (type), `SharedLayerPropDoc` (type), `SizeAreaScaleOptions` (type), `SizeSequentialStyleScaleOptions` (type), `SizeTemporalNumericStyleScaleOptions` (type), `SmoothLayer` (type), `SmoothLayerInput` (type), `SmoothLayerSchema`, `SmoothParams` (type), `SpecAdvisory` (type), `SpecInput` (type), `SpecModule`, `SpokeLayer` (type), `SpokeLayerInput` (type), `SpokeParams` (type), `StackablePosition` (type), `StatName` (type), `StatReferenceEntry` (type), `StepLayer` (type), `StepLayerInput` (type), `StepLayerSchema`, `StepParams` (type), `Steps2ScaleOptions` (type), `StepsScaleOptions` (type), `StepsnScaleOptions` (type), `StyleAesthetic` (type), `SummaryFun` (type), `TEMPORAL_INTERVAL_UNITS`, `TEMPORAL_LABEL_TOKENS`, `TEMPORAL_PARSER_NAMES`, `TEMPORAL_WEEKDAYS`, `THEME_NAMES`, `THEME_NAME_ALIASES`, `TemporalColorScaleOptions` (type), `TemporalDecision` (type), `TemporalDisambiguation` (type), `TemporalFailure` (type), `TemporalInterval` (type), `TemporalIntervalError`, `TemporalIntervalSpec` (type), `TemporalIntervalSpecSchema`, `TemporalIntervalUnit` (type), `TemporalKind` (type), `TemporalLabelSpecSchema`, `TemporalNumericStyleScaleOptions` (type), `TemporalParseError`, `TemporalParseOptions` (type), `TemporalParseResult` (type), `TemporalParserName` (type), `TemporalParserSpec` (type), `TemporalParserSpecSchema`, `TemporalParserSpecSchemaRef`, `TemporalPrecision` (type), `TemporalScaleKind` (type), `TemporalScaleOptions` (type), `TemporalWeekStart` (type), `TemporalWeekStartSchema`, `TextLayer` (type), `TextLayerInput` (type), `TextLayerSchema`, `TextParams` (type), `ThemeName` (type), `ThemeSpec` (type), `TileLayer` (type), `TileLayerInput` (type), `TileLayerSchema`, `TileParams` (type), `TransformedColorScaleOptions` (type), `TransformedPositionScaleOptions` (type), `UnportableSpecError`, `ValidateLimits` (type), `ValidateOptions` (type), `ViolinLayer` (type), `ViolinLayerInput` (type), `ViolinLayerSchema`, `ViolinParams` (type), `ViridisOptionName` (type), `ViridisScaleOptions` (type), `VlineLayer` (type), `VlineLayerInput` (type), `VlineLayerSchema`, `VlineParams` (type), `aes`, `allScaleHelpers`, `assertStructuralGate`, `buildGreyPalette`, `buildHuePalette`, `buildSchemaArtifact`, `builderScaleHelperNames`, `calendarDateFields`, `canonicalMultiScaleChannel`, `canonicalTemporalParserKey`, `componentNameForGeom`, `componentNameForScaleHelper`, `configuredColorScaleType`, `coordEqual`, `coordFixed`, `coordPolar`, `coordRadial`, `coordReferenceList`, `coordSf`, `coordTransform`, `coord_equal`, `coord_fixed`, `coord_polar`, `coord_radial`, `coord_sf`, `coord_transform`, `didYouMean`, `dmy`, `dmy_hm`, `dmy_hms`, `dym`, `dym_hm`, `dym_hms`, `effectiveChannel`, `ensureTemporalPolyfill`, `fillPaintLinear`, `fillPaintRadial`, `fromEpochMilliseconds`, `fromEpochSeconds`, `geomReferenceList`, `gg`, `glow`, `guideAxis`, `guideColorbar`, `guideColorsteps`, `guideLegend`, `guideNone`, `guideReferenceList`, `guide_axis`, `guide_colorbar`, `guide_colorsteps`, `guide_legend`, `guide_none`, `guides`, `hslToHex`, `inferTemporalColumn`, `isPortable`, `knownScaleSlugs`, `levenshtein`, `lintSpec`, `mdy`, `mdy_hm`, `mdy_hms`, `my`, `myd`, `myd_hm`, `myd_hms`, `normalizeChannel`, `parseTemporal`, `parseTemporalColumn`, `parseTemporalFormat`, `parseTemporalInterval`, `portabilityIssues`, `positionReferenceList`, `primaryScaleHelpers`, `scaleAlpha`, `scaleAlphaBinned`, `scaleAlphaContinuous`, `scaleAlphaDate`, `scaleAlphaDatetime`, `scaleAlphaDiscrete`, `scaleAlphaIdentity`, `scaleAlphaManual`, `scaleAlphaOrdinal`, `scaleCapabilityCamelHelpers`, `scaleColorBinned`, `scaleColorBrewer`, `scaleColorContinuous`, `scaleColorDate`, `scaleColorDatetime`, `scaleColorDiscrete`, `scaleColorDistiller`, `scaleColorFermenter`, `scaleColorGradient`, `scaleColorGradient2`, `scaleColorGradientn`, `scaleColorGray`, `scaleColorGrey`, `scaleColorHue`, `scaleColorIdentity`, `scaleColorLog10`, `scaleColorManual`, `scaleColorOrdinal`, `scaleColorSqrt`, `scaleColorSteps`, `scaleColorSteps2`, `scaleColorStepsn`, `scaleColorViridisB`, `scaleColorViridisC`, `scaleColorViridisD`, `scaleColourBinned`, `scaleColourBrewer`, `scaleColourContinuous`, `scaleColourDate`, `scaleColourDatetime`, `scaleColourDiscrete`, `scaleColourDistiller`, `scaleColourFermenter`, `scaleColourGradient`, `scaleColourGradient2`, `scaleColourGradientn`, `scaleColourGray`, `scaleColourGrey`, `scaleColourHue`, `scaleColourIdentity`, `scaleColourLog10`, `scaleColourManual`, `scaleColourOrdinal`, `scaleColourSqrt`, `scaleColourSteps`, `scaleColourSteps2`, `scaleColourStepsn`, `scaleColourViridisB`, `scaleColourViridisC`, `scaleColourViridisD`, `scaleContinuousIdentity`, `scaleDiscreteIdentity`, `scaleDiscreteManual`, `scaleFillBinned`, `scaleFillBrewer`, `scaleFillContinuous`, `scaleFillDate`, `scaleFillDatetime`, `scaleFillDiscrete`, `scaleFillDistiller`, `scaleFillFermenter`, `scaleFillGradient`, `scaleFillGradient2`, `scaleFillGradientn`, `scaleFillGray`, `scaleFillGrey`, `scaleFillHue`, `scaleFillIdentity`, `scaleFillLog10`, `scaleFillManual`, `scaleFillOrdinal`, `scaleFillSqrt`, `scaleFillSteps`, `scaleFillSteps2`, `scaleFillStepsn`, `scaleFillViridisB`, `scaleFillViridisC`, `scaleFillViridisD`, `scaleLinetype`, `scaleLinetypeBinned`, `scaleLinetypeDiscrete`, `scaleLinetypeIdentity`, `scaleLinetypeManual`, `scaleLinewidth`, `scaleLinewidthBinned`, `scaleLinewidthContinuous`, `scaleLinewidthDate`, `scaleLinewidthDatetime`, `scaleLinewidthDiscrete`, `scaleLinewidthIdentity`, `scaleLinewidthManual`, `scaleLinewidthOrdinal`, `scaleRadius`, `scaleReferenceByFamily`, `scaleReferenceList`, `scaleReferencePrimaries`, `scaleShape`, `scaleShapeBinned`, `scaleShapeDiscrete`, `scaleShapeIdentity`, `scaleShapeManual`, `scaleShapeOrdinal`, `scaleSize`, `scaleSizeArea`, `scaleSizeBinned`, `scaleSizeBinnedArea`, `scaleSizeContinuous`, `scaleSizeDate`, `scaleSizeDatetime`, `scaleSizeDiscrete`, `scaleSizeIdentity`, `scaleSizeManual`, `scaleSizeOrdinal`, `scaleType`, `scaleXBinned`, `scaleXContinuous`, `scaleXDate`, `scaleXDatetime`, `scaleXDiscrete`, `scaleXLog10`, `scaleXMonthDay`, `scaleXReverse`, `scaleXSqrt`, `scaleXTime`, `scaleYBinned`, `scaleYContinuous`, `scaleYDate`, `scaleYDatetime`, `scaleYDiscrete`, `scaleYLog10`, `scaleYMonthDay`, `scaleYReverse`, `scaleYSqrt`, `scaleYTime`, `scale_alpha`, `scale_alpha_binned`, `scale_alpha_continuous`, `scale_alpha_date`, `scale_alpha_datetime`, `scale_alpha_discrete`, `scale_alpha_identity`, `scale_alpha_manual`, `scale_alpha_ordinal`, `scale_color_binned`, `scale_color_brewer`, `scale_color_continuous`, `scale_color_date`, `scale_color_datetime`, `scale_color_discrete`, `scale_color_distiller`, `scale_color_fermenter`, `scale_color_gradient`, `scale_color_gradient2`, `scale_color_gradientn`, `scale_color_gray`, `scale_color_grey`, `scale_color_hue`, `scale_color_identity`, `scale_color_log10`, `scale_color_manual`, `scale_color_ordinal`, `scale_color_sqrt`, `scale_color_steps`, `scale_color_steps2`, `scale_color_stepsn`, `scale_color_viridis_b`, `scale_color_viridis_c`, `scale_color_viridis_d`, `scale_colour_binned`, `scale_colour_brewer`, `scale_colour_continuous`, `scale_colour_date`, `scale_colour_datetime`, `scale_colour_discrete`, `scale_colour_distiller`, `scale_colour_fermenter`, `scale_colour_gradient`, `scale_colour_gradient2`, `scale_colour_gradientn`, `scale_colour_gray`, `scale_colour_grey`, `scale_colour_hue`, `scale_colour_identity`, `scale_colour_log10`, `scale_colour_manual`, `scale_colour_ordinal`, `scale_colour_sqrt`, `scale_colour_steps`, `scale_colour_steps2`, `scale_colour_stepsn`, `scale_colour_viridis_b`, `scale_colour_viridis_c`, `scale_colour_viridis_d`, `scale_continuous_identity`, `scale_discrete_identity`, `scale_discrete_manual`, `scale_fill_binned`, `scale_fill_brewer`, `scale_fill_continuous`, `scale_fill_date`, `scale_fill_datetime`, `scale_fill_discrete`, `scale_fill_distiller`, `scale_fill_fermenter`, `scale_fill_gradient`, `scale_fill_gradient2`, `scale_fill_gradientn`, `scale_fill_gray`, `scale_fill_grey`, `scale_fill_hue`, `scale_fill_identity`, `scale_fill_log10`, `scale_fill_manual`, `scale_fill_ordinal`, `scale_fill_sqrt`, `scale_fill_steps`, `scale_fill_steps2`, `scale_fill_stepsn`, `scale_fill_viridis_b`, `scale_fill_viridis_c`, `scale_fill_viridis_d`, `scale_linetype`, `scale_linetype_binned`, `scale_linetype_discrete`, `scale_linetype_identity`, `scale_linetype_manual`, `scale_linewidth`, `scale_linewidth_binned`, `scale_linewidth_continuous`, `scale_linewidth_date`, `scale_linewidth_datetime`, `scale_linewidth_discrete`, `scale_linewidth_identity`, `scale_linewidth_manual`, `scale_linewidth_ordinal`, `scale_radius`, `scale_shape`, `scale_shape_binned`, `scale_shape_discrete`, `scale_shape_identity`, `scale_shape_manual`, `scale_shape_ordinal`, `scale_size`, `scale_size_area`, `scale_size_binned`, `scale_size_binned_area`, `scale_size_continuous`, `scale_size_date`, `scale_size_datetime`, `scale_size_discrete`, `scale_size_identity`, `scale_size_manual`, `scale_size_ordinal`, `scale_type`, `scale_x_binned`, `scale_x_continuous`, `scale_x_date`, `scale_x_datetime`, `scale_x_discrete`, `scale_x_log10`, `scale_x_month_day`, `scale_x_reverse`, `scale_x_sqrt`, `scale_x_time`, `scale_y_binned`, `scale_y_continuous`, `scale_y_date`, `scale_y_datetime`, `scale_y_discrete`, `scale_y_log10`, `scale_y_month_day`, `scale_y_reverse`, `scale_y_sqrt`, `scale_y_time`, `schemaArtifactJSON`, `slugForScaleHelper`, `statReferenceList`, `strokePaintLinear`, `strokePaintRadial`, `structuralGateErrors`, `temporalIntervalTicks`, `temporalLabelConfigurationError`, `temporalLocaleConfigurationError`, `temporalParserConfigurationError`, `toAuthoringDataRef`, `toDataRef`, `toPortable`, `toPortableLossy`, `ydm`, `ydm_hm`, `ydm_hms`, `ym`, `ymd`, `ymd_hm`, `ymd_hms`, `yq` ### stable-intent (8) `PortableSpec` (type), `SpecError` (type), `SpecErrorCode` (type), `SpecErrorFix` (type), `SpecValidationError`, `ValidateResult` (type), `normalize`, `validate` ## @ggsvelte/spec (./portable) ### experimental (21) `AesInput` (type), `AuthoringCellValue` (type), `AuthoringColumns` (type), `AuthoringDataRef` (type), `AuthoringRows` (type), `DataInput` (type), `GGBuilder`, `GeomAreaOptions` (type), `GeomBarOptions` (type), `GeomBoxplotOptions` (type), `GeomColOptions` (type), `GeomDensityOptions` (type), `GeomHistogramOptions` (type), `GeomLineOptions` (type), `GeomPointOptions` (type), `GeomSmoothOptions` (type), `GeomTextOptions` (type), `SpecInput` (type), `aes`, `gg`, `normalize` ## @ggsvelte/core ### experimental (388) `ADVISORY_CATALOG`, `Advisory` (type), `AdvisoryCode` (type), `AesMapping` (type), `AxisEditModel` (type), `AxisGuidePlan` (type), `AxisGuideTick` (type), `AxisResult` (type), `AxisValueFormatter` (type), `BUILTIN_THEMES`, `BandAxisPlan` (type), `BandAxisPlanInput` (type), `BandConfig` (type), `BandGuideConfig` (type), `BandLabelMode` (type), `BandScale` (type), `BatchInteractionMask` (type), `CANVAS_AUTO_THRESHOLD`, `CANVA_PALETTE`, `CATEGORICAL_PALETTE_10`, `CATEGORICAL_SCHEMES`, `CLIDiagnosticCode` (type), `CLIIO` (type), `CLI_DIAGNOSTIC_CATALOG`, `COLORBLIND_PALETTE`, `CandidateBatchFacts` (type), `CandidateBuildFacts` (type), `CandidateDatum` (type), `CandidateDatumColumns` (type), `CandidateFacts` (type), `CandidateGroup` (type), `CandidateInspectMode` (type), `CandidateMatch` (type), `CandidateRange` (type), `CandidateStore` (type), `CandidateStoreOptions` (type), `CandidateStyleColumn` (type), `CanonicalAxisToken` (type), `CellValue` (type), `ClientRect` (type), `ColorScale` (type), `ColorbarGuidePlan` (type), `ColorbarGuideTick` (type), `ColorstepsGuidePlan` (type), `ColorstepsGuideStep` (type), `ColumnTable`, `Columns` (type), `ContinuousConfig` (type), `ContinuousScale` (type), `ContinuousTraining` (type), `CoordAxisProjector` (type), `CoordProjectorInput` (type), `CountStatInput` (type), `CountStatResult` (type), `CreateSemanticViewportInput` (type), `DEFAULT_FONT_STACK`, `DEFAULT_LAYOUT_THEME`, `DeclaredDiscreteness` (type), `DiagnosticCode` (type), `DiscreteGuideEntry` (type), `DiscreteGuidePlan` (type), `DiscreteLegendInput` (type), `DiscreteScaleSpec` (type), `Discreteness` (type), `DodgeInput` (type), `DodgeResult` (type), `Domain` (type), `ECONOMIST_PALETTE`, `EDITION_DEFAULTS`, `EcdfStatInput` (type), `EcdfStatResult` (type), `EditionDefaults` (type), `FEW_DARK_PALETTE`, `FEW_LIGHT_PALETTE`, `FEW_PALETTE`, `FIVETHIRTYEIGHT_PALETTE`, `FLEXOKI_PALETTE`, `FONT_METRICS`, `FacetPanelIdentity` (type), `FacetPanelIdentityInput` (type), `FacetPanelRole` (type), `FacetPanelValueIdentity` (type), `FieldType` (type), `FocusedPrimitive` (type), `GeometryBatch` (type), `GlyphsBatch` (type), `GroupDerivation` (type), `GuidePlan` (type), `HIGH_CARDINALITY_DISCRETE_THRESHOLD`, `INSPECT_GEOM_DIAGNOSTIC_CATALOG`, `INSPECT_INTENT_MODES`, `IPSUM_PALETTE`, `InspectGeomAdvisory` (type), `InspectGeomAdvisoryCode` (type), `InspectIntentMode` (type), `LEGEND_ROW_HEIGHT`, `LINETYPE_DASHES`, `LayerBackend` (type), `LayoutAxisPresentation` (type), `LayoutInput` (type), `LayoutResult` (type), `LayoutTheme` (type), `LegendBlock` (type), `LegendInput` (type), `LegendOrder` (type), `LetterboxRect` (type), `LineageRef` (type), `LineageStore`, `Linetype` (type), `MAX_BINNED_BREAKS`, `MappedField` (type), `Margins` (type), `MetricsTable` (type), `MetricsTableMeasurer`, `NamedData` (type), `NormalizedSpan` (type), `NumberFormatter` (type), `OrdinalColorConfig` (type), `PANDER_PALETTE`, `PANEL_SPACING`, `PIPELINE_ERROR_CATALOG`, `PIPELINE_WARNING_CATALOG`, `POSITION_TRANSFORM_NAMES`, `PTOL_PALETTE`, `PaletteExhaustedError`, `PanelCoordProjector` (type), `PassResult` (type), `PathsBatch` (type), `PipelineError`, `PipelineErrorCatalogEntry` (type), `PipelineErrorCode` (type), `PipelineWarning` (type), `PipelineWarningCode` (type), `PlotRect` (type), `PointShape` (type), `PointShapeGeometry` (type), `PointsBatch` (type), `PolarBBox` (type), `PolarProjector` (type), `PolarProjectorConfig` (type), `PolarReverse` (type), `PolarTheta` (type), `PositionScale` (type), `PositionTransformName` (type), `RAMP_LUT_STEPS`, `RampLegendInput` (type), `RectsBatch` (type), `RenderModel` (type), `ResolvedCandidateInspectMode` (type), `ResolvedColorScale` (type), `ResolvedEdition` (type), `ResolvedGlow` (type), `ResolvedGlyphMark` (type), `ResolvedGradientPaint` (type), `ResolvedPathMark` (type), `ResolvedPointMark` (type), `ResolvedRectMark` (type), `ResolvedSegmentMark` (type), `ResolvedStyleScale` (type), `Rows` (type), `RunOptions` (type), `RuntimeRowFilterClause` (type), `RuntimeRowFilterMode` (type), `RuntimeRowIndexFilter` (type), `SCALE_STATE_VERSION`, `SOLARIZED_PALETTE`, `STATA_PALETTE`, `STRIP_BAND`, `ScaleConfigError`, `ScaleDecision` (type), `ScaleDiagnostic` (type), `ScaleDiagnosticFix` (type), `ScaleDomainSnapshot` (type), `ScaleState` (type), `ScaleTransform` (type), `ScaleWarning` (type), `ScaleWarningCode` (type), `Scene` (type), `SceneAxis` (type), `SceneDiscreteLegend` (type), `SceneLegend` (type), `SceneLegendEntry` (type), `ScenePanel` (type), `SceneRampLegend` (type), `SceneStepsLegend` (type), `SceneTick` (type), `SegmentsBatch` (type), `SemanticCandidateKeys` (type), `SemanticViewport` (type), `SemanticViewportAxisSelection` (type), `SemanticViewportDomains` (type), `SemanticViewportPanel` (type), `SemanticViewportSelection` (type), `SequentialColorScale` (type), `SequentialConfig` (type), `StackInput` (type), `StackResult` (type), `StepsLegendInput` (type), `Stratum` (type), `StyleOutput` (type), `StyleScale` (type), `SumStatInput` (type), `SumStatResult` (type), `SummaryRollingStatInput` (type), `SummaryRollingStatResult` (type), `TABLEAU20_PALETTE`, `TABLEAU_COLORBLIND_PALETTE`, `TABLEAU_HUE_CIRCLE_PALETTE`, `TABLEAU_JEWEL_BRIGHT_PALETTE`, `TABLEAU_SEATTLE_GRAYS_PALETTE`, `TemporalAxisPlanInput` (type), `TemporalLabelFormatOptions` (type), `TemporalTickLabel` (type), `TextMeasurer` (type), `ThemeColorRole` (type), `ThemeRole` (type), `ThemeTokens` (type), `Tick` (type), `TickFormatter` (type), `TimeTicksResult` (type), `TrainMode` (type), `TrainResult` (type), `TrainedScales` (type), `TraversalDirection` (type), `UnknownThemeError`, `VIRIDIS_RAMP_10`, `WSJ_BLACK_GREEN_PALETTE`, `WSJ_DEM_REP_PALETTE`, `WSJ_PALETTE`, `WSJ_RED_GREEN_PALETTE`, `WSJ_RGBY_PALETTE`, `adoptScaleState`, `areaOutlineActive`, `bandKey`, `batchMarkCount`, `buildCandidateStore`, `buildCoordAxisProjector`, `buildInteractionMasks`, `buildLegends`, `buildPanelCoordProjector`, `buildPolarProjector`, `buildPrimitiveInteractionMasks`, `buildRampLut`, `canonicalAxisToken`, `cellToNumber`, `cellsToNumeric`, `collectInspectIntentDiagnostics`, `compileRuntimeRowIndexFilter`, `compileTemporalLabelFormat`, `countMarks`, `createFacetPanelIdentity`, `decodeKey`, `defaultLogTickFormat`, `defaultTickFormat`, `defaultTimeTickFormat`, `deriveGroups`, `disambiguatedLabels`, `discreteColorFillDomainSizes`, `discretenessOf`, `encodeKey`, `finiteExtent`, `fnv1a`, `formatTemporalTickSequence`, `formatTime`, `freshScaleState`, `getScaleTransform`, `groupBatchesByPanel`, `humanizeFieldTitle`, `inferDiscreteness`, `inferFieldType`, `inspectAxisOnBarColDiagnostics`, `inspectAxisOnDistributionDiagnostics`, `inspectHighCardinalityDiagnostics`, `installCandidates`, `installTemporal`, `isISODateString`, `isInspectIntentMode`, `layerGeomsFromSpecLayers`, `layout`, `layoutPass`, `letterboxGutterRects`, `linearTicks`, `linetypeDash`, `linetypeIndex`, `logTicks`, `marginDelta`, `markLinetype`, `niceLinearDomain`, `numberFormatter`, `paintResourceId`, `paletteFingerprint`, `pathData`, `perfMark`, `perfMeasure`, `planBandAxis`, `planStrata`, `planTemporalAxis`, `pointShapeGeometry`, `pointShapeIndex`, `pointShapePathD`, `polarBBox`, `polarProject`, `polarProjectPanelPoint`, `polarUnproject`, `positionDodge`, `positionStack`, `rampColor`, `registerAbline`, `registerAlign`, `registerAll`, `registerAllGeomBatches`, `registerAllStatFrames`, `registerBasic`, `registerBasicAreas`, `registerBasicBars`, `registerBasicGlyphs`, `registerBasicLines`, `registerBasicPoints`, `registerBasicRects`, `registerBasicSegments`, `registerBin`, `registerBin2d`, `registerBoxplot`, `registerConnect`, `registerContour`, `registerCrossbar`, `registerCurve`, `registerDensity`, `registerDensity2d`, `registerDensity2dFilled`, `registerDotplot`, `registerEcdf`, `registerEllipse`, `registerErrorbar`, `registerFunction`, `registerHex`, `registerLinerange`, `registerManual`, `registerMap`, `registerPointrange`, `registerPolygon`, `registerQq`, `registerQqLine`, `registerQuantile`, `registerRaster`, `registerRug`, `registerSf`, `registerSfLabel`, `registerSfText`, `registerSmooth`, `registerSpoke`, `registerSummary`, `registerSummaryBin`, `registerSummaryRolling`, `registerTile`, `registerUnique`, `registerViolin`, `resolveEditionDefaults`, `resolveGlyphMark`, `resolvePathMark`, `resolvePointMark`, `resolveRectMark`, `resolveSegmentMark`, `resolveTheme`, `runCLI`, `runPipeline`, `sampleRampLut`, `sampleSequentialPalette`, `scaleTransform`, `scalesForCoordExpand`, `sceneLabel`, `sceneToSVGString`, `sequentialSchemeRamp`, `serializeScaleState`, `spaceFieldName`, `statCount`, `statEcdf`, `statSum`, `statSummaryRolling`, `themeVar`, `tickStep`, `timeTicks`, `trainBand`, `trainColor`, `trainContinuous`, `trainDiscrete`, `trainLinear`, `trainSequential` ### stable-intent (2) `RenderSVGOptions` (type), `renderToSVGString` ## @ggsvelte/core (./render) ### experimental (43) `Advisory` (type), `AxisValueFormatter` (type), `CANVAS_AUTO_THRESHOLD`, `GeometryBatch` (type), `GlyphsBatch` (type), `LayerBackend` (type), `MappedField` (type), `NamedData` (type), `PathsBatch` (type), `PipelineError`, `PipelineWarning` (type), `PointsBatch` (type), `RectsBatch` (type), `RenderModel` (type), `RenderSVGOptions` (type), `ResolvedColorScale` (type), `RunOptions` (type), `ScaleDecision` (type), `ScaleDiagnostic` (type), `ScaleDiagnosticFix` (type), `ScaleDomainSnapshot` (type), `Scene` (type), `SceneAxis` (type), `SceneDiscreteLegend` (type), `SceneLegend` (type), `SceneLegendEntry` (type), `ScenePanel` (type), `SceneRampLegend` (type), `SceneStepsLegend` (type), `SceneTick` (type), `SegmentsBatch` (type), `Stratum` (type), `TrainedScales` (type), `batchMarkCount`, `countMarks`, `pathData`, `planStrata`, `registerGeomBatch`, `registerStatFrame`, `renderToSVGString`, `runPipeline`, `sceneLabel`, `sceneToSVGString` ## @ggsvelte/core (./headless) ### experimental (41) `Advisory` (type), `AxisValueFormatter` (type), `CANVAS_AUTO_THRESHOLD`, `GeometryBatch` (type), `GlyphsBatch` (type), `LayerBackend` (type), `MappedField` (type), `NamedData` (type), `PathsBatch` (type), `PipelineError`, `PipelineWarning` (type), `PointsBatch` (type), `RectsBatch` (type), `RenderModel` (type), `RenderSVGOptions` (type), `ResolvedColorScale` (type), `RunOptions` (type), `ScaleDecision` (type), `ScaleDiagnostic` (type), `ScaleDiagnosticFix` (type), `ScaleDomainSnapshot` (type), `Scene` (type), `SceneAxis` (type), `SceneDiscreteLegend` (type), `SceneLegend` (type), `SceneLegendEntry` (type), `ScenePanel` (type), `SceneRampLegend` (type), `SceneStepsLegend` (type), `SceneTick` (type), `SegmentsBatch` (type), `Stratum` (type), `TrainedScales` (type), `batchMarkCount`, `countMarks`, `pathData`, `planStrata`, `renderToSVGString`, `runPipeline`, `sceneLabel`, `sceneToSVGString` ## @ggsvelte/core (./headless/register) ### experimental (9) `registerBasicAreas`, `registerBasicBars`, `registerBasicGlyphs`, `registerBasicLines`, `registerBasicPoints`, `registerBasicRects`, `registerBasicSegments`, `registerGeomBatch`, `registerStatFrame` ## @ggsvelte/core (./temporal) ### experimental (11) `AxisGuidePlan` (type), `TemporalAxisPlanInput` (type), `TemporalLabelFormatOptions` (type), `TemporalTickLabel` (type), `compileTemporalLabelFormat`, `formatTemporalTickSequence`, `formatTime`, `getTemporalRuntime`, `installTemporal`, `installTemporalRuntime`, `planTemporalAxis` ## @ggsvelte/core (./dom) ### experimental (9) `CanvasFocusPresentation` (type), `ColorResolver` (type), `PrimitiveFocusMask` (type), `StaticQuadtree`, `cssColorResolver`, `drawBatch`, `drawClippedToPanel`, `drawStratum`, `sizeCanvasForDpr` ## @ggsvelte/svelte ### experimental (526) `A11yMode` (type), `Advisory` (type), `Aes` (type), `AesInput` (type), `AlphaScaleSpec` (type), `AreaMode` (type), `AuthoringCellValue` (type), `AuthoringColumns` (type), `AuthoringDataRef` (type), `AuthoringRows` (type), `AxisGuideOptions` (type), `AxisGuidePlan` (type), `AxisGuideSpec` (type), `AxisGuideTick` (type), `BinnedColorScaleOptions` (type), `BinnedFiniteStyleScaleOptions` (type), `BoxplotParams` (type), `BrushSelection` (type), `COMPOSITION_DIAGNOSTIC_CATALOG`, `ChannelValue` (type), `ColorScaleOptions` (type), `ColorScaleSpec` (type), `ColorbarGuideOptions` (type), `ColorbarGuidePlan` (type), `ColorbarGuideSpec` (type), `ColorbarGuideTick` (type), `ColorstepsGuideOptions` (type), `ColorstepsGuidePlan` (type), `ColorstepsGuideSpec` (type), `ColorstepsGuideStep` (type), `CompositionDiagnostic` (type), `CompositionDiagnosticCode` (type), `ContinuousPositionScaleOptions` (type), `ControllerDatumIdentity` (type), `CoordFixedOptions` (type), `CoordFixedSpec` (type), `CoordPolar`, `CoordPolarOptions` (type), `CoordRadial`, `CoordRadialOptions` (type), `CoordRadialReverse` (type), `CoordRadialSpec` (type), `CoordRadialTheta` (type), `CoordSf`, `CoordSfOptions` (type), `CoordSfSpec` (type), `CoordSpec` (type), `CoordTransformAxisOptions` (type), `CoordTransformAxisSpec` (type), `CoordTransformName` (type), `CoordTransformOptions` (type), `CoordTransformSpec` (type), `CreatePlotInteractionOptions` (type), `DEPRECATION_DIAGNOSTIC_CATALOG`, `DataInput` (type), `DataProfile` (type), `DataRef` (type), `DensityParams` (type), `DeprecationDiagnostic` (type), `DeprecationDiagnosticCode` (type), `DiscreteColorScaleOptions` (type), `DiscreteFiniteStyleScaleOptions` (type), `DiscreteGuideEntry` (type), `DiscreteGuidePlan` (type), `DiscreteNumericStyleScaleOptions` (type), `DiscretePositionScaleOptions` (type), `DuplicateMergeKeyDiagnostic` (type), `DuplicateMergeKeyKind` (type), `DuplicatePlotLayerDiagnostic` (type), `DuplicatePlotLayerKind` (type), `DuplicateScaleChannelDiagnostic` (type), `ErrorbarParams` (type), `FacetInput` (type), `FacetIntervalPreset` (type), `FacetScales` (type), `FacetSpec` (type), `FiniteStyleScaleOptions` (type), `GGBuilder`, `GeomProps` (type), `GuidePlan` (type), `GuideSpec` (type), `GuideThemeSpec` (type), `GuidesSpec` (type), `INTERACTION_DIAGNOSTIC_CATALOG`, `IdentityColorScaleOptions` (type), `IdentityFiniteStyleScaleOptions` (type), `IdentityNumericStyleScaleOptions` (type), `Inspect`, `InspectInput` (type), `InspectMode` (type), `InspectOptions` (type), `InteractionDiagnostic` (type), `InteractionDiagnosticCode` (type), `InteractionSource` (type), `InteractionTool` (type), `IntervalSelection` (type), `LINETYPE_NAMES`, `LabsSpec` (type), `Layer` (type), `LayerBackend` (type), `LayerInput` (type), `LayerRegistry` (type), `LayerSpec` (type), `LegendFilterClause` (type), `LegendFilterEvent` (type), `LegendFilterInput` (type), `LegendFilterOptions` (type), `LegendFocusChange` (type), `LegendFocusClear` (type), `LegendFocusEvent` (type), `LegendFocusInput` (type), `LegendFocusOptions` (type), `LegendGuideOptions` (type), `LegendGuideSpec` (type), `LegendSpec` (type), `Linetype` (type), `LinetypeName` (type), `LinetypeScaleSpec` (type), `MAX_TEMPORAL_CANDIDATES`, `MAX_TEMPORAL_MAJOR_TICKS`, `MAX_TEMPORAL_MINOR_TICKS`, `MIN_TEMPORAL_LABEL_GAP_PX`, `ManualColorScaleOptions` (type), `ManualFiniteStyleScaleOptions` (type), `ManualNumericStyleScaleOptions` (type), `MappedField` (type), `MarkLayerDescriptor` (type), `MultiIdentityScaleOptions` (type), `MultiManualScaleOptions` (type), `MultiScaleAesthetic` (type), `MultiScaleChannel` (type), `NonEmptyReadonlyArray` (type), `NoneGuideSpec` (type), `NumericStyleScaleOptions` (type), `POINT_SHAPE_NAMES`, `PipelineError`, `PipelineWarning` (type), `PlotDatum` (type), `PlotDiagnostic` (type), `PlotInspection` (type), `PlotInspectionChange` (type), `PlotInspectionClear` (type), `PlotInteractionChange` (type), `PlotInteractionController` (type), `PlotInteractionEvent` (type), `PlotInteractionInterval` (type), `PlotInteractionMutationOptions` (type), `PlotInteractionScope` (type), `PlotInteractionSnapshot` (type), `PlotInteractionTransition` (type), `PlotInteractionZoomOptions` (type), `PlotSelection` (type), `PointPosition` (type), `PointSelection` (type), `PointShape` (type), `PointShapeName` (type), `PositionParams` (type), `PositionScaleSpec` (type), `PositiveStyleScaleSpec` (type), `ReadonlyIntervalDomains` (type), `ReadonlyZoomDomains` (type), `RecommendedScaleType` (type), `RenderBackend` (type), `RenderModel` (type), `RenderSVGOptions` (type), `ResolvedColorScale` (type), `ResolvedInspectMode` (type), `ResolvedInteractionConfig` (type), `ResolvedStyleScale` (type), `RunOptions` (type), `RuntimeSpec` (type), `SCALE_CAPABILITIES`, `STYLE_AESTHETIC_GEOMS`, `ScaleCapability` (type), `ScaleDecision` (type), `ScaleDiagnostic` (type), `ScaleDiagnosticFix` (type), `ScaleExpansion` (type), `ScaleState` (type), `ScaleTypeAesthetic` (type), `Scales` (type), `Scene` (type), `ScopedInteractionDomain` (type), `ScopedInteractionInterval` (type), `ScopedInteractionKeys` (type), `SelectInput` (type), `SelectOptions` (type), `SemanticIntervalAxis` (type), `SequentialColorScaleOptions` (type), `SequentialStyleScaleOptions` (type), `ShapeScaleSpec` (type), `SmoothParams` (type), `SpecAdvisory` (type), `SpecError` (type), `SpecInput` (type), `StackablePosition` (type), `Stratum` (type), `StyleAesthetic` (type), `TEMPORAL_INTERVAL_UNITS`, `TEMPORAL_LABEL_TOKENS`, `TEMPORAL_WEEKDAYS`, `THEME_NAMES`, `TemporalColorScaleOptions` (type), `TemporalDecision` (type), `TemporalDisambiguation` (type), `TemporalInterval` (type), `TemporalIntervalSpec` (type), `TemporalIntervalUnit` (type), `TemporalKind` (type), `TemporalNumericStyleScaleOptions` (type), `TemporalParserName` (type), `TemporalParserSpec` (type), `TemporalPrecision` (type), `TemporalScaleOptions` (type), `TemporalWeekStart` (type), `ThemeName` (type), `ThemeSpec` (type), `ThemeTokens` (type), `Tooltip`, `TooltipContext` (type), `TooltipField` (type), `TransformedColorScaleOptions` (type), `TransformedPositionScaleOptions` (type), `ValidateResult` (type), `ViridisOptionName` (type), `ViridisScaleOptions` (type), `ZoomDomains` (type), `ZoomEvent` (type), `ZoomInput` (type), `ZoomOptions` (type), `aes`, `coordEqual`, `coordFixed`, `coordPolar`, `coordRadial`, `coordSf`, `coordTransform`, `coord_equal`, `coord_fixed`, `coord_polar`, `coord_radial`, `coord_sf`, `coord_transform`, `createGeomLayer`, `createPlotInteraction`, `deprecatedPropDiagnostic`, `dmy`, `dmy_hm`, `dmy_hms`, `duplicateMergeKeyDiagnostic`, `duplicatePlotLayerDiagnostic`, `duplicateScaleChannelDiagnostic`, `dym`, `dym_hm`, `dym_hms`, `fromEpochMilliseconds`, `fromEpochSeconds`, `gg`, `guideAxis`, `guideColorbar`, `guideColorsteps`, `guideLegend`, `guideNone`, `guide_axis`, `guide_colorbar`, `guide_colorsteps`, `guide_legend`, `guide_none`, `guides`, `isCompositionDiagnostic`, `isDuplicateMergeKeyDiagnostic`, `isDuplicatePlotLayerDiagnostic`, `isDuplicateScaleChannelDiagnostic`, `isPortable`, `lintSpec`, `mdy`, `mdy_hm`, `mdy_hms`, `my`, `myd`, `myd_hm`, `myd_hms`, `normalizeInteractionConfig`, `parseTemporalFormat`, `parseTemporalInterval`, `planStrata`, `registerAbline`, `registerAlign`, `registerAll`, `registerBasic`, `registerBin`, `registerBin2d`, `registerBoxplot`, `registerConnect`, `registerContour`, `registerCrossbar`, `registerCurve`, `registerDensity`, `registerDensity2d`, `registerDensity2dFilled`, `registerDotplot`, `registerEcdf`, `registerEllipse`, `registerErrorbar`, `registerFunction`, `registerHex`, `registerLayer`, `registerLinerange`, `registerManual`, `registerMap`, `registerPlotLayer`, `registerPointrange`, `registerPolygon`, `registerQq`, `registerQqLine`, `registerQuantile`, `registerRaster`, `registerRug`, `registerSf`, `registerSfLabel`, `registerSfText`, `registerSmooth`, `registerSpoke`, `registerSummary`, `registerSummaryBin`, `registerSummaryRolling`, `registerTile`, `registerUnique`, `registerViolin`, `runPipeline`, `scaleAlpha`, `scaleAlphaBinned`, `scaleAlphaContinuous`, `scaleAlphaDate`, `scaleAlphaDatetime`, `scaleAlphaDiscrete`, `scaleAlphaIdentity`, `scaleAlphaManual`, `scaleColorBinned`, `scaleColorContinuous`, `scaleColorDate`, `scaleColorDatetime`, `scaleColorDiscrete`, `scaleColorIdentity`, `scaleColorLog10`, `scaleColorManual`, `scaleColorSqrt`, `scaleColorViridisB`, `scaleColorViridisC`, `scaleColorViridisD`, `scaleColourBinned`, `scaleColourContinuous`, `scaleColourDate`, `scaleColourDatetime`, `scaleColourDiscrete`, `scaleColourIdentity`, `scaleColourLog10`, `scaleColourManual`, `scaleColourSqrt`, `scaleColourViridisB`, `scaleColourViridisC`, `scaleColourViridisD`, `scaleContinuousIdentity`, `scaleDiscreteIdentity`, `scaleDiscreteManual`, `scaleFillBinned`, `scaleFillContinuous`, `scaleFillDate`, `scaleFillDatetime`, `scaleFillDiscrete`, `scaleFillIdentity`, `scaleFillLog10`, `scaleFillManual`, `scaleFillSqrt`, `scaleFillViridisB`, `scaleFillViridisC`, `scaleFillViridisD`, `scaleLinetype`, `scaleLinetypeBinned`, `scaleLinetypeDiscrete`, `scaleLinetypeIdentity`, `scaleLinetypeManual`, `scaleLinewidth`, `scaleLinewidthBinned`, `scaleLinewidthContinuous`, `scaleLinewidthDate`, `scaleLinewidthDatetime`, `scaleLinewidthDiscrete`, `scaleLinewidthIdentity`, `scaleLinewidthManual`, `scaleShape`, `scaleShapeBinned`, `scaleShapeDiscrete`, `scaleShapeIdentity`, `scaleShapeManual`, `scaleSize`, `scaleSizeBinned`, `scaleSizeContinuous`, `scaleSizeDate`, `scaleSizeDatetime`, `scaleSizeDiscrete`, `scaleSizeIdentity`, `scaleSizeManual`, `scaleType`, `scaleXBinned`, `scaleXContinuous`, `scaleXDate`, `scaleXDatetime`, `scaleXDiscrete`, `scaleXLog10`, `scaleXMonthDay`, `scaleXReverse`, `scaleXSqrt`, `scaleXTime`, `scaleYBinned`, `scaleYContinuous`, `scaleYDate`, `scaleYDatetime`, `scaleYDiscrete`, `scaleYLog10`, `scaleYMonthDay`, `scaleYReverse`, `scaleYSqrt`, `scaleYTime`, `scale_alpha_binned`, `scale_alpha_continuous`, `scale_alpha_date`, `scale_alpha_datetime`, `scale_alpha_discrete`, `scale_alpha_identity`, `scale_alpha_manual`, `scale_color_binned`, `scale_color_continuous`, `scale_color_date`, `scale_color_datetime`, `scale_color_discrete`, `scale_color_identity`, `scale_color_log10`, `scale_color_manual`, `scale_color_sqrt`, `scale_color_viridis_b`, `scale_color_viridis_c`, `scale_color_viridis_d`, `scale_colour_binned`, `scale_colour_continuous`, `scale_colour_date`, `scale_colour_datetime`, `scale_colour_discrete`, `scale_colour_identity`, `scale_colour_log10`, `scale_colour_manual`, `scale_colour_sqrt`, `scale_colour_viridis_b`, `scale_colour_viridis_c`, `scale_colour_viridis_d`, `scale_continuous_identity`, `scale_discrete_identity`, `scale_discrete_manual`, `scale_fill_binned`, `scale_fill_continuous`, `scale_fill_date`, `scale_fill_datetime`, `scale_fill_discrete`, `scale_fill_identity`, `scale_fill_log10`, `scale_fill_manual`, `scale_fill_sqrt`, `scale_fill_viridis_b`, `scale_fill_viridis_c`, `scale_fill_viridis_d`, `scale_linetype`, `scale_linetype_binned`, `scale_linetype_discrete`, `scale_linetype_identity`, `scale_linetype_manual`, `scale_linewidth_binned`, `scale_linewidth_continuous`, `scale_linewidth_date`, `scale_linewidth_datetime`, `scale_linewidth_discrete`, `scale_linewidth_identity`, `scale_linewidth_manual`, `scale_shape`, `scale_shape_binned`, `scale_shape_discrete`, `scale_shape_identity`, `scale_shape_manual`, `scale_size_binned`, `scale_size_continuous`, `scale_size_date`, `scale_size_datetime`, `scale_size_discrete`, `scale_size_identity`, `scale_size_manual`, `scale_type`, `scale_x_binned`, `scale_x_continuous`, `scale_x_date`, `scale_x_datetime`, `scale_x_discrete`, `scale_x_log10`, `scale_x_month_day`, `scale_x_reverse`, `scale_x_sqrt`, `scale_x_time`, `scale_y_binned`, `scale_y_continuous`, `scale_y_date`, `scale_y_datetime`, `scale_y_discrete`, `scale_y_log10`, `scale_y_month_day`, `scale_y_reverse`, `scale_y_sqrt`, `scale_y_time`, `temporalIntervalTicks`, `toPortable`, `toPortableLossy`, `ydm`, `ydm_hm`, `ydm_hms`, `ym`, `ymd`, `ymd_hm`, `ymd_hms`, `yq` ### stable-intent (238) `Coord`, `CoordCartesian`, `CoordEqual`, `CoordFixed`, `CoordFlip`, `CoordTransform`, `Facet`, `FacetGrid`, `FacetWrap`, `GGPlot`, `GeomAbline`, `GeomArea`, `GeomBar`, `GeomBin2d`, `GeomBlank`, `GeomBoxplot`, `GeomCol`, `GeomContour`, `GeomCount`, `GeomCrossbar`, `GeomCurve`, `GeomDensity`, `GeomDensity2d`, `GeomDensity2dFilled`, `GeomDotplot`, `GeomErrorbar`, `GeomFreqpoly`, `GeomFunction`, `GeomHex`, `GeomHistogram`, `GeomHline`, `GeomJitter`, `GeomLabel`, `GeomLine`, `GeomLinerange`, `GeomMap`, `GeomPath`, `GeomPoint`, `GeomPointrange`, `GeomPolygon`, `GeomQq`, `GeomQqLine`, `GeomQuantile`, `GeomRaster`, `GeomRect`, `GeomRibbon`, `GeomRug`, `GeomRule`, `GeomSegment`, `GeomSf`, `GeomSfLabel`, `GeomSfText`, `GeomSmooth`, `GeomSpoke`, `GeomStep`, `GeomText`, `GeomTile`, `GeomViolin`, `GeomVline`, `GuideAxis`, `GuideChannel` (type), `GuideColorbar`, `GuideColorsteps`, `GuideLegend`, `GuideNone`, `Guides`, `Labs`, `Legend`, `NonPositionGuideChannel` (type), `PortableSpec` (type), `PositionGuideChannel` (type), `Scale`, `ScaleAlphaBinned`, `ScaleAlphaContinuous`, `ScaleAlphaDate`, `ScaleAlphaDatetime`, `ScaleAlphaDiscrete`, `ScaleAlphaIdentity`, `ScaleAlphaManual`, `ScaleAlphaOrdinal`, `ScaleColorBinned`, `ScaleColorBrewer`, `ScaleColorContinuous`, `ScaleColorDate`, `ScaleColorDatetime`, `ScaleColorDiscrete`, `ScaleColorDistiller`, `ScaleColorFermenter`, `ScaleColorGradient`, `ScaleColorGradient2`, `ScaleColorGradientn`, `ScaleColorGrey`, `ScaleColorHue`, `ScaleColorIdentity`, `ScaleColorLog10`, `ScaleColorManual`, `ScaleColorOrdinal`, `ScaleColorSqrt`, `ScaleColorSteps`, `ScaleColorSteps2`, `ScaleColorStepsn`, `ScaleColorViridisB`, `ScaleColorViridisC`, `ScaleColorViridisD`, `ScaleColourBinned`, `ScaleColourBrewer`, `ScaleColourContinuous`, `ScaleColourDate`, `ScaleColourDatetime`, `ScaleColourDiscrete`, `ScaleColourDistiller`, `ScaleColourFermenter`, `ScaleColourGradient`, `ScaleColourGradient2`, `ScaleColourGradientn`, `ScaleColourGrey`, `ScaleColourHue`, `ScaleColourIdentity`, `ScaleColourLog10`, `ScaleColourManual`, `ScaleColourOrdinal`, `ScaleColourSqrt`, `ScaleColourSteps`, `ScaleColourSteps2`, `ScaleColourStepsn`, `ScaleColourViridisB`, `ScaleColourViridisC`, `ScaleColourViridisD`, `ScaleFillBinned`, `ScaleFillBrewer`, `ScaleFillContinuous`, `ScaleFillDate`, `ScaleFillDatetime`, `ScaleFillDiscrete`, `ScaleFillDistiller`, `ScaleFillFermenter`, `ScaleFillGradient`, `ScaleFillGradient2`, `ScaleFillGradientn`, `ScaleFillGrey`, `ScaleFillHue`, `ScaleFillIdentity`, `ScaleFillLog10`, `ScaleFillManual`, `ScaleFillOrdinal`, `ScaleFillSqrt`, `ScaleFillSteps`, `ScaleFillSteps2`, `ScaleFillStepsn`, `ScaleFillViridisB`, `ScaleFillViridisC`, `ScaleFillViridisD`, `ScaleLinetypeBinned`, `ScaleLinetypeDiscrete`, `ScaleLinetypeIdentity`, `ScaleLinetypeManual`, `ScaleLinewidthBinned`, `ScaleLinewidthContinuous`, `ScaleLinewidthDate`, `ScaleLinewidthDatetime`, `ScaleLinewidthDiscrete`, `ScaleLinewidthIdentity`, `ScaleLinewidthManual`, `ScaleLinewidthOrdinal`, `ScaleRadius`, `ScaleShapeBinned`, `ScaleShapeDiscrete`, `ScaleShapeIdentity`, `ScaleShapeManual`, `ScaleShapeOrdinal`, `ScaleSizeArea`, `ScaleSizeBinned`, `ScaleSizeBinnedArea`, `ScaleSizeContinuous`, `ScaleSizeDate`, `ScaleSizeDatetime`, `ScaleSizeDiscrete`, `ScaleSizeIdentity`, `ScaleSizeManual`, `ScaleSizeOrdinal`, `ScaleXBinned`, `ScaleXContinuous`, `ScaleXDate`, `ScaleXDatetime`, `ScaleXDiscrete`, `ScaleXLog10`, `ScaleXMonthDay`, `ScaleXReverse`, `ScaleXSqrt`, `ScaleXTime`, `ScaleYBinned`, `ScaleYContinuous`, `ScaleYDate`, `ScaleYDatetime`, `ScaleYDiscrete`, `ScaleYLog10`, `ScaleYMonthDay`, `ScaleYReverse`, `ScaleYSqrt`, `ScaleYTime`, `SpecValidationError`, `Theme`, `ThemeBase`, `ThemeBw`, `ThemeClassic`, `ThemeClean`, `ThemeDark`, `ThemeDefault`, `ThemeEconomist`, `ThemeEconomistwhite`, `ThemeFew`, `ThemeFivethirtyeight`, `ThemeGgplot2`, `ThemeGray`, `ThemeGrey`, `ThemeHc`, `ThemeHcdark`, `ThemeHrbr`, `ThemeIgray`, `ThemeLight`, `ThemeLinedraw`, `ThemeMap`, `ThemeMinimal`, `ThemePander`, `ThemeSolarized`, `ThemeSolarized2`, `ThemeSolarized2dark`, `ThemeSolarizeddark`, `ThemeSolid`, `ThemeStata`, `ThemeStatas1color`, `ThemeTest`, `ThemeTufte`, `ThemeVoid`, `ThemeWsj`, `normalize`, `renderToSVGString`, `validate` ## @ggsvelte/cli ### experimental (2) `CLIIO` (type), `runCLI` --- # Upgrade guide One section per released 0.x transition, newest first. Each heading is a stable anchor that changesets and release notes link to. Pre-1.0, breaking changes ride minor releases; every deprecation or removal ships with a migration note here. ## Five-minute path - Check that linked `@ggsvelte/*` packages (`svelte`, `spec`, `core`, `cli`, `skill`) resolve to one compatible release. - Read only the adjacent transition sections needed for the installed version. - Apply the before/after source change backed by the migration fixtures. - Run strict type, build, render, and visual checks before deploying. - Follow a stable diagnostic anchor if blocked; roll package versions back together if needed. The accepted lifecycle and deprecation policy remains in [Lifecycle and editions](https://ggsvelte.sh/guide/lifecycle#lifecycle-tags); this page applies it rather than creating a second policy. ## 0.28 to 0.29 ### Removed Tableau 10, Summer, Winter, and stone schemes Six categorical `scheme` names (and matching public `*_PALETTE` constants) are gone: - `tableau10` - `tableau_summer`, `tableau_winter` - `tableau_miller_stone`, `tableau_superfishel_stone`, `tableau_nuriel_stone` Prefer `observable10`, `colorblind`, `Dark2`, `pander`, or another remaining Tableau scheme (`tableau20`, `tableau_colorblind`, `tableau_jewel_bright`, …), or pass an explicit `range`. ```json fragment // Before { "scales": { "color": { "type": "ordinal", "scheme": "tableau10" } } } ``` ```json fragment // After { "scales": { "color": { "type": "ordinal", "scheme": "observable10" } } } ``` ## 0.27 to 0.28 ### Removed spreadsheet, Highcharts, and extra Stata schemes and themes Nine categorical `scheme` names (and the matching public `*_PALETTE` constants from `@ggsvelte/core`) are gone: - `stata_s1color`, `stata_s1rcolor`, `stata_mono` - `hc`, `hc_dark` - `calc`, `excel`, `excel_fill`, `excel_new` A PortableSpec that still names one of those schemes fails validation. Switch to a remaining scheme — `stata`, `observable10`, `Dark2`, and `pander` are the usual replacements — or pass an explicit `range` of hex color stops. Four chart **themes** are also gone: `stata_mono`, `calc`, `excel`, and `excel_new` (and their Svelte shells `ThemeStatamono`, `ThemeCalc`, `ThemeExcel`, `ThemeExcelnew`). Prefer `stata`, `stata_s1color`, `bw`, `classic`, or `minimal`. ```json fragment // Before: scheme / theme names that no longer validate { "theme": "excel_new", "scales": { "color": { "type": "ordinal", "scheme": "excel_new" } } } ``` ```json fragment // After: pick remaining theme + scheme (or an explicit color range) { "theme": "minimal", "scales": { "color": { "type": "ordinal", "scheme": "observable10" } } } ``` ### Removed Accent, Paired, Grey, Google Docs, and Tableau multi-hue schemes Eight more categorical `scheme` names (and matching public `*_PALETTE` constants where they existed) are gone: - `Accent`, `Paired` - `grey`, `gray` - `gdocs` - `tableau_green_orange_teal`, `tableau_red_blue_brown`, `tableau_purple_pink_gray` Theme `gdocs` (and Svelte shell `ThemeGdocs`) is also gone. Prefer `minimal`, `classic`, or `bw`. `scaleColorGrey()` / `<ScaleColorGrey />` still work: they bake an explicit greyscale `range` (optional `start`/`end`). They no longer emit `scheme: "grey"`. ```json fragment // Before { "theme": "gdocs", "scales": { "color": { "type": "ordinal", "scheme": "Accent" } } } ``` ```json fragment // After { "theme": "minimal", "scales": { "color": { "type": "ordinal", "scheme": "Dark2" } } } ``` ## 0.26 to 0.27 ### Explicit registration for spec-driven charts Apps that declare layers with components (`<GeomPoint>`, `<GeomSmooth>`, …) need **no change**: each component now registers its own geom batch and default stat on import, and GGPlot bundles only the geoms and stats a chart declares. Identity charts (point, line, path, col, bar, area, rule, hline, vline, text, label, rect, ribbon, segment, count, blank, jitter, step) work out of the box as before. Apps that drive GGPlot with a `layers` prop or a `spec`, or call `runPipeline` / `renderToSVGString` directly, must register specialty geoms/stats (smooth, boxplot, violin, hex, contour, density_2d, sf, qq, …) explicitly. A missing registration fails loudly: `Geom "smooth" is not registered in this build. Call registerAll() …`. ```svelte fragment <script lang="ts"> import { GGPlot } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 10 }, { x: 2, y: 20 }, { x: 3, y: 15 }, { x: 4, y: 25 }, ]; </script> <!-- Before 0.27: any geom worked from the layers prop — the core barrel bundled and registered the full grammar for every app. --> <GGPlot data={rows} aes={{ x: "x", y: "y" }} layers={[{ geom: "smooth" }]} width={480} height={320} /> ``` ```svelte fragment <script lang="ts"> import { GGPlot, registerAll } from "@ggsvelte/svelte"; // After 0.27: spec-driven charts (layers prop / spec, no <Geom*> components // to self-register) opt into the full grammar once at app startup. Component // children need no call — they self-register on import. registerAll(); const rows = [ { x: 1, y: 10 }, { x: 2, y: 20 }, { x: 3, y: 15 }, { x: 4, y: 25 }, ]; </script> <GGPlot data={rows} aes={{ x: "x", y: "y" }} layers={[{ geom: "smooth" }]} width={480} height={320} /> ``` - Prefer one `registerAll()` at app startup for the pre-0.27 "full grammar" behavior — grammar + Temporal + interaction candidates (also re-exported from `@ggsvelte/svelte`). - Prefer a per-family call (`registerSmooth()`, `registerBoxplot()`, …, from `@ggsvelte/core`) for granular opt-in without the full grammar. - Overriding `stat` to a specialty stat (e.g. `<GeomPoint stat="density_2d" />`) needs that stat's register call too — the component registers only its default stat. - Direct `@ggsvelte/core` barrel importers: the barrel is side-effect-free now — `registerAll()` restores pre-0.27 import-time registration. The lean `@ggsvelte/core/render` entry is unchanged (basic registration on import). ### Skill moved to @ggsvelte/skill The agent skill (`SKILL.md` + `references/`) no longer ships inside `@ggsvelte/svelte`. It is its own package, `@ggsvelte/skill`, versioned in lock-step with the rest of ggsvelte — the package root IS the skill directory: ```sh fragment npm install --save-dev @ggsvelte/skill # then copy or symlink it into your agent's skills directory: cp -R node_modules/@ggsvelte/skill .claude/skills/ggsvelte ``` Or point agents at the stable path `node_modules/@ggsvelte/skill/SKILL.md` directly. Re-copy on every version bump; dependabot (or npm-check-updates) surfaces those bumps now. Any checkout that previously read `node_modules/@ggsvelte/svelte/skills/ggsvelte/` must switch — that directory is gone. ## 0.22 to 0.23 ### CLI moved to @ggsvelte/cli The `ggsvelte-render` bin no longer ships with `@ggsvelte/svelte`. It is its own package, `@ggsvelte/cli`, with no Svelte dependency — install it wherever the command runs (agent sandboxes above all): ```sh fragment npm install -g @ggsvelte/cli # or add @ggsvelte/cli as a dependency of the project that invokes it ``` The command name, flags, exit codes, and JSONL diagnostics are unchanged. `ggsvelte-codemod` still ships with `@ggsvelte/svelte`. If a sandbox image or CI step ran `ggsvelte-render` via the Svelte package's bin, add `@ggsvelte/cli` there before upgrading `@ggsvelte/svelte`. ## 0.20 to 0.21 ### Row identity on interaction Durable row identity no longer belongs on the grammar root. Ordinary charts omit identity entirely: the engine uses an `id` column when present, otherwise the **row index** (order-stable only — not reorder-safe across data refresh). Custom durable identity (non-`id` natural keys, composite accessors, pin rebind across reorder) lives on interaction surfaces: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, Inspect } from "@ggsvelte/svelte"; const rows = [ { country: "Japan", year: 812, temp: 6.1 }, { country: "Korea", year: 900, temp: 5.8 }, ]; </script> <!-- Before 0.21: plot-level key. --> <GGPlot data={rows} aes={{ x: "year", y: "temp" }} key="country"> <GeomPoint /> <Inspect /> </GGPlot> ``` ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, Inspect } from "@ggsvelte/svelte"; const rows = [ { country: "Japan", year: 812, temp: 6.1 }, { country: "Korea", year: 900, temp: 5.8 }, ]; </script> <!-- After 0.21: identity on Inspect (or select / createPlotInteraction). --> <GGPlot data={rows} aes={{ x: "year", y: "temp" }}> <GeomPoint /> <Inspect identity="country" /> </GGPlot> ``` - Prefer `<Inspect identity="…" />` or `inspect={{ identity: "…" }}` when inspect is enabled. - Prefer `select={{ type: "point", identity: "…" }}` (or interval) when selection owns the key without inspect. - Prefer `createPlotInteraction({ identity: "…" })` when linked plots share one controller and the same identity field. - Resolution order: Inspect → Select → controller → deprecated `key` → auto `id` → row index. - `<GGPlot key>` still dual-reads through 0.21.x and emits `DEPRECATED_PLOT_PROP`; it is removed in 0.22.0. ## 0.18 to 0.19 ### Legend focus on GuideLegend Discrete legend focus is no longer a plot-host capability prop. Opt in on the guide child that owns the aesthetic: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; const rows = [ { id: "a", x: 1, y: 2, series: "North" }, { id: "b", x: 2, y: 3, series: "South" }, ]; </script> <!-- Before 0.19: legendFocus was a plot-host prop. --> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "series" }} key="id" legendFocus > <GeomPoint /> </GGPlot> ``` ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, GuideLegend } from "@ggsvelte/svelte"; const rows = [ { id: "a", x: 1, y: 2, series: "North" }, { id: "b", x: 2, y: 3, series: "South" }, ]; </script> <!-- After 0.19: focus lives on GuideLegend for that aesthetic. --> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "series" }} key="id"> <GeomPoint /> <GuideLegend channel="color" focus /> </GGPlot> ``` - `focus` accepts `true` or `{ preview?: boolean }` (same shape as the old plot prop). It is host-only — not a PortableSpec / `guideLegend()` field. - Only channels with an active `<GuideLegend focus>` get interactive legend targets. Enable multiple aesthetics with multiple GuideLegend children. - A focus-only GuideLegend (no presentation options) does not force `type: "legend"`, so continuous colour scales keep their colorbar. - `<GGPlot legendFocus>` still works plot-wide through 0.19.x and emits `DEPRECATED_PLOT_PROP`; it is removed in 0.20.0. - Handlers stay plot-level: `onlegendfocus`, `oninteraction`, and `key`. ### Legend filter on GuideLegend Discrete legend filter is no longer a plot-host capability prop. Opt in on the guide child that owns the aesthetic: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; const rows = [ { id: "a", x: 1, y: 2, series: "North" }, { id: "b", x: 2, y: 3, series: "South" }, ]; </script> <!-- Before 0.19: legendFilter was a plot-host prop. --> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "series" }} key="id" legendFilter > <GeomPoint /> </GGPlot> ``` ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, GuideLegend } from "@ggsvelte/svelte"; const rows = [ { id: "a", x: 1, y: 2, series: "North" }, { id: "b", x: 2, y: 3, series: "South" }, ]; </script> <!-- After 0.19: filter lives on GuideLegend for that aesthetic. --> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "series" }} key="id"> <GeomPoint /> <GuideLegend channel="color" filter /> </GGPlot> ``` - `filter` accepts `true` or `{ mode?, multiple? }` (same shape as the old plot prop). It is host-only — not a PortableSpec / `guideLegend()` field. - Only channels with an active `<GuideLegend filter>` get filter checkboxes. Enable multiple aesthetics with multiple GuideLegend children. - A filter-only GuideLegend (no presentation options) does not force `type: "legend"`, so continuous colour scales keep their colorbar. - `<GGPlot legendFilter>` still works plot-wide through 0.19.x and emits `DEPRECATED_PLOT_PROP`; it is removed in 0.20.0. - Handlers stay plot-level: `onlegendfilter`, `oninteraction`, and `key`. - Focus and filter coexist on one GuideLegend: `<GuideLegend channel="color" focus filter />`. ## 0.11 to 0.12 ### Manual color domain/range diagnostic code Validation used to emit the code `scale-manual-domain-range` when a manual color/fill scale had mismatched domain and range lengths. It now emits `color-manual-domain-range` — the same string the pipeline already used — so agents and the error-reference page have one name for that fault. If your tooling matches `SpecError.code` or docs anchors by string, update: - code: `scale-manual-domain-range` → `color-manual-domain-range` - docs anchor: `#scale-manual-domain-range` → `#color-manual-domain-range` (pipeline entry is now `#color-manual-domain-range-pipeline` when both sources appear on the page) Pipeline-only and validation-only catalogs remain separate objects, but dual codes share one prose source in `@ggsvelte/spec`. `PIPELINE_ERROR_CATALOG` is also exported from `@ggsvelte/spec` (and still re-exported from `@ggsvelte/core`). ## 0.12 to 0.13 ### Grammar props removed from `<GGPlot>` The seven grammar props deprecated in 0.11.0 — `theme`, `scales`, `coord`, `facet`, `labs`, `guides`, and `legend` — are **removed** from `<GGPlot>` in 0.13.0. Compose them only as declaration-only children. The `ggsvelte-codemod` still rewrites old source that uses the prop form. `LayerDescriptor` is removed; use `MarkLayerDescriptor`. ### `normalize()` returns the post-normalize geom union `normalize()` rewrites five convenience geoms to a canonical name — `histogram` to `bar`, `freqpoly` to `line`, `jitter` to `point`, `hline` and `vline` to `rule`. Its declared return type used to name all 49 geoms anyway, so nothing could tell which 44 actually reach the pipeline. It now returns `NormalizedSpec`, whose layers are `NormalizedLayerSpec` — the same shape, minus the five names normalize has already removed. Alongside it, `@ggsvelte/spec` exports `ALIAS_GEOMS`, `GEOM_ALIASES`, `AliasGeomName` and `NormalizedGeomName`. Authored specs are unaffected: `PortableSpec` and the published JSON Schema still accept every one of the 49 names, and `geom: "histogram"` works exactly as before. One kind of caller changes. Code that reads geoms back off a normalized spec and expects all 49 now sees 44: ```ts fragment // Before — the "histogram" arm was reachable in the type, never at runtime. const spec: PortableSpec = normalize(input); for (const layer of spec.layers) { if (layer.geom === "histogram") { /* dead branch */ } } // After — narrow before normalize, or drop the branch. const spec = normalize(input); // NormalizedSpec for (const layer of spec.layers) { if (layer.geom === "bar") { /* what histogram became */ } } ``` Annotating the result as `PortableSpec` still compiles, so passing a normalized spec on to anything that takes one needs no change. ## 0.10 to 0.11 ### Compose the theme as a child layer The `theme` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose the theme as a declaration-only child — named shells for every built-in theme, or the generic `<Theme>` escape hatch for dynamic names and role overrides. Before: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; // Historical pre-0.13 GGPlot grammar prop (removed). Cast for typecheck. /* oxlint-disable-next-line typescript/no-explicit-any -- intentional pre-removal fixture */ const Plot = GGPlot as any; const rows = [ { x: 1, y: 2 }, { x: 2, y: 4 }, ]; </script> <!-- Before 0.11: theme was a top-level GGPlot prop. --> <Plot data={rows} aes={{ x: "x", y: "y" }} theme="dark"> <GeomPoint /> </Plot> ``` After: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, ThemeDark } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 2 }, { x: 2, y: 4 }, ]; </script> <!-- After 0.11: compose the theme as a declaration-only child layer. --> <GGPlot data={rows} aes={{ x: "x", y: "y" }}> <GeomPoint /> <ThemeDark /> </GGPlot> ``` `LayerDescriptor` was renamed to `MarkLayerDescriptor` in 0.11.0 and the alias was removed in 0.13.0. ### Compose scales as child layers The `scales` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose scales as declaration-only children — named shells for every color/fill helper (`<ScaleColorDiscrete/>`, `<ScaleFillManual/>`, British `Colour` aliases, …), or the generic `<Scale value={…}>` escape hatch for raw fragments and computed scales. Two children on one channel emit a `DUPLICATE_SCALE_CHANNEL` advisory (last child still wins). Named shells route through the matching helpers, so migrating a raw fragment like `scales={{color:{scheme:"colorblind"}}}` to `<ScaleColorDiscrete scheme="colorblind"/>` adds `type:"ordinal"` to the PortableSpec (rendering is unchanged). Use `<Scale value={…}>` when you need byte-identical PortableSpec. Before: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, scaleColorDiscrete, } from "@ggsvelte/svelte"; // Historical pre-0.13 GGPlot grammar prop (removed). Cast for typecheck. /* oxlint-disable-next-line typescript/no-explicit-any -- intentional pre-removal fixture */ const Plot = GGPlot as any; const rows = [ { x: 1, y: 2, c: "a" }, { x: 2, y: 4, c: "b" }, ]; </script> <!-- Before 0.11: scales was a top-level GGPlot prop. --> <Plot data={rows} aes={{ x: "x", y: "y", color: "c" }} scales={scaleColorDiscrete({ scheme: "colorblind" })} > <GeomPoint /> </Plot> ``` After: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, ScaleColorDiscrete, } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 2, c: "a" }, { x: 2, y: 4, c: "b" }, ]; </script> <!-- After 0.11: compose scales as declaration-only child layers. --> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "c" }}> <GeomPoint /> <ScaleColorDiscrete scheme="colorblind" /> </GGPlot> ``` `PlotDiagnostic` also widens to include `CompositionDiagnostic` (`DUPLICATE_SCALE_CHANNEL`, `DUPLICATE_PLOT_LAYER`). Exhaustive `switch` on `.code` needs new arms; handlers annotated `PlotDiagnostic` keep compiling. ### Compose coord as a child layer The `coord` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose the coordinate system as a declaration-only child — `<CoordFlip/>`, `<CoordFixed/>` / `<CoordEqual/>`, `<CoordTransform/>`, `<CoordCartesian/>`, or the generic `<Coord value={…}>` escape hatch. Two coord children emit a `DUPLICATE_PLOT_LAYER` advisory (last child still wins). ### Compose facet as a child layer The `facet` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose facets as declaration-only children — `<FacetWrap field="g"/>`, `<FacetGrid rows="a" cols="b"/>`, or the complete `<Facet wrap={…} />` surface. Keep `strip` nested (`strip={{position,show}}`). Two facet children emit a `DUPLICATE_PLOT_LAYER` advisory (last child still wins). Bare `<Facet/>` with no wrap/rows/cols fails validation (`facet-form-missing`). ### Compose labs as a child layer The `labs` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose labels as a declaration-only child — `<Labs title="Sales" subtitle="FY25" x="Quarter" color="Region"/>`. There is no `<Labs value={…}>` escape hatch because Labs is a flat bag of strings: `<Labs {...computed} />` already covers the computed case. labs is a MERGE family: two `<Labs/>` children setting different keys both survive. Two children setting the SAME key emit a `DUPLICATE_MERGE_KEY` advisory and the later one wins. ### Compose guides as child layers The `guides` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Guides are keyed by aesthetic, so the child form is one shell per guide TYPE taking a `channel` prop — the aesthetic is a key, never part of the component name: `<GuideAxis channel="x" showTicks={false}/>`, `<GuideLegend channel="color" position="bottom"/>`, `<GuideColorbar channel="fill"/>`, `<GuideColorsteps channel="color"/>`, `<GuideNone channel="size"/>`, plus `<Guides value={…}>` for raw or computed guide bags. guides is a MERGE family keyed by channel, but the value AT a channel is replaced whole. Two guide children on one channel emit a `DUPLICATE_MERGE_KEY` advisory (last child still wins). A top-level guide child still wins over a scale-local `guide` on the same channel. The shells carry no scale knowledge and do not guess: `<GuideColorbar/>` over a discrete color scale fails loudly rather than silently degrading to a legend. ### Compose legend as a child layer The `legend` prop on `<GGPlot>` was deprecated in 0.11.0 and **removed in 0.13.0**. Compose it as `<Legend order="sorted"/>`. `<Legend order>` is the plot-wide entry-SORT enum (`"stable-domain"` | `"present-first-seen"` | `"sorted"`); ordering never changes color assignments. It is NOT `<GuideLegend order={2}/>`, which is a per-aesthetic INTEGER placement rank. Same word, unrelated concepts — the two compose independently on one plot. ### Migrate the grammar props with the codemod All seven grammar props above move mechanically, so `@ggsvelte/svelte` ships a codemod. It is opt-in and prints a diff by default — it writes nothing until you pass `--write`: ```bash complete # see what would change npx ggsvelte-codemod src # apply it npx ggsvelte-codemod --write src ``` It rewrites `facet`, `coord`, `scales`, `guides`, `legend`, `theme` and `labs` into their child layers and adds the components to the `@ggsvelte/svelte` import that already provided `GGPlot`. Migrated children are inserted BEFORE any child the file already had, because props apply before children — so a hand-written `<ScaleColorDiscrete/>` keeps winning over a migrated `scales` prop exactly as it did before. The rewrite is meaning-preserving, never a style rewrite. It targets the generic escape hatches (`<Coord value={…}/>`, `<Scale value={…}/>`, `<Guides value={…}/>`) rather than the named shells this guide recommends by hand: for scales the named form is not byte-identical (`normalize()` does not infer a scale `type`), so choosing it is a judgment call the tool does not make for you. Flat prop bags become named props — `labs={{ title: "Sales" }}` → `<Labs title="Sales"/>` — falling back to `<Labs {...expr}/>` for anything it cannot expand losslessly. One shape is deliberately left alone: `theme={expr}` where `expr` is not a string literal. `theme` accepts `ThemeName | ThemeSpec` and `<Theme>` has no `value` hatch, so routing a dynamic value needs a human. Those sites are printed as `manual change required` with a link back to this guide — the tool reports them rather than half-migrating them. Two more things worth knowing: the codemod only touches files that import `GGPlot` from `@ggsvelte/svelte` (a `GGPlot` of your own is never rewritten), and it edits only the ranges it changes, so run your formatter afterwards if you keep multi-line open tags. ### Diagnostic handlers receive `PlotDiagnostic` `ondiagnostic` now receives the `PlotDiagnostic` union (`InteractionDiagnostic | DeprecationDiagnostic`). Explicitly annotated handlers that named `InteractionDiagnostic` alone need a one-line type widening; inline arrow props continue to infer. Before: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; import type { InteractionDiagnostic, PlotDiagnostic, } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 2 }, { x: 2, y: 4 }, ]; function legacy(diagnostic: InteractionDiagnostic): void { console.warn(diagnostic.code, diagnostic.message); } // @ts-expect-error Pre-0.11 InteractionDiagnostic-only handlers are not assignable to PlotDiagnostic. const ondiagnostic: (diagnostic: PlotDiagnostic) => void = legacy; </script> <!-- Before 0.11: ondiagnostic was typed as InteractionDiagnostic only. --> <GGPlot data={rows} aes={{ x: "x", y: "y" }} {ondiagnostic}> <GeomPoint /> </GGPlot> ``` After: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; import type { PlotDiagnostic } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 2 }, { x: 2, y: 4 }, ]; function ondiagnostic(diagnostic: PlotDiagnostic): void { console.warn(diagnostic.code, diagnostic.message); } </script> <!-- After 0.11: ondiagnostic receives PlotDiagnostic (interaction ∪ deprecation). --> <GGPlot data={rows} aes={{ x: "x", y: "y" }} {ondiagnostic}> <GeomPoint /> </GGPlot> ``` ## 0.7 to 0.8 ### Map style semantics instead of precomputing outputs Mapped `size`, `linewidth`, and `alpha` now train and render complete scales. `shape` and `linetype` now use closed finite symbol sets. Remove application-side radius, opacity, stroke-width, and dash lookup columns when they only existed to compensate for ignored style mappings. Map the semantic source field and select a scale family instead. Before 0.8, applications commonly precomputed a point radius and passed it through identity: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; // Historical pre-0.13 GGPlot grammar prop (removed). Cast for typecheck. /* oxlint-disable-next-line typescript/no-explicit-any -- intentional pre-removal fixture */ const Plot = GGPlot as any; // Before 0.8, applications precomputed symbol radii. const rows = [ { x: 1, y: 2, radius: 2 }, { x: 2, y: 3, radius: 5 }, { x: 3, y: 4, radius: 9 }, ]; </script> <Plot data={rows} aes={{ x: "x", y: "y", size: "radius" }} scales={{ size: { type: "identity" } }} > <GeomPoint /> </Plot> ``` In 0.8, keep the source measure and let the scale interpolate in symbol area: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, ScaleSizeContinuous, } from "@ggsvelte/svelte"; // In 0.8, map the semantic measure and let size interpolate in symbol area. const rows = [ { x: 1, y: 2, magnitude: 4 }, { x: 2, y: 3, magnitude: 25 }, { x: 3, y: 4, magnitude: 81 }, ]; </script> <GGPlot data={rows} aes={{ x: "x", y: "y", size: "magnitude" }}> <GeomPoint /> <ScaleSizeContinuous range={[2, 9]} /> </GGPlot> ``` Review implicit grouping on line, area, smooth, errorbar, and boxplot layers. Discrete and binned style mappings now split groups, as color/fill mappings do; continuous numeric styles do not. If a discrete style is descriptive rather than structural, author an explicit `group` mapping. Shape/linetype do not silently interpolate quantitative values: use `type: "binned"` or move the measure to a numeric style channel. A mapped `alpha` is now the complete authored opacity aesthetic; it is not multiplied by a competing scalar geom `alpha` parameter. Remove that scalar parameter and set the mapped scale's `range` when you need a lower opacity ceiling. ### Move guide layout into the guide API Automatic non-position guides now move below the chart when the viewport is at most 480px or a right guide would leave less than 320px of readable panel. Bottom colorbars/colorsteps are horizontal and discrete keys wrap without shrinking text. If an application positioned or hid the old fixed right legend with surrounding CSS, remove that workaround and author portable guide intent: ```svelte fragment <script lang="ts"> import { GGPlot, GeomPoint } from "@ggsvelte/svelte"; // Before 0.8, automatic legends always occupied the fixed right column. const rows = [ { x: 1, y: 2, region: "North" }, { x: 2, y: 3, region: "South" }, ]; </script> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "region" }}> <GeomPoint /> </GGPlot> ``` In 0.8, declare the alternate presentation directly: ```svelte fragment <script lang="ts"> import { GGPlot, GeomPoint, GuideLegend } from "@ggsvelte/svelte"; // Since 0.8, guide presentation is portable and responsive without changing scale math. const rows = [ { x: 1, y: 2, region: "North" }, { x: 2, y: 3, region: "South" }, ]; </script> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "region" }}> <GeomPoint /> <GuideLegend channel="color" position="bottom" direction="horizontal" /> </GGPlot> ``` Top-level `guides` override scale-local `guide` settings. Use `guideNone()` for suppression and `force: true` only when an identity or single-value manual guide is intentional. Guide appearance does not alter scale domains or assignments. Exact discrete entries remain focus/filter targets; numeric guide ticks and bins remain representative and non-interactive. ## 0.8 to 0.9 ### Constrain the data rectangle instead of the outer box Before 0.9, CSS `aspect-ratio` on a chart wrapper constrained the complete SVG. Axes, titles, and guides still changed the inner panel ratio, so equal data units could render at unequal physical lengths: ```svelte fragment <script lang="ts"> import { GGPlot, GeomLine } from "@ggsvelte/svelte"; // Before 0.9, an outer CSS aspect ratio could not preserve data-unit lengths // after axes, titles, and guides consumed chart space. const circle = [ { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }, { x: 1, y: 0 }, ]; </script> <div style="aspect-ratio: 1"> <GGPlot data={circle} aes={{ x: "x", y: "y" }}> <GeomLine /> </GGPlot> </div> ``` Since 0.9, remove that wrapper workaround and author the coordinate directly: ```svelte fragment <script lang="ts"> import { CoordFixed, GGPlot, GeomLine } from "@ggsvelte/svelte"; // Since 0.9, constrain the measured data rectangle instead of the outer box. const circle = [ { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }, { x: 1, y: 0 }, ]; </script> <GGPlot data={circle} aes={{ x: "x", y: "y" }}> <GeomLine /> <CoordFixed /> </GGPlot> ``` `coordFixed({ ratio: 1 })` measures the trained data rectangle after chart chrome is allocated and letterboxes it without distortion. Fixed aspect now rejects `facet.scales` values `"free"`, `"free_x"`, and `"free_y"`; switch those facets to `"fixed"` or remove the fixed coordinate rather than presenting a false shared physical scale. ## 0.6 to 0.7 ### Choose explicit color/fill families Color/fill now exposes complete continuous, discrete, binned, transformed, temporal, manual, and identity helpers. Existing `ordinal` and `sequential` JSON remains canonical. Review charts that relied on implicit continuous clamping: with an explicit domain, the default `oob: "censor"` now uses `unknownValue`; opt into `oob: "squish"` to clamp deliberately. Use `type: "binned"` plus semantic `breaks` for colorsteps. Manual scales require one range color per explicit domain value and never recycle extras. Identity scales accept validated hex source values and suppress their guide by default. Replace ad-hoc preprocessing with `scaleColorDate`/ `scaleColorDatetime` and an explicit parser when date order is ambiguous. Before 0.7, an explicit continuous color domain clamped implicitly: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot } from "@ggsvelte/svelte"; // Historical pre-0.13 GGPlot grammar prop (removed). Cast for typecheck. /* oxlint-disable-next-line typescript/no-explicit-any -- intentional pre-removal fixture */ const Plot = GGPlot as any; const rows = [ { x: 1, y: 2, score: -10 }, { x: 2, y: 3, score: 50 }, { x: 3, y: 4, score: 110 }, ]; </script> <Plot data={rows} aes={{ x: "x", y: "y", color: "score" }} scales={{ color: { type: "sequential", domain: [0, 100] } }} > <GeomPoint /> </Plot> ``` In 0.7, opt into clamping when it is the intended encoding: ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, ScaleColorContinuous, } from "@ggsvelte/svelte"; const rows = [ { x: 1, y: 2, score: -10 }, { x: 2, y: 3, score: 50 }, { x: 3, y: 4, score: 110 }, ]; </script> <GGPlot data={rows} aes={{ x: "x", y: "y", color: "score" }}> <GeomPoint /> <ScaleColorContinuous domain={[0, 100]} oob="squish" /> </GGPlot> ``` `RenderModel.guidePlans` now includes serializable `discrete`, `colorbar`, and `colorsteps` plans beside axes. Code that assumed every plan was an axis must narrow on `plan.type === "axis"` before reading axis-only fields. ## 0.5 to 0.6 ### Move position transforms before statistics Position transforms now follow ggplot2 staging: parsing, source-limit OOB, and the scale transform happen before statistics and positions. The old late projection produced incorrect smooths, bins, densities, summaries, and boxplots. This is the pre-1.0 semantic-correctness exception in decision 0015; there is no legacy staging switch. Authored `type: "log"` still validates, but canonical specs now store `type: "linear", transform: "log10"`. Prefer the explicit transform or `scaleXLog10`/`scaleYLog10` helpers. A codemod would only rewrite spelling and cannot decide whether changed statistics are intended, so migration remains a manual chart review. Before 0.6, this fit used the old late log projection: ```svelte fragment <script lang="ts"> import { GeomPoint, GeomSmooth, GGPlot } from "@ggsvelte/svelte"; // Historical pre-0.13 GGPlot grammar prop (removed). Cast for typecheck. /* oxlint-disable-next-line typescript/no-explicit-any -- intentional pre-removal fixture */ const Plot = GGPlot as any; const rows = [ { latency: 1, throughput: 8 }, { latency: 10, throughput: 18 }, { latency: 100, throughput: 31 }, { latency: 1000, throughput: 47 }, ]; </script> <Plot data={rows} aes={{ x: "latency", y: "throughput" }} scales={{ x: { type: "log", domain: [1, 1000] } }} > <GeomPoint /> <GeomSmooth method="lm" /> </Plot> ``` In 0.6, make the pre-stat transform and limit policy explicit, then compare the fit with the intended analysis. The zero expansion below restores flush bounds; the new default for non-temporal continuous and binned scales is 5% multiplicative display expansion, including pinned domains. ```svelte fragment <script lang="ts"> import { GeomPoint, GeomSmooth, GGPlot, ScaleXLog10, } from "@ggsvelte/svelte"; const rows = [ { latency: 1, throughput: 8 }, { latency: 10, throughput: 18 }, { latency: 100, throughput: 31 }, { latency: 1000, throughput: 47 }, ]; </script> <GGPlot data={rows} aes={{ x: "latency", y: "throughput" }}> <GeomPoint /> <GeomSmooth method="lm" /> <ScaleXLog10 domain={[1, 1000]} oob="censor" expand={{ mult: 0, add: 0 }} nice={false} /> </GGPlot> ``` ### Review limits, zoom, and transformed units A pinned `domain` is now an unexpanded source limit. The default `oob: "censor"` removes out-of-limit values before stats; `oob: "squish"` clamps them before transform/stats. Brush zoom writes a semantic domain with `nice: false` and zero expansion, so it intentionally re-runs stats on the zoomed subset rather than acting like a post-stat coordinate crop. Use a wider scale domain or squish only when that is the intended analysis; a future coordinate-transform API owns visual-only zoom. Position offsets and stack totals are transformed-space units. Under log10 or sqrt, numeric `stat_bin` `binwidth`, `boundary`, and `center` are also transformed-space units: for example log10 `boundary: 0` means semantic 1, not `log10(0)`. ### Update scale and interaction inspection Continuous log10/sqrt scales no longer report trained `type: "log"`. `RenderModel.scales`, `AxisGuidePlan`, interval selections, and precise bounds use family-plus-transform contracts: ```text fragment scale type / guide scaleType / interval kind: "linear" transform: "identity" | "log10" | "sqrt" ``` Reject or migrate transient snapshots containing `kind: "log"`; pre-1.0 interaction snapshots do not have a compatibility branch. Keep semantic source-space domains and apply the named transform exactly once. ## 0.2 to 0.3 ### Replace custom hit indexes with CandidateStore The experimental `buildHitIndex` export and its `SceneHitIndex` types have been removed. Every render model already owns a lazy `CandidateStore` with the same exact geometry hit behavior, so custom browser hosts no longer build and retain a second spatial index. Before 0.3: ```ts fragment import { buildHitIndex } from "@ggsvelte/core/dom"; const hitIndex = buildHitIndex(model.scene); const hit = hitIndex.hitTest(plotX, plotY); ``` In 0.3, use the model-owned candidate identity directly. `hitTest()` follows paint order, honors panel clipping, and returns `CandidateFacts`. Rectangle queries remain available as `model.candidates.queryRect(...)` candidate ids. ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, Inspect, type RenderModel, } from "@ggsvelte/svelte"; const rows = [ { id: "a", x: 1, y: 3 }, { id: "b", x: 2, y: 4 }, ]; let model = $state<RenderModel | null>(null); let hitRow = $state<number | null>(null); function inspectPlotPixel(x: number, y: number): void { hitRow = model?.candidates.hitTest(x, y)?.rowIndex ?? null; } </script> <GGPlot data={rows} aes={{ x: "x", y: "y" }} key="id" onrender={(next) => (model = next)} > <GeomPoint /> <Inspect /> </GGPlot> <button type="button" onclick={() => inspectPlotPixel(100, 100)}> Resolve plot pixel </button> <p>{hitRow === null ? "No hit" : `Row ${hitRow}`}</p> ``` For a separately constructed scene, call `buildCandidateStore(scene, { hitTolerance })` from `@ggsvelte/core`. The old tolerance default remains 3 plot pixels. ## 0.1 to 0.2 No source changes are required: every 0.1 prop, callback, and export keeps working in 0.2. One environment requirement changed: the `svelte` peer dependency floor rose from `^5.29.0` to `^5.33.1`, so upgrade Svelte first. The additions below are optional to adopt. ### Optional: shared interaction state with a controller 0.2 adds `createPlotInteraction` for linked views: selection, emphasis, and zoom state shared across plots, controls, and tables. Chart-local props and callbacks remain fully supported — reach for a controller only when more than one surface consumes the same interaction state. Chart-local (unchanged from 0.1): ```svelte fragment <script lang="ts"> import { GeomPoint, GGPlot, type PlotSelection, } from "@ggsvelte/svelte"; const rows = [ { id: "a", flipper: 181, mass: 3750, species: "Adelie" }, { id: "b", flipper: 195, mass: 3800, species: "Chinstrap" }, { id: "c", flipper: 217, mass: 4500, species: "Gentoo" }, ]; let selection = $state<PlotSelection<string> | null>(null); </script> <GGPlot data={rows} aes={{ x: "flipper", y: "mass", color: "species" }} key="id" select={{ type: "point", multiple: true }} onselect={(event) => (selection = event)} > <GeomPoint /> </GGPlot> <p>{selection === null ? 0 : selection.keys.length} selected</p> ``` Shared controller (new in 0.2, optional): ```svelte fragment <script lang="ts"> import { createPlotInteraction, GeomPoint, GGPlot, } from "@ggsvelte/svelte"; const rows = [ { id: "a", flipper: 181, mass: 3750, species: "Adelie" }, { id: "b", flipper: 195, mass: 3800, species: "Chinstrap" }, { id: "c", flipper: 217, mass: 4500, species: "Gentoo" }, ]; const scope = { keys: "row-id", x: "flipper-mm", y: "mass-g" } as const; const interaction = createPlotInteraction<string>(); const selected = $derived(interaction.selected(scope)); </script> <GGPlot data={rows} aes={{ x: "flipper", y: "mass", color: "species" }} key="id" select={{ type: "point", multiple: true }} {interaction} interactionScope={scope} > <GeomPoint /> </GGPlot> <p>{selected.length} selected</p> ``` See the [linked views example](https://ggsvelte.sh/examples/interaction/linked-views) and [Interactions](https://ggsvelte.sh/guide/interactions) for the full controller contract. ### Deprecated type aliases Unchanged in 0.2: these pre-0.1 names have been deprecated since 0.1.0 and still compile. Replace them when convenient: - `BrushSelection` → `IntervalSelection` - `TooltipContext` → `PlotInspectionChange` - `ZoomDomains` → `ReadonlyZoomDomains` See [Interactions](https://ggsvelte.sh/guide/interactions) for current options, event shapes, and identity requirements. --- # Examples ## Survivors from a cohort of one thousand (area/basic) A single series drawn as a filled area under the curve. Use this when the area itself carries the quantity. Tags: area, line, layers, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "age": 1, "survivors": 1000 }, { "age": 2, "survivors": 855 }, { "age": 3, "survivors": 798 }, { "age": 4, "survivors": 760 }, { "age": 5, "survivors": 732 }, { "age": 6, "survivors": 710 }, { "age": 7, "survivors": 692 }, { "age": 8, "survivors": 680 }, { "age": 9, "survivors": 670 }, { "age": 10, "survivors": 661 }, { "age": 11, "survivors": 653 }, { "age": 12, "survivors": 646 }, { "age": 13, "survivors": 640 }, { "age": 14, "survivors": 634 }, { "age": 15, "survivors": 628 }, { "age": 16, "survivors": 622 }, { "age": 17, "survivors": 616 }, { "age": 18, "survivors": 610 }, { "age": 19, "survivors": 604 }, { "age": 20, "survivors": 598 } ] }, "layers": [ { "geom": "area", "stat": "identity", "position": "stack", "aes": { "x": { "field": "age" }, "y": { "field": "survivors" } }, "params": { "alpha": 0.7 } }, { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "age" }, "y": { "field": "survivors" } }, "params": { "linewidth": 1.5 } } ], "scales": { "x": { "breaks": [ 1, 10, 20, 30, 40, 50, 60, 70, 80 ], "nice": false }, "y": { "breaks": [ 0, 200, 400, 600, 800, 1000 ] } }, "labs": { "title": "Survivors from a cohort of one thousand", "subtitle": "Filled area under a life table from birth to old age", "x": "Age", "y": "Surviving" }, "theme": "classic" } // note: inline data truncated (64 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomArea, GeomLine, GGPlot, Inspect, Labs, ScaleXContinuous, ScaleYContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import { halleyLifeTable } from "./data.js"; </script> <GGPlot data={halleyLifeTable} aes={{ x: "age", y: "survivors" }} width={640} height={400} > <GeomArea alpha={0.7} /> <GeomLine linewidth={1.5} /> <ScaleXContinuous breaks={[1, 10, 20, 30, 40, 50, 60, 70, 80]} nice={false} /> <ScaleYContinuous breaks={[0, 200, 400, 600, 800, 1000]} /> <ThemeClassic /> <Labs title="Survivors from a cohort of one thousand" subtitle="Filled area under a life table from birth to old age" x="Age" y="Surviving" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Stacked deaths by cause over time (area/stacked) Stacked areas for parts of a whole that change over time. Tags: area, stack, fill, legend, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "month": "1854-04-01", "cause": "Disease", "deaths": 1.4 }, { "month": "1854-04-01", "cause": "Wounds", "deaths": 0 }, { "month": "1854-04-01", "cause": "Other", "deaths": 7 }, { "month": "1854-05-01", "cause": "Disease", "deaths": 6.2 }, { "month": "1854-05-01", "cause": "Wounds", "deaths": 0 }, { "month": "1854-05-01", "cause": "Other", "deaths": 4.6 }, { "month": "1854-06-01", "cause": "Disease", "deaths": 4.7 }, { "month": "1854-06-01", "cause": "Wounds", "deaths": 0 }, { "month": "1854-06-01", "cause": "Other", "deaths": 2.5 }, { "month": "1854-07-01", "cause": "Disease", "deaths": 150 }, { "month": "1854-07-01", "cause": "Wounds", "deaths": 0 }, { "month": "1854-07-01", "cause": "Other", "deaths": 9.6 }, { "month": "1854-08-01", "cause": "Disease", "deaths": 328.5 }, { "month": "1854-08-01", "cause": "Wounds", "deaths": 0.4 }, { "month": "1854-08-01", "cause": "Other", "deaths": 11.9 }, { "month": "1854-09-01", "cause": "Disease", "deaths": 312.2 }, { "month": "1854-09-01", "cause": "Wounds", "deaths": 32.1 }, { "month": "1854-09-01", "cause": "Other", "deaths": 27.7 }, { "month": "1854-10-01", "cause": "Disease", "deaths": 197 }, { "month": "1854-10-01", "cause": "Wounds", "deaths": 51.7 } ] }, "layers": [ { "geom": "area", "stat": "identity", "position": "stack", "aes": { "x": { "field": "month" }, "y": { "field": "deaths" }, "fill": { "field": "cause" } }, "params": { "alpha": 0.9 } } ], "scales": { "x": { "labels": "%b %Y" }, "fill": { "type": "ordinal", "domain": [ "Disease", "Wounds", "Other" ], "scheme": "stata" } }, "labs": { "title": "Stacked deaths by cause over time", "subtitle": "Monthly rates split so each band is one cause of death", "x": "Month", "y": "Deaths per 1,000 per year", "fill": "Cause" }, "theme": "economist" } // note: inline data truncated (52 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomArea, GGPlot, GuideLegend, Inspect, Labs, ScaleFillDiscrete, ScaleXDate, ThemeEconomist, } from "@ggsvelte/svelte"; import { crimeanMortality } from "./data.js"; </script> <GGPlot data={crimeanMortality} aes={{ x: "month", y: "deaths", fill: "cause" }} width={640} height={400} > <GeomArea alpha={0.9} /> <ScaleXDate labels="%b %Y" /> <ScaleFillDiscrete domain={["Disease", "Wounds", "Other"]} scheme="stata" /> <ThemeEconomist /> <GuideLegend channel="fill" focus /> <Labs title="Stacked deaths by cause over time" subtitle="Monthly rates split so each band is one cause of death" x="Month" y="Deaths per 1,000 per year" fill="Cause" /> <Inspect mode="x" pin /> </GGPlot> ``` ## US beer production by package type (bar/dodged) Dodged bars of national production by year and package. Tags: bar, count, dodge, fill, legend, weight, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": "2008", "package": "Bottles and cans", "barrelsMillions": 166.93 }, { "year": "2008", "package": "Kegs and barrels", "barrelsMillions": 17.29 }, { "year": "2008", "package": "On premises", "barrelsMillions": 0.47 }, { "year": "2009", "package": "Bottles and cans", "barrelsMillions": 165.43 }, { "year": "2009", "package": "Kegs and barrels", "barrelsMillions": 17.46 }, { "year": "2009", "package": "On premises", "barrelsMillions": 0.47 }, { "year": "2010", "package": "Bottles and cans", "barrelsMillions": 162.97 }, { "year": "2010", "package": "Kegs and barrels", "barrelsMillions": 17.73 }, { "year": "2010", "package": "On premises", "barrelsMillions": 0.51 }, { "year": "2011", "package": "Bottles and cans", "barrelsMillions": 159.71 }, { "year": "2011", "package": "Kegs and barrels", "barrelsMillions": 17.82 }, { "year": "2011", "package": "On premises", "barrelsMillions": 0.55 }, { "year": "2012", "package": "Bottles and cans", "barrelsMillions": 161.69 }, { "year": "2012", "package": "Kegs and barrels", "barrelsMillions": 18.16 }, { "year": "2012", "package": "On premises", "barrelsMillions": 0.55 }, { "year": "2013", "package": "Bottles and cans", "barrelsMillions": 159.41 }, { "year": "2013", "package": "Kegs and barrels", "barrelsMillions": 18.1 }, { "year": "2013", "package": "On premises", "barrelsMillions": 0.57 }, { "year": "2014", "package": "Bottles and cans", "barrelsMillions": 158.54 }, { "year": "2014", "package": "Kegs and barrels", "barrelsMillions": 18.18 } ] }, "layers": [ { "geom": "bar", "stat": "count", "position": "dodge", "aes": { "x": { "field": "year" }, "y": { "stat": "count" }, "fill": { "field": "package" }, "weight": { "field": "barrelsMillions" } } } ], "scales": { "fill": { "type": "ordinal", "scheme": "observable10" } }, "labs": { "title": "US beer production by package type", "subtitle": "Millions of barrels each year as bottles and cans, kegs, or on-premises pours", "x": "Year", "y": "Millions of barrels", "fill": "Package" }, "theme": "few" } // note: inline data truncated (16 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBar, GGPlot, GuideLegend, Inspect, Labs, ScaleFillDiscrete, ThemeFew, } from "@ggsvelte/svelte"; import { beerProduction } from "./data.js"; </script> <GGPlot data={beerProduction} aes={{ x: "year", fill: "package", weight: "barrelsMillions" }} width={640} height={400} > <GeomBar position="dodge" /> <ScaleFillDiscrete scheme="observable10" /> <ThemeFew /> <GuideLegend channel="fill" focus /> <Labs title="US beer production by package type" subtitle="Millions of barrels each year as bottles and cans, kegs, or on-premises pours" x="Year" y="Millions of barrels" fill="Package" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Category totals, flipped so labels read across (bar/horizontal) Horizontal bars when category labels are long and the ranking matters. Tags: bar, col, coord, flip, horizontal, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "squadron": "Galleys", "men": 362 }, { "squadron": "Pataches", "men": 1093 }, { "squadron": "Naples", "men": 1341 }, { "squadron": "Guipúzcoa", "men": 2608 }, { "squadron": "Biscay", "men": 2800 }, { "squadron": "Andalusia", "men": 3105 }, { "squadron": "Levant", "men": 3523 }, { "squadron": "Hulks", "men": 3729 }, { "squadron": "Castile", "men": 4171 }, { "squadron": "Portugal", "men": 4623 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "squadron" }, "y": { "field": "men" } } } ], "coord": { "type": "flip" }, "labs": { "title": "Category totals, flipped so labels read across", "subtitle": "Ordered smallest to largest so the flip reads bottom-up", "x": "Squadron", "y": "Men" }, "theme": "fivethirtyeight" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFlip, GeomCol, GGPlot, Inspect, Labs, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { armadaMen } from "./data.js"; </script> <GGPlot data={armadaMen} aes={{ x: "squadron", y: "men" }} width={640} height={400} > <GeomCol /> <CoordFlip /> <ThemeFivethirtyeight /> <Labs title="Category totals, flipped so labels read across" subtitle="Ordered smallest to largest so the flip reads bottom-up" x="Squadron" y="Men" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Parts of a whole within each group (bar/proportions) Bars stacked to a full height so each segment reads as a share of its group. Tags: bar, fill, proportion, legend, weight, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "squadron": "Portugal", "role": "Soldiers", "men": 3330 }, { "squadron": "Portugal", "role": "Sailors", "men": 1293 }, { "squadron": "Biscay", "role": "Soldiers", "men": 1937 }, { "squadron": "Biscay", "role": "Sailors", "men": 863 }, { "squadron": "Castile", "role": "Soldiers", "men": 2458 }, { "squadron": "Castile", "role": "Sailors", "men": 1719 }, { "squadron": "Andalusia", "role": "Soldiers", "men": 2325 }, { "squadron": "Andalusia", "role": "Sailors", "men": 780 }, { "squadron": "Guipúzcoa", "role": "Soldiers", "men": 1992 }, { "squadron": "Guipúzcoa", "role": "Sailors", "men": 616 }, { "squadron": "Levant", "role": "Soldiers", "men": 2780 }, { "squadron": "Levant", "role": "Sailors", "men": 767 }, { "squadron": "Hulks", "role": "Soldiers", "men": 3121 }, { "squadron": "Hulks", "role": "Sailors", "men": 608 }, { "squadron": "Pataches", "role": "Soldiers", "men": 479 }, { "squadron": "Pataches", "role": "Sailors", "men": 574 }, { "squadron": "Naples", "role": "Soldiers", "men": 873 }, { "squadron": "Naples", "role": "Sailors", "men": 468 }, { "squadron": "Galleys", "role": "Soldiers", "men": 0 }, { "squadron": "Galleys", "role": "Sailors", "men": 362 } ] }, "layers": [ { "geom": "bar", "stat": "count", "position": "fill", "aes": { "x": { "field": "squadron" }, "y": { "stat": "count" }, "fill": { "field": "role" }, "weight": { "field": "men" } } } ], "scales": { "y": { "labels": ".0%" }, "fill": { "type": "ordinal", "domain": [ "Soldiers", "Sailors" ], "scheme": "fivethirtyeight" } }, "labs": { "title": "Parts of a whole within each group", "subtitle": "Share of soldiers and sailors in each squadron", "x": "Squadron", "y": "Share of complement", "fill": "Role" }, "theme": "fivethirtyeight" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBar, GGPlot, Inspect, Labs, ScaleFillDiscrete, ScaleYContinuous, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { armadaCrews } from "./data.js"; </script> <GGPlot data={armadaCrews} aes={{ x: "squadron", fill: "role", weight: "men" }} width={640} height={400} > <GeomBar position="fill" /> <ScaleYContinuous labels=".0%" /> <ScaleFillDiscrete domain={["Soldiers", "Sailors"]} scheme="fivethirtyeight" /> <ThemeFivethirtyeight /> <Labs title="Parts of a whole within each group" subtitle="Share of soldiers and sailors in each squadron" x="Squadron" y="Share of complement" fill="Role" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Stacked counts inside each bag (bar/stacked) Stacked bars when you need counts and composition in the same mark. Tags: bar, stack, fill, legend, weight, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "bag": "1 and 2", "group": "near std", "deviation": "Below -R", "count": 34 }, { "bag": "3", "group": "near std", "deviation": "Below -R", "count": 11 }, { "bag": "4", "group": "near std", "deviation": "Below -R", "count": 20 }, { "bag": "5", "group": "below std", "deviation": "Below -R", "count": 30 }, { "bag": "6", "group": "below std", "deviation": "Below -R", "count": 32 }, { "bag": "7", "group": "below std", "deviation": "Below -R", "count": 47 }, { "bag": "8", "group": "above std", "deviation": "Below -R", "count": 11 }, { "bag": "9", "group": "above std", "deviation": "Below -R", "count": 10 }, { "bag": "10", "group": "above std", "deviation": "Below -R", "count": 14 }, { "bag": "1 and 2", "group": "near std", "deviation": "(-R to -.2)", "count": 57 }, { "bag": "3", "group": "near std", "deviation": "(-R to -.2)", "count": 17 }, { "bag": "4", "group": "near std", "deviation": "(-R to -.2)", "count": 22 }, { "bag": "5", "group": "below std", "deviation": "(-R to -.2)", "count": 102 }, { "bag": "6", "group": "below std", "deviation": "(-R to -.2)", "count": 27 }, { "bag": "7", "group": "below std", "deviation": "(-R to -.2)", "count": 65 }, { "bag": "8", "group": "above std", "deviation": "(-R to -.2)", "count": 21 }, { "bag": "9", "group": "above std", "deviation": "(-R to -.2)", "count": 38 }, { "bag": "10", "group": "above std", "deviation": "(-R to -.2)", "count": 13 }, { "bag": "1 and 2", "group": "near std", "deviation": "(-.2 to -.1)", "count": 172 }, { "bag": "3", "group": "near std", "deviation": "(-.2 to -.1)", "count": 100 } ] }, "layers": [ { "geom": "bar", "stat": "count", "position": "stack", "aes": { "x": { "field": "bag" }, "y": { "stat": "count" }, "fill": { "field": "deviation" }, "weight": { "field": "count" } } } ], "scales": { "x": { "type": "band", "domain": [ "1 and 2", "3", "4", "5", "6", "7", "8", "9", "10" ] }, "fill": { "type": "ordinal", "domain": [ "Below -R", "(-R to -.2)", "(-.2 to -.1)", "(-.1 to 0)", "(0 to .1)", "(.1 to .2)", "(.2 to R)", "Above R" ], "scheme": "flexoki" } }, "labs": { "title": "Stacked counts inside each bag", "subtitle": "Each bar is one group, split by how far the coins miss standard weight", "x": "Bag", "y": "Sovereigns", "fill": "Deviation" }, "theme": "light" } // note: inline data truncated (52 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBar, GGPlot, GuideLegend, Inspect, Labs, ScaleFillDiscrete, ScaleXDiscrete, ThemeLight, } from "@ggsvelte/svelte"; import { pyxTrial } from "./data.js"; </script> <GGPlot data={pyxTrial} aes={{ x: "bag", fill: "deviation", weight: "count" }} width={640} height={400} > <GeomBar /> <ScaleXDiscrete domain={["1 and 2", "3", "4", "5", "6", "7", "8", "9", "10"]} /> <ScaleFillDiscrete domain={[ "Below -R", "(-R to -.2)", "(-.2 to -.1)", "(-.1 to 0)", "(0 to .1)", "(.1 to .2)", "(.2 to R)", "Above R", ]} scheme="flexoki" /> <ThemeLight /> <GuideLegend channel="fill" focus /> <Labs title="Stacked counts inside each bag" subtitle="Each bar is one group, split by how far the coins miss standard weight" x="Bag" y="Sovereigns" fill="Deviation" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Two-dimensional bins for a dense cloud (bin2d/basic) 2D bins when a scatter is too dense for individual points. Tags: bin2d, heatmap, density Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "duration": 3.6, "waiting": 79 }, { "duration": 1.8, "waiting": 54 }, { "duration": 3.333, "waiting": 74 }, { "duration": 2.283, "waiting": 62 }, { "duration": 4.533, "waiting": 85 }, { "duration": 2.883, "waiting": 55 }, { "duration": 4.7, "waiting": 88 }, { "duration": 3.6, "waiting": 85 }, { "duration": 1.95, "waiting": 51 }, { "duration": 4.35, "waiting": 85 }, { "duration": 1.833, "waiting": 54 }, { "duration": 3.917, "waiting": 84 }, { "duration": 4.2, "waiting": 78 }, { "duration": 1.75, "waiting": 47 }, { "duration": 4.7, "waiting": 83 }, { "duration": 2.167, "waiting": 52 }, { "duration": 1.75, "waiting": 62 }, { "duration": 4.8, "waiting": 84 }, { "duration": 1.6, "waiting": 52 }, { "duration": 4.25, "waiting": 79 } ] }, "layers": [ { "geom": "bin_2d", "stat": "bin_2d", "position": "identity", "aes": { "x": { "field": "duration" }, "y": { "field": "waiting" }, "fill": { "stat": "count" } }, "params": { "bins": 16 } } ], "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }, "labs": { "title": "Two-dimensional bins for a dense cloud", "subtitle": "Waiting time against eruption length for Old Faithful", "x": "Eruption length (minutes)", "y": "Wait for the next eruption (minutes)", "fill": "Eruptions" }, "theme": "minimal" } // note: inline data truncated (252 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBin2d, GGPlot, Inspect, Labs, ScaleFillContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { oldFaithful } from "./data.js"; </script> <GGPlot data={oldFaithful} aes={{ x: "duration", y: "waiting" }} width={640} height={400} > <GeomBin2d bins={16} /> <ScaleFillContinuous scheme="viridis" /> <ThemeMinimal /> <Labs title="Two-dimensional bins for a dense cloud" subtitle="Waiting time against eruption length for Old Faithful" x="Eruption length (minutes)" y="Wait for the next eruption (minutes)" fill="Eruptions" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Axes without marks (blank/axes-only) Blank axes when you need the trained domain without a geom. Tags: blank, scales, domain, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "age": 1, "survivors": 0 }, { "age": 84, "survivors": 1000 } ] }, "layers": [ { "geom": "blank", "stat": "identity", "position": "identity", "aes": { "x": { "field": "age" }, "y": { "field": "survivors" } } } ], "scales": { "x": { "breaks": [ 1, 10, 20, 30, 40, 50, 60, 70, 80 ], "nice": false }, "y": { "breaks": [ 0, 200, 400, 600, 800, 1000 ] } }, "labs": { "title": "Axes without marks", "subtitle": "The trained frame alone, before any geometry is drawn", "x": "Age", "y": "Surviving" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBlank, GGPlot, Labs, ScaleXContinuous, ScaleYContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import { halleyFrame } from "./data.js"; </script> <GGPlot data={halleyFrame} aes={{ x: "age", y: "survivors" }} width={640} height={400} > <GeomBlank /> <ScaleXContinuous breaks={[1, 10, 20, 30, 40, 50, 60, 70, 80]} nice={false} /> <ScaleYContinuous breaks={[0, 200, 400, 600, 800, 1000]} /> <ThemeClassic /> <Labs title="Axes without marks" subtitle="The trained frame alone, before any geometry is drawn" x="Age" y="Surviving" /> </GGPlot> ``` ## Expanded domain with no marks (blank/domain-expand) Domain expansion when a reference value must stay inside the plot area. Tags: blank, point, scales, domain, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "trial": 1, "density": 5.5 }, { "trial": 2, "density": 5.61 }, { "trial": 3, "density": 4.88 }, { "trial": 4, "density": 5.07 }, { "trial": 5, "density": 5.26 }, { "trial": 6, "density": 5.55 }, { "trial": 7, "density": 5.36 }, { "trial": 8, "density": 5.29 }, { "trial": 9, "density": 5.58 }, { "trial": 10, "density": 5.65 }, { "trial": 11, "density": 5.57 }, { "trial": 12, "density": 5.53 }, { "trial": 13, "density": 5.62 }, { "trial": 14, "density": 5.29 }, { "trial": 15, "density": 5.44 }, { "trial": 16, "density": 5.34 }, { "trial": 17, "density": 5.79 }, { "trial": 18, "density": 5.1 }, { "trial": 19, "density": 5.27 }, { "trial": 20, "density": 5.39 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "trial" }, "y": { "field": "density" } }, "params": { "size": 3.5, "alpha": 0.9 } }, { "geom": "blank", "stat": "identity", "position": "identity", "aes": { "x": { "field": "trial" }, "y": { "field": "density" } }, "data": { "values": [ { "trial": 15, "density": 1 } ] } } ], "labs": { "title": "Expanded domain with no marks", "subtitle": "Domain opened so a known constant sits inside the frame", "x": "Determination", "y": "Density of the earth, water = 1" }, "theme": "classic" } // note: inline data truncated (9 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBlank, GeomPoint, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { earthDensity, waterDensity } from "./data.js"; </script> <GGPlot data={earthDensity} aes={{ x: "trial", y: "density" }} width={640} height={400} > <GeomPoint size={3.5} alpha={0.9} /> <GeomBlank data={waterDensity} aes={{ x: "trial", y: "density" }} /> <ThemeClassic /> <Labs title="Expanded domain with no marks" subtitle="Domain opened so a known constant sits inside the frame" x="Determination" y="Density of the earth, water = 1" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Boxplots for five runs of the same experiment (boxplot/by-category) Category boxplots for comparing distributions side by side. Tags: boxplot, stat, summary, outliers, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "run": "Jun 5", "velocity": 850 }, { "run": "Jun 5", "velocity": 850 }, { "run": "Jun 5", "velocity": 1000 }, { "run": "Jun 5", "velocity": 810 }, { "run": "Jun 5", "velocity": 960 }, { "run": "Jun 5", "velocity": 800 }, { "run": "Jun 5", "velocity": 830 }, { "run": "Jun 5", "velocity": 830 }, { "run": "Jun 5", "velocity": 880 }, { "run": "Jun 5", "velocity": 720 }, { "run": "Jun 5", "velocity": 880 }, { "run": "Jun 5", "velocity": 840 }, { "run": "Jun 5", "velocity": 890 }, { "run": "Jun 5", "velocity": 770 }, { "run": "Jun 5", "velocity": 910 }, { "run": "Jun 5", "velocity": 720 }, { "run": "Jun 5", "velocity": 890 }, { "run": "Jun 5", "velocity": 810 }, { "run": "Jun 5", "velocity": 870 }, { "run": "Jun 5", "velocity": 940 } ] }, "layers": [ { "geom": "boxplot", "stat": "boxplot", "position": "dodge", "aes": { "x": { "field": "run" }, "y": { "field": "velocity" } } } ], "scales": { "x": { "domain": [ "Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2" ] } }, "labs": { "title": "Boxplots for five runs of the same experiment", "subtitle": "Runs differ more than readings within a run", "x": "Run", "y": "Velocity (km/s − 299,000)" }, "theme": "few" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomBoxplot, GGPlot, Inspect, Labs, ScaleXDiscrete, ThemeFew, } from "@ggsvelte/svelte"; import { michelsonRuns } from "./data.js"; </script> <GGPlot data={michelsonRuns} aes={{ x: "run", y: "velocity" }} width={640} height={400} > <GeomBoxplot /> <ScaleXDiscrete domain={["Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2"]} /> <ThemeFew /> <Labs title="Boxplots for five runs of the same experiment" subtitle="Runs differ more than readings within a run" x="Run" y="Velocity (km/s − 299,000)" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Violin plots for the same five runs (boxplot/violin) Violins when the full shape of each group matters more than the five-number summary alone. Tags: violin, density, distribution, boxplot Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "run": "Jun 5", "velocity": 850 }, { "run": "Jun 5", "velocity": 850 }, { "run": "Jun 5", "velocity": 1000 }, { "run": "Jun 5", "velocity": 810 }, { "run": "Jun 5", "velocity": 960 }, { "run": "Jun 5", "velocity": 800 }, { "run": "Jun 5", "velocity": 830 }, { "run": "Jun 5", "velocity": 830 }, { "run": "Jun 5", "velocity": 880 }, { "run": "Jun 5", "velocity": 720 }, { "run": "Jun 5", "velocity": 880 }, { "run": "Jun 5", "velocity": 840 }, { "run": "Jun 5", "velocity": 890 }, { "run": "Jun 5", "velocity": 770 }, { "run": "Jun 5", "velocity": 910 }, { "run": "Jun 5", "velocity": 720 }, { "run": "Jun 5", "velocity": 890 }, { "run": "Jun 5", "velocity": 810 }, { "run": "Jun 5", "velocity": 870 }, { "run": "Jun 5", "velocity": 940 } ] }, "layers": [ { "geom": "violin", "stat": "ydensity", "position": "dodge", "aes": { "x": { "field": "run" }, "y": { "field": "velocity" }, "fill": { "field": "run" } }, "params": { "scale": "width", "trim": true, "alpha": 0.75, "linewidth": 0.6 } } ], "scales": { "x": { "domain": [ "Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2" ] }, "fill": { "type": "ordinal", "domain": [ "Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2" ], "scheme": "few" } }, "labs": { "title": "Violin plots for the same five runs", "subtitle": "Mirrored density of velocity so the shape of each run is visible", "x": "Run", "y": "Velocity (km/s − 299,000)", "fill": "" }, "theme": "few" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomViolin, GGPlot, Inspect, Labs, ScaleFillDiscrete, ScaleXDiscrete, ThemeFew, } from "@ggsvelte/svelte"; import { michelsonRuns } from "../by-category/data.js"; </script> <GGPlot data={michelsonRuns} aes={{ x: "run", y: "velocity", fill: "run" }} width={640} height={400} > <GeomViolin scale="width" trim={true} alpha={0.75} linewidth={0.6} /> <ScaleXDiscrete domain={["Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2"]} /> <ScaleFillDiscrete domain={["Jun 5", "Jun 7", "Jun 9", "Jun 12", "Jul 2"]} scheme="few" /> <ThemeFew /> <Labs title="Violin plots for the same five runs" subtitle="Mirrored density of velocity so the shape of each run is visible" x="Run" y="Velocity (km/s − 299,000)" fill="" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Counts across ordered chest sizes (col/basic) Simple columns for discrete counts along one ordered axis. Tags: col, bars, identity, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "chest": 33, "soldiers": 3 }, { "chest": 34, "soldiers": 18 }, { "chest": 35, "soldiers": 81 }, { "chest": 36, "soldiers": 185 }, { "chest": 37, "soldiers": 420 }, { "chest": 38, "soldiers": 749 }, { "chest": 39, "soldiers": 1073 }, { "chest": 40, "soldiers": 1079 }, { "chest": 41, "soldiers": 934 }, { "chest": 42, "soldiers": 658 }, { "chest": 43, "soldiers": 370 }, { "chest": 44, "soldiers": 92 }, { "chest": 45, "soldiers": 50 }, { "chest": 46, "soldiers": 21 }, { "chest": 47, "soldiers": 4 }, { "chest": 48, "soldiers": 1 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "chest" }, "y": { "field": "soldiers" } } } ], "scales": { "x": { "nice": false } }, "labs": { "title": "Counts across ordered chest sizes", "subtitle": "How many men fell in each chest-measure class", "x": "Chest circumference (inches)", "y": "Soldiers" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCol, GGPlot, Inspect, Labs, ScaleXContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import { chestSizes } from "./data.js"; </script> <GGPlot data={chestSizes} aes={{ x: "chest", y: "soldiers" }} width={640} height={400} > <GeomCol /> <ScaleXContinuous nice={false} /> <ThemeClassic /> <Labs title="Counts across ordered chest sizes" subtitle="How many men fell in each chest-measure class" x="Chest circumference (inches)" y="Soldiers" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Long category labels at a narrow width (col/long-labels) A layout specimen: Spanish multi-word names and a long German compound at about 480px, where the band axis has to wrap and rotate to fit. Tags: col, band, labels, i18n, vr Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "category": "Resolución", "count": 9000 }, { "category": "Corrección (errores o erratas)", "count": 3200 }, { "category": "Sentencia", "count": 2800 }, { "category": "Anlageverwaltungsgesellschaft", "count": 2500 }, { "category": "Orden", "count": 900 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "category" }, "y": { "field": "count" } } } ], "labs": { "title": "Long category labels at a narrow width", "subtitle": "The band axis has to wrap and rotate to fit these names at 480px", "x": "Category", "y": "Count" } } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCol, GGPlot, Inspect, Labs } from "@ggsvelte/svelte"; import { filings } from "./data.js"; </script> <!-- 480px width is intentional (#406): band label planner escalates wrap→rotate for long Spanish multi-word and German single-token categories. --> <GGPlot data={filings} aes={{ x: "category", y: "count" }} width={480} height={420} > <GeomCol /> <Labs title="Long category labels at a narrow width" subtitle="The band axis has to wrap and rotate to fit these names at 480px" x="Category" y="Count" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## One long label among short ones (col/mixed-outlier-labels) A layout specimen: short categories with a single four-word outlier at a normal desktop width, where the axis should wrap rather than rotate everything. Tags: col, band, labels, i18n, vr Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "category": "Real Decreto", "count": 7800 }, { "category": "Orden", "count": 7000 }, { "category": "Ley", "count": 6711 }, { "category": "Resolución", "count": 3000 }, { "category": "Corrección (errores o erratas)", "count": 2200 }, { "category": "Sentencia", "count": 1800 }, { "category": "Directiva", "count": 1200 }, { "category": "Reglamento", "count": 1000 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "category" }, "y": { "field": "count" } } } ], "labs": { "title": "One long label among short ones", "subtitle": "At a normal panel width the axis should wrap the outlier, not rotate every label", "x": "Category", "y": "Count" } } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCol, GGPlot, Inspect, Labs } from "@ggsvelte/svelte"; import { filings } from "./data.js"; </script> <!-- 640px width is intentional (#634): mixed short + one multi-word outlier must wrap at desktop width, not escalate to −90° + truncate. --> <GGPlot data={filings} aes={{ x: "category", y: "count" }} width={640} height={320} > <GeomCol /> <Labs title="One long label among short ones" subtitle="At a normal panel width the axis should wrap the outlier, not rotate every label" x="Category" y="Count" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Linedraw theme on chest-size columns (col/theme-linedraw) Linedraw theme on ordered chest counts: hard black axes and grid on white, with no grey chrome. Tags: col, bars, theme, linedraw Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "chest": 33, "soldiers": 3 }, { "chest": 34, "soldiers": 18 }, { "chest": 35, "soldiers": 81 }, { "chest": 36, "soldiers": 185 }, { "chest": 37, "soldiers": 420 }, { "chest": 38, "soldiers": 749 }, { "chest": 39, "soldiers": 1073 }, { "chest": 40, "soldiers": 1079 }, { "chest": 41, "soldiers": 934 }, { "chest": 42, "soldiers": 658 }, { "chest": 43, "soldiers": 370 }, { "chest": 44, "soldiers": 92 }, { "chest": 45, "soldiers": 50 }, { "chest": 46, "soldiers": 21 }, { "chest": 47, "soldiers": 4 }, { "chest": 48, "soldiers": 1 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "chest" }, "y": { "field": "soldiers" } } } ], "scales": { "x": { "nice": false } }, "labs": { "title": "Linedraw theme on chest-size columns", "subtitle": "Hard black lines, white panel, no grey fill", "x": "Chest circumference (inches)", "y": "Soldiers" }, "theme": "linedraw" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCol, GGPlot, Inspect, Labs, ScaleXContinuous, ThemeLinedraw, } from "@ggsvelte/svelte"; import { chestSizes } from "./data.js"; </script> <GGPlot data={chestSizes} aes={{ x: "chest", y: "soldiers" }} width={640} height={400} > <GeomCol /> <ScaleXContinuous nice={false} /> <ThemeLinedraw /> <Labs title="Linedraw theme on chest-size columns" subtitle="Hard black lines, white panel, no grey fill" x="Chest circumference (inches)" y="Soldiers" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Columns with the rate printed on each bar (col/value-labels) Value labels when the exact number is the point of the chart. Tags: col, text, labels, annotation, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "Vaccinated", "rate": 16.4, "label": "16.4" }, { "group": "Placebo", "rate": 57.1, "label": "57.1" }, { "group": "Not inoculated", "rate": 35.7, "label": "35.7" } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "group" }, "y": { "field": "rate" } }, "params": { "width": 0.7 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "group" }, "y": { "field": "rate" }, "label": { "field": "label" } }, "params": { "dy": -8 } } ], "scales": { "x": { "domain": [ "Vaccinated", "Placebo", "Not inoculated" ] } }, "labs": { "title": "Columns with the rate printed on each bar", "subtitle": "Paralytic polio per 100,000 children in each arm of the trial", "x": "Group", "y": "Cases per 100,000" }, "theme": "fivethirtyeight" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCol, GeomText, GGPlot, Inspect, Labs, ScaleXDiscrete, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { polioTrial } from "./data.js"; </script> <GGPlot data={polioTrial} aes={{ x: "group", y: "rate" }} width={640} height={400} > <GeomCol width={0.7} /> <GeomText aes={{ label: "label" }} dy={-8} /> <ScaleXDiscrete domain={["Vaccinated", "Placebo", "Not inoculated"]} /> <ThemeFivethirtyeight /> <Labs title="Columns with the rate printed on each bar" subtitle="Paralytic polio per 100,000 children in each arm of the trial" x="Group" y="Cases per 100,000" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## How many items you can tell apart at once (color/binned) Binned continuous colour when discrete steps read clearer than a smooth ramp. Tags: point, color, binned, colorsteps, legend Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "actual": 3, "estimated": 3, "trials": 23 }, { "actual": 4, "estimated": 4, "trials": 65 }, { "actual": 5, "estimated": 5, "trials": 102 }, { "actual": 6, "estimated": 5, "trials": 7 }, { "actual": 5, "estimated": 6, "trials": 4 }, { "actual": 6, "estimated": 6, "trials": 120 }, { "actual": 7, "estimated": 6, "trials": 18 }, { "actual": 5, "estimated": 7, "trials": 1 }, { "actual": 6, "estimated": 7, "trials": 20 }, { "actual": 7, "estimated": 7, "trials": 113 }, { "actual": 8, "estimated": 7, "trials": 30 }, { "actual": 9, "estimated": 7, "trials": 2 }, { "actual": 7, "estimated": 8, "trials": 25 }, { "actual": 8, "estimated": 8, "trials": 76 }, { "actual": 9, "estimated": 8, "trials": 24 }, { "actual": 10, "estimated": 8, "trials": 6 }, { "actual": 11, "estimated": 8, "trials": 1 }, { "actual": 8, "estimated": 9, "trials": 28 }, { "actual": 9, "estimated": 9, "trials": 76 }, { "actual": 10, "estimated": 9, "trials": 37 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "actual" }, "y": { "field": "estimated" }, "color": { "field": "trials" } }, "params": { "size": 5 } } ], "scales": { "color": { "type": "binned", "breaks": [ 0, 5, 20, 60, 130 ], "range": [ "#d3d3d0", "#8ba89c", "#4f8a6e", "#1e5c43" ] } }, "guides": { "color": { "type": "colorsteps", "position": "bottom", "direction": "horizontal" } }, "labs": { "title": "How many items you can tell apart at once", "subtitle": "Binned colour so nearby values share a fill step", "x": "Beans actually thrown", "y": "Beans estimated", "color": "Trials" }, "theme": "bw" } // note: inline data truncated (30 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, GuideColorsteps, Inspect, Labs, ScaleColorBinned, ThemeBw, } from "@ggsvelte/svelte"; import { jevonsTrials } from "./data.js"; </script> <GGPlot data={jevonsTrials} aes={{ x: "actual", y: "estimated", color: "trials" }} width={640} height={400} > <GeomPoint size={5} /> <ScaleColorBinned breaks={[0, 5, 20, 60, 130]} range={["#d3d3d0", "#8ba89c", "#4f8a6e", "#1e5c43"]} /> <ThemeBw /> <GuideColorsteps channel="color" position="bottom" direction="horizontal" /> <Labs title="How many items you can tell apart at once" subtitle="Binned colour so nearby values share a fill step" x="Beans actually thrown" y="Beans estimated" color="Trials" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Contours of height on a grid (contour/basic) Contours when the input is a continuous surface on a grid. Tags: contour, stat, path, grid, surface, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "east": 0, "north": 0, "height": 100 }, { "east": 40, "north": 0, "height": 104 }, { "east": 80, "north": 0, "height": 107 }, { "east": 120, "north": 0, "height": 110 }, { "east": 160, "north": 0, "height": 118 }, { "east": 200, "north": 0, "height": 122 }, { "east": 240, "north": 0, "height": 123 }, { "east": 280, "north": 0, "height": 117 }, { "east": 320, "north": 0, "height": 113 }, { "east": 360, "north": 0, "height": 108 }, { "east": 400, "north": 0, "height": 107 }, { "east": 440, "north": 0, "height": 111 }, { "east": 480, "north": 0, "height": 113 }, { "east": 520, "north": 0, "height": 114 }, { "east": 560, "north": 0, "height": 111 }, { "east": 600, "north": 0, "height": 113 }, { "east": 640, "north": 0, "height": 115 }, { "east": 680, "north": 0, "height": 117 }, { "east": 720, "north": 0, "height": 109 }, { "east": 760, "north": 0, "height": 101 } ] }, "layers": [ { "geom": "contour", "stat": "contour", "position": "identity", "aes": { "x": { "field": "east" }, "y": { "field": "north" }, "z": { "field": "height" } }, "params": { "bins": 10, "linewidth": 1 } } ], "coord": { "type": "fixed" }, "labs": { "title": "Contours of height on a grid", "subtitle": "Elevation bands on Maunga Whau from a regular height matrix", "x": "Metres east", "y": "Metres north" }, "theme": "classic" } // note: inline data truncated (332 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomContour, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { maungaWhau } from "./data.js"; </script> <GGPlot data={maungaWhau} aes={{ x: "east", y: "north", z: "height" }} width={640} height={400} > <GeomContour bins={10} linewidth={1} /> <CoordFixed /> <ThemeClassic /> <Labs title="Contours of height on a grid" subtitle="Elevation bands on Maunga Whau from a regular height matrix" x="Metres east" y="Metres north" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Interval boxes with mid line (crossbar/boxes) Three tall identity boxes with a mid horizontal line — crossbar's mark shape at index thumbnail size. Tags: crossbar, identity, box, ymin, ymax Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "A", "mid": 5, "lo": 1.5, "hi": 8.5 }, { "group": "B", "mid": 7.5, "lo": 4, "hi": 11 }, { "group": "C", "mid": 3.5, "lo": 0.5, "hi": 6.5 } ] }, "layers": [ { "geom": "crossbar", "stat": "identity", "position": "identity", "aes": { "x": { "field": "group" }, "y": { "field": "mid" }, "color": { "value": "#1a202c" }, "fill": { "value": "#cbd5e1" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "width": 0.55, "linewidth": 1.8, "fatten": 3.2 } } ], "scales": { "x": { "type": "band", "domain": [ "A", "B", "C" ] }, "y": { "type": "linear", "domain": [ 0, 12 ] } }, "labs": { "title": "Interval boxes with mid line", "subtitle": "Three identity boxes from ymin to ymax with a horizontal line at y", "x": "Group", "y": "Value" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCrossbar, GGPlot, Inspect, Labs, ScaleXDiscrete, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { groupIntervals } from "./data.js"; </script> <GGPlot data={groupIntervals} aes={{ x: "group", y: "mid", ymin: "lo", ymax: "hi" }} width={640} height={400} > <GeomCrossbar width={0.55} linewidth={1.8} fatten={3.2} aes={{ color: { value: "#1a202c" }, fill: { value: "#cbd5e1" }, }} /> <ScaleXDiscrete domain={["A", "B", "C"]} /> <ScaleYContinuous limits={[0, 12]} /> <ThemeMinimal /> <Labs title="Interval boxes with mid line" subtitle="Three identity boxes from ymin to ymax with a horizontal line at y" x="Group" y="Value" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Paired points joined by a curve (curve/connectors) Curved connectors when straight segments would crowd and you want origin→end pairs. Tags: curve, annotation, connector, bezier Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pair": 1, "self": 17.375, "cross": 23.5, "winner": "Cross-fertilised taller" }, { "pair": 2, "self": 20.375, "cross": 12, "winner": "Self-fertilised taller" }, { "pair": 3, "self": 20, "cross": 21, "winner": "Cross-fertilised taller" }, { "pair": 4, "self": 20, "cross": 22, "winner": "Cross-fertilised taller" }, { "pair": 5, "self": 18.375, "cross": 19.125, "winner": "Cross-fertilised taller" }, { "pair": 6, "self": 18.625, "cross": 21.5, "winner": "Cross-fertilised taller" }, { "pair": 7, "self": 18.625, "cross": 22.125, "winner": "Cross-fertilised taller" }, { "pair": 8, "self": 15.25, "cross": 20.375, "winner": "Cross-fertilised taller" }, { "pair": 9, "self": 16.5, "cross": 18.25, "winner": "Cross-fertilised taller" }, { "pair": 10, "self": 18, "cross": 21.625, "winner": "Cross-fertilised taller" }, { "pair": 11, "self": 16.25, "cross": 23.25, "winner": "Cross-fertilised taller" }, { "pair": 12, "self": 18, "cross": 21, "winner": "Cross-fertilised taller" }, { "pair": 13, "self": 12.75, "cross": 22.125, "winner": "Cross-fertilised taller" }, { "pair": 14, "self": 15.5, "cross": 23, "winner": "Cross-fertilised taller" }, { "pair": 15, "self": 18, "cross": 12, "winner": "Self-fertilised taller" } ] }, "layers": [ { "geom": "curve", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "self" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "linewidth": 2, "lineend": "round", "curvature": 0.4, "alpha": 0.9 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "self" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "size": 2.5, "alpha": 0.6 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "cross" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "size": 3.2 } } ], "scales": { "color": { "type": "ordinal", "domain": [ "Cross-fertilised taller", "Self-fertilised taller" ], "scheme": "wsj_red_green" } }, "labs": { "title": "Paired points joined by a curve", "subtitle": "Each curve runs from the self-fertilised plant to its cross-fertilised pair", "x": "Pair", "y": "Final height (inches)", "color": "" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCurve, GeomPoint, GGPlot, Inspect, Labs, ScaleColorDiscrete, ThemeClassic, } from "@ggsvelte/svelte"; import { darwinMaize } from "./data.js"; </script> <GGPlot data={darwinMaize} aes={{ x: "pair", y: "self", xend: "pair", yend: "cross", color: "winner" }} width={640} height={400} > <GeomCurve linewidth={2} lineend="round" curvature={0.4} alpha={0.9} /> <GeomPoint size={2.5} alpha={0.6} /> <GeomPoint aes={{ x: "pair", y: "cross" }} size={3.2} /> <ScaleColorDiscrete domain={["Cross-fertilised taller", "Self-fertilised taller"]} scheme="wsj_red_green" /> <ThemeClassic /> <Labs title="Paired points joined by a curve" subtitle="Each curve runs from the self-fertilised plant to its cross-fertilised pair" x="Pair" y="Final height (inches)" color="" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## 2D density isolines (density/kde-2d) Kernel density drawn as contour lines over 578 point locations. The rings tighten where the points cluster — here, the streets around one pump on Snow's 1854 Soho map. Tags: density, density_2d, stat, kde, contour, point, map Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 13.59, "y": 11.1 }, { "x": 9.88, "y": 12.56 }, { "x": 14.65, "y": 10.18 }, { "x": 15.22, "y": 9.99 }, { "x": 13.16, "y": 12.96 }, { "x": 13.81, "y": 8.89 }, { "x": 13.1, "y": 10.56 }, { "x": 11, "y": 11.87 }, { "x": 15.15, "y": 11.7 }, { "x": 11.13, "y": 9.64 }, { "x": 11.71, "y": 13.59 }, { "x": 12.34, "y": 11.48 }, { "x": 10.59, "y": 11.87 }, { "x": 14.57, "y": 10.58 }, { "x": 16.57, "y": 14.32 }, { "x": 9.51, "y": 10.67 }, { "x": 13.34, "y": 10.71 }, { "x": 15.03, "y": 10.19 }, { "x": 15.14, "y": 10.04 }, { "x": 10.87, "y": 9.82 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "Deaths", "scale": true } }, "params": { "alpha": 0.55, "size": 1.6 } }, { "geom": "density_2d", "stat": "density_2d", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" } }, "params": { "bins": 6, "n": 48, "linewidth": 1 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "Pumps", "scale": true } }, "data": { "values": [ { "x": 8.65, "y": 17.89, "label": "Oxford Market" }, { "x": 10.98, "y": 18.52, "label": "Castle St E" }, { "x": 13.38, "y": 17.39, "label": "Oxford St #1" }, { "x": 14.88, "y": 17.81, "label": "Oxford St #2" }, { "x": 8.69, "y": 14.91, "label": "Gt Marlborough" }, { "x": 8.86, "y": 12.75, "label": "Crown Chapel" }, { "x": 12.57, "y": 11.73, "label": "Broad St" }, { "x": 10.66, "y": 7.43, "label": "Warwick" }, { "x": 13.52, "y": 7.96, "label": "Briddle St" }, { "x": 16.43, "y": 9.25, "label": "So Soho" }, { "x": 18.91, "y": 9.74, "label": "Dean St" }, { "x": 16.01, "y": 5.05, "label": "Coventry St" }, { "x": 9, "y": 5.1, "label": "Vigo St" } ] }, "params": { "size": 4, "shape": "cross" } } ], "coord": { "type": "fixed" }, "scales": { "color": { "type": "ordinal", "domain": [ "Deaths", "Pumps" ], "scheme": "observable10" } }, "labs": { "title": "Density isolines over points", "subtitle": "Cholera deaths in Soho, 1854", "x": "", "y": "", "color": "" }, "theme": "map" } // note: inline data truncated (558 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomDensity2d, GeomPoint, GGPlot, Inspect, Labs, ScaleColorDiscrete, ThemeMap, } from "@ggsvelte/svelte"; import { choleraDeaths, waterPumps } from "./data.js"; </script> <GGPlot data={choleraDeaths} aes={{ x: "x", y: "y" }} width={640} height={400}> <GeomPoint alpha={0.55} size={1.6} aes={{ color: { value: "Deaths", scale: true } }} /> <GeomDensity2d bins={6} n={48} linewidth={1} /> <GeomPoint data={waterPumps} aes={{ x: "x", y: "y", color: { value: "Pumps", scale: true } }} size={4} shape="cross" /> <ScaleColorDiscrete domain={["Deaths", "Pumps"]} scheme="observable10" /> <CoordFixed /> <ThemeMap /> <Labs title="Density isolines over points" subtitle="Cholera deaths in Soho, 1854" x="" y="" color="" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Filled 2D density bands (density/kde-2d-filled) Kernel density drawn as shaded rings over 578 point locations. Darker bands sit where the points are densest — here, the streets around one pump on Snow's 1854 Soho map. Tags: density, density_2d_filled, kde, fill, stat, contour, map Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 13.59, "y": 11.1 }, { "x": 9.88, "y": 12.56 }, { "x": 14.65, "y": 10.18 }, { "x": 15.22, "y": 9.99 }, { "x": 13.16, "y": 12.96 }, { "x": 13.81, "y": 8.89 }, { "x": 13.1, "y": 10.56 }, { "x": 11, "y": 11.87 }, { "x": 15.15, "y": 11.7 }, { "x": 11.13, "y": 9.64 }, { "x": 11.71, "y": 13.59 }, { "x": 12.34, "y": 11.48 }, { "x": 10.59, "y": 11.87 }, { "x": 14.57, "y": 10.58 }, { "x": 16.57, "y": 14.32 }, { "x": 9.51, "y": 10.67 }, { "x": 13.34, "y": 10.71 }, { "x": 15.03, "y": 10.19 }, { "x": 15.14, "y": 10.04 }, { "x": 10.87, "y": 9.82 } ] }, "layers": [ { "geom": "density_2d_filled", "stat": "density_2d_filled", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "fill": { "stat": "level" } }, "params": { "bins": 6, "n": 48, "alpha": 0.8 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "Deaths", "scale": true } }, "params": { "alpha": 0.55, "size": 1.6 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "Pumps", "scale": true } }, "data": { "values": [ { "x": 8.65, "y": 17.89, "label": "Oxford Market" }, { "x": 10.98, "y": 18.52, "label": "Castle St E" }, { "x": 13.38, "y": 17.39, "label": "Oxford St #1" }, { "x": 14.88, "y": 17.81, "label": "Oxford St #2" }, { "x": 8.69, "y": 14.91, "label": "Gt Marlborough" }, { "x": 8.86, "y": 12.75, "label": "Crown Chapel" }, { "x": 12.57, "y": 11.73, "label": "Broad St" }, { "x": 10.66, "y": 7.43, "label": "Warwick" }, { "x": 13.52, "y": 7.96, "label": "Briddle St" }, { "x": 16.43, "y": 9.25, "label": "So Soho" }, { "x": 18.91, "y": 9.74, "label": "Dean St" }, { "x": 16.01, "y": 5.05, "label": "Coventry St" }, { "x": 9, "y": 5.1, "label": "Vigo St" } ] }, "params": { "size": 4, "shape": "cross" } } ], "coord": { "type": "fixed" }, "scales": { "color": { "type": "ordinal", "domain": [ "Deaths", "Pumps" ], "scheme": "wsj_dem_rep" } }, "labs": { "title": "Filled density bands over points", "subtitle": "Cholera deaths in Soho, 1854", "x": "", "y": "", "fill": "Density", "color": "" }, "theme": "map" } // note: inline data truncated (558 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomDensity2dFilled, GeomPoint, GGPlot, Inspect, Labs, ScaleColorDiscrete, ThemeMap, } from "@ggsvelte/svelte"; import { choleraDeaths, waterPumps } from "./data.js"; </script> <GGPlot data={choleraDeaths} aes={{ x: "x", y: "y" }} width={640} height={400}> <GeomDensity2dFilled bins={6} n={48} alpha={0.8} /> <GeomPoint alpha={0.55} size={1.6} aes={{ color: { value: "Deaths", scale: true } }} /> <GeomPoint data={waterPumps} aes={{ x: "x", y: "y", color: { value: "Pumps", scale: true } }} size={4} shape="cross" /> <ScaleColorDiscrete domain={["Deaths", "Pumps"]} scheme="wsj_dem_rep" /> <CoordFixed /> <ThemeMap /> <Labs title="Filled density bands over points" subtitle="Cholera deaths in Soho, 1854" x="" y="" fill="Density" color="" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Two densities on one axis (density/overlay) Overlapping density curves for comparing continuous distributions. Tags: density, kde, fill, alpha, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "gender": "Sons", "height": 73.2 }, { "gender": "Daughters", "height": 69.2 }, { "gender": "Daughters", "height": 69 }, { "gender": "Daughters", "height": 69 }, { "gender": "Sons", "height": 73.5 }, { "gender": "Sons", "height": 72.5 }, { "gender": "Daughters", "height": 65.5 }, { "gender": "Daughters", "height": 65.5 }, { "gender": "Sons", "height": 71 }, { "gender": "Daughters", "height": 68 }, { "gender": "Sons", "height": 70.5 }, { "gender": "Sons", "height": 68.5 }, { "gender": "Daughters", "height": 67 }, { "gender": "Daughters", "height": 64.5 }, { "gender": "Daughters", "height": 63 }, { "gender": "Sons", "height": 72 }, { "gender": "Sons", "height": 69 }, { "gender": "Sons", "height": 68 }, { "gender": "Daughters", "height": 66.5 }, { "gender": "Daughters", "height": 62.5 } ] }, "layers": [ { "geom": "density", "stat": "density", "position": "identity", "aes": { "x": { "field": "height" }, "y": { "stat": "density" }, "fill": { "field": "gender" } }, "params": { "alpha": 0.45 } } ], "scales": { "fill": { "type": "ordinal", "domain": [ "Daughters", "Sons" ], "scheme": "wsj_red_green" } }, "labs": { "title": "Two densities on one axis", "subtitle": "Son and daughter heights", "x": "Height (inches)", "y": "Density", "fill": "Child" }, "theme": "minimal" } // note: inline data truncated (914 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomDensity, GGPlot, Inspect, Labs, ScaleFillDiscrete, ThemeMinimal, } from "@ggsvelte/svelte"; import { galtonChildren } from "./data.js"; </script> <GGPlot data={galtonChildren} aes={{ x: "height", fill: "gender" }} width={640} height={400} > <GeomDensity alpha={0.45} /> <ScaleFillDiscrete domain={["Daughters", "Sons"]} scheme="wsj_red_green" /> <ThemeMinimal /> <Labs title="Two densities on one axis" subtitle="Son and daughter heights" x="Height (inches)" y="Density" fill="Child" /> <Inspect mode="x" pin /> </GGPlot> ``` ## One dot per measurement, stacked in bins (dotplot/histodot) Dotplots when the sample is small enough that every observation should stay visible. Tags: dotplot, bindot, stat, bin, point, stack, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "density": 5.5 }, { "density": 5.61 }, { "density": 4.88 }, { "density": 5.07 }, { "density": 5.26 }, { "density": 5.55 }, { "density": 5.36 }, { "density": 5.29 }, { "density": 5.58 }, { "density": 5.65 }, { "density": 5.57 }, { "density": 5.53 }, { "density": 5.62 }, { "density": 5.29 }, { "density": 5.44 }, { "density": 5.34 }, { "density": 5.79 }, { "density": 5.1 }, { "density": 5.27 }, { "density": 5.39 } ] }, "layers": [ { "geom": "dotplot", "stat": "bindot", "position": "identity", "aes": { "x": { "field": "density" }, "y": { "stat": "stackpos" } }, "params": { "binwidth": 0.05, "boundary": 0, "stackdir": "up" } } ], "scales": { "y": { "breaks": [ 0, 1, 2, 3, 4, 5 ] } }, "labs": { "title": "One dot per measurement, stacked in bins", "subtitle": "29 Cavendish density readings (modern 5.517)", "x": "Density of the earth, water = 1", "y": "Runs" }, "theme": "classic" } // note: inline data truncated (9 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomDotplot, GGPlot, Inspect, Labs, ScaleYContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import { earthDensity } from "./data.js"; </script> <GGPlot data={earthDensity} aes={{ x: "density" }} width={640} height={400}> <GeomDotplot binwidth={0.05} boundary={0} stackdir="up" /> <ScaleYContinuous breaks={[0, 1, 2, 3, 4, 5]} /> <ThemeClassic /> <Labs title="One dot per measurement, stacked in bins" subtitle="29 Cavendish density readings (modern 5.517)" x="Density of the earth, water = 1" y="Runs" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Capped error bars (errorbar/caps) Three tall identity intervals with end caps — the mark shape that reads at index thumbnail size. Tags: errorbar, identity, caps, ymin, ymax Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "A", "mid": 5, "lo": 1.5, "hi": 8.5 }, { "group": "B", "mid": 7.5, "lo": 4, "hi": 11 }, { "group": "C", "mid": 3.5, "lo": 0.5, "hi": 6.5 } ] }, "layers": [ { "geom": "errorbar", "stat": "identity", "position": "identity", "aes": { "x": { "field": "group" }, "color": { "value": "#1a202c" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "width": 0.55, "linewidth": 2.6 } } ], "scales": { "x": { "type": "band", "domain": [ "A", "B", "C" ] }, "y": { "type": "linear", "domain": [ 0, 12 ] } }, "labs": { "title": "Capped error bars", "subtitle": "Three identity intervals with end caps at ymin and ymax", "x": "Group", "y": "Value" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomErrorbar, GGPlot, Inspect, Labs, ScaleXDiscrete, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { groupIntervals } from "./data.js"; </script> <GGPlot data={groupIntervals} aes={{ x: "group", ymin: "lo", ymax: "hi" }} width={640} height={400} > <GeomErrorbar width={0.55} linewidth={2.6} aes={{ color: { value: "#1a202c" } }} /> <ScaleXDiscrete domain={["A", "B", "C"]} /> <ScaleYContinuous limits={[0, 12]} /> <ThemeMinimal /> <Labs title="Capped error bars" subtitle="Three identity intervals with end caps at ymin and ymax" x="Group" y="Value" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Mean and standard error by group (errorbar/mean-se) Mean ± SE for a control and three hypnotic treatments. Tags: errorbar, summary, mean-se, jitter Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "drug": "Control", "extraSleep": 0.6 }, { "drug": "Control", "extraSleep": 3 }, { "drug": "Control", "extraSleep": 4.7 }, { "drug": "Control", "extraSleep": 5.5 }, { "drug": "Control", "extraSleep": 6.2 }, { "drug": "Control", "extraSleep": 3.2 }, { "drug": "Control", "extraSleep": 2.5 }, { "drug": "Control", "extraSleep": 2.8 }, { "drug": "Control", "extraSleep": 1.1 }, { "drug": "Control", "extraSleep": 2.9 }, { "drug": "L-hyoscyamine", "extraSleep": 1.3 }, { "drug": "L-hyoscyamine", "extraSleep": 1.4 }, { "drug": "L-hyoscyamine", "extraSleep": 4.5 }, { "drug": "L-hyoscyamine", "extraSleep": 4.3 }, { "drug": "L-hyoscyamine", "extraSleep": 6.1 }, { "drug": "L-hyoscyamine", "extraSleep": 6.6 }, { "drug": "L-hyoscyamine", "extraSleep": 6.2 }, { "drug": "L-hyoscyamine", "extraSleep": 3.6 }, { "drug": "L-hyoscyamine", "extraSleep": 1.1 }, { "drug": "L-hyoscyamine", "extraSleep": 4.9 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "jitter", "positionParams": { "width": 0.12, "height": 0, "seed": 7 }, "aes": { "x": { "field": "drug" }, "y": { "field": "extraSleep" } }, "inspect": false, "params": { "alpha": 0.4, "size": 2.4 } }, { "geom": "errorbar", "stat": "summary", "position": "identity", "aes": { "x": { "field": "drug" }, "y": { "field": "extraSleep" } }, "params": { "width": 0.35, "linewidth": 1.5 } } ], "scales": { "x": { "type": "band", "domain": [ "Control", "L-hyoscyamine", "L-hyoscine", "DL-hyoscine" ] } }, "labs": { "title": "Mean and standard error by group", "subtitle": "Extra sleep under a control and three hypnotics, with bars for the SE of each mean", "x": "Treatment", "y": "Extra sleep (hours)" }, "theme": "hrbr" } // note: inline data truncated (23 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomErrorbar, GeomPoint, GGPlot, Inspect, Labs, registerSummary, ScaleXDiscrete, ThemeHrbr, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerSummary(); import { soporifics } from "./data.js"; </script> <GGPlot data={soporifics} aes={{ x: "drug", y: "extraSleep" }} width={640} height={400} > <GeomPoint position="jitter" positionParams={{ width: 0.12, height: 0, seed: 7 }} alpha={0.4} size={2.4} inspect={false} /> <GeomErrorbar stat="summary" width={0.35} linewidth={1.5} /> <ScaleXDiscrete domain={["Control", "L-hyoscyamine", "L-hyoscine", "DL-hyoscine"]} /> <ThemeHrbr /> <Labs title="Mean and standard error by group" subtitle="Extra sleep under a control and three hypnotics, with bars for the SE of each mean" x="Treatment" y="Extra sleep (hours)" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Mean and standard error in each x class (errorbar/summary-bin) Summary error bars after binning x when you need mean and uncertainty together. Tags: errorbar, summary_bin, bin, mean-se, line, stat Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "parent": 70.5, "child": 61.7 }, { "parent": 68.5, "child": 61.7 }, { "parent": 65.5, "child": 61.7 }, { "parent": 64.5, "child": 61.7 }, { "parent": 64, "child": 61.7 }, { "parent": 67.5, "child": 62.2 }, { "parent": 67.5, "child": 62.2 }, { "parent": 67.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 64.5, "child": 62.2 }, { "parent": 70.5, "child": 63.2 }, { "parent": 69.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "parent" }, "y": { "field": "child" } }, "inspect": false, "params": { "alpha": 0.15, "size": 2.4 } }, { "geom": "errorbar", "stat": "summary_bin", "position": "identity", "aes": { "x": { "field": "parent" }, "y": { "field": "child" } }, "params": { "binwidth": 1, "boundary": 0, "width": 0.35, "linewidth": 1.4 } }, { "geom": "line", "stat": "summary_bin", "position": "identity", "aes": { "x": { "field": "parent" }, "y": { "field": "child" } }, "params": { "binwidth": 1, "boundary": 0, "linewidth": 1.6 } } ], "labs": { "title": "Mean and standard error in each x class", "subtitle": "Mean child height ± one standard error in each one-inch class of mid-parent height", "x": "Mid-parent height (inches)", "y": "Child height (inches)" }, "theme": "classic" } // note: inline data truncated (908 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomErrorbar, GeomLine, GeomPoint, GGPlot, Inspect, Labs, registerSummaryBin, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerSummaryBin(); import { galtonHeights } from "./data.js"; </script> <GGPlot data={galtonHeights} aes={{ x: "parent", y: "child" }} width={640} height={400} > <GeomPoint alpha={0.15} size={2.4} inspect={false} /> <GeomErrorbar stat="summary_bin" binwidth={1} boundary={0} width={0.35} linewidth={1.4} /> <GeomLine stat="summary_bin" binwidth={1} boundary={0} linewidth={1.6} /> <ThemeClassic /> <Labs title="Mean and standard error in each x class" subtitle="Mean child height ± one standard error in each one-inch class of mid-parent height" x="Mid-parent height (inches)" y="Child height (inches)" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Facets with ordered side strips (facet/ordered-side-strips) Facet strips ordered to match the sample order of the experiment. Tags: facet, order, labels, strip, wrap, col Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "sample": "A", "cells": 0, "squares": 213 }, { "sample": "A", "cells": 1, "squares": 128 }, { "sample": "A", "cells": 2, "squares": 37 }, { "sample": "A", "cells": 3, "squares": 18 }, { "sample": "A", "cells": 4, "squares": 3 }, { "sample": "A", "cells": 5, "squares": 1 }, { "sample": "B", "cells": 0, "squares": 103 }, { "sample": "B", "cells": 1, "squares": 143 }, { "sample": "B", "cells": 2, "squares": 98 }, { "sample": "B", "cells": 3, "squares": 42 }, { "sample": "B", "cells": 4, "squares": 8 }, { "sample": "B", "cells": 5, "squares": 4 }, { "sample": "B", "cells": 6, "squares": 2 }, { "sample": "C", "cells": 0, "squares": 75 }, { "sample": "C", "cells": 1, "squares": 103 }, { "sample": "C", "cells": 2, "squares": 121 }, { "sample": "C", "cells": 3, "squares": 54 }, { "sample": "C", "cells": 4, "squares": 30 }, { "sample": "C", "cells": 5, "squares": 13 }, { "sample": "C", "cells": 6, "squares": 2 } ] }, "layers": [ { "geom": "col", "stat": "identity", "position": "stack", "aes": { "x": { "field": "cells" }, "y": { "field": "squares" }, "fill": { "field": "sample" } }, "params": { "alpha": 0.9 } } ], "facet": { "wrap": { "field": "sample", "levels": [ "A", "B", "C", "D" ], "labels": { "A": "Sample A", "B": "Sample B", "C": "Sample C", "D": "Sample D" } }, "ncol": 1, "strip": { "position": "left" } }, "scales": { "fill": { "type": "ordinal", "domain": [ "A", "B", "C", "D" ], "scheme": "flexoki" } }, "guides": { "fill": { "type": "none" } }, "labs": { "title": "Facets with ordered side strips", "subtitle": "Yeast counts under a microscope, one panel per sample (A–D)", "x": "Cells in a square", "y": "Squares" }, "theme": "light" } // note: inline data truncated (16 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { FacetWrap, GeomCol, GGPlot, GuideNone, Inspect, Labs, ScaleFillDiscrete, ThemeLight, } from "@ggsvelte/svelte"; import { SAMPLE_LABELS, SAMPLES, yeastCounts } from "./data.js"; </script> <GGPlot data={yeastCounts} aes={{ x: "cells", y: "squares", fill: "sample" }} width={640} height={400} > <GeomCol alpha={0.9} /> <FacetWrap field={{ field: "sample", levels: [...SAMPLES], labels: { ...SAMPLE_LABELS }, }} ncol={1} strip={{ position: "left" }} /> <ScaleFillDiscrete domain={[...SAMPLES]} scheme="flexoki" /> <ThemeLight /> <GuideNone channel="fill" /> <Labs title="Facets with ordered side strips" subtitle="Yeast counts under a microscope, one panel per sample (A–D)" x="Cells in a square" y="Squares" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## One panel per parent–child pair type (facet/wrap) Facet wrap when a grouping field should become a grid of small multiples. Tags: facet, histogram, bin, wrap, weight Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pair": "Father → Son", "child": 59.5, "n": 2 }, { "pair": "Father → Son", "child": 60.5, "n": 1.5 }, { "pair": "Father → Son", "child": 61.5, "n": 3.5 }, { "pair": "Father → Son", "child": 62.5, "n": 20.5 }, { "pair": "Father → Son", "child": 63.5, "n": 38.5 }, { "pair": "Father → Son", "child": 64.5, "n": 61.5 }, { "pair": "Father → Son", "child": 65.5, "n": 89.5 }, { "pair": "Father → Son", "child": 66.5, "n": 148 }, { "pair": "Father → Son", "child": 67.5, "n": 173.5 }, { "pair": "Father → Son", "child": 68.5, "n": 149.5 }, { "pair": "Father → Son", "child": 69.5, "n": 128 }, { "pair": "Father → Son", "child": 70.5, "n": 108 }, { "pair": "Father → Son", "child": 71.5, "n": 63 }, { "pair": "Father → Son", "child": 72.5, "n": 42 }, { "pair": "Father → Son", "child": 73.5, "n": 29 }, { "pair": "Father → Son", "child": 74.5, "n": 8.5 }, { "pair": "Father → Son", "child": 75.5, "n": 4 }, { "pair": "Father → Son", "child": 76.5, "n": 4 }, { "pair": "Father → Son", "child": 77.5, "n": 3 }, { "pair": "Father → Son", "child": 78.5, "n": 0.5 } ] }, "layers": [ { "geom": "bar", "stat": "bin", "position": "stack", "aes": { "x": { "field": "child" }, "y": { "stat": "count" }, "weight": { "field": "n" } }, "params": { "bins": 18 } } ], "facet": { "wrap": { "field": "pair" }, "ncol": 2 }, "labs": { "title": "One panel per parent–child pair type", "subtitle": "Child height histograms for father→son, father→daughter, mother→son, mother→daughter", "x": "Child's height (inches)", "y": "Children" }, "theme": "ggplot2" } // note: inline data truncated (61 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { FacetWrap, GeomHistogram, GGPlot, Inspect, Labs, ThemeGgplot2, } from "@ggsvelte/svelte"; import { familyHeights } from "./data.js"; </script> <GGPlot data={familyHeights} aes={{ x: "child", weight: "n" }} width={640} height={400} > <GeomHistogram bins={18} /> <FacetWrap field="pair" ncol={2} /> <ThemeGgplot2 /> <Labs title="One panel per parent–child pair type" subtitle="Child height histograms for father→son, father→daughter, mother→son, mother→daughter" x="Child's height (inches)" y="Children" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Facets with free y scales (facet/wrap-free-y) Free y scales when panels share an x axis but live on different magnitudes. Tags: facet, free-scales, line, wrap Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1629, "measure": "Male / female ratio", "value": 1.1142 }, { "year": 1630, "measure": "Male / female ratio", "value": 1.09 }, { "year": 1631, "measure": "Male / female ratio", "value": 1.078 }, { "year": 1632, "measure": "Male / female ratio", "value": 1.088 }, { "year": 1633, "measure": "Male / female ratio", "value": 1.0659 }, { "year": 1634, "measure": "Male / female ratio", "value": 1.0446 }, { "year": 1635, "measure": "Male / female ratio", "value": 1.0361 }, { "year": 1636, "measure": "Male / female ratio", "value": 1.0678 }, { "year": 1637, "measure": "Male / female ratio", "value": 1.0552 }, { "year": 1638, "measure": "Male / female ratio", "value": 1.0822 }, { "year": 1639, "measure": "Male / female ratio", "value": 1.1217 }, { "year": 1640, "measure": "Male / female ratio", "value": 1.0349 }, { "year": 1641, "measure": "Male / female ratio", "value": 1.0519 }, { "year": 1642, "measure": "Male / female ratio", "value": 1.112 }, { "year": 1643, "measure": "Male / female ratio", "value": 1.0381 }, { "year": 1644, "measure": "Male / female ratio", "value": 1.0275 }, { "year": 1645, "measure": "Male / female ratio", "value": 1.0327 }, { "year": 1646, "measure": "Male / female ratio", "value": 1.1099 }, { "year": 1647, "measure": "Male / female ratio", "value": 1.0735 }, { "year": 1648, "measure": "Male / female ratio", "value": 1.0572 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "measure" } }, "params": { "linewidth": 1.6 } } ], "facet": { "wrap": { "field": "measure", "levels": [ "Male / female ratio", "Male christenings", "All burials", "Plague deaths" ] }, "ncol": 2, "scales": "free_y" }, "scales": { "x": { "labels": "d" } }, "guides": { "color": { "type": "none" } }, "labs": { "title": "Facets with free y scales", "subtitle": "Christenings and burials on their own vertical ranges so neither series flattens", "x": "Year", "y": "" }, "theme": "fivethirtyeight" } // note: inline data truncated (308 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { FacetWrap, GeomLine, GGPlot, GuideNone, Inspect, Labs, ScaleXContinuous, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { londonBills } from "./data.js"; </script> <GGPlot data={londonBills} aes={{ x: "year", y: "value", color: "measure" }} width={640} height={400} > <GeomLine linewidth={1.6} /> <FacetWrap field={{ field: "measure", levels: [ "Male / female ratio", "Male christenings", "All burials", "Plague deaths", ], }} ncol={2} scales="free_y" /> <ScaleXContinuous labels="d" /> <ThemeFivethirtyeight /> <GuideNone channel="color" /> <Labs title="Facets with free y scales" subtitle="Christenings and burials on their own vertical ranges so neither series flattens" x="Year" y="" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Frequency polygon through bin centres (freqpoly/basic) Frequency polygons when you want a histogram's bins without the solid bars. Tags: freqpoly, bin, line, stat, rule, annotation, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "velocity": 850 }, { "velocity": 740 }, { "velocity": 900 }, { "velocity": 1070 }, { "velocity": 930 }, { "velocity": 850 }, { "velocity": 950 }, { "velocity": 980 }, { "velocity": 980 }, { "velocity": 880 }, { "velocity": 1000 }, { "velocity": 980 }, { "velocity": 930 }, { "velocity": 650 }, { "velocity": 760 }, { "velocity": 810 }, { "velocity": 1000 }, { "velocity": 1000 }, { "velocity": 960 }, { "velocity": 960 } ] }, "layers": [ { "geom": "line", "stat": "bin", "position": "identity", "aes": { "x": { "field": "velocity" }, "y": { "stat": "count" } }, "params": { "binwidth": 40, "linewidth": 1.4 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#d14d41" } }, "params": { "xintercept": 734.5, "linewidth": 1.2 } } ], "labs": { "title": "Frequency polygon through bin centres", "subtitle": "Same speed-of-light runs as the histogram, joined at the tops of the bins", "x": "Velocity (km/s − 299,000)", "y": "Runs" }, "theme": "fivethirtyeight" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomFreqpoly, GeomRule, GGPlot, Inspect, Labs, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { michelson } from "./data.js"; </script> <GGPlot data={michelson} aes={{ x: "velocity" }} width={640} height={400}> <GeomFreqpoly binwidth={40} linewidth={1.4} /> <GeomRule xintercept={734.5} linewidth={1.2} aes={{ color: { value: "#d14d41" } }} /> <ThemeFivethirtyeight /> <Labs title="Frequency polygon through bin centres" subtitle="Same speed-of-light runs as the histogram, joined at the tops of the bins" x="Velocity (km/s − 299,000)" y="Runs" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Hex bins for a dense cloud (hex/basic) Hex bins as an alternative to rectangular 2D bins. Tags: hex, heatmap, density Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "long": 181.62, "lat": -20.42 }, { "long": 181.03, "lat": -20.62 }, { "long": 184.1, "lat": -26 }, { "long": 181.66, "lat": -17.97 }, { "long": 181.96, "lat": -20.42 }, { "long": 184.31, "lat": -19.68 }, { "long": 166.1, "lat": -11.7 }, { "long": 181.93, "lat": -28.11 }, { "long": 181.74, "lat": -28.74 }, { "long": 179.59, "lat": -17.47 }, { "long": 180.69, "lat": -21.44 }, { "long": 167, "lat": -12.26 }, { "long": 182.11, "lat": -18.54 }, { "long": 181.66, "lat": -21 }, { "long": 169.92, "lat": -20.7 }, { "long": 184.95, "lat": -15.94 }, { "long": 165.96, "lat": -13.64 }, { "long": 181.5, "lat": -17.83 }, { "long": 179.78, "lat": -23.5 }, { "long": 180.31, "lat": -22.63 } ] }, "layers": [ { "geom": "hex", "stat": "bin_hex", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "lat" }, "fill": { "stat": "count" } }, "params": { "bins": 18 } } ], "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }, "labs": { "title": "Hex bins for a dense cloud", "subtitle": "A thousand earthquakes by location off Fiji", "x": "Longitude (°E)", "y": "Latitude (°)", "fill": "Events" }, "theme": "minimal" } // note: inline data truncated (980 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomHex, GGPlot, Inspect, Labs, ScaleFillContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { fijiQuakes } from "./data.js"; </script> <GGPlot data={fijiQuakes} aes={{ x: "long", y: "lat" }} width={640} height={400} > <GeomHex bins={18} /> <ScaleFillContinuous scheme="viridis" /> <ThemeMinimal /> <Labs title="Hex bins for a dense cloud" subtitle="A thousand earthquakes by location off Fiji" x="Longitude (°E)" y="Latitude (°)" fill="Events" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Histogram of a hundred experimental runs (histogram/basic) A basic histogram for a single continuous measurement. Tags: histogram, bins, stat, rule, annotation, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "velocity": 850 }, { "velocity": 740 }, { "velocity": 900 }, { "velocity": 1070 }, { "velocity": 930 }, { "velocity": 850 }, { "velocity": 950 }, { "velocity": 980 }, { "velocity": 980 }, { "velocity": 880 }, { "velocity": 1000 }, { "velocity": 980 }, { "velocity": 930 }, { "velocity": 650 }, { "velocity": 760 }, { "velocity": 810 }, { "velocity": 1000 }, { "velocity": 1000 }, { "velocity": 960 }, { "velocity": 960 } ] }, "layers": [ { "geom": "bar", "stat": "bin", "position": "stack", "aes": { "x": { "field": "velocity" }, "y": { "stat": "count" } }, "params": { "binwidth": 40 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#d14d41" } }, "params": { "xintercept": 734.5, "linewidth": 1.2 } } ], "labs": { "title": "Histogram of a hundred experimental runs", "subtitle": "Speed of light, km/s less 299,000", "x": "Velocity (km/s − 299,000)", "y": "Runs" }, "theme": "fivethirtyeight" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomHistogram, GeomRule, GGPlot, Inspect, Labs, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { michelson } from "./data.js"; </script> <GGPlot data={michelson} aes={{ x: "velocity" }} width={640} height={400}> <GeomHistogram binwidth={40} /> <GeomRule xintercept={734.5} linewidth={1.2} aes={{ color: { value: "#d14d41" } }} /> <ThemeFivethirtyeight /> <Labs title="Histogram of a hundred experimental runs" subtitle="Speed of light, km/s less 299,000" x="Velocity (km/s − 299,000)" y="Runs" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## One horizontal threshold (hline/threshold) Horizontal rules for thresholds, targets, or known reference values across a panel. Tags: hline, rule, annotation, line, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "trial": 1, "density": 5.5 }, { "trial": 2, "density": 5.61 }, { "trial": 3, "density": 4.88 }, { "trial": 4, "density": 5.07 }, { "trial": 5, "density": 5.26 }, { "trial": 6, "density": 5.55 }, { "trial": 7, "density": 5.36 }, { "trial": 8, "density": 5.29 }, { "trial": 9, "density": 5.58 }, { "trial": 10, "density": 5.65 }, { "trial": 11, "density": 5.57 }, { "trial": 12, "density": 5.53 }, { "trial": 13, "density": 5.62 }, { "trial": 14, "density": 5.29 }, { "trial": 15, "density": 5.44 }, { "trial": 16, "density": 5.34 }, { "trial": 17, "density": 5.79 }, { "trial": 18, "density": 5.1 }, { "trial": 19, "density": 5.27 }, { "trial": 20, "density": 5.39 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "trial" }, "y": { "field": "density" } }, "params": { "size": 2.2, "alpha": 0.55 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#2f6fed" } }, "params": { "yintercept": 5.517, "linewidth": 2.6 } } ], "labs": { "title": "One horizontal threshold", "subtitle": "Twenty-nine density readings with a line at the modern earth density 5.517", "x": "Determination", "y": "Density of the earth (water = 1)" }, "theme": "minimal" } // note: inline data truncated (9 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomHline, GeomPoint, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { earthDensity } from "./data.js"; </script> <GGPlot data={earthDensity} aes={{ x: "trial", y: "density" }} width={640} height={400} > <GeomPoint size={2.2} alpha={0.55} /> <GeomHline yintercept={5.517} linewidth={2.6} aes={{ color: { value: "#2f6fed" } }} /> <ThemeMinimal /> <Labs title="One horizontal threshold" subtitle="Twenty-nine density readings with a line at the modern earth density 5.517" x="Determination" y="Density of the earth (water = 1)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Interval selection and zoom (interaction/brush-zoom) 333 Palmer penguins: brush a rectangle to select, or zoom into the crowded middle where the species overlap. Tags: interaction, select, interval, brush, zoom, events Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "adelie-001", "species": "Adelie", "bill": 39.1, "mass": 3750 }, { "id": "adelie-002", "species": "Adelie", "bill": 39.5, "mass": 3800 }, { "id": "adelie-003", "species": "Adelie", "bill": 40.3, "mass": 3250 }, { "id": "adelie-004", "species": "Adelie", "bill": 36.7, "mass": 3450 }, { "id": "adelie-005", "species": "Adelie", "bill": 39.3, "mass": 3650 }, { "id": "adelie-006", "species": "Adelie", "bill": 38.9, "mass": 3625 }, { "id": "adelie-007", "species": "Adelie", "bill": 39.2, "mass": 4675 }, { "id": "adelie-008", "species": "Adelie", "bill": 41.1, "mass": 3200 }, { "id": "adelie-009", "species": "Adelie", "bill": 38.6, "mass": 3800 }, { "id": "adelie-010", "species": "Adelie", "bill": 34.6, "mass": 4400 }, { "id": "adelie-011", "species": "Adelie", "bill": 36.6, "mass": 3700 }, { "id": "adelie-012", "species": "Adelie", "bill": 38.7, "mass": 3450 }, { "id": "adelie-013", "species": "Adelie", "bill": 42.5, "mass": 4500 }, { "id": "adelie-014", "species": "Adelie", "bill": 34.4, "mass": 3325 }, { "id": "adelie-015", "species": "Adelie", "bill": 46, "mass": 4200 }, { "id": "adelie-016", "species": "Adelie", "bill": 37.8, "mass": 3400 }, { "id": "adelie-017", "species": "Adelie", "bill": 37.7, "mass": 3600 }, { "id": "adelie-018", "species": "Adelie", "bill": 35.9, "mass": 3800 }, { "id": "adelie-019", "species": "Adelie", "bill": 38.2, "mass": 3950 }, { "id": "adelie-020", "species": "Adelie", "bill": 38.8, "mass": 3800 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "bill" }, "y": { "field": "mass" }, "color": { "field": "species" } }, "params": { "size": 2.5, "alpha": 0.8 } } ], "labs": { "title": "Select an interval or brush to zoom", "subtitle": "333 Palmer penguins", "x": "Bill length (mm)", "y": "Body mass (g)", "color": "Species" }, "theme": "light" } // note: inline data truncated (313 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, ThemeLight, } from "@ggsvelte/svelte"; import { field } from "./data.js"; let selectionStatus = $state("Choose Select area and drag a rectangle."); let zoomStatus = $state("Choose Zoom area to rescale, then Reset zoom."); </script> <!-- Zoom area is deliberately armed from the tool rail; a finished drag inverts the rect through the trained scales into explicit continuous domains (an intentional respec). prevScales flow through the commit path, so the group colors NEVER shift while zooming. Double-click resets. --> <GGPlot data={field} aes={{ x: "bill", y: "mass", color: "species" }} select={{ type: "interval", mode: "xy", persistent: true }} zoom={{ mode: "xy" }} onselect={(event) => { if (event.mode !== "point") { selectionStatus = event.phase === "clear" ? "Selection cleared." : `${event.phase}: ${String(event.keys.length)} keyed rows, ${String(event.lineageCount)} marks`; } }} onzoom={(event) => { zoomStatus = event.phase === "clear" ? "Zoom reset." : `Zoomed to x ${event.domains?.x?.map((value) => value.toFixed(2)).join("–") ?? "all"}, y ${event.domains?.y?.map((value) => value.toFixed(2)).join("–") ?? "all"}.`; }} width="container" height={400} > <GeomPoint size={2.5} alpha={0.8} /> <ThemeLight /> <Labs title="Select an interval or brush to zoom" x="x" y="y" color="Group" /> <Inspect /> </GGPlot> <!-- Visual callback evidence only. GGPlot owns the single concise live region. --> <div class="event-status gg-demo-chrome"> <p><strong>Selection:</strong> {selectionStatus}</p> <p><strong>Zoom:</strong> {zoomStatus}</p> </div> <style> .event-status { max-width: 640px; margin-top: 0.6rem; color: var(--muted, #59636e); font: 0.82rem/1.4 var(--gg-font-family, system-ui, sans-serif); } .event-status p { margin: 0.15rem 0; } </style> ``` ## One interval, applied in every panel (interaction/facet-intervals) 333 Palmer penguins split by island, with a coordinate interval selection that holds across all the facets at once. Tags: interaction, facet, select, interval, brush, keyboard Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "adelie-001", "island": "Torgersen", "bill": 39.1, "mass": 3750 }, { "id": "adelie-002", "island": "Torgersen", "bill": 39.5, "mass": 3800 }, { "id": "adelie-003", "island": "Torgersen", "bill": 40.3, "mass": 3250 }, { "id": "adelie-004", "island": "Torgersen", "bill": 36.7, "mass": 3450 }, { "id": "adelie-005", "island": "Torgersen", "bill": 39.3, "mass": 3650 }, { "id": "adelie-006", "island": "Torgersen", "bill": 38.9, "mass": 3625 }, { "id": "adelie-007", "island": "Torgersen", "bill": 39.2, "mass": 4675 }, { "id": "adelie-008", "island": "Torgersen", "bill": 41.1, "mass": 3200 }, { "id": "adelie-009", "island": "Torgersen", "bill": 38.6, "mass": 3800 }, { "id": "adelie-010", "island": "Torgersen", "bill": 34.6, "mass": 4400 }, { "id": "adelie-011", "island": "Torgersen", "bill": 36.6, "mass": 3700 }, { "id": "adelie-012", "island": "Torgersen", "bill": 38.7, "mass": 3450 }, { "id": "adelie-013", "island": "Torgersen", "bill": 42.5, "mass": 4500 }, { "id": "adelie-014", "island": "Torgersen", "bill": 34.4, "mass": 3325 }, { "id": "adelie-015", "island": "Torgersen", "bill": 46, "mass": 4200 }, { "id": "adelie-016", "island": "Biscoe", "bill": 37.8, "mass": 3400 }, { "id": "adelie-017", "island": "Biscoe", "bill": 37.7, "mass": 3600 }, { "id": "adelie-018", "island": "Biscoe", "bill": 35.9, "mass": 3800 }, { "id": "adelie-019", "island": "Biscoe", "bill": 38.2, "mass": 3950 }, { "id": "adelie-020", "island": "Biscoe", "bill": 38.8, "mass": 3800 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "bill" }, "y": { "field": "mass" } }, "params": { "size": 3.2, "alpha": 0.8 } } ], "facet": { "wrap": { "field": "island" }, "ncol": 3 }, "labs": { "title": "Coordinate interval selection across facets", "subtitle": "333 Palmer penguins by island — one interval, applied in every panel", "x": "Bill length (mm)", "y": "Body mass (g)" }, "theme": "clean" } // note: inline data truncated (313 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import type { FacetIntervalPreset } from "@ggsvelte/svelte"; import { FacetWrap, GeomPoint, GGPlot, Inspect, Labs, ThemeClean, } from "@ggsvelte/svelte"; import { observations } from "./data.js"; const presets: readonly FacetIntervalPreset[] = [ "independent", "union", "cross-panel", ]; let preset = $state<FacetIntervalPreset>("independent"); let status = $state("Choose Select area, then draw inside any facet."); const descriptions: Record<FacetIntervalPreset, string> = { independent: "The newest interval replaces only its origin panel.", union: "Each panel keeps its own interval; selected keys are combined.", "cross-panel": "One semantic domain is projected through every compatible panel.", }; </script> <div class="facet-interval-demo"> <fieldset class="gg-demo-chrome"> <legend>Facet interval behavior</legend> {#each presets as option} <label> <input type="radio" name="facet-preset" value={option} bind:group={preset} /> <span>{option}</span> </label> {/each} </fieldset> <p class="explanation gg-demo-chrome">{descriptions[preset]}</p> <p class="status gg-demo-chrome" role="status" aria-live="polite">{status}</p> <GGPlot data={observations} aes={{ x: "bill", y: "mass" }} select={{ type: "interval", mode: "xy", persistent: true, preset }} width="container" height={430} onselect={(event) => { if (event.mode === "point") return; status = event.phase === "clear" ? "Facet intervals cleared." : `${event.phase}: ${String(event.keys.length)} rows selected from ${event.panelId}.`; }} > <GeomPoint size={3.8} /> <FacetWrap field="island" ncol={3} /> <ThemeClean /> <Labs title="Palmer penguins by island" subtitle={`${preset} interval semantics`} x="Bill length (mm)" y="Body mass (g)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> </div> <style> .facet-interval-demo { display: grid; gap: 0.65rem; } fieldset { display: flex; flex-wrap: wrap; gap: 0.45rem 0.8rem; margin: 0; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); border-radius: 0.55rem; padding: 0.7rem 0.85rem 0.8rem; color: var(--fg, #17202a); font: 0.86rem/1.4 var(--gg-font-family, system-ui, sans-serif); } legend { padding-inline: 0.3rem; font-weight: 700; } label { display: inline-flex; min-height: 44px; align-items: center; gap: 0.38rem; border-radius: 0.4rem; padding: 0.3rem 0.45rem; } label:has(:focus-visible) { outline: 3px solid color-mix(in srgb, var(--accent, #2c7fb8) 45%, transparent); outline-offset: 1px; } input { width: 1rem; height: 1rem; } .explanation, .status { margin: 0; color: var(--muted, #59636e); font: 0.84rem/1.4 var(--gg-font-family, system-ui, sans-serif); } .status { min-height: 1.2rem; } </style> ``` ## Filter series from the legend (interaction/legend-filter) Legend filter when readers need to hide series without losing colour identity. Tags: interaction, legend, filter, stable-color, keyboard, touch Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "debt-1770", "year": 1770, "series": "National debt", "value": 13.96 }, { "id": "debt-1771", "year": 1771, "series": "National debt", "value": 13.97 }, { "id": "debt-1772", "year": 1772, "series": "National debt", "value": 13.67 }, { "id": "debt-1773", "year": 1773, "series": "National debt", "value": 13.68 }, { "id": "debt-1774", "year": 1774, "series": "National debt", "value": 13.69 }, { "id": "debt-1775", "year": 1775, "series": "National debt", "value": 14.88 }, { "id": "debt-1776", "year": 1776, "series": "National debt", "value": 16.07 }, { "id": "debt-1777", "year": 1777, "series": "National debt", "value": 16.97 }, { "id": "debt-1778", "year": 1778, "series": "National debt", "value": 18.16 }, { "id": "debt-1779", "year": 1779, "series": "National debt", "value": 19.06 }, { "id": "debt-1780", "year": 1780, "series": "National debt", "value": 20.55 }, { "id": "debt-1781", "year": 1781, "series": "National debt", "value": 21.74 }, { "id": "debt-1782", "year": 1782, "series": "National debt", "value": 22.94 }, { "id": "debt-1783", "year": 1783, "series": "National debt", "value": 24.43 }, { "id": "debt-1784", "year": 1784, "series": "National debt", "value": 24.43 }, { "id": "debt-1785", "year": 1785, "series": "National debt", "value": 24.44 }, { "id": "debt-1786", "year": 1786, "series": "National debt", "value": 24.44 }, { "id": "debt-1787", "year": 1787, "series": "National debt", "value": 24.45 }, { "id": "debt-1788", "year": 1788, "series": "National debt", "value": 24.46 }, { "id": "debt-1789", "year": 1789, "series": "National debt", "value": 24.46 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "series" } }, "params": { "linewidth": 2.2 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "series" } }, "params": { "size": 2.6 } } ], "scales": { "x": { "labels": "d" } }, "labs": { "title": "Filter series from the legend", "subtitle": "Debt, revenue, and expenditure", "x": "Year", "y": "Playfair's index units", "color": "Series" }, "theme": "fivethirtyeight" } // note: inline data truncated (142 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomPoint, GGPlot, GuideLegend, Inspect, Labs, ScaleXContinuous, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { britishFinances } from "./data.js"; let status = $state( "All three series are included. Toggle a checkbox to filter the grammar pipeline.", ); </script> <div class="legend-filter-demo"> <p class="status gg-demo-chrome" role="status" aria-live="polite">{status}</p> <GGPlot data={britishFinances} aes={{ x: "year", y: "value", color: "series" }} width="container" height={430} onlegendfilter={(event) => { status = event.phase === "clear" || event.clause === null ? "All series restored. Color identity did not change." : `${String(event.clause.values.length)} ${event.clause.values.length === 1 ? "series is" : "series are"} hidden; the legend keeps every series available.`; }} > <GeomLine linewidth={2.2} /> <GeomPoint size={3.2} /> <ScaleXContinuous labels="d" /> <ThemeFivethirtyeight /> <GuideLegend channel="color" filter /> <Labs title="Filter series from the legend" subtitle="Debt, revenue, and expenditure" x="Year" y="Playfair's index units" color="Series" /> <Inspect mode="x" pin /> </GGPlot> <p class="note gg-demo-chrome"> <strong>Why this is filtering:</strong> statistics, facets, and domains see only the visible rows. For visual comparison without changing data, use <code><GuideLegend focus /></code> instead. </p> </div> <style> .legend-filter-demo { display: grid; gap: 0.8rem; } .status, .note { margin: 0; color: inherit; font: 0.86rem/1.45 var(--gg-font-family, system-ui, sans-serif); } .status { border-inline-start: 3px solid var(--accent, #2c7fb8); padding: 0.55rem 0.75rem; background: color-mix(in srgb, var(--accent, #2c7fb8) 7%, transparent); } .note { color: var(--muted, #59636e); } code { color: inherit; } </style> ``` ## Focus a legend group without changing the data (interaction/legend-focus) Three of Playfair's commodity series. Focusing a group dims the others rather than dropping them, so the scales never move under you. Tags: interaction, legend, focus, controller, linked-views, canvas, keyboard, touch Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "stocks-1770", "year": 1770, "series": "Price of stocks", "value": 79.6 }, { "id": "stocks-1771", "year": 1771, "series": "Price of stocks", "value": 76.34 }, { "id": "stocks-1772", "year": 1772, "series": "Price of stocks", "value": 78.72 }, { "id": "stocks-1773", "year": 1773, "series": "Price of stocks", "value": 75.76 }, { "id": "stocks-1774", "year": 1774, "series": "Price of stocks", "value": 77.25 }, { "id": "stocks-1775", "year": 1775, "series": "Price of stocks", "value": 74.28 }, { "id": "stocks-1776", "year": 1776, "series": "Price of stocks", "value": 68.35 }, { "id": "stocks-1777", "year": 1777, "series": "Price of stocks", "value": 63.9 }, { "id": "stocks-1778", "year": 1778, "series": "Price of stocks", "value": 55.89 }, { "id": "stocks-1779", "year": 1779, "series": "Price of stocks", "value": 50.84 }, { "id": "stocks-1780", "year": 1780, "series": "Price of stocks", "value": 49.96 }, { "id": "stocks-1781", "year": 1781, "series": "Price of stocks", "value": 48.77 }, { "id": "stocks-1782", "year": 1782, "series": "Price of stocks", "value": 49.08 }, { "id": "stocks-1783", "year": 1783, "series": "Price of stocks", "value": 49.97 }, { "id": "stocks-1784", "year": 1784, "series": "Price of stocks", "value": 53.25 }, { "id": "stocks-1785", "year": 1785, "series": "Price of stocks", "value": 55.92 }, { "id": "stocks-1786", "year": 1786, "series": "Price of stocks", "value": 55.63 }, { "id": "stocks-1787", "year": 1787, "series": "Price of stocks", "value": 59.2 }, { "id": "stocks-1788", "year": 1788, "series": "Price of stocks", "value": 63.37 }, { "id": "stocks-1789", "year": 1789, "series": "Price of stocks", "value": 66.04 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "series" } }, "params": { "size": 4 } } ], "scales": { "x": { "labels": "d" } }, "labs": { "title": "Focus a legend group without changing the data", "x": "Year", "y": "Playfair's index units", "color": "Series" }, "theme": "few" } // note: inline data truncated (145 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { createPlotInteraction, GeomLine, GeomPoint, GGPlot, GuideLegend, Inspect, Labs, ScaleXContinuous, ThemeFew, } from "@ggsvelte/svelte"; import { rows } from "./data.js"; const scope = { keys: "legend-focus-rows" } as const; const mapping = { x: "year", y: "value", color: "series" } as const; const interaction = createPlotInteraction<string>(); const emphasized = $derived(interaction.emphasized(scope)); let status = $state( "Hover a legend entry to preview. Click, tap, Enter, or Space to pin it.", ); function describe(event: { phase: "change" | "clear"; state?: "transient" | "committed"; label?: string; keys?: readonly string[]; source: string; }): void { status = event.phase === "clear" ? `Legend focus cleared by ${event.source}.` : `${event.label} ${event.state === "committed" ? "pinned across three views" : "previewed here"}; ${String(event.keys?.length ?? 0)} rows.`; } </script> <div class="legend-focus-demo"> <p class="status gg-demo-chrome" role="status" aria-live="polite">{status}</p> <div class="plots"> <GGPlot data={rows} aes={mapping} {interaction} interactionScope={scope} width="container" height={310} onlegendfocus={describe} > <GeomPoint size={4} /> <ScaleXContinuous labels="d" /> <ThemeFew /> <GuideLegend channel="color" focus /> <Labs title="SVG points" x="Year" y="Index units" color="Series" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> <GGPlot data={rows} aes={mapping} {interaction} interactionScope={scope} width="container" height={310} onlegendfocus={describe} > <GeomPoint size={4} render="canvas" /> <ScaleXContinuous labels="d" /> <ThemeFew /> <GuideLegend channel="color" focus /> <Labs title="Canvas points" x="Year" y="Index units" color="Series" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> <GGPlot data={rows} aes={mapping} {interaction} interactionScope={scope} width="container" height={310} onlegendfocus={describe} > <GeomLine linewidth={2} /> <GeomPoint /> <ScaleXContinuous labels="d" /> <ThemeFew /> <GuideLegend channel="color" focus /> <Labs title="SVG lines" x="Year" y="Index units" color="Series" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> </div> <div class="summary gg-demo-chrome"> <strong>{emphasized.length} rows focused</strong> <button type="button" disabled={emphasized.length === 0} onclick={() => interaction.clearEmphasis({ scope, source: "programmatic" })} >Clear from ordinary Svelte UI</button > </div> </div> <style> .legend-focus-demo { display: grid; gap: 1rem; } .plots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.8rem; } .status, .summary { margin: 0; color: var(--fg, #17202a); font: 0.86rem/1.4 var(--gg-font-family, system-ui, sans-serif); } .summary { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.75rem 0.9rem; border: 1px solid color-mix(in srgb, currentColor 16%, transparent); border-radius: 0.5rem; } button { min-height: 2.25rem; border: 1px solid color-mix(in srgb, currentColor 28%, transparent); border-radius: 0.4rem; padding: 0.4rem 0.7rem; background: var(--surface, white); color: inherit; font: inherit; } @media (max-width: 900px) { .plots { grid-template-columns: 1fr; } } </style> ``` ## Link plots, controls, and a table (interaction/linked-views) Five Palmer penguins per species, so every row a selection touches is visible in the table beside the chart. Tags: interaction, controller, linked-views, select, emphasis, table, svelte Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "adelie-001", "species": "Adelie", "flipper": 181, "mass": 3750 }, { "id": "adelie-030", "species": "Adelie", "flipper": 195, "mass": 3325 }, { "id": "adelie-059", "species": "Adelie", "flipper": 184, "mass": 2850 }, { "id": "adelie-088", "species": "Adelie", "flipper": 186, "mass": 4450 }, { "id": "adelie-117", "species": "Adelie", "flipper": 176, "mass": 3450 }, { "id": "chinstrap-001", "species": "Chinstrap", "flipper": 192, "mass": 3500 }, { "id": "chinstrap-014", "species": "Chinstrap", "flipper": 201, "mass": 4050 }, { "id": "chinstrap-027", "species": "Chinstrap", "flipper": 200, "mass": 3400 }, { "id": "chinstrap-040", "species": "Chinstrap", "flipper": 205, "mass": 4500 }, { "id": "chinstrap-053", "species": "Chinstrap", "flipper": 193, "mass": 3600 }, { "id": "gentoo-001", "species": "Gentoo", "flipper": 211, "mass": 4500 }, { "id": "gentoo-024", "species": "Gentoo", "flipper": 215, "mass": 5050 }, { "id": "gentoo-047", "species": "Gentoo", "flipper": 225, "mass": 5400 }, { "id": "gentoo-070", "species": "Gentoo", "flipper": 221, "mass": 5000 }, { "id": "gentoo-093", "species": "Gentoo", "flipper": 214, "mass": 4850 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "flipper" }, "y": { "field": "mass" }, "color": { "field": "species" } }, "params": { "size": 4, "alpha": 0.86 } } ], "labs": { "title": "Selection shared by plots, controls, and a table", "subtitle": "Five Palmer penguins per species, so every linked row fits in the table", "x": "Flipper length (mm)", "y": "Body mass (g)", "color": "Species" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { createPlotInteraction, GeomPoint, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { penguins, type PenguinRow } from "./data.js"; const scope = { keys: "penguin-id", x: "flipper-mm", y: "mass-g" } as const; // The emphasis control targets the second Adelie row. Derive it rather than // naming a key: an id that no longer exists would leave the button reading as // pressed while emphasizing nothing at all. const emphasisTarget = penguins.filter((row) => row.species === "Adelie")[1]; if (emphasisTarget === undefined) { throw new Error( "linked-views needs at least two Adelie rows for the emphasis control", ); } let rows = $state<readonly PenguinRow[]>(penguins); let showingAllRows = $state(true); let status = $state("Select a point in either plot, or use a control below."); const interaction = createPlotInteraction<string>({ onchange: (transition) => { status = `${transition.kind} changed by ${transition.source}; revision ${String(transition.revision)}.`; }, }); const selected = $derived(interaction.selected(scope)); const emphasized = $derived(interaction.emphasized(scope)); function selectSpecies(species: PenguinRow["species"]): void { interaction.setSelection( rows.filter((row) => row.species === species).map((row) => row.id), { scope, source: "programmatic" }, ); } function emphasize(id: string): void { interaction.setEmphasis([id], { scope, source: "programmatic" }); } function clearEmphasis(): void { interaction.clearEmphasis({ scope, source: "programmatic" }); } function replaceRows(): void { showingAllRows = !showingAllRows; rows = showingAllRows ? penguins : penguins.filter((row) => row.species !== "Chinstrap"); interaction.reconcileKeys( rows.map((row) => row.id), { scope, source: "programmatic" }, ); status = showingAllRows ? "Restored every row; stable selections remain linked." : "Removed Chinstrap rows and explicitly reconciled shared keys."; } function clearAll(): void { interaction.clearSelection({ scope, source: "programmatic" }); interaction.clearEmphasis({ scope, source: "programmatic" }); interaction.resetZoom({ scope, source: "programmatic" }); status = "Selection, emphasis, and zoom cleared."; } </script> <div class="linked-demo"> <div class="plots"> <GGPlot data={rows} aes={{ x: "flipper", y: "mass", color: "species" }} select={{ type: "point", multiple: true }} zoom={{ mode: "x" }} {interaction} interactionScope={scope} width="container" height={360} ariaLabel="Penguins by flipper length and body mass, colored by species" > <GeomPoint size={4} alpha={0.86} /> <ThemeMinimal /> <Labs title="Select in either view" x="Flipper length (mm)" y="Body mass (g)" color="Species" /> <Inspect mode="xy" pin maxDistance={24} identity="id" /> </GGPlot> <GGPlot data={rows} aes={{ x: "flipper", y: "mass" }} select={{ type: "point", multiple: true }} zoom={{ mode: "x" }} {interaction} interactionScope={scope} width="container" height={360} ariaLabel="Penguins by flipper length and body mass in a quieter style" > <GeomPoint size={3.2} color="#287271" alpha={0.68} /> <ThemeMinimal /> <Labs title="Minimal theme, linked selection" x="Flipper length (mm)" y="Body mass (g)" /> <Inspect mode="xy" pin maxDistance={24} identity="id" /> </GGPlot> </div> <section class="controls gg-demo-chrome" aria-labelledby="linked-controls-heading" > <div> <p class="eyebrow" id="linked-controls-heading">External controls</p> <p class="status">{status}</p> </div> <div class="button-row"> <button type="button" onclick={() => selectSpecies("Gentoo")} >Select Gentoo</button > <button type="button" aria-pressed={emphasized.includes(emphasisTarget.id)} onclick={() => emphasize(emphasisTarget.id)}>Emphasize Adelie 2</button > <button type="button" onclick={replaceRows}> {showingAllRows ? "Remove Chinstrap rows" : "Restore all rows"} </button> <button type="button" onclick={clearAll}>Clear all</button> </div> </section> <div class="table-wrap gg-demo-chrome"> <table> <caption> Linked semantic rows — {selected.length} selected, {emphasized.length} emphasized </caption> <thead> <tr ><th scope="col">Penguin</th><th scope="col">Species</th><th scope="col">Flipper</th ><th scope="col">Mass</th><th scope="col">Selection</th></tr > </thead> <tbody> {#each rows as row (row.id)} <tr aria-selected={selected.includes(row.id)} class:emphasized={emphasized.includes(row.id)} > <th scope="row">{row.id}</th> <td>{row.species}</td> <td>{row.flipper} mm</td> <td>{row.mass.toLocaleString()} g</td> <td> <button type="button" class="row-button" aria-pressed={selected.includes(row.id)} onmouseenter={() => emphasize(row.id)} onmouseleave={clearEmphasis} onfocus={() => emphasize(row.id)} onblur={clearEmphasis} onclick={() => interaction.toggleSelection(row.id, { scope })} >{selected.includes(row.id) ? "Selected" : "Select"}</button > </td> </tr> {/each} </tbody> </table> </div> </div> <style> .linked-demo { display: grid; gap: 1rem; } .plots { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } .controls { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.1rem; border: 1px solid color-mix(in srgb, var(--fg, #17202a) 16%, transparent); border-radius: 0.7rem; background: color-mix(in srgb, var(--surface, white) 94%, #287271 6%); } .eyebrow, .status { margin: 0; } .eyebrow { color: var(--fg, #17202a); font: 650 0.78rem/1.2 var(--gg-font-family, system-ui, sans-serif); letter-spacing: 0.05em; text-transform: uppercase; } .status { margin-top: 0.25rem; color: var(--muted, #59636e); font: 0.84rem/1.35 var(--gg-font-family, system-ui, sans-serif); } .button-row { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.45rem; } button { min-height: 2.25rem; padding: 0.42rem 0.72rem; border: 1px solid color-mix(in srgb, var(--fg, #17202a) 26%, transparent); border-radius: 0.42rem; background: var(--surface, white); color: var(--fg, #17202a); font: 600 0.78rem/1.2 var(--gg-font-family, system-ui, sans-serif); cursor: pointer; } button:hover, button:focus-visible, button[aria-pressed="true"] { border-color: #287271; background: color-mix(in srgb, #287271 13%, var(--surface, white)); outline: none; } button:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, #287271 28%, transparent); } .table-wrap { overflow-x: auto; } table { width: 100%; border-collapse: collapse; color: var(--fg, #17202a); font: 0.82rem/1.35 var(--gg-font-family, system-ui, sans-serif); } caption { padding: 0 0 0.55rem; font-weight: 650; text-align: left; } th, td { padding: 0.5rem 0.65rem; border-bottom: 1px solid color-mix(in srgb, var(--fg, #17202a) 12%, transparent); text-align: left; } thead th { color: var(--muted, #59636e); font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase; } tbody tr[aria-selected="true"] { background: color-mix(in srgb, #287271 12%, transparent); } tbody tr.emphasized { box-shadow: inset 3px 0 #e09f3e; } .row-button { min-height: 2rem; min-width: 5rem; } @media (max-width: 860px) { .plots { grid-template-columns: 1fr; } .controls { align-items: stretch; flex-direction: column; } .button-row { justify-content: flex-start; } } </style> ``` ## Inspect and pin data (interaction/tooltip) Palmer penguin measurements with a crosshair that reads every series at one x, and a pin so the reading stays while you look elsewhere. Tags: interaction, inspect, tooltip, crosshair, keyboard, pin Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "adelie-001", "species": "Adelie", "island": "Torgersen", "flipper": 181, "mass": 3750, "bill": 39.1, "sex": "male" }, { "id": "adelie-002", "species": "Adelie", "island": "Torgersen", "flipper": 186, "mass": 3800, "bill": 39.5, "sex": "female" }, { "id": "adelie-003", "species": "Adelie", "island": "Torgersen", "flipper": 195, "mass": 3250, "bill": 40.3, "sex": "female" }, { "id": "adelie-004", "species": "Adelie", "island": "Torgersen", "flipper": 193, "mass": 3450, "bill": 36.7, "sex": "female" }, { "id": "adelie-005", "species": "Adelie", "island": "Torgersen", "flipper": 190, "mass": 3650, "bill": 39.3, "sex": "male" }, { "id": "adelie-006", "species": "Adelie", "island": "Torgersen", "flipper": 181, "mass": 3625, "bill": 38.9, "sex": "female" }, { "id": "adelie-007", "species": "Adelie", "island": "Torgersen", "flipper": 195, "mass": 4675, "bill": 39.2, "sex": "male" }, { "id": "adelie-008", "species": "Adelie", "island": "Torgersen", "flipper": 182, "mass": 3200, "bill": 41.1, "sex": "female" }, { "id": "adelie-009", "species": "Adelie", "island": "Torgersen", "flipper": 191, "mass": 3800, "bill": 38.6, "sex": "male" }, { "id": "adelie-010", "species": "Adelie", "island": "Torgersen", "flipper": 198, "mass": 4400, "bill": 34.6, "sex": "male" }, { "id": "adelie-011", "species": "Adelie", "island": "Torgersen", "flipper": 185, "mass": 3700, "bill": 36.6, "sex": "female" }, { "id": "adelie-012", "species": "Adelie", "island": "Torgersen", "flipper": 195, "mass": 3450, "bill": 38.7, "sex": "female" }, { "id": "adelie-013", "species": "Adelie", "island": "Torgersen", "flipper": 197, "mass": 4500, "bill": 42.5, "sex": "male" }, { "id": "adelie-014", "species": "Adelie", "island": "Torgersen", "flipper": 184, "mass": 3325, "bill": 34.4, "sex": "female" }, { "id": "adelie-015", "species": "Adelie", "island": "Torgersen", "flipper": 194, "mass": 4200, "bill": 46, "sex": "male" }, { "id": "adelie-016", "species": "Adelie", "island": "Biscoe", "flipper": 174, "mass": 3400, "bill": 37.8, "sex": "female" }, { "id": "adelie-017", "species": "Adelie", "island": "Biscoe", "flipper": 180, "mass": 3600, "bill": 37.7, "sex": "male" }, { "id": "adelie-018", "species": "Adelie", "island": "Biscoe", "flipper": 189, "mass": 3800, "bill": 35.9, "sex": "female" }, { "id": "adelie-019", "species": "Adelie", "island": "Biscoe", "flipper": 185, "mass": 3950, "bill": 38.2, "sex": "male" }, { "id": "adelie-020", "species": "Adelie", "island": "Biscoe", "flipper": 180, "mass": 3800, "bill": 38.8, "sex": "male" } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "flipper" }, "y": { "field": "mass" }, "color": { "field": "species" } }, "params": { "size": 3, "alpha": 0.8 } } ], "labs": { "title": "Inspect a shared x value, then pin", "subtitle": "333 penguins. Many share a flipper length", "x": "Flipper length (mm)", "y": "Body mass (g)", "color": "Species" }, "theme": "light" } // note: inline data truncated (313 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, ThemeLight, } from "@ggsvelte/svelte"; import { penguins } from "./data.js"; let inspectionStatus = $state("Move across the plot or use the arrow keys."); </script> <!-- Inspection is opt-in. The default HTML tooltip, crosshair, keyboard traversal, and pinning all consume the same semantic event. --> <GGPlot data={penguins} aes={{ x: "flipper", y: "mass", color: "species" }} oninspect={(event) => { inspectionStatus = event.phase === "clear" ? `Inspection cleared by ${event.source}.` : `${event.state === "pinned" ? "Pinned" : "Inspecting"} ${String(event.focus.row?.species ?? "datum")} · ${String(event.members.length)} member${event.members.length === 1 ? "" : "s"} · ${event.source}`; }} width="container" height={400} > <GeomPoint size={4} alpha={0.85} /> <ThemeLight /> <Labs title="Inspect a shared x value, then pin" subtitle="333 penguins. Many share a flipper length" x="Flipper length (mm)" y="Body mass (g)" color="Species" /> <Inspect mode="x" pin maxDistance={24} /> </GGPlot> <!-- Visual callback evidence only. GGPlot owns the single concise live region. --> <p class="event-status gg-demo-chrome">{inspectionStatus}</p> <style> .event-status { max-width: 640px; margin: 0.6rem 0 0; color: var(--muted, #59636e); font: 0.82rem/1.4 var(--gg-font-family, system-ui, sans-serif); } </style> ``` ## Menu calories, spread so items do not stack (jitter/basic) Jittered entrée calories by restaurant. Use this when many points share a category and would otherwise cover each other. Tags: jitter, scatter, categorical, overplotting, color Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "item-0001", "restaurant": "McDonald's", "item": "Artisan Grilled Chicken Sandwich", "calories": 380, "totalFat": 7, "protein": 37, "sodium": 1110 }, { "id": "item-0002", "restaurant": "McDonald's", "item": "Single Bacon Smokehouse Burger", "calories": 840, "totalFat": 45, "protein": 46, "sodium": 1580 }, { "id": "item-0003", "restaurant": "McDonald's", "item": "Double Bacon Smokehouse Burger", "calories": 1130, "totalFat": 67, "protein": 70, "sodium": 1920 }, { "id": "item-0004", "restaurant": "McDonald's", "item": "Grilled Bacon Smokehouse Chicken Sandwich", "calories": 750, "totalFat": 31, "protein": 55, "sodium": 1940 }, { "id": "item-0005", "restaurant": "McDonald's", "item": "Crispy Bacon Smokehouse Chicken Sandwich", "calories": 920, "totalFat": 45, "protein": 46, "sodium": 1980 }, { "id": "item-0006", "restaurant": "McDonald's", "item": "Big Mac", "calories": 540, "totalFat": 28, "protein": 25, "sodium": 950 }, { "id": "item-0007", "restaurant": "McDonald's", "item": "Cheeseburger", "calories": 300, "totalFat": 12, "protein": 15, "sodium": 680 }, { "id": "item-0008", "restaurant": "McDonald's", "item": "Classic Chicken Sandwich", "calories": 510, "totalFat": 24, "protein": 25, "sodium": 1040 }, { "id": "item-0009", "restaurant": "McDonald's", "item": "Double Cheeseburger", "calories": 430, "totalFat": 21, "protein": 25, "sodium": 1040 }, { "id": "item-0010", "restaurant": "McDonald's", "item": "Double Quarter Pounder® with Cheese", "calories": 770, "totalFat": 45, "protein": 51, "sodium": 1290 }, { "id": "item-0011", "restaurant": "McDonald's", "item": "Filet-O-Fish®", "calories": 380, "totalFat": 18, "protein": 15, "sodium": 640 }, { "id": "item-0012", "restaurant": "McDonald's", "item": "Garlic White Cheddar Burger", "calories": 620, "totalFat": 34, "protein": 32, "sodium": 790 }, { "id": "item-0013", "restaurant": "McDonald's", "item": "Grilled Garlic White Cheddar Chicken Sandwich", "calories": 530, "totalFat": 20, "protein": 42, "sodium": 1150 }, { "id": "item-0014", "restaurant": "McDonald's", "item": "Crispy Garlic White Cheddar Chicken Sandwich", "calories": 700, "totalFat": 34, "protein": 33, "sodium": 1190 }, { "id": "item-0015", "restaurant": "McDonald's", "item": "Hamburger", "calories": 250, "totalFat": 8, "protein": 13, "sodium": 480 }, { "id": "item-0016", "restaurant": "McDonald's", "item": "Lobster Roll", "calories": 290, "totalFat": 5, "protein": 24, "sodium": 630 }, { "id": "item-0017", "restaurant": "McDonald's", "item": "Maple Bacon Dijon 1/4 lb Burger", "calories": 640, "totalFat": 36, "protein": 37, "sodium": 1260 }, { "id": "item-0018", "restaurant": "McDonald's", "item": "Grilled Maple Bacon Dijon Chicken Sandwich", "calories": 580, "totalFat": 21, "protein": 48, "sodium": 1890 }, { "id": "item-0019", "restaurant": "McDonald's", "item": "Crispy Maple Bacon Dijon Chicken Sandwich", "calories": 740, "totalFat": 35, "protein": 39, "sodium": 1780 }, { "id": "item-0020", "restaurant": "McDonald's", "item": "McChicken", "calories": 350, "totalFat": 15, "protein": 15, "sodium": 600 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "jitter", "positionParams": { "width": 0.22, "height": 0 }, "aes": { "x": { "field": "restaurant" }, "y": { "field": "calories" }, "color": { "field": "restaurant" } }, "params": { "alpha": 0.65 } } ], "scales": { "color": { "type": "ordinal", "scheme": "observable10" } }, "guides": { "color": { "type": "none" } }, "labs": { "title": "Menu calories, spread so items do not stack", "subtitle": "Each point is one entrée. Jitter separates items that share a restaurant", "x": "Restaurant", "y": "Calories" }, "theme": "clean" } // note: inline data truncated (495 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomJitter, GGPlot, GuideNone, Inspect, Labs, ScaleColorDiscrete, ThemeClean, } from "@ggsvelte/svelte"; import { fastfoodMenu } from "./data.js"; </script> <GGPlot data={fastfoodMenu} aes={{ x: "restaurant", y: "calories", color: "restaurant" }} width={640} height={400} > <GeomJitter width={0.22} height={0} alpha={0.65} /> <ScaleColorDiscrete scheme="observable10" /> <ThemeClean /> <GuideNone channel="color" /> <Labs title="Menu calories, spread so items do not stack" subtitle="Each point is one entrée. Jitter separates items that share a restaurant" x="Restaurant" y="Calories" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Jittered points by group (jitter/spread) Twelve large points in three columns with wide horizontal jitter — the alias mark that reads at index thumbnail size. Tags: jitter, point, overplotting, categorical Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "A", "value": 2 }, { "group": "A", "value": 4 }, { "group": "A", "value": 6 }, { "group": "A", "value": 8 }, { "group": "B", "value": 3 }, { "group": "B", "value": 5 }, { "group": "B", "value": 7 }, { "group": "B", "value": 9 }, { "group": "C", "value": 1.5 }, { "group": "C", "value": 3.5 }, { "group": "C", "value": 5.5 }, { "group": "C", "value": 7.5 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "jitter", "positionParams": { "width": 0.35, "height": 0, "seed": 7 }, "aes": { "x": { "field": "group" }, "y": { "field": "value" }, "color": { "value": "#1a202c" } }, "params": { "size": 6.5, "alpha": 0.9 } } ], "scales": { "x": { "type": "band", "domain": [ "A", "B", "C" ] }, "y": { "type": "linear", "domain": [ 0, 10 ] } }, "labs": { "title": "Jittered points by group", "subtitle": "Sideways jitter unstacks points", "x": "Group", "y": "Value" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomJitter, GGPlot, Inspect, Labs, ScaleXDiscrete, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { sparseGroups } from "./data.js"; </script> <GGPlot data={sparseGroups} aes={{ x: "group", y: "value" }} width={640} height={400} > <GeomJitter width={0.35} height={0} seed={7} size={6.5} alpha={0.9} aes={{ color: { value: "#1a202c" } }} /> <ScaleXDiscrete domain={["A", "B", "C"]} /> <ScaleYContinuous limits={[0, 10]} /> <ThemeMinimal /> <Labs title="Jittered points by group" subtitle="Sideways jitter unstacks points" x="Group" y="Value" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Boxed labels (label/basic) A few large rounded boxes behind short names so GeomLabel reads at index thumbnail size. Tags: label, text, point Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "rank": 1, "name": "Brahe", "longitude": 20 }, { "rank": 2, "name": "Clavius", "longitude": 24 }, { "rank": 3, "name": "Maginus", "longitude": 28 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "longitude" }, "y": { "field": "rank" }, "color": { "value": "#4a5568" } }, "params": { "size": 4, "alpha": 0.5 } }, { "geom": "label", "stat": "identity", "position": "identity", "aes": { "x": { "field": "longitude" }, "y": { "field": "rank" }, "color": { "value": "#0f172a" }, "fill": { "value": "#b8c9e0" }, "label": { "field": "name" } }, "params": { "anchor": "middle", "dy": -18, "size": 20, "padding": 7, "radius": 6, "linewidth": 2.2 } } ], "scales": { "x": { "type": "linear", "domain": [ 18, 30 ] }, "y": { "type": "linear", "domain": [ 0.2, 3.8 ] } }, "labs": { "title": "Boxed labels", "subtitle": "Same three names, each in a rounded box", "x": "Estimated longitude (°)", "y": "Order" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLabel, GeomPoint, GGPlot, Inspect, Labs, ScaleXContinuous, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { langrenLabels } from "./data.js"; </script> <GGPlot data={langrenLabels} aes={{ x: "longitude", y: "rank" }} width={640} height={400} > <GeomPoint size={4} alpha={0.5} aes={{ color: { value: "#4a5568" } }} /> <GeomLabel aes={{ label: "name", fill: { value: "#b8c9e0" }, color: { value: "#0f172a" }, }} anchor="middle" dy={-18} size={20} padding={7} radius={6} linewidth={2.2} /> <ScaleXContinuous limits={[18, 30]} /> <ScaleYContinuous limits={[0.2, 3.8]} /> <ThemeMinimal /> <Labs title="Boxed labels" subtitle="Same three names, each in a rounded box" x="Estimated longitude (°)" y="Order" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Empirical distribution of event sizes (line/ecdf) ECDF lines for the cumulative share of observations at or below each value. Tags: line, ecdf, stat, step Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1918, "magnitude": 7.2 }, { "year": 1915, "magnitude": 7.2 }, { "year": 1916, "magnitude": 7.2 }, { "year": 1918, "magnitude": 7.2 }, { "year": 1916, "magnitude": 7.2 }, { "year": 1917, "magnitude": 7.2 }, { "year": 1917, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 } ] }, "layers": [ { "geom": "line", "stat": "ecdf", "position": "identity", "aes": { "x": { "field": "magnitude" }, "y": { "stat": "ecdf" } }, "params": { "curve": "step-hv", "linewidth": 1.8, "pad": true } } ], "labs": { "title": "Empirical distribution of event sizes", "subtitle": "How many wars reach each magnitude on a step from zero to one", "x": "Magnitude (log10 killed)", "y": "Share of quarrels at or below" }, "theme": "classic" } // note: inline data truncated (759 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GGPlot, Inspect, Labs, registerEcdf, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerEcdf(); import { deadlyQuarrels } from "./data.js"; </script> <GGPlot data={deadlyQuarrels} aes={{ x: "magnitude" }} width={640} height={400}> <GeomLine stat="ecdf" curve="step-hv" linewidth={1.8} pad={true} /> <ThemeClassic /> <Labs title="Empirical distribution of event sizes" subtitle="How many wars reach each magnitude on a step from zero to one" x="Magnitude (log10 killed)" y="Share of quarrels at or below" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Observed counts against a fitted curve (line/function) Function lines when theory (or a fit) should sit on top of the data. Tags: function, dnorm, overlay, line Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "chest": 33, "count": 3, "share": 0.0005 }, { "chest": 34, "count": 18, "share": 0.0031 }, { "chest": 35, "count": 81, "share": 0.0141 }, { "chest": 36, "count": 185, "share": 0.0322 }, { "chest": 37, "count": 420, "share": 0.0732 }, { "chest": 38, "count": 749, "share": 0.1305 }, { "chest": 39, "count": 1073, "share": 0.187 }, { "chest": 40, "count": 1079, "share": 0.188 }, { "chest": 41, "count": 934, "share": 0.1628 }, { "chest": 42, "count": 658, "share": 0.1147 }, { "chest": 43, "count": 370, "share": 0.0645 }, { "chest": 44, "count": 92, "share": 0.016 }, { "chest": 45, "count": 50, "share": 0.0087 }, { "chest": 46, "count": 21, "share": 0.0037 }, { "chest": 47, "count": 4, "share": 0.0007 }, { "chest": 48, "count": 1, "share": 0.0002 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "chest" }, "y": { "field": "share" } }, "params": { "size": 3 } }, { "geom": "function", "stat": "function", "position": "identity", "aes": { "x": { "field": "chest" }, "y": { "stat": "y" } }, "params": { "fun": "dnorm", "n": 201, "xlim": [ 32, 49 ], "args": { "mean": 39.83, "sd": 2.05 }, "linewidth": 2, "alpha": 0.95 } } ], "labs": { "title": "Observed counts against a fitted curve", "subtitle": "Chest measurements with a normal of the same mean and spread drawn through them", "x": "Chest circumference (inches)", "y": "Share of soldiers" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomFunction, GeomPoint, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { chestSizes } from "./data.js"; </script> <GGPlot data={chestSizes} aes={{ x: "chest" }} width={640} height={400}> <GeomPoint aes={{ y: "share" }} size={3} /> <GeomFunction fun="dnorm" n={201} xlim={[32, 49]} args={{ mean: 39.83, sd: 2.05 }} linewidth={2} alpha={0.95} /> <ThemeClassic /> <Labs title="Observed counts against a fitted curve" subtitle="Chest measurements with a normal of the same mean and spread drawn through them" x="Chest circumference (inches)" y="Share of soldiers" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Weeks of work for a quarter of wheat (line/labor-cost-of-wheat) Playfair's purchasing-power argument as a single series: wheat price divided by a mechanic's weekly wage. A quarter is about eight bushels of grain — lower is cheaper grain in labor terms. Tags: line, point, ratio, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1565, "weeks": 8.2 }, { "year": 1570, "weeks": 8.91 }, { "year": 1575, "weeks": 8.27 }, { "year": 1580, "weeks": 9.57 }, { "year": 1585, "weeks": 8.06 }, { "year": 1590, "weeks": 8.95 }, { "year": 1595, "weeks": 11.55 }, { "year": 1600, "weeks": 4.81 }, { "year": 1605, "weeks": 5.8 }, { "year": 1610, "weeks": 5.54 }, { "year": 1615, "weeks": 5.56 }, { "year": 1620, "weeks": 5.82 }, { "year": 1625, "weeks": 5.39 }, { "year": 1630, "weeks": 7.23 }, { "year": 1635, "weeks": 5.24 }, { "year": 1640, "weeks": 6.12 }, { "year": 1645, "weeks": 8.22 }, { "year": 1650, "weeks": 6.46 }, { "year": 1655, "weeks": 6.14 }, { "year": 1660, "weeks": 6.89 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "weeks" }, "color": { "value": "#014d64" } }, "params": { "linewidth": 2 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "weeks" }, "color": { "value": "#014d64" } }, "params": { "size": 1.6 } } ], "scales": { "x": { "breaks": [ 1600, 1650, 1700, 1750, 1800 ], "labels": "d", "nice": false } }, "labs": { "title": "Weeks of work for a quarter of wheat", "subtitle": "Wheat price ÷ weekly mechanic wage (~8 bushels)", "x": "Year", "y": "Weeks of work" }, "theme": "economist" } // note: inline data truncated (30 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomPoint, GGPlot, Inspect, Labs, ScaleXContinuous, ThemeEconomist, } from "@ggsvelte/svelte"; import { laborCostOfWheat } from "./data.js"; </script> <GGPlot data={laborCostOfWheat} aes={{ x: "year", y: "weeks" }} width={640} height={400} > <GeomLine linewidth={2} aes={{ color: { value: "#014d64" } }} /> <GeomPoint size={1.6} aes={{ color: { value: "#014d64" } }} /> <ScaleXContinuous breaks={[1600, 1650, 1700, 1750, 1800]} labels="d" nice={false} /> <ThemeEconomist /> <Labs title="Weeks of work for a quarter of wheat" subtitle="Wheat price ÷ weekly mechanic wage (~8 bushels)" x="Year" y="Weeks of work" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Wheat price and a mechanic's weekly wage (line/multi-series) Playfair's 1821 levels chart: the price of one quarter of wheat (~8 bushels) and a good mechanic's weekly pay, both in shillings. Same currency, different baskets — he wanted you to eye the ratio, not add the series. Tags: line, point, color, legend, layers, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1565, "series": "Wheat / quarter", "value": 41 }, { "year": 1565, "series": "Wage / week", "value": 5 }, { "year": 1570, "series": "Wheat / quarter", "value": 45 }, { "year": 1570, "series": "Wage / week", "value": 5.05 }, { "year": 1575, "series": "Wheat / quarter", "value": 42 }, { "year": 1575, "series": "Wage / week", "value": 5.08 }, { "year": 1580, "series": "Wheat / quarter", "value": 49 }, { "year": 1580, "series": "Wage / week", "value": 5.12 }, { "year": 1585, "series": "Wheat / quarter", "value": 41.5 }, { "year": 1585, "series": "Wage / week", "value": 5.15 }, { "year": 1590, "series": "Wheat / quarter", "value": 47 }, { "year": 1590, "series": "Wage / week", "value": 5.25 }, { "year": 1595, "series": "Wheat / quarter", "value": 64 }, { "year": 1595, "series": "Wage / week", "value": 5.54 }, { "year": 1600, "series": "Wheat / quarter", "value": 27 }, { "year": 1600, "series": "Wage / week", "value": 5.61 }, { "year": 1605, "series": "Wheat / quarter", "value": 33 }, { "year": 1605, "series": "Wage / week", "value": 5.69 }, { "year": 1610, "series": "Wheat / quarter", "value": 32 }, { "year": 1610, "series": "Wage / week", "value": 5.78 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "series" } }, "params": { "linewidth": 2 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" }, "color": { "field": "series" } }, "params": { "size": 1.6 } } ], "scales": { "x": { "breaks": [ 1600, 1650, 1700, 1750, 1800 ], "labels": "d", "nice": false }, "color": { "type": "ordinal", "domain": [ "Wheat / quarter", "Wage / week" ], "scheme": "wsj_rgby" } }, "labs": { "title": "Wheat price and a mechanic's weekly wage", "subtitle": "Price of one quarter of wheat (~8 bushels) beside a week's pay — both in shillings", "x": "Year", "y": "Shillings", "color": "Series" }, "theme": "economist" } // note: inline data truncated (83 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomPoint, GGPlot, GuideLegend, Inspect, Labs, ScaleColorDiscrete, ScaleXContinuous, ThemeEconomist, } from "@ggsvelte/svelte"; import { wheatAndWages } from "./data.js"; </script> <GGPlot data={wheatAndWages} aes={{ x: "year", y: "value", color: "series" }} width={640} height={400} > <GeomLine linewidth={2} /> <GeomPoint size={1.6} /> <ScaleXContinuous breaks={[1600, 1650, 1700, 1750, 1800]} labels="d" nice={false} /> <ScaleColorDiscrete domain={["Wheat / quarter", "Wage / week"]} scheme="wsj_rgby" /> <ThemeEconomist /> <GuideLegend channel="color" focus /> <Labs title="Wheat price and a mechanic's weekly wage" subtitle="Price of one quarter of wheat (~8 bushels) beside a week's pay — both in shillings" x="Year" y="Shillings" color="Series" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Years inferred from raw four-digit strings (line/time-axis) Temporal scales from plain year strings when the calendar meaning is obvious. Tags: line, time, scales, inference, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": "1855", "value": 95.7 }, { "year": "1856", "value": 115.8 }, { "year": "1857", "value": 122 }, { "year": "1858", "value": 116.6 }, { "year": "1859", "value": 130.4 }, { "year": "1860", "value": 135.9 }, { "year": "1861", "value": 125.1 }, { "year": "1862", "value": 124 }, { "year": "1863", "value": 146.5 }, { "year": "1864", "value": 160.4 }, { "year": "1865", "value": 165.8 }, { "year": "1866", "value": 188.9 }, { "year": "1867", "value": 181 }, { "year": "1868", "value": 179.7 }, { "year": "1869", "value": 190 }, { "year": "1870", "value": 199.6 }, { "year": "1871", "value": 223.1 }, { "year": "1872", "value": 256.3 }, { "year": "1873", "value": 255.2 }, { "year": "1874", "value": 239.6 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" } }, "params": { "linewidth": 1.5 } } ], "labs": { "title": "Years inferred from raw four-digit strings", "subtitle": "Export totals over time without pre-parsing dates in the table", "x": "Year", "y": "£ millions" }, "theme": "fivethirtyeight" } // note: inline data truncated (25 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GGPlot, Inspect, Labs, ThemeFivethirtyeight, } from "@ggsvelte/svelte"; import { britishExports } from "./data.js"; </script> <GGPlot data={britishExports} aes={{ x: "year", y: "value" }} width="container" height={400} > <GeomLine linewidth={1.5} /> <ThemeFivethirtyeight /> <Labs title="Years inferred from raw four-digit strings" subtitle="Export totals over time without pre-parsing dates in the table" x="Year" y="£ millions" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Bare vertical stems (linerange/stems) Three tall identity stems without end caps — linerange's mark shape at index thumbnail size. Tags: linerange, identity, stem, ymin, ymax Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "A", "mid": 5, "lo": 1.5, "hi": 8.5 }, { "group": "B", "mid": 7.5, "lo": 4, "hi": 11 }, { "group": "C", "mid": 3.5, "lo": 0.5, "hi": 6.5 } ] }, "layers": [ { "geom": "linerange", "stat": "identity", "position": "identity", "aes": { "x": { "field": "group" }, "color": { "value": "#1a202c" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "linewidth": 3.2 } } ], "scales": { "x": { "type": "band", "domain": [ "A", "B", "C" ] }, "y": { "type": "linear", "domain": [ 0, 12 ] } }, "labs": { "title": "Bare vertical stems", "subtitle": "Three identity intervals from ymin to ymax without end caps", "x": "Group", "y": "Value" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLinerange, GGPlot, Inspect, Labs, ScaleXDiscrete, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { groupIntervals } from "./data.js"; </script> <GGPlot data={groupIntervals} aes={{ x: "group", ymin: "lo", ymax: "hi" }} width={640} height={400} > <GeomLinerange linewidth={3.2} aes={{ color: { value: "#1a202c" } }} /> <ScaleXDiscrete domain={["A", "B", "C"]} /> <ScaleYContinuous limits={[0, 12]} /> <ThemeMinimal /> <Labs title="Bare vertical stems" subtitle="Three identity intervals from ymin to ymax without end caps" x="Group" y="Value" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Snow's outbreak by nearest pump (map/choropleth) Thirteen Soho neighbourhoods shaded by cholera deaths inside each. One region holds 359 of the 578, around Broad Street. Tags: map, choropleth, region, join, fill Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pump": "Oxford Market", "deaths": 0 }, { "pump": "Castle St E", "deaths": 1 }, { "pump": "Oxford St #1", "deaths": 12 }, { "pump": "Oxford St #2", "deaths": 24 }, { "pump": "Gt Marlborough", "deaths": 6 }, { "pump": "Crown Chapel", "deaths": 61 }, { "pump": "Broad St", "deaths": 359 }, { "pump": "Warwick", "deaths": 16 }, { "pump": "Briddle St", "deaths": 27 }, { "pump": "So Soho", "deaths": 64 }, { "pump": "Dean St", "deaths": 2 }, { "pump": "Coventry St", "deaths": 2 }, { "pump": "Vigo St", "deaths": 4 } ] }, "layers": [ { "geom": "map", "stat": "identity", "position": "identity", "aes": { "fill": { "field": "deaths" }, "map_id": { "field": "pump" } }, "params": { "map": { "values": [ { "x": 3.39, "y": 16.3215, "pump": "Oxford Market" }, { "x": 10.2964, "y": 16.4222, "pump": "Oxford Market" }, { "x": 9.6784, "y": 18.725, "pump": "Oxford Market" }, { "x": 3.39, "y": 18.725, "pump": "Oxford Market" }, { "x": 3.39, "y": 16.3215, "pump": "Oxford Market" }, { "x": 10.2964, "y": 16.4222, "pump": "Castle St E" }, { "x": 11.1944, "y": 15.8529, "pump": "Castle St E" }, { "x": 12.5423, "y": 18.725, "pump": "Castle St E" }, { "x": 9.6784, "y": 18.725, "pump": "Castle St E" }, { "x": 10.2964, "y": 16.4222, "pump": "Castle St E" }, { "x": 11.1944, "y": 15.8529, "pump": "Oxford St #1" }, { "x": 11.7915, "y": 14.7293, "pump": "Oxford St #1" }, { "x": 15.0521, "y": 14.2651, "pump": "Oxford St #1" }, { "x": 13.8184, "y": 18.725, "pump": "Oxford St #1" }, { "x": 12.5423, "y": 18.725, "pump": "Oxford St #1" }, { "x": 11.1944, "y": 15.8529, "pump": "Oxford St #1" }, { "x": 15.0521, "y": 14.2651, "pump": "Oxford St #2" }, { "x": 16.5561, "y": 13.6943, "pump": "Oxford St #2" }, { "x": 16.8419, "y": 13.7463, "pump": "Oxford St #2" }, { "x": 19.912, "y": 15.2808, "pump": "Oxford St #2" }, { "x": 19.912, "y": 18.725, "pump": "Oxford St #2" }, { "x": 13.8184, "y": 18.725, "pump": "Oxford St #2" }, { "x": 15.0521, "y": 14.2651, "pump": "Oxford St #2" }, { "x": 3.39, "y": 13.4046, "pump": "Gt Marlborough" }, { "x": 11.211, "y": 14.0212, "pump": "Gt Marlborough" }, { "x": 11.7915, "y": 14.7293, "pump": "Gt Marlborough" }, { "x": 11.1944, "y": 15.8529, "pump": "Gt Marlborough" }, { "x": 10.2964, "y": 16.4222, "pump": "Gt Marlborough" }, { "x": 3.39, "y": 16.3215, "pump": "Gt Marlborough" }, { "x": 3.39, "y": 13.4046, "pump": "Gt Marlborough" }, { "x": 3.39, "y": 8.8295, "pump": "Crown Chapel" }, { "x": 6.1687, "y": 8.8785, "pump": "Crown Chapel" }, { "x": 10.1599, "y": 10.2251, "pump": "Crown Chapel" }, { "x": 11.211, "y": 14.0212, "pump": "Crown Chapel" }, { "x": 3.39, "y": 13.4046, "pump": "Crown Chapel" }, { "x": 3.39, "y": 8.8295, "pump": "Crown Chapel" }, { "x": 10.1599, "y": 10.2251, "pump": "Broad St" }, { "x": 11.7536, "y": 9.5168, "pump": "Broad St" }, { "x": 14.2894, "y": 10.1561, "pump": "Broad St" }, { "x": 16.5561, "y": 13.6943, "pump": "Broad St" }, { "x": 15.0521, "y": 14.2651, "pump": "Broad St" }, { "x": 11.7915, "y": 14.7293, "pump": "Broad St" }, { "x": 11.211, "y": 14.0212, "pump": "Broad St" }, { "x": 10.1599, "y": 10.2251, "pump": "Broad St" }, { "x": 12.4968, "y": 4.3614, "pump": "Warwick" }, { "x": 12.6461, "y": 4.6964, "pump": "Warwick" }, { "x": 11.7536, "y": 9.5168, "pump": "Warwick" }, { "x": 10.1599, "y": 10.2251, "pump": "Warwick" }, { "x": 6.1687, "y": 8.8785, "pump": "Warwick" }, { "x": 12.4968, "y": 4.3614, "pump": "Warwick" }, { "x": 12.6461, "y": 4.6964, "pump": "Briddle St" }, { "x": 15.5964, "y": 7.2132, "pump": "Briddle St" }, { "x": 14.2894, "y": 10.1561, "pump": "Briddle St" }, { "x": 11.7536, "y": 9.5168, "pump": "Briddle St" }, { "x": 12.6461, "y": 4.6964, "pump": "Briddle St" }, { "x": 15.5964, "y": 7.2132, "pump": "So Soho" }, { "x": 18.1732, "y": 6.9499, "pump": "So Soho" }, { "x": 16.8419, "y": 13.7463, "pump": "So Soho" }, { "x": 16.5561, "y": 13.6943, "pump": "So Soho" }, { "x": 14.2894, "y": 10.1561, "pump": "So Soho" }, { "x": 15.5964, "y": 7.2132, "pump": "So Soho" }, { "x": 18.1732, "y": 6.9499, "pump": "Dean St" }, { "x": 19.912, "y": 5.8715, "pump": "Dean St" }, { "x": 19.912, "y": 15.2808, "pump": "Dean St" }, { "x": 16.8419, "y": 13.7463, "pump": "Dean St" }, { "x": 18.1732, "y": 6.9499, "pump": "Dean St" }, { "x": 12.6461, "y": 4.6964, "pump": "Coventry St" }, { "x": 12.4968, "y": 4.3614, "pump": "Coventry St" }, { "x": 12.4881, "y": 3.235, "pump": "Coventry St" }, { "x": 19.912, "y": 3.235, "pump": "Coventry St" }, { "x": 19.912, "y": 5.8715, "pump": "Coventry St" }, { "x": 18.1732, "y": 6.9499, "pump": "Coventry St" }, { "x": 15.5964, "y": 7.2132, "pump": "Coventry St" }, { "x": 12.6461, "y": 4.6964, "pump": "Coventry St" }, { "x": 3.39, "y": 3.235, "pump": "Vigo St" }, { "x": 12.4881, "y": 3.235, "pump": "Vigo St" }, { "x": 12.4968, "y": 4.3614, "pump": "Vigo St" }, { "x": 6.1687, "y": 8.8785, "pump": "Vigo St" }, { "x": 3.39, "y": 8.8295, "pump": "Vigo St" }, { "x": 3.39, "y": 3.235, "pump": "Vigo St" } ] }, "mapId": "pump", "linewidth": 1.2, "alpha": 0.95 } } ], "coord": { "type": "fixed" }, "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }, "labs": { "title": "359 of 578 deaths were nearest the Broad Street pump", "subtitle": "The 1854 Soho outbreak counted into the area closest to each public pump", "fill": "Deaths" }, "theme": "map" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomMap, GGPlot, Inspect, Labs, ScaleFillContinuous, ThemeMap, } from "@ggsvelte/svelte"; import { neighbourhoodDeaths, pumpNeighbourhoods } from "./data.js"; </script> <GGPlot data={neighbourhoodDeaths} aes={{ map_id: "pump", fill: "deaths" }} width={640} height={400} > <GeomMap map={{ values: pumpNeighbourhoods }} mapId="pump" linewidth={1.2} alpha={0.95} /> <CoordFixed /> <ScaleFillContinuous scheme="viridis" /> <ThemeMap /> <Labs title="359 of 578 deaths were nearest the Broad Street pump" subtitle="The 1854 Soho outbreak counted into the area closest to each public pump" fill="Deaths" /> <Inspect mode="exact" pin identity="pump" /> </GGPlot> ``` ## Minard's retreat thermometer (path/connect-hv) Nine temperature readings taken along the road back from Moscow, joined by horizontal-then-vertical elbows so each reading holds until the next one was taken. Tags: path, connect, stat, step, hv, trajectory Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "long": 37.6, "temp": 0, "date": "Oct18" }, { "long": 36, "temp": 0, "date": "Oct24" }, { "long": 33.2, "temp": -9, "date": "Nov09" }, { "long": 32, "temp": -21, "date": "Nov14" }, { "long": 29.2, "temp": -11, "date": "" }, { "long": 28.5, "temp": -20, "date": "Nov28" }, { "long": 27.2, "temp": -24, "date": "Dec01" }, { "long": 26.7, "temp": -30, "date": "Dec06" }, { "long": 25.3, "temp": -26, "date": "Dec07" } ] }, "layers": [ { "geom": "path", "stat": "connect", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "temp" } }, "params": { "connection": "hv", "linewidth": 2.5, "alpha": 0.95 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "temp" } }, "params": { "size": 3.5, "alpha": 0.85 } } ], "scales": { "x": { "reverse": true } }, "labs": { "title": "The cold Minard drew under the retreat", "subtitle": "Nine readings between Moscow and Wilna, each carried west until the next", "x": "Longitude east", "y": "Degrees Réaumur" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPath, GeomPoint, GGPlot, Inspect, Labs, registerConnect, ScaleXContinuous, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerConnect(); import { retreatCold } from "./data.js"; </script> <GGPlot data={retreatCold} aes={{ x: "long", y: "temp" }} width={640} height={400} > <GeomPath stat="connect" connection="hv" linewidth={2.5} alpha={0.95} /> <GeomPoint size={3.5} alpha={0.85} /> <ScaleXContinuous reverse={true} /> <ThemeClassic /> <Labs title="The cold Minard drew under the retreat" subtitle="Nine readings between Moscow and Wilna, each carried west until the next" x="Longitude east" y="Degrees Réaumur" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Confidence ellipses around groups (path/ellipse-rings) Ellipse rings when each group needs a region of typical bivariate spread. Tags: path, ellipse, stat, confidence, group, point, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "species": "Adelie", "bill": 39.1, "flipper": 181 }, { "species": "Adelie", "bill": 39.5, "flipper": 186 }, { "species": "Adelie", "bill": 40.3, "flipper": 195 }, { "species": "Adelie", "bill": 36.7, "flipper": 193 }, { "species": "Adelie", "bill": 39.3, "flipper": 190 }, { "species": "Adelie", "bill": 38.9, "flipper": 181 }, { "species": "Adelie", "bill": 39.2, "flipper": 195 }, { "species": "Adelie", "bill": 41.1, "flipper": 182 }, { "species": "Adelie", "bill": 38.6, "flipper": 191 }, { "species": "Adelie", "bill": 34.6, "flipper": 198 }, { "species": "Adelie", "bill": 36.6, "flipper": 185 }, { "species": "Adelie", "bill": 38.7, "flipper": 195 }, { "species": "Adelie", "bill": 42.5, "flipper": 197 }, { "species": "Adelie", "bill": 34.4, "flipper": 184 }, { "species": "Adelie", "bill": 46, "flipper": 194 }, { "species": "Adelie", "bill": 37.8, "flipper": 174 }, { "species": "Adelie", "bill": 37.7, "flipper": 180 }, { "species": "Adelie", "bill": 35.9, "flipper": 189 }, { "species": "Adelie", "bill": 38.2, "flipper": 185 }, { "species": "Adelie", "bill": 38.8, "flipper": 180 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "bill" }, "y": { "field": "flipper" }, "color": { "field": "species" } }, "params": { "alpha": 0.6, "size": 2.4 } }, { "geom": "path", "stat": "ellipse", "position": "identity", "aes": { "x": { "field": "bill" }, "y": { "field": "flipper" }, "color": { "field": "species" } }, "params": { "level": 0.95, "segments": 51, "linewidth": 1.5 } } ], "scales": { "color": { "type": "ordinal", "scheme": "observable10" } }, "labs": { "title": "Confidence ellipses around groups", "subtitle": "95% bivariate normal rings for three species in bill and flipper length", "x": "Bill length (mm)", "y": "Flipper length (mm)", "color": "Species" }, "theme": "classic" } // note: inline data truncated (313 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPath, GeomPoint, GGPlot, Inspect, Labs, registerEllipse, ScaleColorDiscrete, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerEllipse(); import { penguins } from "./data.js"; </script> <GGPlot data={penguins} aes={{ x: "bill", y: "flipper", color: "species" }} width={640} height={400} > <GeomPoint alpha={0.6} size={2.4} /> <GeomPath stat="ellipse" level={0.95} segments={51} linewidth={1.5} /> <ScaleColorDiscrete scheme="observable10" /> <ThemeClassic /> <Labs title="Confidence ellipses around groups" subtitle="95% bivariate normal rings for three species in bill and flipper length" x="Bill length (mm)" y="Flipper length (mm)" color="Species" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Napoleon's march on Moscow, by Minard (path/trajectory) Path map of army strength with a temperature strip. Tags: path, map, linewidth, annotation, text, data-order, inspect Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "long": 24, "lat": 54.9, "survivors": 340000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 24.5, "lat": 55, "survivors": 340000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 25.5, "lat": 54.5, "survivors": 340000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 26, "lat": 54.7, "survivors": 320000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 27, "lat": 54.8, "survivors": 300000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 28, "lat": 54.9, "survivors": 280000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 28.5, "lat": 55, "survivors": 240000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 29, "lat": 55.1, "survivors": 210000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 30, "lat": 55.2, "survivors": 180000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 30.3, "lat": 55.3, "survivors": 175000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 32, "lat": 54.8, "survivors": 145000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 33.2, "lat": 54.9, "survivors": 140000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 34.4, "lat": 55.5, "survivors": 127100, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 35.5, "lat": 55.4, "survivors": 100000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 36, "lat": 55.5, "survivors": 100000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 37.6, "lat": 55.8, "survivors": 100000, "direction": "Advance", "leg": "Column 1 Advance" }, { "long": 37.7, "lat": 55.7, "survivors": 100000, "direction": "Retreat", "leg": "Column 1 Retreat" }, { "long": 37.5, "lat": 55.7, "survivors": 98000, "direction": "Retreat", "leg": "Column 1 Retreat" }, { "long": 37, "lat": 55, "survivors": 97000, "direction": "Retreat", "leg": "Column 1 Retreat" }, { "long": 36.8, "lat": 55, "survivors": 96000, "direction": "Retreat", "leg": "Column 1 Retreat" } ] }, "layers": [ { "geom": "path", "stat": "identity", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "lat" }, "color": { "value": "#8fa8c0" }, "group": { "field": "river" } }, "data": { "values": [ { "river": "Niemen", "long": 26.648, "lat": 53.5001 }, { "river": "Niemen", "long": 26.6507, "lat": 53.5072 }, { "river": "Niemen", "long": 26.6426, "lat": 53.5119 }, { "river": "Niemen", "long": 26.6223, "lat": 53.5148 }, { "river": "Niemen", "long": 26.5967, "lat": 53.5229 }, { "river": "Niemen", "long": 26.5798, "lat": 53.5336 }, { "river": "Niemen", "long": 26.5609, "lat": 53.5433 }, { "river": "Niemen", "long": 26.55, "lat": 53.5421 }, { "river": "Niemen", "long": 26.542, "lat": 53.5483 }, { "river": "Niemen", "long": 26.5283, "lat": 53.5527 }, { "river": "Niemen", "long": 26.5178, "lat": 53.5572 }, { "river": "Niemen", "long": 26.5037, "lat": 53.569 }, { "river": "Niemen", "long": 26.4922, "lat": 53.5708 }, { "river": "Niemen", "long": 26.4687, "lat": 53.5793 }, { "river": "Niemen", "long": 26.4442, "lat": 53.5834 }, { "river": "Niemen", "long": 26.4357, "lat": 53.5854 }, { "river": "Niemen", "long": 26.4248, "lat": 53.5844 }, { "river": "Niemen", "long": 26.4007, "lat": 53.5882 }, { "river": "Niemen", "long": 26.3861, "lat": 53.5843 }, { "river": "Niemen", "long": 26.3736, "lat": 53.5781 }, { "river": "Niemen", "long": 26.3676, "lat": 53.5744 }, { "river": "Niemen", "long": 26.3449, "lat": 53.5742 }, { "river": "Niemen", "long": 26.327, "lat": 53.6037 }, { "river": "Niemen", "long": 26.3093, "lat": 53.6113 }, { "river": "Niemen", "long": 26.3, "lat": 53.6191 }, { "river": "Niemen", "long": 26.26, "lat": 53.6291 }, { "river": "Niemen", "long": 26.2408, "lat": 53.6404 }, { "river": "Niemen", "long": 26.194, "lat": 53.6615 }, { "river": "Niemen", "long": 26.1825, "lat": 53.6747 }, { "river": "Niemen", "long": 26.1829, "lat": 53.6892 }, { "river": "Niemen", "long": 26.1796, "lat": 53.7122 }, { "river": "Niemen", "long": 26.1731, "lat": 53.724 }, { "river": "Niemen", "long": 26.1522, "lat": 53.7429 }, { "river": "Niemen", "long": 26.1245, "lat": 53.7529 }, { "river": "Niemen", "long": 26.1011, "lat": 53.769 }, { "river": "Niemen", "long": 26.0853, "lat": 53.7567 }, { "river": "Niemen", "long": 26.0413, "lat": 53.7634 }, { "river": "Niemen", "long": 26.0254, "lat": 53.7726 }, { "river": "Niemen", "long": 26.0109, "lat": 53.7793 }, { "river": "Niemen", "long": 26.0012, "lat": 53.7852 }, { "river": "Niemen", "long": 25.9835, "lat": 53.7981 }, { "river": "Niemen", "long": 25.9619, "lat": 53.8093 }, { "river": "Niemen", "long": 25.9175, "lat": 53.8252 }, { "river": "Niemen", "long": 25.8488, "lat": 53.8395 }, { "river": "Niemen", "long": 25.7773, "lat": 53.8573 }, { "river": "Niemen", "long": 25.6894, "lat": 53.8662 }, { "river": "Niemen", "long": 25.6449, "lat": 53.8355 }, { "river": "Niemen", "long": 25.5807, "lat": 53.8102 }, { "river": "Niemen", "long": 25.5012, "lat": 53.7798 }, { "river": "Niemen", "long": 25.4652, "lat": 53.7428 }, { "river": "Niemen", "long": 25.4445, "lat": 53.6712 }, { "river": "Niemen", "long": 25.3495, "lat": 53.6508 }, { "river": "Niemen", "long": 25.2688, "lat": 53.6134 }, { "river": "Niemen", "long": 25.1951, "lat": 53.561 }, { "river": "Niemen", "long": 25.0712, "lat": 53.5281 }, { "river": "Niemen", "long": 24.9839, "lat": 53.5004 }, { "river": "Niemen (2)", "long": 24.229, "lat": 53.5013 }, { "river": "Niemen (2)", "long": 24.1375, "lat": 53.5372 }, { "river": "Niemen (2)", "long": 24.0629, "lat": 53.5156 }, { "river": "Niemen (2)", "long": 24.0359, "lat": 53.573 }, { "river": "Niemen (2)", "long": 24.0036, "lat": 53.6115 }, { "river": "Niemen (2)", "long": 23.9799, "lat": 53.6421 }, { "river": "Niemen (2)", "long": 23.9378, "lat": 53.6435 }, { "river": "Niemen (2)", "long": 23.8953, "lat": 53.6534 }, { "river": "Niemen (2)", "long": 23.8511, "lat": 53.6631 }, { "river": "Niemen (2)", "long": 23.815, "lat": 53.6781 }, { "river": "Niemen (2)", "long": 23.781, "lat": 53.6776 }, { "river": "Niemen (2)", "long": 23.7742, "lat": 53.701 }, { "river": "Niemen (2)", "long": 23.7748, "lat": 53.7198 }, { "river": "Niemen (2)", "long": 23.7909, "lat": 53.7377 }, { "river": "Niemen (2)", "long": 23.8353, "lat": 53.7751 }, { "river": "Niemen (2)", "long": 23.8233, "lat": 53.8317 }, { "river": "Niemen (2)", "long": 23.7563, "lat": 53.8806 }, { "river": "Niemen (2)", "long": 23.8062, "lat": 53.9065 }, { "river": "Niemen (2)", "long": 23.8074, "lat": 53.9389 }, { "river": "Niemen (2)", "long": 23.8869, "lat": 53.9439 }, { "river": "Niemen (2)", "long": 23.9244, "lat": 53.9916 }, { "river": "Niemen (2)", "long": 23.9828, "lat": 54.0195 }, { "river": "Niemen (2)", "long": 24.0302, "lat": 54.0708 }, { "river": "Niemen (2)", "long": 24.1076, "lat": 54.0678 }, { "river": "Niemen (2)", "long": 24.1546, "lat": 54.096 }, { "river": "Niemen (2)", "long": 24.1598, "lat": 54.1368 }, { "river": "Niemen (2)", "long": 24.1433, "lat": 54.161 }, { "river": "Niemen (2)", "long": 24.0608, "lat": 54.1834 }, { "river": "Niemen (2)", "long": 24.0058, "lat": 54.167 }, { "river": "Niemen (2)", "long": 23.9497, "lat": 54.1927 }, { "river": "Niemen (2)", "long": 23.9835, "lat": 54.2549 }, { "river": "Niemen (2)", "long": 24.0162, "lat": 54.3048 }, { "river": "Niemen (2)", "long": 24.0335, "lat": 54.3387 }, { "river": "Niemen (2)", "long": 24.0291, "lat": 54.3776 }, { "river": "Niemen (2)", "long": 24.0901, "lat": 54.3798 }, { "river": "Niemen (2)", "long": 24.0411, "lat": 54.4225 }, { "river": "Niemen (2)", "long": 24.058, "lat": 54.4701 }, { "river": "Niemen (2)", "long": 24.0925, "lat": 54.5138 }, { "river": "Niemen (2)", "long": 24.0501, "lat": 54.5537 }, { "river": "Niemen (2)", "long": 24.0035, "lat": 54.5128 }, { "river": "Niemen (2)", "long": 23.9288, "lat": 54.5144 }, { "river": "Niemen (2)", "long": 23.952, "lat": 54.5597 }, { "river": "Niemen (2)", "long": 24.0047, "lat": 54.6039 }, { "river": "Niemen (2)", "long": 23.9886, "lat": 54.6463 }, { "river": "Niemen (2)", "long": 24.0668, "lat": 54.6063 }, { "river": "Niemen (2)", "long": 24.1307, "lat": 54.6725 }, { "river": "Niemen (2)", "long": 24.1355, "lat": 54.7586 }, { "river": "Niemen (2)", "long": 24.2394, "lat": 54.8104 }, { "river": "Niemen (2)", "long": 24.0843, "lat": 54.9006 }, { "river": "Niemen (2)", "long": 23.9639, "lat": 54.893 }, { "river": "Niemen (2)", "long": 23.926, "lat": 54.8812 }, { "river": "Niemen (2)", "long": 23.8232, "lat": 54.906 }, { "river": "Niemen (2)", "long": 23.6522, "lat": 54.9311 }, { "river": "Niemen (2)", "long": 23.5917, "lat": 55.0028 }, { "river": "Niemen (2)", "long": 23.4386, "lat": 55.0679 }, { "river": "Niemen (2)", "long": 23.2004, "lat": 55.0864 }, { "river": "Vilija", "long": 27.8836, "lat": 54.68 }, { "river": "Vilija", "long": 27.8252, "lat": 54.6596 }, { "river": "Vilija", "long": 27.7153, "lat": 54.6708 }, { "river": "Vilija", "long": 27.5688, "lat": 54.6329 }, { "river": "Vilija", "long": 27.4795, "lat": 54.5869 }, { "river": "Vilija", "long": 27.3164, "lat": 54.562 }, { "river": "Vilija", "long": 27.2744, "lat": 54.5414 }, { "river": "Vilija", "long": 27.2268, "lat": 54.5069 }, { "river": "Vilija", "long": 27.1397, "lat": 54.4883 }, { "river": "Vilija", "long": 26.9107, "lat": 54.4756 }, { "river": "Vilija", "long": 26.8355, "lat": 54.4307 }, { "river": "Vilija", "long": 26.708, "lat": 54.4246 }, { "river": "Vilija", "long": 26.6225, "lat": 54.43 }, { "river": "Vilija", "long": 26.5596, "lat": 54.4371 }, { "river": "Vilija", "long": 26.5134, "lat": 54.4538 }, { "river": "Vilija", "long": 26.4381, "lat": 54.5048 }, { "river": "Vilija", "long": 26.4107, "lat": 54.5504 }, { "river": "Vilija", "long": 26.4216, "lat": 54.5696 }, { "river": "Vilija", "long": 26.4185, "lat": 54.6312 }, { "river": "Vilija", "long": 26.4107, "lat": 54.6596 }, { "river": "Vilija", "long": 26.3012, "lat": 54.7105 }, { "river": "Vilija", "long": 26.2962, "lat": 54.7455 }, { "river": "Vilija", "long": 26.2288, "lat": 54.7838 }, { "river": "Vilija", "long": 26.1858, "lat": 54.7892 }, { "river": "Vilija", "long": 26.1121, "lat": 54.835 }, { "river": "Vilija", "long": 26.063, "lat": 54.8308 }, { "river": "Vilija", "long": 25.9138, "lat": 54.7868 }, { "river": "Vilija", "long": 25.8062, "lat": 54.86 }, { "river": "Vilija", "long": 25.7417, "lat": 54.8309 }, { "river": "Vilija", "long": 25.7143, "lat": 54.8631 }, { "river": "Vilija", "long": 25.6665, "lat": 54.8966 }, { "river": "Vilija", "long": 25.5125, "lat": 54.8316 }, { "river": "Vilija", "long": 25.3855, "lat": 54.8555 }, { "river": "Vilija", "long": 25.36, "lat": 54.8153 }, { "river": "Vilija", "long": 25.3652, "lat": 54.7626 }, { "river": "Vilija", "long": 25.2983, "lat": 54.722 }, { "river": "Vilija", "long": 25.2797, "lat": 54.6922 }, { "river": "Vilija", "long": 25.204, "lat": 54.6596 }, { "river": "Vilija", "long": 25.1006, "lat": 54.6793 }, { "river": "Vilija", "long": 25.0693, "lat": 54.6978 }, { "river": "Vilija", "long": 25.0122, "lat": 54.7285 }, { "river": "Vilija", "long": 24.9952, "lat": 54.7353 }, { "river": "Vilija", "long": 24.9917, "lat": 54.749 }, { "river": "Vilija", "long": 24.9485, "lat": 54.7878 }, { "river": "Vilija", "long": 24.9058, "lat": 54.79 }, { "river": "Vilija", "long": 24.876, "lat": 54.8445 }, { "river": "Vilija", "long": 24.8027, "lat": 54.8902 }, { "river": "Vilija", "long": 24.6753, "lat": 54.932 }, { "river": "Vilija", "long": 24.4679, "lat": 55.0118 }, { "river": "Vilija", "long": 24.4386, "lat": 55.0305 }, { "river": "Vilija", "long": 24.3806, "lat": 55.0607 }, { "river": "Vilija", "long": 24.3339, "lat": 55.0991 }, { "river": "Vilija", "long": 24.12, "lat": 55.0509 }, { "river": "Vilija", "long": 24.0603, "lat": 54.9843 }, { "river": "Vilija", "long": 23.928, "lat": 54.9539 }, { "river": "Vilija", "long": 23.8366, "lat": 54.9076 }, { "river": "Berezina", "long": 27.7556, "lat": 54.8961 }, { "river": "Berezina", "long": 27.7758, "lat": 54.9059 }, { "river": "Berezina", "long": 27.7867, "lat": 54.9182 }, { "river": "Berezina", "long": 27.7702, "lat": 54.9324 }, { "river": "Berezina", "long": 27.8711, "lat": 54.9786 }, { "river": "Berezina", "long": 28.0528, "lat": 54.9327 }, { "river": "Berezina", "long": 28.0826, "lat": 54.9203 }, { "river": "Berezina", "long": 28.1073, "lat": 54.9191 }, { "river": "Berezina", "long": 28.1319, "lat": 54.9214 }, { "river": "Berezina", "long": 28.1527, "lat": 54.9242 }, { "river": "Berezina", "long": 28.2059, "lat": 54.9065 }, { "river": "Berezina", "long": 28.2278, "lat": 54.8775 }, { "river": "Berezina", "long": 28.221, "lat": 54.865 }, { "river": "Berezina", "long": 28.227, "lat": 54.8541 }, { "river": "Berezina", "long": 28.2276, "lat": 54.8374 }, { "river": "Berezina", "long": 28.2255, "lat": 54.8232 }, { "river": "Berezina", "long": 28.2305, "lat": 54.8096 }, { "river": "Berezina", "long": 28.2231, "lat": 54.7951 }, { "river": "Berezina", "long": 28.218, "lat": 54.7741 }, { "river": "Berezina", "long": 28.2024, "lat": 54.7555 }, { "river": "Berezina", "long": 28.1881, "lat": 54.742 }, { "river": "Berezina", "long": 28.1724, "lat": 54.7239 }, { "river": "Berezina", "long": 28.1758, "lat": 54.7088 }, { "river": "Berezina", "long": 28.1892, "lat": 54.6948 }, { "river": "Berezina", "long": 28.1955, "lat": 54.6795 }, { "river": "Berezina", "long": 28.2239, "lat": 54.6673 }, { "river": "Berezina", "long": 28.2384, "lat": 54.6495 }, { "river": "Berezina", "long": 28.2664, "lat": 54.6285 }, { "river": "Berezina", "long": 28.2748, "lat": 54.6157 }, { "river": "Berezina", "long": 28.2954, "lat": 54.6056 }, { "river": "Berezina", "long": 28.3186, "lat": 54.5927 }, { "river": "Berezina", "long": 28.3361, "lat": 54.584 }, { "river": "Berezina", "long": 28.3592, "lat": 54.5608 }, { "river": "Berezina", "long": 28.3913, "lat": 54.5537 }, { "river": "Berezina", "long": 28.4109, "lat": 54.5418 }, { "river": "Berezina", "long": 28.433, "lat": 54.5312 }, { "river": "Berezina", "long": 28.3792, "lat": 54.4558 }, { "river": "Berezina", "long": 28.3404, "lat": 54.4163 }, { "river": "Berezina", "long": 28.3315, "lat": 54.3852 }, { "river": "Berezina", "long": 28.3413, "lat": 54.3444 }, { "river": "Berezina", "long": 28.3711, "lat": 54.2971 }, { "river": "Berezina", "long": 28.4106, "lat": 54.26 }, { "river": "Berezina", "long": 28.4931, "lat": 54.246 }, { "river": "Berezina", "long": 28.6044, "lat": 54.1913 }, { "river": "Berezina", "long": 28.6221, "lat": 54.1564 }, { "river": "Berezina", "long": 28.6977, "lat": 54.1164 }, { "river": "Berezina", "long": 28.7597, "lat": 54.0935 }, { "river": "Berezina", "long": 28.8265, "lat": 54.0616 }, { "river": "Berezina", "long": 28.8703, "lat": 54.0289 }, { "river": "Berezina", "long": 28.9475, "lat": 53.9571 }, { "river": "Berezina", "long": 28.9867, "lat": 53.8924 }, { "river": "Berezina", "long": 28.9859, "lat": 53.8117 }, { "river": "Berezina", "long": 28.937, "lat": 53.7409 }, { "river": "Berezina", "long": 28.958, "lat": 53.6911 }, { "river": "Berezina", "long": 28.9493, "lat": 53.5773 }, { "river": "Berezina", "long": 28.9722, "lat": 53.5 }, { "river": "Dvina", "long": 32.0872, "lat": 56.1946 }, { "river": "Dvina", "long": 31.9954, "lat": 56.1343 }, { "river": "Dvina", "long": 31.9164, "lat": 56.0648 }, { "river": "Dvina", "long": 31.7164, "lat": 56.0458 }, { "river": "Dvina", "long": 31.583, "lat": 56.0699 }, { "river": "Dvina", "long": 31.5475, "lat": 56.0591 }, { "river": "Dvina", "long": 31.5025, "lat": 56.0408 }, { "river": "Dvina", "long": 31.4478, "lat": 56.0129 }, { "river": "Dvina", "long": 31.3683, "lat": 55.9717 }, { "river": "Dvina", "long": 31.406, "lat": 55.8408 }, { "river": "Dvina", "long": 31.4829, "lat": 55.7242 }, { "river": "Dvina", "long": 31.4781, "lat": 55.6602 }, { "river": "Dvina", "long": 31.4221, "lat": 55.6668 }, { "river": "Dvina", "long": 31.3569, "lat": 55.6362 }, { "river": "Dvina", "long": 31.1962, "lat": 55.6213 }, { "river": "Dvina", "long": 31.1165, "lat": 55.585 }, { "river": "Dvina", "long": 31.0558, "lat": 55.5093 }, { "river": "Dvina", "long": 30.8047, "lat": 55.4476 }, { "river": "Dvina", "long": 30.666, "lat": 55.4175 }, { "river": "Dvina", "long": 30.5056, "lat": 55.4117 }, { "river": "Dvina", "long": 30.3404, "lat": 55.3176 }, { "river": "Dvina", "long": 30.186, "lat": 55.1813 }, { "river": "Dvina", "long": 29.867, "lat": 55.2173 }, { "river": "Dvina", "long": 29.7324, "lat": 55.1238 }, { "river": "Dvina", "long": 29.5867, "lat": 55.1258 }, { "river": "Dvina", "long": 29.4595, "lat": 55.0506 }, { "river": "Dvina", "long": 29.4124, "lat": 55.131 }, { "river": "Dvina", "long": 29.2864, "lat": 55.1693 }, { "river": "Dvina", "long": 29.2481, "lat": 55.226 }, { "river": "Dvina", "long": 29.1895, "lat": 55.2642 }, { "river": "Dvina", "long": 29.1197, "lat": 55.2662 }, { "river": "Dvina", "long": 29.0144, "lat": 55.3523 }, { "river": "Dvina", "long": 28.9903, "lat": 55.3891 }, { "river": "Dvina", "long": 28.9939, "lat": 55.4274 }, { "river": "Dvina", "long": 28.8878, "lat": 55.4589 }, { "river": "Dvina", "long": 28.626, "lat": 55.5582 }, { "river": "Dvina", "long": 28.4902, "lat": 55.5313 }, { "river": "Dvina", "long": 28.1183, "lat": 55.6009 }, { "river": "Dvina", "long": 28.0554, "lat": 55.6676 }, { "river": "Dvina", "long": 27.9351, "lat": 55.712 }, { "river": "Dvina", "long": 27.9233, "lat": 55.7497 }, { "river": "Dvina", "long": 27.9073, "lat": 55.8238 }, { "river": "Dvina", "long": 27.4384, "lat": 55.799 }, { "river": "Dvina", "long": 27.2703, "lat": 55.8663 }, { "river": "Dvina", "long": 27.1431, "lat": 55.8929 }, { "river": "Dvina", "long": 27.0735, "lat": 55.8909 }, { "river": "Dvina", "long": 27.0332, "lat": 55.8936 }, { "river": "Dvina", "long": 26.9034, "lat": 55.9208 }, { "river": "Dvina", "long": 26.8464, "lat": 55.8947 }, { "river": "Dvina", "long": 26.7672, "lat": 55.8922 }, { "river": "Dvina", "long": 26.7048, "lat": 55.9283 }, { "river": "Dvina", "long": 26.6167, "lat": 55.8812 }, { "river": "Dvina", "long": 26.5265, "lat": 55.8663 }, { "river": "Dvina", "long": 26.4202, "lat": 55.9445 }, { "river": "Dvina", "long": 26.357, "lat": 56.0554 }, { "river": "Dvina", "long": 26.3281, "lat": 56.1625 }, { "river": "Dnieper", "long": 33.3906, "lat": 54.9172 }, { "river": "Dnieper", "long": 33.2773, "lat": 54.9135 }, { "river": "Dnieper", "long": 33.1983, "lat": 54.9533 }, { "river": "Dnieper", "long": 33.1544, "lat": 54.9674 }, { "river": "Dnieper", "long": 33.0949, "lat": 54.9769 }, { "river": "Dnieper", "long": 33.0518, "lat": 54.9862 }, { "river": "Dnieper", "long": 32.9898, "lat": 55.0104 }, { "river": "Dnieper", "long": 32.8748, "lat": 54.9758 }, { "river": "Dnieper", "long": 32.8037, "lat": 54.9482 }, { "river": "Dnieper", "long": 32.7695, "lat": 54.9346 }, { "river": "Dnieper", "long": 32.6957, "lat": 54.8955 }, { "river": "Dnieper", "long": 32.6455, "lat": 54.852 }, { "river": "Dnieper", "long": 32.6733, "lat": 54.8179 }, { "river": "Dnieper", "long": 32.5732, "lat": 54.7623 }, { "river": "Dnieper", "long": 32.5379, "lat": 54.7049 }, { "river": "Dnieper", "long": 32.4959, "lat": 54.6876 }, { "river": "Dnieper", "long": 32.4876, "lat": 54.6678 }, { "river": "Dnieper", "long": 32.4449, "lat": 54.6743 }, { "river": "Dnieper", "long": 32.3233, "lat": 54.6827 }, { "river": "Dnieper", "long": 32.2925, "lat": 54.7377 }, { "river": "Dnieper", "long": 32.2114, "lat": 54.7498 }, { "river": "Dnieper", "long": 32.0161, "lat": 54.7974 }, { "river": "Dnieper", "long": 31.6803, "lat": 54.749 }, { "river": "Dnieper", "long": 31.4895, "lat": 54.6655 }, { "river": "Dnieper", "long": 31.3845, "lat": 54.7071 }, { "river": "Dnieper", "long": 31.2918, "lat": 54.7112 }, { "river": "Dnieper", "long": 31.1987, "lat": 54.6769 }, { "river": "Dnieper", "long": 31.1282, "lat": 54.6671 }, { "river": "Dnieper", "long": 30.9298, "lat": 54.6671 }, { "river": "Dnieper", "long": 30.7344, "lat": 54.6192 }, { "river": "Dnieper", "long": 30.6692, "lat": 54.5777 }, { "river": "Dnieper", "long": 30.6109, "lat": 54.5607 }, { "river": "Dnieper", "long": 30.5737, "lat": 54.5398 }, { "river": "Dnieper", "long": 30.5221, "lat": 54.5398 }, { "river": "Dnieper", "long": 30.4872, "lat": 54.5574 }, { "river": "Dnieper", "long": 30.3887, "lat": 54.4742 }, { "river": "Dnieper", "long": 30.398, "lat": 54.4389 }, { "river": "Dnieper", "long": 30.3626, "lat": 54.3838 }, { "river": "Dnieper", "long": 30.2822, "lat": 54.3083 }, { "river": "Dnieper", "long": 30.2822, "lat": 54.2665 }, { "river": "Dnieper", "long": 30.3053, "lat": 54.2241 }, { "river": "Dnieper", "long": 30.2979, "lat": 54.2087 }, { "river": "Dnieper", "long": 30.3272, "lat": 54.1949 }, { "river": "Dnieper", "long": 30.3668, "lat": 54.1277 }, { "river": "Dnieper", "long": 30.3694, "lat": 54.0844 }, { "river": "Dnieper", "long": 30.3889, "lat": 53.9927 }, { "river": "Dnieper", "long": 30.4378, "lat": 53.9431 }, { "river": "Dnieper", "long": 30.4404, "lat": 53.9014 }, { "river": "Dnieper", "long": 30.2887, "lat": 53.8794 }, { "river": "Dnieper", "long": 30.2825, "lat": 53.8481 }, { "river": "Dnieper", "long": 30.2781, "lat": 53.805 }, { "river": "Dnieper", "long": 30.2669, "lat": 53.7647 }, { "river": "Dnieper", "long": 30.2778, "lat": 53.6114 }, { "river": "Dnieper", "long": 30.2675, "lat": 53.5784 }, { "river": "Dnieper", "long": 30.2558, "lat": 53.5271 }, { "river": "Dnieper", "long": 30.2996, "lat": 53.502 }, { "river": "Dnieper (2)", "long": 33.7354, "lat": 55.8737 }, { "river": "Dnieper (2)", "long": 33.7588, "lat": 55.8565 }, { "river": "Dnieper (2)", "long": 33.8268, "lat": 55.8292 }, { "river": "Dnieper (2)", "long": 33.801, "lat": 55.8079 }, { "river": "Dnieper (2)", "long": 33.7901, "lat": 55.8048 }, { "river": "Dnieper (2)", "long": 33.7813, "lat": 55.7976 }, { "river": "Dnieper (2)", "long": 33.7686, "lat": 55.7749 }, { "river": "Dnieper (2)", "long": 33.7804, "lat": 55.77 }, { "river": "Dnieper (2)", "long": 33.7886, "lat": 55.7679 }, { "river": "Dnieper (2)", "long": 33.8341, "lat": 55.7454 }, { "river": "Dnieper (2)", "long": 33.8486, "lat": 55.7437 }, { "river": "Dnieper (2)", "long": 33.8638, "lat": 55.7441 }, { "river": "Dnieper (2)", "long": 33.9063, "lat": 55.7317 }, { "river": "Dnieper (2)", "long": 33.9067, "lat": 55.7193 }, { "river": "Dnieper (2)", "long": 33.8492, "lat": 55.6627 }, { "river": "Dnieper (2)", "long": 33.8398, "lat": 55.6502 }, { "river": "Dnieper (2)", "long": 33.8272, "lat": 55.6461 }, { "river": "Dnieper (2)", "long": 33.8135, "lat": 55.643 }, { "river": "Dnieper (2)", "long": 33.7934, "lat": 55.6302 }, { "river": "Dnieper (2)", "long": 33.791, "lat": 55.6251 }, { "river": "Dnieper (2)", "long": 33.7918, "lat": 55.6188 }, { "river": "Dnieper (2)", "long": 33.7813, "lat": 55.5696 }, { "river": "Dnieper (2)", "long": 33.7422, "lat": 55.5407 }, { "river": "Dnieper (2)", "long": 33.7293, "lat": 55.5393 }, { "river": "Dnieper (2)", "long": 33.6882, "lat": 55.5577 }, { "river": "Dnieper (2)", "long": 33.6674, "lat": 55.5651 }, { "river": "Dnieper (2)", "long": 33.6376, "lat": 55.572 }, { "river": "Dnieper (2)", "long": 33.5817, "lat": 55.5611 }, { "river": "Dnieper (2)", "long": 33.5733, "lat": 55.5466 }, { "river": "Dnieper (2)", "long": 33.5584, "lat": 55.442 }, { "river": "Dnieper (2)", "long": 33.5912, "lat": 55.3692 }, { "river": "Dnieper (2)", "long": 33.5987, "lat": 55.3319 }, { "river": "Dnieper (2)", "long": 33.5698, "lat": 55.275 }, { "river": "Dnieper (2)", "long": 33.5647, "lat": 55.2524 }, { "river": "Dnieper (2)", "long": 33.5592, "lat": 55.2426 }, { "river": "Dnieper (2)", "long": 33.5315, "lat": 55.231 }, { "river": "Dnieper (2)", "long": 33.5044, "lat": 55.227 }, { "river": "Dnieper (2)", "long": 33.501, "lat": 55.2206 }, { "river": "Dnieper (2)", "long": 33.5025, "lat": 55.2015 }, { "river": "Dnieper (2)", "long": 33.4967, "lat": 55.184 }, { "river": "Dnieper (2)", "long": 33.4889, "lat": 55.1679 }, { "river": "Dnieper (2)", "long": 33.4692, "lat": 55.1537 }, { "river": "Dnieper (2)", "long": 33.4677, "lat": 55.1478 }, { "river": "Dnieper (2)", "long": 33.4685, "lat": 55.1354 }, { "river": "Dnieper (2)", "long": 33.4522, "lat": 55.1129 }, { "river": "Dnieper (2)", "long": 33.4457, "lat": 55.1023 }, { "river": "Dnieper (2)", "long": 33.4449, "lat": 55.0956 }, { "river": "Dnieper (2)", "long": 33.4487, "lat": 55.0728 }, { "river": "Dnieper (2)", "long": 33.4457, "lat": 55.06 }, { "river": "Dnieper (2)", "long": 33.4418, "lat": 55.0506 }, { "river": "Dnieper (2)", "long": 33.3897, "lat": 54.9988 }, { "river": "Dnieper (2)", "long": 33.3965, "lat": 54.9892 }, { "river": "Dnieper (2)", "long": 33.4086, "lat": 54.98 }, { "river": "Dnieper (2)", "long": 33.4157, "lat": 54.9357 }, { "river": "Dnieper (2)", "long": 33.4063, "lat": 54.9203 }, { "river": "Dnieper (2)", "long": 33.3906, "lat": 54.9172 }, { "river": "Moskva", "long": 38.3749, "lat": 55.4156 }, { "river": "Moskva", "long": 38.3421, "lat": 55.4141 }, { "river": "Moskva", "long": 38.3184, "lat": 55.4168 }, { "river": "Moskva", "long": 38.3059, "lat": 55.4225 }, { "river": "Moskva", "long": 38.2409, "lat": 55.4584 }, { "river": "Moskva", "long": 38.228, "lat": 55.4691 }, { "river": "Moskva", "long": 38.2139, "lat": 55.4872 }, { "river": "Moskva", "long": 38.1526, "lat": 55.5454 }, { "river": "Moskva", "long": 38.1316, "lat": 55.5555 }, { "river": "Moskva", "long": 38.1145, "lat": 55.5584 }, { "river": "Moskva", "long": 38.0973, "lat": 55.5654 }, { "river": "Moskva", "long": 38.0442, "lat": 55.5794 }, { "river": "Moskva", "long": 38.023, "lat": 55.5733 }, { "river": "Moskva", "long": 38.0017, "lat": 55.564 }, { "river": "Moskva", "long": 37.9317, "lat": 55.5672 }, { "river": "Moskva", "long": 37.8951, "lat": 55.583 }, { "river": "Moskva", "long": 37.86, "lat": 55.6143 }, { "river": "Moskva", "long": 37.8046, "lat": 55.6304 }, { "river": "Moskva", "long": 37.768, "lat": 55.6516 }, { "river": "Moskva", "long": 37.7036, "lat": 55.6562 }, { "river": "Moskva", "long": 37.6826, "lat": 55.6621 }, { "river": "Moskva", "long": 37.6709, "lat": 55.6833 }, { "river": "Moskva", "long": 37.6661, "lat": 55.6917 }, { "river": "Moskva", "long": 37.6382, "lat": 55.6993 }, { "river": "Moskva", "long": 37.6296, "lat": 55.7124 }, { "river": "Moskva", "long": 37.5613, "lat": 55.7323 }, { "river": "Moskva", "long": 37.5139, "lat": 55.7634 }, { "river": "Moskva", "long": 37.4478, "lat": 55.7867 }, { "river": "Moskva", "long": 37.4117, "lat": 55.824 }, { "river": "Moskva", "long": 37.3965, "lat": 55.8317 }, { "river": "Moskva", "long": 37.3756, "lat": 55.8275 }, { "river": "Moskva", "long": 37.3176, "lat": 55.7999 }, { "river": "Moskva", "long": 37.2672, "lat": 55.7664 }, { "river": "Moskva", "long": 37.249, "lat": 55.7607 }, { "river": "Moskva", "long": 37.2302, "lat": 55.7588 }, { "river": "Moskva", "long": 37.1406, "lat": 55.7461 }, { "river": "Moskva", "long": 37.1089, "lat": 55.7333 }, { "river": "Moskva", "long": 37.0874, "lat": 55.7305 }, { "river": "Moskva", "long": 37.0278, "lat": 55.7395 }, { "river": "Moskva", "long": 36.9913, "lat": 55.7376 }, { "river": "Moskva", "long": 36.939, "lat": 55.7389 }, { "river": "Moskva", "long": 36.8795, "lat": 55.7293 }, { "river": "Moskva", "long": 36.7486, "lat": 55.7164 }, { "river": "Moskva", "long": 36.6879, "lat": 55.708 }, { "river": "Moskva", "long": 36.6798, "lat": 55.7003 }, { "river": "Moskva", "long": 36.6602, "lat": 55.6764 }, { "river": "Moskva", "long": 36.6345, "lat": 55.6652 }, { "river": "Moskva", "long": 36.6062, "lat": 55.6578 }, { "river": "Moskva", "long": 36.5844, "lat": 55.6475 }, { "river": "Moskva", "long": 36.5425, "lat": 55.6096 }, { "river": "Moskva", "long": 36.5209, "lat": 55.6143 }, { "river": "Moskva", "long": 36.5112, "lat": 55.6197 }, { "river": "Moskva", "long": 36.44, "lat": 55.6192 }, { "river": "Moskva", "long": 36.4185, "lat": 55.6239 }, { "river": "Moskva", "long": 36.4043, "lat": 55.6295 }, { "river": "Moskva", "long": 36.375, "lat": 55.6323 }, { "river": "Moskva (2)", "long": 35.3119, "lat": 55.572 }, { "river": "Moskva (2)", "long": 35.3013, "lat": 55.6088 }, { "river": "Moskva (2)", "long": 35.3007, "lat": 55.624 }, { "river": "Moskva (2)", "long": 35.3119, "lat": 55.6329 }, { "river": "Moskva (2)", "long": 35.3538, "lat": 55.6232 }, { "river": "Moskva (2)", "long": 35.3774, "lat": 55.6243 }, { "river": "Moskva (2)", "long": 35.3877, "lat": 55.6431 }, { "river": "Moskva (2)", "long": 35.4065, "lat": 55.6606 }, { "river": "Moskva (2)", "long": 35.4501, "lat": 55.6699 }, { "river": "Moskva (2)", "long": 35.5344, "lat": 55.6744 }, { "river": "Moskva (2)", "long": 35.5532, "lat": 55.6766 }, { "river": "Moskva (2)", "long": 35.5836, "lat": 55.686 }, { "river": "Moskva (2)", "long": 35.603, "lat": 55.6881 }, { "river": "Moskva (2)", "long": 35.624, "lat": 55.6845 }, { "river": "Moskva (2)", "long": 35.6682, "lat": 55.6602 }, { "river": "Moskva (2)", "long": 35.7551, "lat": 55.6411 }, { "river": "Moskva (2)", "long": 35.7944, "lat": 55.6241 }, { "river": "Moskva (2)", "long": 35.7986, "lat": 55.5987 }, { "river": "Moskva (2)", "long": 35.8088, "lat": 55.5955 }, { "river": "Moskva (2)", "long": 35.8297, "lat": 55.5942 }, { "river": "Moskva (2)", "long": 35.8449, "lat": 55.5913 }, { "river": "Moskva (2)", "long": 35.8557, "lat": 55.5926 }, { "river": "Moskva (2)", "long": 35.8606, "lat": 55.5919 }, { "river": "Moskva (2)", "long": 35.8652, "lat": 55.5872 }, { "river": "Moskva (2)", "long": 35.867, "lat": 55.5747 }, { "river": "Moskva (2)", "long": 35.8701, "lat": 55.572 }, { "river": "Moskva (2)", "long": 35.8906, "lat": 55.5684 }, { "river": "Moskva (2)", "long": 35.9567, "lat": 55.5441 }, { "river": "Moskva (2)", "long": 35.9869, "lat": 55.5404 }, { "river": "Moskva (2)", "long": 35.9965, "lat": 55.5373 }, { "river": "Moskva (2)", "long": 36.0256, "lat": 55.5191 }, { "river": "Moskva (2)", "long": 36.0351, "lat": 55.5168 }, { "river": "Moskva (2)", "long": 36.0503, "lat": 55.5244 }, { "river": "Moskva (2)", "long": 36.0777, "lat": 55.5626 }, { "river": "Moskva (2)", "long": 36.0927, "lat": 55.5782 }, { "river": "Moskva (2)", "long": 36.1308, "lat": 55.5954 }, { "river": "Moskva (2)", "long": 36.1539, "lat": 55.6026 }, { "river": "Moskva (2)", "long": 36.1918, "lat": 55.6093 }, { "river": "Moskva (2)", "long": 36.2333, "lat": 55.6247 }, { "river": "Moskva (2)", "long": 36.2578, "lat": 55.6267 }, { "river": "Moskva (2)", "long": 36.2876, "lat": 55.6187 }, { "river": "Moskva (2)", "long": 36.2988, "lat": 55.6192 }, { "river": "Moskva (2)", "long": 36.3121, "lat": 55.6232 }, { "river": "Moskva (2)", "long": 36.3305, "lat": 55.6314 }, { "river": "Moskva (2)", "long": 36.3435, "lat": 55.6329 }, { "river": "Moskva (2)", "long": 36.375, "lat": 55.6323 } ] }, "inspect": false, "params": { "linewidth": 0.8, "alpha": 0.7 } }, { "geom": "path", "stat": "identity", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "lat" }, "color": { "field": "direction" }, "linewidth": { "field": "survivors" }, "group": { "field": "leg" } } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "lx" }, "y": { "field": "ly" }, "color": { "value": "#4a4237" }, "label": { "field": "city" } }, "data": { "values": [ { "lx": 23.7, "ly": 55.05, "city": "Kowno" }, { "lx": 25.3, "ly": 54.46, "city": "Wilna" }, { "lx": 26.299999999999997, "ly": 54.18, "city": "Smorgoni" }, { "lx": 26.900000000000002, "ly": 54.44, "city": "Molodezno" }, { "lx": 27.7, "ly": 55.2, "city": "Gloubokoe" }, { "lx": 27.6, "ly": 53.9, "city": "Minsk" }, { "lx": 28.5, "ly": 53.959999999999994, "city": "Studienska" }, { "lx": 28.7, "ly": 55.62, "city": "Polotzk" }, { "lx": 29.4, "ly": 54.5, "city": "Bobr" }, { "lx": 30.2, "ly": 55.3, "city": "Witebsk" }, { "lx": 30.4, "ly": 54.5, "city": "Orscha" }, { "lx": 30.4, "ly": 53.9, "city": "Mohilow" }, { "lx": 32, "ly": 54.8, "city": "Smolensk" }, { "lx": 33.2, "ly": 54.9, "city": "Dorogobouge" }, { "lx": 34.3, "ly": 55.2, "city": "Wixma" }, { "lx": 34.4, "ly": 55.5, "city": "Chjat" }, { "lx": 36, "ly": 55.5, "city": "Mojaisk" }, { "lx": 37.72, "ly": 55.86, "city": "Moscou" }, { "lx": 36.6, "ly": 55.3, "city": "Tarantino" }, { "lx": 36.5, "ly": 54.8, "city": "Malo-Jarosewii" } ] }, "inspect": false, "params": { "size": 10, "dy": -9 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "long" }, "y": { "field": "lat" }, "color": { "value": "#6b5d4a" }, "label": { "field": "count" } }, "data": { "values": [ { "long": 25.5, "lat": 55.3, "count": "340,000" }, { "long": 24.2, "lat": 55.5, "count": "60,000" }, { "long": 24.2, "lat": 55.62, "count": "22,000" }, { "long": 26.4, "lat": 55.92, "count": "40,000" }, { "long": 30.3, "lat": 55.66, "count": "175,000" }, { "long": 32.9, "lat": 55.36, "count": "140,000" }, { "long": 37.2, "lat": 56.12, "count": "100,000" }, { "long": 36.5, "lat": 54.72, "count": "97,000" }, { "long": 34.3, "lat": 54.88, "count": "55,000" }, { "long": 33.3, "lat": 54.48, "count": "37,000" }, { "long": 31.6, "lat": 54.3, "count": "24,000" }, { "long": 28.9, "lat": 53.78, "count": "20,000" }, { "long": 25, "lat": 54.14, "count": "8,000" }, { "long": 24.1, "lat": 54.12, "count": "4,000" } ] }, "inspect": false, "params": { "size": 9 } } ], "coord": { "type": "fixed", "ratio": 1.6 }, "scales": { "x": { "type": "linear", "domain": [ 23.5, 38.2 ] }, "color": { "type": "manual", "domain": [ "Advance", "Retreat" ], "guide": { "type": "legend", "position": "bottom" }, "range": [ "#d3a05e", "#25221e" ] }, "linewidth": { "type": "sequential", "range": [ 1, 18 ], "guide": { "type": "legend", "position": "bottom" } } }, "labs": { "title": "The Grande Armée's march to Moscow and back, 1812–13", "subtitle": "Band width is the number of men still with the column — after Minard's 1869 figurative map", "x": "", "y": "", "color": "", "linewidth": "Survivors" }, "theme": "classic" } // note: inline data truncated (31 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomPath, GeomPoint, GeomText, GGPlot, Inspect, Labs, ScaleColorManual, ScaleLinewidthContinuous, ScaleXContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import type { PlotInspectionChange } from "@ggsvelte/svelte"; import { campaignRivers, minardCityLabels, minardCold, minardStrengthLabels, minardTroops, } from "./data.js"; import { coldStripTooltipFields, mapMarchTooltipFields } from "./tooltip.js"; type Inspection = PlotInspectionChange<Record<string, unknown>, PropertyKey>; /** * Map uses CoordFixed; cold strip does not. Independent layout passes then * pick different left chrome and, when the map letterboxes, different panel * widths — shared lon limits stop lining up. After both plots paint, pin the * cold host so its data panel matches the map panel. * * Observe the *map* only. Resizing the cold host used to re-enter align * (ResizeObserver + style MutationObserver), which recomputed width from the * new cold margins and oscillated forever on the right edge. */ let minardEl = $state<HTMLDivElement | null>(null); let mapHost = $state<HTMLDivElement | null>(null); let coldHost = $state<HTMLDivElement | null>(null); let coldShiftPx = $state(0); let coldWidthPx = $state<number | null>(null); /** Cold plot chrome sampled once per map-root width (not every cold reflow). */ let frozenColdLeftMargin: number | null = null; let frozenColdRightMargin: number | null = null; let frozenForMapRootWidth = -1; $effect(() => { const map = mapHost; const cold = coldHost; if (map === null || cold === null) return; let raf = 0; let alive = true; const schedule = (): void => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { if (!alive) return; alignColdPanelToMap(schedule); }); }; function alignColdPanelToMap(reschedule: () => void): void { const host = minardEl; const mapRoot = mapHost?.querySelector(".gg-plot-root"); const mapPanel = mapHost?.querySelector(".gg-panel"); const coldPanel = coldHost?.querySelector(".gg-panel"); const coldRoot = coldHost?.querySelector(".gg-plot-root"); if ( host === null || mapRoot === null || mapRoot === undefined || mapPanel === null || mapPanel === undefined || coldPanel === null || coldPanel === undefined || coldRoot === null || coldRoot === undefined ) { return; } if ( mapRoot.dataset["ggReady"] !== "true" || coldRoot.dataset["ggReady"] !== "true" ) { return; } const hostBox = host.getBoundingClientRect(); const mapRootBox = mapRoot.getBoundingClientRect(); const mapPanelBox = mapPanel.getBoundingClientRect(); if (mapPanelBox.width < 8 || mapRootBox.width < 8) return; const mapRootWidth = Math.round(mapRootBox.width); // Container width changed: drop the pin, sample chrome at full width next frame. if (frozenForMapRootWidth !== mapRootWidth && coldWidthPx !== null) { frozenColdLeftMargin = null; frozenColdRightMargin = null; frozenForMapRootWidth = mapRootWidth; coldWidthPx = null; coldShiftPx = 0; // Tracked via schedule() so effect cleanup cancels this rAF on unmount. reschedule(); return; } frozenForMapRootWidth = mapRootWidth; // Sample cold left/right chrome once per container width, while the strip // is still full-bleed — never after our pin reflow (that caused right-edge // width oscillation). Measure against the <svg>, not .gg-plot-root: after // unpin the root is already 100% wide while the SVG may still hold the // previous scene width until GGPlot's own ResizeObserver rAF commits. if (frozenColdLeftMargin === null || frozenColdRightMargin === null) { const coldSvg = coldRoot.querySelector("svg"); if (coldSvg === null) return; const coldPanelBox = coldPanel.getBoundingClientRect(); const coldSvgBox = coldSvg.getBoundingClientRect(); const coldRootBox = coldRoot.getBoundingClientRect(); if (coldPanelBox.width < 8 || coldSvgBox.width < 8) return; // Wait until the plot has remeasured to the host (full-bleed after unpin). if (Math.abs(coldSvgBox.width - coldRootBox.width) > 2) { reschedule(); return; } frozenColdLeftMargin = coldPanelBox.left - coldSvgBox.left; frozenColdRightMargin = coldSvgBox.right - coldPanelBox.right; } const nextWidth = Math.round( mapPanelBox.width + frozenColdLeftMargin + frozenColdRightMargin, ); const nextShift = Math.round( mapPanelBox.left - hostBox.left - frozenColdLeftMargin, ); if ( !Number.isFinite(nextWidth) || !Number.isFinite(nextShift) || nextWidth < 32 ) { return; } if (coldWidthPx === nextWidth && coldShiftPx === nextShift) return; coldWidthPx = nextWidth; coldShiftPx = nextShift; } // Map size / letterbox is the source of truth. Do not observe cold: writing // coldWidthPx/coldShiftPx resizes cold and would re-enter align forever. const ro = new ResizeObserver(schedule); ro.observe(map); if (minardEl !== null) ro.observe(minardEl); const mo = new MutationObserver(schedule); mo.observe(map, { attributes: true, attributeFilter: ["data-gg-ready"], subtree: true, childList: true, }); mo.observe(cold, { attributes: true, attributeFilter: ["data-gg-ready"], subtree: true, childList: true, }); schedule(); return () => { alive = false; cancelAnimationFrame(raf); ro.disconnect(); mo.disconnect(); }; }); </script> {#snippet tip(fields: { label: string; value: string }[])} {#if fields.length > 0} <dl class="minard-tip"> {#each fields as field (field.label)} <div> <dt>{field.label}</dt> <dd>{field.value}</dd> </div> {/each} </dl> {/if} {/snippet} {#snippet mapTooltip(inspection: Inspection)} {@render tip(mapMarchTooltipFields(inspection.focus.row ?? {}))} {/snippet} {#snippet coldTooltip(inspection: Inspection)} {@render tip(coldStripTooltipFields(inspection.focus.row ?? {}))} {/snippet} <div class="minard" bind:this={minardEl}> <div class="minard-map" bind:this={mapHost}> <GGPlot width="container" height={520}> <GeomPath data={campaignRivers} aes={{ x: "long", y: "lat", group: "river", color: { value: "#8fa8c0" }, }} linewidth={0.8} alpha={0.7} inspect={false} /> <GeomPath data={minardTroops} aes={{ x: "long", y: "lat", group: "leg", color: "direction", linewidth: "survivors", }} /> <GeomText data={minardCityLabels} aes={{ x: "lx", y: "ly", label: "city", color: { value: "#4a4237" } }} size={10} dy={-9} inspect={false} /> <GeomText data={minardStrengthLabels} aes={{ x: "long", y: "lat", label: "count", color: { value: "#6b5d4a" }, }} size={9} inspect={false} /> <!-- lon/lat degrees are not the same length on the ground at 55°N --> <CoordFixed ratio={1.6} /> <ScaleXContinuous limits={[23.5, 38.2]} /> <!-- legends below the panel so side legends do not steal panel width --> <ScaleColorManual domain={["Advance", "Retreat"]} values={["#d3a05e", "#25221e"]} guide={{ type: "legend", position: "bottom" }} /> <ScaleLinewidthContinuous range={[1, 18]} guide={{ type: "legend", position: "bottom" }} /> <ThemeClassic /> <Labs title="The Grande Armée's march to Moscow and back, 1812–13" subtitle="Band width is the number of men still with the column — after Minard's 1869 figurative map" x="" y="" color="" linewidth="Survivors" /> <Inspect mode="xy" pin maxDistance={24} content={mapTooltip} /> </GGPlot> </div> <!-- Units live in the subtitle so both plots omit a y-axis title band. Host size/offset is rewritten to the map data panel after paint (see script). --> <div class="minard-cold" bind:this={coldHost} style:margin-left={coldWidthPx === null ? undefined : `${String(coldShiftPx)}px`} style:width={coldWidthPx === null ? "100%" : `${String(coldWidthPx)}px`} > <GGPlot data={minardCold} width="container" height={190}> <GeomPath aes={{ x: "long", y: "temp", color: { value: "#6b7280" } }} linewidth={1.5} /> <GeomPoint aes={{ x: "long", y: "temp", color: { value: "#374151" } }} size={2.5} /> <GeomText aes={{ x: "long", y: "temp", label: "date", color: { value: "#374151" }, }} size={10} dy={-11} inspect={false} /> <ScaleXContinuous limits={[23.5, 38.2]} /> <ThemeClassic /> <Labs title="The cold on the road back" subtitle="Temperature on the retreat, degrees Réaumur — dates as Minard marked them" x="Longitude east" y="" /> <Inspect mode="xy" pin maxDistance={24} content={coldTooltip} /> </GGPlot> </div> </div> <style> .minard { display: flex; flex-direction: column; gap: 0.5rem; /* fullWidth frame + container-width plots: fill the shell so live matches the stretched gallery PNG instead of snapping to a fixed px. */ width: 100%; min-width: 0; } .minard-map, .minard-cold { min-width: 0; box-sizing: border-box; } .minard-map { width: 100%; } .minard-tip { margin: 0; display: grid; gap: 0.2rem; font: 0.8rem/1.3 var(--gg-font-family, system-ui, sans-serif); } .minard-tip div { display: grid; grid-template-columns: auto 1fr; gap: 0.35rem 0.65rem; align-items: baseline; } .minard-tip dt { margin: 0; color: var(--muted, #59636e); font-weight: 600; } .minard-tip dd { margin: 0; color: var(--fg, #17202a); } </style> ``` ## Points against the identity line (point/abline-identity) Identity ablines when agreement with y = x is the claim. Tags: point, abline, annotation, reference Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "patient": 1, "hyoscyamine": 1.3, "hyoscine": 2.5 }, { "patient": 2, "hyoscyamine": 1.4, "hyoscine": 3.8 }, { "patient": 3, "hyoscyamine": 4.5, "hyoscine": 5.8 }, { "patient": 4, "hyoscyamine": 4.3, "hyoscine": 5.6 }, { "patient": 5, "hyoscyamine": 6.1, "hyoscine": 6.1 }, { "patient": 6, "hyoscyamine": 6.6, "hyoscine": 7.6 }, { "patient": 7, "hyoscyamine": 6.2, "hyoscine": 8 }, { "patient": 8, "hyoscyamine": 3.6, "hyoscine": 4.4 }, { "patient": 9, "hyoscyamine": 1.1, "hyoscine": 5.7 }, { "patient": 10, "hyoscyamine": 4.9, "hyoscine": 6.3 }, { "patient": 11, "hyoscyamine": 6.3, "hyoscine": 6.8 } ] }, "layers": [ { "geom": "abline", "stat": "identity", "position": "identity", "params": { "slope": 1, "intercept": 0, "linewidth": 1.2 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "hyoscyamine" }, "y": { "field": "hyoscine" } }, "params": { "size": 3.5 } } ], "labs": { "title": "Points against the identity line", "subtitle": "Ten of eleven patients improve more on one drug than the other", "x": "Extra sleep on L-hyoscyamine (hours)", "y": "Extra sleep on L-hyoscine (hours)" }, "theme": "dark" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomAbline, GeomPoint, GGPlot, Inspect, Labs, ThemeDark, } from "@ggsvelte/svelte"; import { soporifics } from "./data.js"; </script> <GGPlot data={soporifics} aes={{ x: "hyoscyamine", y: "hyoscine" }} width={640} height={400} > <GeomAbline slope={1} intercept={0} linewidth={1.2} /> <GeomPoint size={3.5} /> <ThemeDark /> <Labs title="Points against the identity line" subtitle="Ten of eleven patients improve more on one drug than the other" x="Extra sleep on L-hyoscyamine (hours)" y="Extra sleep on L-hyoscine (hours)" /> <Inspect mode="xy" pin maxDistance={24} identity="patient" /> </GGPlot> ``` ## Overlapping points sized by how many share a cell (point/count) Count stat on points when discrete rounding collapses many rows onto few coordinates. Tags: point, count, size, overplot, stat Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "parent": 70.5, "child": 61.7 }, { "parent": 68.5, "child": 61.7 }, { "parent": 65.5, "child": 61.7 }, { "parent": 64.5, "child": 61.7 }, { "parent": 64, "child": 61.7 }, { "parent": 67.5, "child": 62.2 }, { "parent": 67.5, "child": 62.2 }, { "parent": 67.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 66.5, "child": 62.2 }, { "parent": 64.5, "child": 62.2 }, { "parent": 70.5, "child": 63.2 }, { "parent": 69.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 }, { "parent": 68.5, "child": 63.2 } ] }, "layers": [ { "geom": "count", "stat": "sum", "position": "identity", "aes": { "x": { "field": "parent" }, "y": { "field": "child" }, "size": { "stat": "n" } }, "params": { "alpha": 0.7 } } ], "labs": { "title": "Overlapping points sized by how many share a cell", "subtitle": "Heights to the nearest inch", "x": "Mid-parent height (inches)", "y": "Child height (inches)", "size": "Children" }, "theme": "classic" } // note: inline data truncated (908 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomCount, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { galtonHeights } from "./data.js"; </script> <GGPlot data={galtonHeights} aes={{ x: "parent", y: "child" }} width={640} height={400} > <GeomCount alpha={0.7} /> <ThemeClassic /> <Labs title="Overlapping points sized by how many share a cell" subtitle="Heights to the nearest inch" x="Mid-parent height (inches)" y="Child height (inches)" size="Children" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Equal data units on both axes (point/fixed-aspect) Fixed aspect when shape in data units must not stretch with the panel. Tags: point, coordinates, fixed-aspect, responsive Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 1, "y": 0, "order": 0 }, { "x": 0.866025, "y": 0.5, "order": 1 }, { "x": 0.5, "y": 0.866025, "order": 2 }, { "x": 0, "y": 1, "order": 3 }, { "x": -0.5, "y": 0.866025, "order": 4 }, { "x": -0.866025, "y": 0.5, "order": 5 }, { "x": -1, "y": 0, "order": 6 }, { "x": -0.866025, "y": -0.5, "order": 7 }, { "x": -0.5, "y": -0.866025, "order": 8 }, { "x": 0, "y": -1, "order": 9 }, { "x": 0.5, "y": -0.866025, "order": 10 }, { "x": 0.866025, "y": -0.5, "order": 11 }, { "x": 1, "y": 0, "order": 12 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" } }, "params": { "size": 3 } } ], "coord": { "type": "fixed" }, "labs": { "title": "Equal data units on both axes", "subtitle": "A circular cloud stays circular when the aspect ratio locks x and y", "x": "x", "y": "y" } } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomPoint, GGPlot, Inspect, Labs, } from "@ggsvelte/svelte"; import { unitCircle } from "./data.js"; </script> <GGPlot data={unitCircle} aes={{ x: "x", y: "y" }} width="container" height={440} > <GeomPoint size={3} /> <CoordFixed /> <Labs title="Equal data units on both axes" subtitle="A circular cloud stays circular when the aspect ratio locks x and y" x="x" y="y" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Many years folded onto one seasonal axis (point/gradient-continuous) Continuous colour gradients when a third continuous channel carries identity. Tags: point, color, gradient, sequential, scale Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1812, "month": 1, "registered": 1110 }, { "year": 1812, "month": 2, "registered": 1124 }, { "year": 1812, "month": 3, "registered": 1180 }, { "year": 1812, "month": 4, "registered": 1235 }, { "year": 1812, "month": 5, "registered": 1267 }, { "year": 1812, "month": 6, "registered": 1296 }, { "year": 1812, "month": 7, "registered": 1299 }, { "year": 1812, "month": 8, "registered": 1372 }, { "year": 1812, "month": 9, "registered": 1403 }, { "year": 1812, "month": 10, "registered": 1396 }, { "year": 1812, "month": 11, "registered": 1388 }, { "year": 1812, "month": 12, "registered": 1453 }, { "year": 1813, "month": 1, "registered": 1449 }, { "year": 1813, "month": 2, "registered": 1521 }, { "year": 1813, "month": 3, "registered": 1542 }, { "year": 1813, "month": 4, "registered": 1621 }, { "year": 1813, "month": 5, "registered": 1648 }, { "year": 1813, "month": 6, "registered": 1679 }, { "year": 1813, "month": 7, "registered": 1686 }, { "year": 1813, "month": 8, "registered": 1752 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "month" }, "y": { "field": "registered" }, "color": { "field": "year" } }, "params": { "size": 2.6, "alpha": 0.85 } } ], "scales": { "x": { "breaks": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ] }, "color": { "type": "sequential", "range": [ "#132b43", "#56b1f7" ] } }, "labs": { "title": "Many years folded onto one seasonal axis", "subtitle": "Colour tracks year", "x": "Month", "y": "Women on the register", "color": "Year" }, "theme": "minimal" } // note: inline data truncated (496 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, ScaleColorGradient, ScaleXContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { parisRegister } from "./data.js"; </script> <GGPlot data={parisRegister} aes={{ x: "month", y: "registered", color: "year" }} width={640} height={400} > <GeomPoint size={2.6} alpha={0.85} /> <ScaleColorGradient low="#132B43" high="#56B1F7" /> <ScaleXContinuous breaks={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]} /> <ThemeMinimal /> <Labs title="Many years folded onto one seasonal axis" subtitle="Colour tracks year" x="Month" y="Women on the register" color="Year" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Scatter coloured by discrete group (point/hue-discrete) Discrete hue when categories are unordered and equal visual weight matters. Tags: point, color, hue, discrete, scale Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "squadron": "Portugal", "ships": 12, "men": 4623 }, { "squadron": "Biscay", "ships": 14, "men": 2800 }, { "squadron": "Castile", "ships": 16, "men": 4171 }, { "squadron": "Andalusia", "ships": 11, "men": 3105 }, { "squadron": "Guipuzcoa", "ships": 14, "men": 2608 }, { "squadron": "Levant", "ships": 10, "men": 3523 }, { "squadron": "Hulks", "ships": 23, "men": 3729 }, { "squadron": "Pataches", "ships": 22, "men": 1093 }, { "squadron": "Naples", "ships": 4, "men": 1341 }, { "squadron": "Galleys", "ships": 4, "men": 362 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "ships" }, "y": { "field": "men" }, "color": { "field": "squadron" } }, "params": { "size": 5 } } ], "scales": { "color": { "type": "ordinal", "scheme": "hue" } }, "labs": { "title": "Scatter coloured by discrete group", "subtitle": "Ten squadrons by ships and men", "x": "Ships", "y": "Soldiers and sailors", "color": "Squadron" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, ScaleColorHue, ThemeMinimal, } from "@ggsvelte/svelte"; import { armadaSquadrons } from "./data.js"; </script> <GGPlot data={armadaSquadrons} aes={{ x: "ships", y: "men", color: "squadron" }} width={640} height={400} > <GeomPoint size={5} /> <ScaleColorHue /> <ThemeMinimal /> <Labs title="Scatter coloured by discrete group" subtitle="Ten squadrons by ships and men" x="Ships" y="Soldiers and sailors" color="Squadron" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Calories by restaurant, with jitter (point/jitter) Fast-food menu calories by restaurant. Position jitter spreads marks that would otherwise stack on the same category. Tags: point, jitter, position, scatter, categorical, color Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "item-0001", "restaurant": "McDonald's", "item": "Artisan Grilled Chicken Sandwich", "calories": 380, "totalFat": 7, "protein": 37, "sodium": 1110 }, { "id": "item-0002", "restaurant": "McDonald's", "item": "Single Bacon Smokehouse Burger", "calories": 840, "totalFat": 45, "protein": 46, "sodium": 1580 }, { "id": "item-0003", "restaurant": "McDonald's", "item": "Double Bacon Smokehouse Burger", "calories": 1130, "totalFat": 67, "protein": 70, "sodium": 1920 }, { "id": "item-0004", "restaurant": "McDonald's", "item": "Grilled Bacon Smokehouse Chicken Sandwich", "calories": 750, "totalFat": 31, "protein": 55, "sodium": 1940 }, { "id": "item-0005", "restaurant": "McDonald's", "item": "Crispy Bacon Smokehouse Chicken Sandwich", "calories": 920, "totalFat": 45, "protein": 46, "sodium": 1980 }, { "id": "item-0006", "restaurant": "McDonald's", "item": "Big Mac", "calories": 540, "totalFat": 28, "protein": 25, "sodium": 950 }, { "id": "item-0007", "restaurant": "McDonald's", "item": "Cheeseburger", "calories": 300, "totalFat": 12, "protein": 15, "sodium": 680 }, { "id": "item-0008", "restaurant": "McDonald's", "item": "Classic Chicken Sandwich", "calories": 510, "totalFat": 24, "protein": 25, "sodium": 1040 }, { "id": "item-0009", "restaurant": "McDonald's", "item": "Double Cheeseburger", "calories": 430, "totalFat": 21, "protein": 25, "sodium": 1040 }, { "id": "item-0010", "restaurant": "McDonald's", "item": "Double Quarter Pounder® with Cheese", "calories": 770, "totalFat": 45, "protein": 51, "sodium": 1290 }, { "id": "item-0011", "restaurant": "McDonald's", "item": "Filet-O-Fish®", "calories": 380, "totalFat": 18, "protein": 15, "sodium": 640 }, { "id": "item-0012", "restaurant": "McDonald's", "item": "Garlic White Cheddar Burger", "calories": 620, "totalFat": 34, "protein": 32, "sodium": 790 }, { "id": "item-0013", "restaurant": "McDonald's", "item": "Grilled Garlic White Cheddar Chicken Sandwich", "calories": 530, "totalFat": 20, "protein": 42, "sodium": 1150 }, { "id": "item-0014", "restaurant": "McDonald's", "item": "Crispy Garlic White Cheddar Chicken Sandwich", "calories": 700, "totalFat": 34, "protein": 33, "sodium": 1190 }, { "id": "item-0015", "restaurant": "McDonald's", "item": "Hamburger", "calories": 250, "totalFat": 8, "protein": 13, "sodium": 480 }, { "id": "item-0016", "restaurant": "McDonald's", "item": "Lobster Roll", "calories": 290, "totalFat": 5, "protein": 24, "sodium": 630 }, { "id": "item-0017", "restaurant": "McDonald's", "item": "Maple Bacon Dijon 1/4 lb Burger", "calories": 640, "totalFat": 36, "protein": 37, "sodium": 1260 }, { "id": "item-0018", "restaurant": "McDonald's", "item": "Grilled Maple Bacon Dijon Chicken Sandwich", "calories": 580, "totalFat": 21, "protein": 48, "sodium": 1890 }, { "id": "item-0019", "restaurant": "McDonald's", "item": "Crispy Maple Bacon Dijon Chicken Sandwich", "calories": 740, "totalFat": 35, "protein": 39, "sodium": 1780 }, { "id": "item-0020", "restaurant": "McDonald's", "item": "McChicken", "calories": 350, "totalFat": 15, "protein": 15, "sodium": 600 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "jitter", "positionParams": { "width": 0.22, "height": 0 }, "aes": { "x": { "field": "restaurant" }, "y": { "field": "calories" }, "color": { "field": "restaurant" } }, "params": { "alpha": 0.65 } } ], "scales": { "color": { "type": "ordinal", "scheme": "observable10" } }, "guides": { "color": { "type": "none" } }, "labs": { "title": "Calories by restaurant, with jitter", "subtitle": "Horizontal jitter spreads marks that share a restaurant", "x": "Restaurant", "y": "Calories" }, "theme": "clean" } // note: inline data truncated (495 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, GuideNone, Inspect, Labs, ScaleColorDiscrete, ThemeClean, } from "@ggsvelte/svelte"; import { fastfoodMenu } from "./data.js"; </script> <GGPlot data={fastfoodMenu} aes={{ x: "restaurant", y: "calories", color: "restaurant" }} width={640} height={400} > <GeomPoint position="jitter" positionParams={{ width: 0.22, height: 0 }} alpha={0.65} /> <ScaleColorDiscrete scheme="observable10" /> <ThemeClean /> <GuideNone channel="color" /> <Labs title="Calories by restaurant, with jitter" subtitle="Horizontal jitter spreads marks that share a restaurant" x="Restaurant" y="Calories" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## What the wars did to the national debt (point/layer-data-bands) Playfair's debt series with two war periods behind it and one note on top, each layer carrying its own table because only one of the three is a measurement. Tags: point, rect, text, layer-data, multi-table Spec (canonical PortableSpec JSON): ```json { "edition": 2, "layers": [ { "geom": "rect", "stat": "identity", "position": "identity", "aes": { "fill": { "field": "war" }, "ymin": { "field": "ymin" }, "ymax": { "field": "ymax" }, "xmin": { "field": "xmin" }, "xmax": { "field": "xmax" } }, "data": { "values": [ { "xmin": 1775, "xmax": 1783, "ymin": 0, "ymax": 80, "war": "American War of Independence" }, { "xmin": 1793, "xmax": 1815, "ymin": 0, "ymax": 80, "war": "French Revolutionary and Napoleonic Wars" } ] }, "params": { "alpha": 0.18 } }, { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "debt" } }, "data": { "values": [ { "year": 1770, "debt": 13.96 }, { "year": 1771, "debt": 13.97 }, { "year": 1772, "debt": 13.67 }, { "year": 1773, "debt": 13.68 }, { "year": 1774, "debt": 13.69 }, { "year": 1775, "debt": 14.88 }, { "year": 1776, "debt": 16.07 }, { "year": 1777, "debt": 16.97 }, { "year": 1778, "debt": 18.16 }, { "year": 1779, "debt": 19.06 }, { "year": 1780, "debt": 20.55 }, { "year": 1781, "debt": 21.74 }, { "year": 1782, "debt": 22.94 }, { "year": 1783, "debt": 24.43 }, { "year": 1784, "debt": 24.43 }, { "year": 1785, "debt": 24.44 }, { "year": 1786, "debt": 24.44 }, { "year": 1787, "debt": 24.45 }, { "year": 1788, "debt": 24.46 }, { "year": 1789, "debt": 24.46 }, { "year": 1790, "debt": 24.76 }, { "year": 1791, "debt": 24.77 }, { "year": 1792, "debt": 24.77 }, { "year": 1793, "debt": 25.08 }, { "year": 1794, "debt": 27.75 }, { "year": 1795, "debt": 30.73 }, { "year": 1796, "debt": 33.71 }, { "year": 1797, "debt": 36.98 }, { "year": 1798, "debt": 39.96 }, { "year": 1799, "debt": 41.15 }, { "year": 1800, "debt": 42.34 }, { "year": 1801, "debt": 43.54 }, { "year": 1802, "debt": 44.73 }, { "year": 1803, "debt": 44.74 }, { "year": 1804, "debt": 45.04 }, { "year": 1805, "debt": 45.94 }, { "year": 1806, "debt": 47.43 }, { "year": 1807, "debt": 49.21 }, { "year": 1808, "debt": 51.6 }, { "year": 1809, "debt": 54.57 }, { "year": 1810, "debt": 57.84 }, { "year": 1811, "debt": 61.12 }, { "year": 1812, "debt": 64.39 }, { "year": 1813, "debt": 67.66 }, { "year": 1814, "debt": 70.94 }, { "year": 1815, "debt": 73.91 }, { "year": 1816, "debt": 74.81 }, { "year": 1817, "debt": 75.11 }, { "year": 1818, "debt": 75.12 }, { "year": 1819, "debt": 75.42 }, { "year": 1820, "debt": 75.42 }, { "year": 1821, "debt": 75.73 }, { "year": 1822, "debt": 75.73 }, { "year": 1823, "debt": 76.03 }, { "year": 1824, "debt": 76.04 } ] }, "params": { "linewidth": 1.8 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "debt" }, "label": { "field": "label" } }, "data": { "values": [ { "year": 1798, "debt": 72, "label": "Debt nearly trebles between 1793 and 1815" } ] }, "params": { "size": 12 } } ], "labs": { "title": "What the wars did to the national debt", "subtitle": "Playfair's series, 1770 to 1824, with the war years drawn behind it", "x": "Year", "y": "Debt (£ millions)", "fill": "" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomRect, GeomText, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { callouts, nationalDebt, warYears } from "./data.js"; </script> <GGPlot width={640} height={400}> <GeomRect data={warYears} aes={{ xmin: "xmin", xmax: "xmax", ymin: "ymin", ymax: "ymax", fill: "war", }} alpha={0.18} /> <GeomLine data={nationalDebt} aes={{ x: "year", y: "debt" }} linewidth={1.8} /> <GeomText data={callouts} aes={{ x: "year", y: "debt", label: "label" }} size={12} /> <ThemeClassic /> <Labs title="What the wars did to the national debt" subtitle="Playfair's series, 1770 to 1824, with the war years drawn behind it" x="Year" y="Debt (£ millions)" fill="" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Cholera, crowding and water in London, 1849 (point/log-scale) Farr's 38 districts: death rate against population density on a log x scale, coloured by which company supplied the water. Tags: point, scales, log, inspect, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "district": "Newington", "density": 101, "deathRate": 144, "water": "Battersea" }, { "district": "Rotherhithe", "density": 19, "deathRate": 205, "water": "Battersea" }, { "district": "Bermondsey", "density": 180, "deathRate": 164, "water": "Battersea" }, { "district": "St George Southwark", "density": 66, "deathRate": 161, "water": "Battersea" }, { "district": "St Olave", "density": 114, "deathRate": 181, "water": "Battersea" }, { "district": "St Saviour", "density": 141, "deathRate": 153, "water": "Battersea" }, { "district": "Westminster", "density": 70, "deathRate": 68, "water": "Battersea" }, { "district": "Lambeth", "density": 34, "deathRate": 120, "water": "Battersea" }, { "district": "Camberwell", "density": 12, "deathRate": 97, "water": "Battersea" }, { "district": "Greenwich", "density": 18, "deathRate": 75, "water": "New River" }, { "district": "Poplar", "density": 15, "deathRate": 71, "water": "New River" }, { "district": "Chelsea", "density": 62, "deathRate": 46, "water": "Battersea" }, { "district": "Hammersmith, Brompton, Kensington and Fulham", "density": 11, "deathRate": 33, "water": "Kew" }, { "district": "St George East", "density": 195, "deathRate": 42, "water": "New River" }, { "district": "Stepney", "density": 85, "deathRate": 47, "water": "New River" }, { "district": "Belgrave", "density": 65, "deathRate": 28, "water": "Battersea" }, { "district": "Wandsworth", "density": 4, "deathRate": 100, "water": "Battersea" }, { "district": "West London", "density": 212, "deathRate": 96, "water": "New River" }, { "district": "Whitechapel", "density": 194, "deathRate": 64, "water": "New River" }, { "district": "Lewisham", "density": 2, "deathRate": 30, "water": "New River" } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "density" }, "y": { "field": "deathRate" }, "color": { "field": "water" } }, "params": { "size": 3.5 } } ], "scales": { "x": { "type": "linear", "transform": "log10", "labels": "~s" }, "color": { "type": "ordinal", "domain": [ "Battersea", "New River", "Kew" ], "scheme": "fivethirtyeight" } }, "labs": { "title": "Cholera, crowding and water in London, 1849", "subtitle": "Death rate against population density, by water company", "x": "People per acre (log scale)", "y": "Cholera deaths per 10,000", "color": "Water supply" }, "theme": "economist" } // note: inline data truncated (18 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, GuideLegend, Inspect, Labs, ScaleColorDiscrete, ScaleXLog10, ThemeEconomist, } from "@ggsvelte/svelte"; import { londonCholera } from "./data.js"; </script> <GGPlot data={londonCholera} aes={{ x: "density", y: "deathRate", color: "water" }} width="container" height={400} > <GeomPoint size={3.5} /> <ScaleXLog10 labels="~s" /> <ScaleColorDiscrete domain={["Battersea", "New River", "Kew"]} scheme="fivethirtyeight" /> <ThemeEconomist /> <GuideLegend channel="color" focus /> <Labs title="Cholera, crowding and water in London, 1849" subtitle="Death rate against population density, by water company" x="People per acre (log scale)" y="Cholera deaths per 10,000" color="Water supply" /> <Inspect mode="xy" pin identity="district" /> </GGPlot> ``` ## Flavor against aroma in cupping scores (point/quantile-lines) Quantile lines through coffee cupping scores. Use this when a mean smooth hides how the spread of y moves with x. Tags: point, quantile, scatter, stat Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "lot-0001", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 90.58, "aroma": 8.67, "flavor": 8.83, "acidity": 8.75, "body": 8.5, "altitudeM": 2075 }, { "id": "lot-0002", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 89.92, "aroma": 8.75, "flavor": 8.67, "acidity": 8.58, "body": 8.42, "altitudeM": 2075 }, { "id": "lot-0003", "country": "Guatemala", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 89.75, "aroma": 8.42, "flavor": 8.5, "acidity": 8.42, "body": 8.33, "altitudeM": 1700 }, { "id": "lot-0004", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 89, "aroma": 8.17, "flavor": 8.58, "acidity": 8.42, "body": 8.5, "altitudeM": 2000 }, { "id": "lot-0005", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 88.83, "aroma": 8.25, "flavor": 8.5, "acidity": 8.5, "body": 8.42, "altitudeM": 2075 }, { "id": "lot-0006", "country": "Brazil", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.83, "aroma": 8.58, "flavor": 8.42, "acidity": 8.5, "body": 8.25, "altitudeM": null }, { "id": "lot-0007", "country": "Peru", "species": "Arabica", "processing": "Washed", "totalCupPoints": 88.75, "aroma": 8.42, "flavor": 8.5, "acidity": 8.5, "body": 8.25, "altitudeM": null }, { "id": "lot-0008", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 88.67, "aroma": 8.25, "flavor": 8.33, "acidity": 8.42, "body": 8.33, "altitudeM": 1635 }, { "id": "lot-0009", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 88.42, "aroma": 8.67, "flavor": 8.67, "acidity": 8.42, "body": 8.33, "altitudeM": 1635 }, { "id": "lot-0010", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.25, "aroma": 8.08, "flavor": 8.58, "acidity": 8.5, "body": 7.67, "altitudeM": 1822.5 }, { "id": "lot-0011", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.08, "aroma": 8.17, "flavor": 8.67, "acidity": 8.5, "body": 7.75, "altitudeM": 1905 }, { "id": "lot-0012", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.92, "aroma": 8.25, "flavor": 8.42, "acidity": 8.33, "body": 8.08, "altitudeM": 1872 }, { "id": "lot-0013", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.92, "aroma": 8.08, "flavor": 8.67, "acidity": 8.42, "body": 8, "altitudeM": 1943 }, { "id": "lot-0014", "country": "United States (Hawaii)", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.92, "aroma": 8.33, "flavor": 8.42, "acidity": 8.25, "body": 8.25, "altitudeM": 609.6 }, { "id": "lot-0015", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.83, "aroma": 8.25, "flavor": 8.33, "acidity": 8.25, "body": 8.58, "altitudeM": 1635 }, { "id": "lot-0016", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.58, "aroma": 8, "flavor": 8.5, "acidity": 8.17, "body": 8.17, "altitudeM": 2080 }, { "id": "lot-0017", "country": "Indonesia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.42, "aroma": 8.33, "flavor": 8.25, "acidity": 7.75, "body": 8.5, "altitudeM": 1500 }, { "id": "lot-0018", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.33, "aroma": 8.17, "flavor": 8.33, "acidity": 8.33, "body": 8.42, "altitudeM": null }, { "id": "lot-0019", "country": "China", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.25, "aroma": 8.42, "flavor": 8.25, "acidity": 8.17, "body": 7.92, "altitudeM": 1450 }, { "id": "lot-0020", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 87.25, "aroma": 8.17, "flavor": 8.17, "acidity": 8.17, "body": 8.08, "altitudeM": 1850 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "aroma" }, "y": { "field": "flavor" } }, "params": { "alpha": 0.3, "size": 2 } }, { "geom": "quantile", "stat": "quantile", "position": "identity", "aes": { "x": { "field": "aroma" }, "y": { "field": "flavor" } }, "params": { "linewidth": 1.4 } } ], "labs": { "title": "Flavor against aroma in cupping scores", "subtitle": "Lower quartile, median, and upper quartile of flavor as aroma rises", "x": "Aroma", "y": "Flavor" }, "theme": "classic" } // note: inline data truncated (1318 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomQuantile, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { coffeeRatings } from "./data.js"; </script> <GGPlot data={coffeeRatings} aes={{ x: "aroma", y: "flavor" }} width={640} height={400} > <GeomPoint alpha={0.3} size={2} /> <GeomQuantile linewidth={1.4} /> <ThemeClassic /> <Labs title="Flavor against aroma in cupping scores" subtitle="Lower quartile, median, and upper quartile of flavor as aroma rises" x="Aroma" y="Flavor" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Two measures coloured by region (point/scatter-color) Scatter with a discrete colour when geography or group is the third channel. Tags: point, color, legend, scatter, theme, palette Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "department": "Ain", "region": "East", "literacy": 37, "crimePersons": 28870, "wealth": 73 }, { "department": "Aisne", "region": "North", "literacy": 51, "crimePersons": 26226, "wealth": 22 }, { "department": "Allier", "region": "Centre", "literacy": 13, "crimePersons": 26747, "wealth": 61 }, { "department": "Basses-Alpes", "region": "East", "literacy": 46, "crimePersons": 12935, "wealth": 76 }, { "department": "Hautes-Alpes", "region": "East", "literacy": 69, "crimePersons": 17488, "wealth": 83 }, { "department": "Ardeche", "region": "South", "literacy": 27, "crimePersons": 9474, "wealth": 84 }, { "department": "Ardennes", "region": "North", "literacy": 67, "crimePersons": 35203, "wealth": 33 }, { "department": "Ariege", "region": "South", "literacy": 18, "crimePersons": 6173, "wealth": 72 }, { "department": "Aube", "region": "East", "literacy": 59, "crimePersons": 19602, "wealth": 14 }, { "department": "Aude", "region": "South", "literacy": 34, "crimePersons": 15647, "wealth": 17 }, { "department": "Aveyron", "region": "South", "literacy": 31, "crimePersons": 8236, "wealth": 50 }, { "department": "Bouches-du-Rhone", "region": "South", "literacy": 38, "crimePersons": 13409, "wealth": 2 }, { "department": "Calvados", "region": "North", "literacy": 52, "crimePersons": 17577, "wealth": 10 }, { "department": "Cantal", "region": "Centre", "literacy": 31, "crimePersons": 18070, "wealth": 59 }, { "department": "Charente", "region": "West", "literacy": 36, "crimePersons": 24964, "wealth": 86 }, { "department": "Charente-Inferieure", "region": "West", "literacy": 39, "crimePersons": 18712, "wealth": 18 }, { "department": "Cher", "region": "Centre", "literacy": 13, "crimePersons": 21934, "wealth": 63 }, { "department": "Correze", "region": "Centre", "literacy": 12, "crimePersons": 15262, "wealth": 74 }, { "department": "Cote-d'Or", "region": "East", "literacy": 60, "crimePersons": 32256, "wealth": 16 }, { "department": "Cotes-du-Nord", "region": "West", "literacy": 16, "crimePersons": 28607, "wealth": 70 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "literacy" }, "y": { "field": "crimePersons" }, "color": { "field": "region" } }, "params": { "size": 3 } } ], "scales": { "color": { "type": "ordinal", "scheme": "observable10" } }, "labs": { "title": "Two measures coloured by region", "subtitle": "Literacy and crime by French department", "x": "Literate conscripts (%)", "y": "Population per crime against persons", "color": "Region" }, "theme": "few" } // note: inline data truncated (65 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, GuideLegend, Inspect, Labs, ScaleColorDiscrete, ThemeFew, } from "@ggsvelte/svelte"; import { guerry } from "./data.js"; </script> <GGPlot data={guerry} aes={{ x: "literacy", y: "crimePersons", color: "region" }} width={640} height={400} > <GeomPoint size={3} /> <ScaleColorDiscrete scheme="observable10" /> <ThemeFew /> <GuideLegend channel="color" focus /> <Labs title="Two measures coloured by region" subtitle="Literacy and crime by French department" x="Literate conscripts (%)" y="Population per crime against persons" color="Region" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Raw points with a manual mean per group (point/stat-manual-mean) Manual summary stats when you want both the cloud and the group mean on one panel. Tags: point, manual, stat, mean, group, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "run": "Run 1", "order": 1, "velocity": 850 }, { "run": "Run 1", "order": 2, "velocity": 850 }, { "run": "Run 1", "order": 3, "velocity": 1000 }, { "run": "Run 1", "order": 4, "velocity": 810 }, { "run": "Run 1", "order": 5, "velocity": 960 }, { "run": "Run 1", "order": 6, "velocity": 800 }, { "run": "Run 1", "order": 7, "velocity": 830 }, { "run": "Run 1", "order": 8, "velocity": 830 }, { "run": "Run 1", "order": 9, "velocity": 880 }, { "run": "Run 1", "order": 10, "velocity": 720 }, { "run": "Run 1", "order": 11, "velocity": 880 }, { "run": "Run 1", "order": 12, "velocity": 840 }, { "run": "Run 1", "order": 13, "velocity": 890 }, { "run": "Run 1", "order": 14, "velocity": 770 }, { "run": "Run 1", "order": 15, "velocity": 910 }, { "run": "Run 1", "order": 16, "velocity": 720 }, { "run": "Run 1", "order": 17, "velocity": 890 }, { "run": "Run 1", "order": 18, "velocity": 810 }, { "run": "Run 1", "order": 19, "velocity": 870 }, { "run": "Run 1", "order": 20, "velocity": 940 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "order" }, "y": { "field": "velocity" }, "color": { "field": "run" } }, "params": { "size": 2.4, "alpha": 0.35 } }, { "geom": "point", "stat": "manual", "position": "identity", "aes": { "x": { "field": "order" }, "y": { "field": "velocity" }, "color": { "field": "run" } }, "params": { "fun": "mean", "size": 6, "alpha": 0.95 } } ], "scales": { "color": { "type": "ordinal", "scheme": "observable10" } }, "labs": { "title": "Raw points with a manual mean per group", "subtitle": "Twenty readings per run", "x": "Measurement, 1 to 100", "y": "Speed of light, km/s less 299,000", "color": "Run" }, "theme": "classic" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, registerManual, ScaleColorDiscrete, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerManual(); import { michelsonRuns } from "./data.js"; </script> <GGPlot data={michelsonRuns} aes={{ x: "order", y: "velocity", color: "run" }} width={640} height={400} > <GeomPoint size={2.4} alpha={0.35} /> <GeomPoint stat="manual" fun="mean" size={6} alpha={0.95} /> <ScaleColorDiscrete scheme="observable10" /> <ThemeClassic /> <Labs title="Raw points with a manual mean per group" subtitle="Twenty readings per run" x="Measurement, 1 to 100" y="Speed of light, km/s less 299,000" color="Run" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Collapse duplicate coordinates to unique marks (point/stat-unique) Unique stat when repeated keys would overplot identical positions. Tags: point, unique, stat, overplotting, dedupe, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1918, "magnitude": 7.2 }, { "year": 1915, "magnitude": 7.2 }, { "year": 1916, "magnitude": 7.2 }, { "year": 1918, "magnitude": 7.2 }, { "year": 1916, "magnitude": 7.2 }, { "year": 1917, "magnitude": 7.2 }, { "year": 1917, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 }, { "year": 1914, "magnitude": 7.2 } ] }, "layers": [ { "geom": "point", "stat": "unique", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "magnitude" } }, "params": { "size": 3, "alpha": 0.8 } } ], "labs": { "title": "Collapse duplicate coordinates to unique marks", "subtitle": "One row per belligerent pair becomes one mark per distinct (year, magnitude)", "x": "Year the quarrel began", "y": "Magnitude (log10 killed)" }, "theme": "classic" } // note: inline data truncated (759 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, registerUnique, ThemeClassic, } from "@ggsvelte/svelte"; // stat= override: the shell registers only its default stat (#1420). registerUnique(); import { deadlyQuarrels } from "./data.js"; </script> <GGPlot data={deadlyQuarrels} aes={{ x: "year", y: "magnitude" }} width={640} height={400} > <GeomPoint stat="unique" size={3} alpha={0.8} /> <ThemeClassic /> <Labs title="Collapse duplicate coordinates to unique marks" subtitle="One row per belligerent pair becomes one mark per distinct (year, magnitude)" x="Year the quarrel began" y="Magnitude (log10 killed)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Cholera against height above the Thames (point/steps-binned) Farr's 38 London districts of 1849, coloured in bands by their poor rate. A stepped ramp reads as brackets, which is how a rate like this gets argued about. Tags: point, color, steps, binned, scale Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "district": "Newington", "elevation": -2, "deathRate": 144, "poorRate": 0.075 }, { "district": "Rotherhithe", "elevation": 0, "deathRate": 205, "poorRate": 0.143 }, { "district": "Bermondsey", "elevation": 0, "deathRate": 164, "poorRate": 0.089 }, { "district": "St George Southwark", "elevation": 0, "deathRate": 161, "poorRate": 0.134 }, { "district": "St Olave", "elevation": 2, "deathRate": 181, "poorRate": 0.079 }, { "district": "St Saviour", "elevation": 2, "deathRate": 153, "poorRate": 0.076 }, { "district": "Westminster", "elevation": 2, "deathRate": 68, "poorRate": 0.039 }, { "district": "Lambeth", "elevation": 3, "deathRate": 120, "poorRate": 0.072 }, { "district": "Camberwell", "elevation": 4, "deathRate": 97, "poorRate": 0.038 }, { "district": "Greenwich", "elevation": 8, "deathRate": 75, "poorRate": 0.081 }, { "district": "Poplar", "elevation": 10, "deathRate": 71, "poorRate": 0.06 }, { "district": "Chelsea", "elevation": 12, "deathRate": 46, "poorRate": 0.067 }, { "district": "Hammersmith, Brompton, Kensington and Fulham", "elevation": 12, "deathRate": 33, "poorRate": 0.039 }, { "district": "St George East", "elevation": 15, "deathRate": 42, "poorRate": 0.08 }, { "district": "Stepney", "elevation": 16, "deathRate": 47, "poorRate": 0.066 }, { "district": "Belgrave", "elevation": 19, "deathRate": 28, "poorRate": 0.018 }, { "district": "Wandsworth", "elevation": 22, "deathRate": 100, "poorRate": 0.072 }, { "district": "West London", "elevation": 28, "deathRate": 96, "poorRate": 0.067 }, { "district": "Whitechapel", "elevation": 28, "deathRate": 64, "poorRate": 0.075 }, { "district": "Lewisham", "elevation": 28, "deathRate": 30, "poorRate": 0.049 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "elevation" }, "y": { "field": "deathRate" }, "color": { "field": "poorRate" } }, "params": { "size": 4 } } ], "scales": { "color": { "type": "binned", "range": [ "#132b43", "#56b1f7" ] } }, "labs": { "title": "Cholera fell away with height above the Thames", "subtitle": "38 London districts in 1849, shaded in bands by their poor rate", "x": "Elevation above high water (feet)", "y": "Cholera deaths per 10,000", "color": "Poor rate" }, "theme": "minimal" } // note: inline data truncated (18 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GGPlot, Inspect, Labs, ScaleColorSteps, ThemeMinimal, } from "@ggsvelte/svelte"; import { londonDistricts } from "./data.js"; </script> <GGPlot data={londonDistricts} aes={{ x: "elevation", y: "deathRate", color: "poorRate" }} width={640} height={400} > <GeomPoint size={4} /> <ScaleColorSteps low="#132B43" high="#56B1F7" /> <ThemeMinimal /> <Labs title="Cholera fell away with height above the Thames" subtitle="38 London districts in 1849, shaded in bands by their poor rate" x="Elevation above high water (feet)" y="Cholera deaths per 10,000" color="Poor rate" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## A sparkline without axes or grid (point/void-chrome) Void theme when the mark alone is the chart. Tags: point, theme, void, chrome-free Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 1855, "value": 95.7 }, { "year": 1856, "value": 115.8 }, { "year": 1857, "value": 122 }, { "year": 1858, "value": 116.6 }, { "year": 1859, "value": 130.4 }, { "year": 1860, "value": 135.9 }, { "year": 1861, "value": 125.1 }, { "year": 1862, "value": 124 }, { "year": 1863, "value": 146.5 }, { "year": 1864, "value": 160.4 }, { "year": 1865, "value": 165.8 }, { "year": 1866, "value": 188.9 }, { "year": 1867, "value": 181 }, { "year": 1868, "value": 179.7 }, { "year": 1869, "value": 190 }, { "year": 1870, "value": 199.6 }, { "year": 1871, "value": 223.1 }, { "year": 1872, "value": 256.3 }, { "year": 1873, "value": 255.2 }, { "year": 1874, "value": 239.6 } ] }, "layers": [ { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" } }, "params": { "linewidth": 1.5 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "value" } }, "params": { "size": 2.4, "alpha": 0.8 } } ], "labs": { "title": "A sparkline without axes or grid", "subtitle": "Exports over decades reduced to a thin line with no chart furniture", "x": "", "y": "" }, "theme": "void" } // note: inline data truncated (25 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomPoint, GGPlot, Inspect, Labs, ThemeVoid, } from "@ggsvelte/svelte"; import { britishExports } from "./data.js"; </script> <GGPlot data={britishExports} aes={{ x: "year", y: "value" }} width={640} height={400} > <GeomLine linewidth={1.5} /> <GeomPoint size={2.4} alpha={0.8} /> <ThemeVoid /> <Labs title="A sparkline without axes or grid" subtitle="Exports over decades reduced to a thin line with no chart furniture" x="" y="" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Stem plus mid point (pointrange/midpoints) Three tall identity stems with a large mid point — pointrange's mark shape at index thumbnail size. Tags: pointrange, identity, point, ymin, ymax Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "group": "A", "mid": 5, "lo": 1.5, "hi": 8.5 }, { "group": "B", "mid": 7.5, "lo": 4, "hi": 11 }, { "group": "C", "mid": 3.5, "lo": 0.5, "hi": 6.5 } ] }, "layers": [ { "geom": "pointrange", "stat": "identity", "position": "identity", "aes": { "x": { "field": "group" }, "y": { "field": "mid" }, "color": { "value": "#1a202c" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "linewidth": 2.6, "size": 5.5 } } ], "scales": { "x": { "type": "band", "domain": [ "A", "B", "C" ] }, "y": { "type": "linear", "domain": [ 0, 12 ] } }, "labs": { "title": "Stem plus mid point", "subtitle": "Three identity intervals with a point at (x, y)", "x": "Group", "y": "Value" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPointrange, GGPlot, Inspect, Labs, ScaleXDiscrete, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { groupIntervals } from "./data.js"; </script> <GGPlot data={groupIntervals} aes={{ x: "group", y: "mid", ymin: "lo", ymax: "hi" }} width={640} height={400} > <GeomPointrange linewidth={2.6} size={5.5} aes={{ color: { value: "#1a202c" } }} /> <ScaleXDiscrete domain={["A", "B", "C"]} /> <ScaleYContinuous limits={[0, 12]} /> <ThemeMinimal /> <Labs title="Stem plus mid point" subtitle="Three identity intervals with a point at (x, y)" x="Group" y="Value" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Which pump was nearest (polygon/regions) The thirteen areas of Soho closest to each public pump in 1854, each drawn as one closed ring of vertices in winding order. Tags: polygon, fill, group, choropleth Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 3.39, "y": 16.3215, "pump": "Oxford Market" }, { "x": 10.2964, "y": 16.4222, "pump": "Oxford Market" }, { "x": 9.6784, "y": 18.725, "pump": "Oxford Market" }, { "x": 3.39, "y": 18.725, "pump": "Oxford Market" }, { "x": 3.39, "y": 16.3215, "pump": "Oxford Market" }, { "x": 10.2964, "y": 16.4222, "pump": "Castle St E" }, { "x": 11.1944, "y": 15.8529, "pump": "Castle St E" }, { "x": 12.5423, "y": 18.725, "pump": "Castle St E" }, { "x": 9.6784, "y": 18.725, "pump": "Castle St E" }, { "x": 10.2964, "y": 16.4222, "pump": "Castle St E" }, { "x": 11.1944, "y": 15.8529, "pump": "Oxford St #1" }, { "x": 11.7915, "y": 14.7293, "pump": "Oxford St #1" }, { "x": 15.0521, "y": 14.2651, "pump": "Oxford St #1" }, { "x": 13.8184, "y": 18.725, "pump": "Oxford St #1" }, { "x": 12.5423, "y": 18.725, "pump": "Oxford St #1" }, { "x": 11.1944, "y": 15.8529, "pump": "Oxford St #1" }, { "x": 15.0521, "y": 14.2651, "pump": "Oxford St #2" }, { "x": 16.5561, "y": 13.6943, "pump": "Oxford St #2" }, { "x": 16.8419, "y": 13.7463, "pump": "Oxford St #2" }, { "x": 19.912, "y": 15.2808, "pump": "Oxford St #2" } ] }, "layers": [ { "geom": "polygon", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "fill": { "field": "pump" }, "group": { "field": "pump" } }, "params": { "alpha": 0.55, "linewidth": 1.2 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "#111827" }, "fill": { "field": "pump" }, "group": { "field": "pump" } }, "data": { "values": [ { "x": 8.65, "y": 17.89, "pump": "Oxford Market" }, { "x": 10.98, "y": 18.52, "pump": "Castle St E" }, { "x": 13.38, "y": 17.39, "pump": "Oxford St #1" }, { "x": 14.88, "y": 17.81, "pump": "Oxford St #2" }, { "x": 8.69, "y": 14.91, "pump": "Gt Marlborough" }, { "x": 8.86, "y": 12.75, "pump": "Crown Chapel" }, { "x": 12.57, "y": 11.73, "pump": "Broad St" }, { "x": 10.66, "y": 7.43, "pump": "Warwick" }, { "x": 13.52, "y": 7.96, "pump": "Briddle St" }, { "x": 16.43, "y": 9.25, "pump": "So Soho" }, { "x": 18.91, "y": 9.74, "pump": "Dean St" }, { "x": 16.01, "y": 5.05, "pump": "Coventry St" }, { "x": 9, "y": 5.1, "pump": "Vigo St" } ] }, "params": { "size": 3.6, "shape": "cross" } } ], "coord": { "type": "fixed" }, "guides": { "fill": { "type": "none" } }, "labs": { "title": "Which pump was nearest", "subtitle": "Soho split into the thirteen areas closest to each public pump, 1854", "x": "Map east", "y": "Map north" }, "theme": "classic" } // note: inline data truncated (60 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomPoint, GeomPolygon, GGPlot, GuideNone, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { pumpNeighbourhoods, waterPumps } from "./data.js"; </script> <GGPlot data={pumpNeighbourhoods} aes={{ x: "x", y: "y", group: "pump", fill: "pump" }} width={640} height={400} > <GeomPolygon alpha={0.55} linewidth={1.2} /> <GeomPoint data={waterPumps} aes={{ x: "x", y: "y", color: { value: "#111827" } }} size={3.6} shape="cross" /> <CoordFixed /> <ThemeClassic /> <GuideNone channel="fill" /> <Labs title="Which pump was nearest" subtitle="Soho split into the thirteen areas closest to each public pump, 1854" x="Map east" y="Map north" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Sparse Q–Q cloud (qq/cloud) Twelve large sample quantiles vs theoretical normal — the scatter mark that reads at index thumbnail size. Tags: qq, diagnostics, distribution, quantiles Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "value": 1.1 }, { "value": 2 }, { "value": 2.6 }, { "value": 3.1 }, { "value": 3.4 }, { "value": 3.9 }, { "value": 4.2 }, { "value": 4.8 }, { "value": 5.3 }, { "value": 5.9 }, { "value": 6.7 }, { "value": 8.1 } ] }, "layers": [ { "geom": "qq", "stat": "qq", "position": "identity", "aes": { "color": { "value": "#1a202c" }, "sample": { "field": "value" } }, "params": { "size": 6, "alpha": 0.95 } } ], "labs": { "title": "Sparse Q–Q cloud", "subtitle": "Twelve sample quantiles against the theoretical normal", "x": "Theoretical quantile", "y": "Sample quantile" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomQq, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { shortSample } from "./data.js"; </script> <GGPlot data={shortSample} aes={{ sample: "value" }} width={640} height={400}> <GeomQq size={6} alpha={0.95} aes={{ color: { value: "#1a202c" } }} /> <ThemeMinimal /> <Labs title="Sparse Q–Q cloud" subtitle="Twelve sample quantiles against the theoretical normal" x="Theoretical quantile" y="Sample quantile" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Sample quantiles against the normal (qq/normal) Normal QQ plots for a quick normality check. Tags: qq, diagnostics, distribution Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "velocity": 850 }, { "velocity": 740 }, { "velocity": 900 }, { "velocity": 1070 }, { "velocity": 930 }, { "velocity": 850 }, { "velocity": 950 }, { "velocity": 980 }, { "velocity": 980 }, { "velocity": 880 }, { "velocity": 1000 }, { "velocity": 980 }, { "velocity": 930 }, { "velocity": 650 }, { "velocity": 760 }, { "velocity": 810 }, { "velocity": 1000 }, { "velocity": 1000 }, { "velocity": 960 }, { "velocity": 960 } ] }, "layers": [ { "geom": "qq", "stat": "qq", "position": "identity", "aes": { "sample": { "field": "velocity" } }, "params": { "size": 2.5, "alpha": 0.85 } }, { "geom": "qq_line", "stat": "qq_line", "position": "identity", "aes": { "sample": { "field": "velocity" } }, "params": { "linewidth": 1.2, "alpha": 0.7 } } ], "labs": { "title": "Sample quantiles against the normal", "subtitle": "A hundred experimental runs compared with the normal they would follow under pure chance", "x": "Normal quantile", "y": "Speed of light, km/s less 299,000" }, "theme": "classic" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomQq, GeomQqLine, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { michelson } from "./data.js"; </script> <GGPlot data={michelson} aes={{ sample: "velocity" }} width={640} height={400}> <GeomQq size={2.5} alpha={0.85} /> <GeomQqLine linewidth={1.2} alpha={0.7} /> <ThemeClassic /> <Labs title="Sample quantiles against the normal" subtitle="A hundred experimental runs compared with the normal they would follow under pure chance" x="Normal quantile" y="Speed of light, km/s less 299,000" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Q–Q reference line (qq_line/match) Thick line through the sample/theoretical quartile match — the mark that reads at index thumbnail size. Tags: qq_line, qq-line, diagnostics, distribution Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "value": 1.1 }, { "value": 2 }, { "value": 2.6 }, { "value": 3.1 }, { "value": 3.4 }, { "value": 3.9 }, { "value": 4.2 }, { "value": 4.8 }, { "value": 5.3 }, { "value": 5.9 }, { "value": 6.7 }, { "value": 8.1 } ] }, "layers": [ { "geom": "qq_line", "stat": "qq_line", "position": "identity", "aes": { "color": { "value": "#1a202c" }, "sample": { "field": "value" } }, "params": { "linewidth": 3.2, "alpha": 1 } } ], "labs": { "title": "Q–Q reference line", "subtitle": "Line through the sample/theoretical quartile match", "x": "Theoretical quantile", "y": "Sample quantile" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomQqLine, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { shortSample } from "./data.js"; </script> <GGPlot data={shortSample} aes={{ sample: "value" }} width={640} height={400}> <GeomQqLine linewidth={3.2} alpha={1} aes={{ color: { value: "#1a202c" } }} /> <ThemeMinimal /> <Labs title="Q–Q reference line" subtitle="Line through the sample/theoretical quartile match" x="Theoretical quantile" y="Sample quantile" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Where chocolate reviews cluster (raster/grid) A complete cocoa-percent × rating grid of bar counts. Use a raster when your table is already a regular grid of fill values. Tags: raster, heatmap, fill, grid Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "cocoaPercent": 60, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 61, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 62, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 63, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 64, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 65, "rating": 1.5, "bars": 1 }, { "cocoaPercent": 66, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 67, "rating": 1.5, "bars": 1 }, { "cocoaPercent": 68, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 69, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 70, "rating": 1.5, "bars": 2 }, { "cocoaPercent": 71, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 72, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 73, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 74, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 75, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 76, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 77, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 78, "rating": 1.5, "bars": 0 }, { "cocoaPercent": 79, "rating": 1.5, "bars": 0 } ] }, "layers": [ { "geom": "raster", "stat": "identity", "position": "identity", "aes": { "x": { "field": "cocoaPercent" }, "y": { "field": "rating" }, "fill": { "field": "bars" } } } ], "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }, "labs": { "title": "Where chocolate reviews cluster", "subtitle": "Count of bars at each cocoa percent and rating. Darker cells hold more reviews", "x": "Cocoa (%)", "y": "Rating (1–4)", "fill": "Bars" }, "theme": "few" } // note: inline data truncated (211 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomRaster, GGPlot, Inspect, Labs, ScaleFillContinuous, ThemeFew, } from "@ggsvelte/svelte"; import { chocolateRatingGrid } from "./data.js"; </script> <GGPlot data={chocolateRatingGrid} aes={{ x: "cocoaPercent", y: "rating", fill: "bars" }} width={640} height={400} > <GeomRaster /> <ScaleFillContinuous scheme="viridis" /> <ThemeFew /> <Labs title="Where chocolate reviews cluster" subtitle="Count of bars at each cocoa percent and rating. Darker cells hold more reviews" x="Cocoa (%)" y="Rating (1–4)" fill="Bars" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Background rectangles for eras (rect/regions) Rect regions when historical or policy eras should sit behind a series. Tags: rect, regions, bands, layers Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "reign": "Elizabeth", "start": 1565, "end": 1603, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "James I", "start": 1603, "end": 1625, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "Charles I", "start": 1625, "end": 1649, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "Cromwell", "start": 1649, "end": 1660, "ymin": 0, "ymax": 105, "rule": "Commonwealth" }, { "reign": "Charles II", "start": 1660, "end": 1685, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "James II", "start": 1685, "end": 1689, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "W&M", "start": 1689, "end": 1702, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "Anne", "start": 1702, "end": 1714, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "George I", "start": 1714, "end": 1727, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "George II", "start": 1727, "end": 1760, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "George III", "start": 1760, "end": 1820, "ymin": 0, "ymax": 105, "rule": "Monarchy" }, { "reign": "George IV", "start": 1820, "end": 1821, "ymin": 0, "ymax": 105, "rule": "Monarchy" } ] }, "layers": [ { "geom": "rect", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#ffffff" }, "fill": { "field": "rule" }, "ymin": { "field": "ymin" }, "ymax": { "field": "ymax" }, "xmin": { "field": "start" }, "xmax": { "field": "end" } }, "params": { "alpha": 0.55, "linewidth": 1 } }, { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "shillings" }, "color": { "value": "#1a1a1a" } }, "data": { "values": [ { "year": 1565, "shillings": 41 }, { "year": 1570, "shillings": 45 }, { "year": 1575, "shillings": 42 }, { "year": 1580, "shillings": 49 }, { "year": 1585, "shillings": 41.5 }, { "year": 1590, "shillings": 47 }, { "year": 1595, "shillings": 64 }, { "year": 1600, "shillings": 27 }, { "year": 1605, "shillings": 33 }, { "year": 1610, "shillings": 32 }, { "year": 1615, "shillings": 33 }, { "year": 1620, "shillings": 35 }, { "year": 1625, "shillings": 33 }, { "year": 1630, "shillings": 45 }, { "year": 1635, "shillings": 33 }, { "year": 1640, "shillings": 39 }, { "year": 1645, "shillings": 53 }, { "year": 1650, "shillings": 42 }, { "year": 1655, "shillings": 40.5 }, { "year": 1660, "shillings": 46.5 }, { "year": 1665, "shillings": 32 }, { "year": 1670, "shillings": 37 }, { "year": 1675, "shillings": 43 }, { "year": 1680, "shillings": 35 }, { "year": 1685, "shillings": 27 }, { "year": 1690, "shillings": 40 }, { "year": 1695, "shillings": 50 }, { "year": 1700, "shillings": 30 }, { "year": 1705, "shillings": 32 }, { "year": 1710, "shillings": 44 }, { "year": 1715, "shillings": 33 }, { "year": 1720, "shillings": 29 }, { "year": 1725, "shillings": 39 }, { "year": 1730, "shillings": 26 }, { "year": 1735, "shillings": 32 }, { "year": 1740, "shillings": 27 }, { "year": 1745, "shillings": 27.5 }, { "year": 1750, "shillings": 31 }, { "year": 1755, "shillings": 35.5 }, { "year": 1760, "shillings": 31 }, { "year": 1765, "shillings": 43 }, { "year": 1770, "shillings": 47 }, { "year": 1775, "shillings": 44 }, { "year": 1780, "shillings": 46 }, { "year": 1785, "shillings": 42 }, { "year": 1790, "shillings": 47.5 }, { "year": 1795, "shillings": 76 }, { "year": 1800, "shillings": 79 }, { "year": 1805, "shillings": 81 }, { "year": 1810, "shillings": 99 }, { "year": 1815, "shillings": 78 }, { "year": 1820, "shillings": 54 }, { "year": 1821, "shillings": 54 } ] }, "params": { "linewidth": 2 } } ], "scales": { "x": { "labels": "d" }, "fill": { "type": "ordinal", "domain": [ "Monarchy", "Commonwealth" ], "scheme": "few_light" } }, "labs": { "title": "Background rectangles for eras", "subtitle": "Wheat prices with a band for each reign that covered those years", "x": "Year", "y": "Shillings per quarter", "fill": "" }, "theme": "economist" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomRect, GGPlot, Inspect, Labs, ScaleFillDiscrete, ScaleXContinuous, ThemeEconomist, } from "@ggsvelte/svelte"; import { reigns, wheatPrices } from "./data.js"; </script> <GGPlot data={reigns} width={640} height={400}> <GeomRect aes={{ xmin: "start", xmax: "end", ymin: "ymin", ymax: "ymax", fill: "rule", color: { value: "#ffffff" }, }} alpha={0.55} linewidth={1} /> <GeomLine data={wheatPrices} aes={{ x: "year", y: "shillings", color: { value: "#1a1a1a" } }} linewidth={2} /> <ScaleXContinuous labels="d" /> <ScaleFillDiscrete domain={["Monarchy", "Commonwealth"]} scheme="few_light" /> <ThemeEconomist /> <Labs title="Background rectangles for eras" subtitle="Wheat prices with a band for each reign that covered those years" x="Year" y="Shillings per quarter" fill="" /> <Inspect mode="x" pin /> </GGPlot> ``` ## A ribbon for year-to-year range (ribbon/bounds) Ribbons for uncertainty or observed range around a central series. Tags: ribbon, interval, bounds, sqrt Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "age": 1, "lo": 312, "hi": 426, "mean": 353 }, { "age": 2, "lo": 79, "hi": 116, "mean": 93.6 }, { "age": 3, "lo": 30, "hi": 53, "mean": 44.6 }, { "age": 4, "lo": 14, "hi": 37, "mean": 23.6 }, { "age": 5, "lo": 8, "hi": 19, "mean": 12.2 }, { "age": 6, "lo": 9, "hi": 22, "mean": 15.2 }, { "age": 7, "lo": 6, "hi": 19, "mean": 11.6 }, { "age": 8, "lo": 7, "hi": 14, "mean": 10.8 }, { "age": 9, "lo": 4, "hi": 10, "mean": 7.2 }, { "age": 10, "lo": 4, "hi": 12, "mean": 7.8 }, { "age": 11, "lo": 3, "hi": 8, "mean": 5 }, { "age": 12, "lo": 2, "hi": 6, "mean": 4.6 }, { "age": 13, "lo": 1, "hi": 5, "mean": 3.6 }, { "age": 14, "lo": 0, "hi": 4, "mean": 2 }, { "age": 15, "lo": 0, "hi": 4, "mean": 2.6 }, { "age": 16, "lo": 1, "hi": 4, "mean": 3 }, { "age": 17, "lo": 1, "hi": 6, "mean": 3.2 }, { "age": 18, "lo": 2, "hi": 9, "mean": 5.8 }, { "age": 19, "lo": 2, "hi": 7, "mean": 4.2 }, { "age": 20, "lo": 2, "hi": 10, "mean": 5.8 } ] }, "layers": [ { "geom": "ribbon", "stat": "identity", "position": "identity", "aes": { "x": { "field": "age" }, "fill": { "value": "#4385be" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "alpha": 0.35 } }, { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "age" }, "y": { "field": "mean" }, "color": { "value": "#1a4a6b" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "linewidth": 1.6 } } ], "scales": { "y": { "type": "linear", "transform": "sqrt" } }, "labs": { "title": "A ribbon for year-to-year range", "subtitle": "Mean burials by age with a band from the lowest to highest year at each age", "x": "Age at death", "y": "Burials per year (square-root scale)" }, "theme": "clean" } // note: inline data truncated (80 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomRibbon, GGPlot, Inspect, Labs, ScaleYSqrt, ThemeClean, } from "@ggsvelte/svelte"; import { breslauBurials } from "./data.js"; </script> <GGPlot data={breslauBurials} aes={{ x: "age", ymin: "lo", ymax: "hi" }} width={640} height={400} > <GeomRibbon alpha={0.35} aes={{ fill: { value: "#4385be" } }} /> <GeomLine aes={{ x: "age", y: "mean", color: { value: "#1a4a6b" } }} linewidth={1.6} /> <ScaleYSqrt /> <ThemeClean /> <Labs title="A ribbon for year-to-year range" subtitle="Mean burials by age with a band from the lowest to highest year at each age" x="Age at death" y="Burials per year (square-root scale)" /> <Inspect mode="x" pin /> </GGPlot> ``` ## A ribbon with gradient fill, stroke and glow (ribbon/paint) The paint options a ribbon can carry, on a precomputed interval: a portable gradient across the band, a stroked edge and a glow beneath. Tags: ribbon, interval, gradient, glow, paint Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "columns": { "x": [ 1, 2, 3, 4, 5, 6, 7, 8 ], "mid": [ 2.1, 2.4, 2.8, 3, 2.7, 2.5, 2.9, 3.2 ], "lo": [ 1.6, 1.9, 2.2, 2.4, 2.1, 1.9, 2.3, 2.6 ], "hi": [ 2.6, 2.9, 3.4, 3.6, 3.3, 3.1, 3.5, 3.8 ] } }, "layers": [ { "geom": "ribbon", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "alpha": 0.85, "outline": "both", "fillPaint": { "type": "linear", "x1": 0, "y1": 0, "x2": 0, "y2": 1, "space": "mark", "stops": [ { "offset": 0, "color": "#4c78a8", "opacity": 0.9 }, { "offset": 1, "color": "#f58518", "opacity": 0.75 } ], "fallback": "#4c78a8" }, "strokePaint": { "type": "linear", "x1": 0, "y1": 0, "x2": 1, "y2": 0, "space": "panel", "stops": [ { "offset": 0, "color": "#1a1a1a" }, { "offset": 1, "color": "#666666" } ], "fallback": "#1a1a1a" }, "glow": { "color": "#4c78a8", "radius": 5, "opacity": 0.35 }, "linewidth": 1.25 } }, { "geom": "line", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "mid" }, "ymin": { "field": "lo" }, "ymax": { "field": "hi" } }, "params": { "linewidth": 1.5 } } ], "labs": { "title": "What a ribbon can be painted with", "subtitle": "One interval band carrying a gradient fill, a gradient stroke and a glow", "x": "x", "y": "value", "caption": "Within-mark paint (not a data scale); solid fallbacks remain for a11y." } } ``` Svelte usage: ```svelte <script lang="ts"> import { fillPaintLinear, glow, strokePaintLinear } from "@ggsvelte/spec"; import { GeomLine, GeomRibbon, GGPlot, Inspect, Labs, } from "@ggsvelte/svelte"; import { series } from "./data.js"; </script> <GGPlot data={series} aes={{ x: "x", ymin: "lo", ymax: "hi" }} width={640} height={360} > <GeomRibbon alpha={0.85} outline="both" fillPaint={fillPaintLinear({ x1: 0, y1: 0, x2: 0, y2: 1, space: "mark", stops: [ { offset: 0, color: "#4c78a8", opacity: 0.9 }, { offset: 1, color: "#f58518", opacity: 0.75 }, ], fallback: "#4c78a8", })} strokePaint={strokePaintLinear({ x1: 0, y1: 0, x2: 1, y2: 0, space: "panel", stops: [ { offset: 0, color: "#1a1a1a" }, { offset: 1, color: "#666666" }, ], fallback: "#1a1a1a", })} glow={glow({ color: "#4c78a8", radius: 5, opacity: 0.35 })} linewidth={1.25} /> <GeomLine aes={{ x: "x", y: "mid" }} linewidth={1.5} /> <Labs title="What a ribbon can be painted with" subtitle="One interval band carrying a gradient fill, a gradient stroke and a glow" x="x" y="value" caption="Within-mark paint (not a data scale); solid fallbacks remain for a11y." /> <Inspect mode="x" pin /> </GGPlot> ``` ## Bottom-edge rug ticks (rug/ticks) Ten long thick ticks along the panel bottom — the marginal mark that reads at index thumbnail size. Tags: rug, distribution, marginal Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "score": 1 }, { "score": 2 }, { "score": 3.2 }, { "score": 4 }, { "score": 5.5 }, { "score": 6 }, { "score": 7.1 }, { "score": 8 }, { "score": 9.3 }, { "score": 10 } ] }, "layers": [ { "geom": "rug", "stat": "identity", "position": "identity", "aes": { "x": { "field": "score" }, "color": { "value": "#1a202c" } }, "params": { "sides": "b", "length": 0.22, "linewidth": 2.8, "alpha": 1 } } ], "scales": { "x": { "type": "linear", "domain": [ 0, 11 ] } }, "guides": { "y": { "type": "none" } }, "labs": { "title": "Bottom-edge rug ticks", "subtitle": "Ten long ticks along the panel bottom for each score", "x": "Score" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomRug, GGPlot, GuideNone, Inspect, Labs, ScaleXContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { spacedScores } from "./data.js"; </script> <GGPlot data={spacedScores} aes={{ x: "score" }} width={640} height={400}> <GeomRug sides="b" length={0.22} linewidth={2.8} alpha={1} aes={{ color: { value: "#1a202c" } }} /> <ScaleXContinuous limits={[0, 11]} /> <ThemeMinimal /> <!-- y is a placeholder domain for edge ticks only; hide the ladder --> <GuideNone channel="y" /> <Labs title="Bottom-edge rug ticks" subtitle="Ten long ticks along the panel bottom for each score" x="Score" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## A crosshair of two fixed intercepts (rule/annotation) Annotation rules for cutoffs and known constants: one vertical and one horizontal intercept on continuous data. Tags: rule, annotation, line, layers Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "trial": 1, "density": 5.5 }, { "trial": 2, "density": 5.61 }, { "trial": 3, "density": 4.88 }, { "trial": 4, "density": 5.07 }, { "trial": 5, "density": 5.26 }, { "trial": 6, "density": 5.55 }, { "trial": 7, "density": 5.36 }, { "trial": 8, "density": 5.29 }, { "trial": 9, "density": 5.58 }, { "trial": 10, "density": 5.65 }, { "trial": 11, "density": 5.57 }, { "trial": 12, "density": 5.53 }, { "trial": 13, "density": 5.62 }, { "trial": 14, "density": 5.29 }, { "trial": 15, "density": 5.44 }, { "trial": 16, "density": 5.34 }, { "trial": 17, "density": 5.79 }, { "trial": 18, "density": 5.1 }, { "trial": 19, "density": 5.27 }, { "trial": 20, "density": 5.39 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "trial" }, "y": { "field": "density" } }, "params": { "size": 2.2, "alpha": 0.55 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#d4615c" } }, "params": { "xintercept": 6.5, "linewidth": 2.2 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#2f6fed" } }, "params": { "yintercept": 5.517, "linewidth": 2.2 } } ], "labs": { "title": "A crosshair of two fixed intercepts", "subtitle": "Apparatus change (vertical) and modern value (horizontal)", "x": "Determination", "y": "Density of the earth (water = 1)" }, "theme": "minimal" } // note: inline data truncated (9 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomRule, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { earthDensity } from "./data.js"; </script> <GGPlot data={earthDensity} aes={{ x: "trial", y: "density" }} width={640} height={400} > <GeomPoint size={2.2} alpha={0.55} /> <GeomRule xintercept={6.5} linewidth={2.2} aes={{ color: { value: "#d4615c" } }} /> <GeomRule yintercept={5.517} linewidth={2.2} aes={{ color: { value: "#2f6fed" } }} /> <ThemeMinimal /> <Labs title="A crosshair of two fixed intercepts" subtitle="Apparatus change (vertical) and modern value (horizontal)" x="Determination" y="Density of the earth (water = 1)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## A rug of every cupping score (rule/data-driven) One vertical rule per coffee lot on total cup points. Use this when you want a 1-D sample drawn as a strip of marks. Tags: rule, distribution, rug Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "lot-0001", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 90.58, "aroma": 8.67, "flavor": 8.83, "acidity": 8.75, "body": 8.5, "altitudeM": 2075 }, { "id": "lot-0002", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 89.92, "aroma": 8.75, "flavor": 8.67, "acidity": 8.58, "body": 8.42, "altitudeM": 2075 }, { "id": "lot-0003", "country": "Guatemala", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 89.75, "aroma": 8.42, "flavor": 8.5, "acidity": 8.42, "body": 8.33, "altitudeM": 1700 }, { "id": "lot-0004", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 89, "aroma": 8.17, "flavor": 8.58, "acidity": 8.42, "body": 8.5, "altitudeM": 2000 }, { "id": "lot-0005", "country": "Ethiopia", "species": "Arabica", "processing": "Washed", "totalCupPoints": 88.83, "aroma": 8.25, "flavor": 8.5, "acidity": 8.5, "body": 8.42, "altitudeM": 2075 }, { "id": "lot-0006", "country": "Brazil", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.83, "aroma": 8.58, "flavor": 8.42, "acidity": 8.5, "body": 8.25, "altitudeM": null }, { "id": "lot-0007", "country": "Peru", "species": "Arabica", "processing": "Washed", "totalCupPoints": 88.75, "aroma": 8.42, "flavor": 8.5, "acidity": 8.5, "body": 8.25, "altitudeM": null }, { "id": "lot-0008", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 88.67, "aroma": 8.25, "flavor": 8.33, "acidity": 8.42, "body": 8.33, "altitudeM": 1635 }, { "id": "lot-0009", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 88.42, "aroma": 8.67, "flavor": 8.67, "acidity": 8.42, "body": 8.33, "altitudeM": 1635 }, { "id": "lot-0010", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.25, "aroma": 8.08, "flavor": 8.58, "acidity": 8.5, "body": 7.67, "altitudeM": 1822.5 }, { "id": "lot-0011", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 88.08, "aroma": 8.17, "flavor": 8.67, "acidity": 8.5, "body": 7.75, "altitudeM": 1905 }, { "id": "lot-0012", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.92, "aroma": 8.25, "flavor": 8.42, "acidity": 8.33, "body": 8.08, "altitudeM": 1872 }, { "id": "lot-0013", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.92, "aroma": 8.08, "flavor": 8.67, "acidity": 8.42, "body": 8, "altitudeM": 1943 }, { "id": "lot-0014", "country": "United States (Hawaii)", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.92, "aroma": 8.33, "flavor": 8.42, "acidity": 8.25, "body": 8.25, "altitudeM": 609.6 }, { "id": "lot-0015", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.83, "aroma": 8.25, "flavor": 8.33, "acidity": 8.25, "body": 8.58, "altitudeM": 1635 }, { "id": "lot-0016", "country": "United States", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.58, "aroma": 8, "flavor": 8.5, "acidity": 8.17, "body": 8.17, "altitudeM": 2080 }, { "id": "lot-0017", "country": "Indonesia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.42, "aroma": 8.33, "flavor": 8.25, "acidity": 7.75, "body": 8.5, "altitudeM": 1500 }, { "id": "lot-0018", "country": "Ethiopia", "species": "Arabica", "processing": "Unknown", "totalCupPoints": 87.33, "aroma": 8.17, "flavor": 8.33, "acidity": 8.33, "body": 8.42, "altitudeM": null }, { "id": "lot-0019", "country": "China", "species": "Arabica", "processing": "Washed", "totalCupPoints": 87.25, "aroma": 8.42, "flavor": 8.25, "acidity": 8.17, "body": 7.92, "altitudeM": 1450 }, { "id": "lot-0020", "country": "Ethiopia", "species": "Arabica", "processing": "Natural", "totalCupPoints": 87.25, "aroma": 8.17, "flavor": 8.17, "acidity": 8.17, "body": 8.08, "altitudeM": 1850 } ] }, "layers": [ { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "x": { "field": "totalCupPoints" } }, "params": { "alpha": 0.2, "linewidth": 1 } } ], "guides": { "y": { "type": "none" } }, "labs": { "title": "A rug of every cupping score", "subtitle": "One vertical line per coffee lot. Most lots sit between about 80 and 88 total cup points", "x": "Total cup points" }, "theme": "minimal" } // note: inline data truncated (1318 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomRule, GGPlot, GuideNone, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { coffeeRatings } from "./data.js"; </script> <GGPlot data={coffeeRatings} aes={{ x: "totalCupPoints" }} width={640} height={400} > <GeomRule alpha={0.2} linewidth={1} /> <ThemeMinimal /> <!-- y is synthetic for panel-spanning vertical rules; hide the 0–1 ladder (#700) --> <GuideNone channel="y" /> <Labs title="A rug of every cupping score" subtitle="One vertical line per coffee lot. Most lots sit between about 80 and 88 total cup points" x="Total cup points" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Segments from start to end of each pair (segment/annotations) Segments when each row is a directed change between two points. Tags: segment, annotation, leader, slope Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pair": 1, "self": 17.375, "cross": 23.5, "winner": "Cross-fertilised taller" }, { "pair": 2, "self": 20.375, "cross": 12, "winner": "Self-fertilised taller" }, { "pair": 3, "self": 20, "cross": 21, "winner": "Cross-fertilised taller" }, { "pair": 4, "self": 20, "cross": 22, "winner": "Cross-fertilised taller" }, { "pair": 5, "self": 18.375, "cross": 19.125, "winner": "Cross-fertilised taller" }, { "pair": 6, "self": 18.625, "cross": 21.5, "winner": "Cross-fertilised taller" }, { "pair": 7, "self": 18.625, "cross": 22.125, "winner": "Cross-fertilised taller" }, { "pair": 8, "self": 15.25, "cross": 20.375, "winner": "Cross-fertilised taller" }, { "pair": 9, "self": 16.5, "cross": 18.25, "winner": "Cross-fertilised taller" }, { "pair": 10, "self": 18, "cross": 21.625, "winner": "Cross-fertilised taller" }, { "pair": 11, "self": 16.25, "cross": 23.25, "winner": "Cross-fertilised taller" }, { "pair": 12, "self": 18, "cross": 21, "winner": "Cross-fertilised taller" }, { "pair": 13, "self": 12.75, "cross": 22.125, "winner": "Cross-fertilised taller" }, { "pair": 14, "self": 15.5, "cross": 23, "winner": "Cross-fertilised taller" }, { "pair": 15, "self": 18, "cross": 12, "winner": "Self-fertilised taller" } ] }, "layers": [ { "geom": "segment", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "self" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "linewidth": 2, "lineend": "round", "alpha": 0.9 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "self" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "size": 2.5, "alpha": 0.6 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "pair" }, "y": { "field": "cross" }, "color": { "field": "winner" }, "xend": { "field": "pair" }, "yend": { "field": "cross" } }, "params": { "size": 3.2 } } ], "scales": { "color": { "type": "ordinal", "domain": [ "Cross-fertilised taller", "Self-fertilised taller" ], "scheme": "wsj_red_green" } }, "labs": { "title": "Segments from start to end of each pair", "subtitle": "Each segment runs from the self-fertilised height to the cross-fertilised height", "x": "Pair", "y": "Final height (inches)", "color": "" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomSegment, GGPlot, Inspect, Labs, ScaleColorDiscrete, ThemeClassic, } from "@ggsvelte/svelte"; import { darwinMaize } from "./data.js"; </script> <GGPlot data={darwinMaize} aes={{ x: "pair", y: "self", xend: "pair", yend: "cross", color: "winner" }} width={640} height={400} > <GeomSegment linewidth={2} lineend="round" alpha={0.9} /> <GeomPoint size={2.5} alpha={0.6} /> <GeomPoint aes={{ x: "pair", y: "cross" }} size={3.2} /> <ScaleColorDiscrete domain={["Cross-fertilised taller", "Self-fertilised taller"]} scheme="wsj_red_green" /> <ThemeClassic /> <Labs title="Segments from start to end of each pair" subtitle="Each segment runs from the self-fertilised height to the cross-fertilised height" x="Pair" y="Final height (inches)" color="" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Simple features as filled polygons (sf/basic) Basic sf polygons for map-like regions without a full basemap stack. Tags: sf, geojson, polygon, fill, map, geom Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "height": 130, "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[40,330],[38.3,340],[36.4,360],[38.2,380],[40,386.7],[44.4,400],[60,417.5],[62,420],[78,440],[80,442.2],[100,457.5],[102.5,460],[117.1,480],[120,483.3],[139.6,500],[140,500.2],[160,511.7],[176.7,520],[180,521.7],[200,533.3],[212.5,540],[220,545],[240,546.2],[260,546.2],[280,548.3],[300,549.1],[320,543.6],[330,540],[340,535],[353.3,520],[360,515],[380,500.3],[380.5,500],[400,484],[404,480],[416.7,460],[420,453.3],[433.3,440],[440,430],[460,420.6],[480,428],[500,430],[520,425.7],[540,427.5],[560,432],[580,437.5],[598.2,440],[600,440.2],[600.3,440],[620,422.9],[622,420],[631.1,400],[633.3,380],[633.3,360],[633.3,340],[640,330],[645,320],[656,300],[660,295],[672,280],[680,272],[695,260],[700,253.3],[708,240],[714.3,220],[717.5,200],[712.3,180],[707.3,160],[710,140],[700,122.9],[693.3,120],[680,110],[660.9,100],[660,99.5],[640,96.7],[620,95.6],[600,94],[580,96],[560,98],[540,99.8],[538.2,100],[520,103.3],[500,106.7],[480,112],[460,112],[440,119.6],[438.2,120],[420,122.2],[400,124.4],[380,124.4],[370,120],[360,114.3],[343.3,100],[340,97.5],[320,82.9],[317.1,80],[302.2,60],[300,57.8],[280,43.3],[276,40],[260,26.7],[246.7,20],[240,17.5],[230,20],[220,25],[200,39.4],[199.5,40],[188,60],[180,79.1],[179.6,80],[172,100],[160,112],[150,120],[140,124.4],[122.5,140],[120,141.8],[104.6,160],[100,170],[94,180],[84.3,200],[80,219.4],[79.8,220],[72.7,240],[66,260],[60,279.4],[59.8,280],[43.3,300],[41.5,320],[40,330]]]}" }, { "height": 140, "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[60,299.8],[59.8,300],[56.9,320],[58,340],[59.8,360],[59.8,380],[60,380.6],[70,400],[80,415],[84,420],[100,431.4],[110,440],[120,450],[128.6,460],[140,473.3],[145.7,480],[160,492.5],[170,500],[180,505],[199.8,520],[200,520.1],[220,524.4],[240,528],[260,531.4],[280,532.3],[300,530.9],[320,520.2],[320.5,520],[340,512],[357.1,500],[360,498],[380,480.2],[380.2,480],[387.5,460],[392.5,440],[397.8,420],[400,416.7],[420,403.3],[423.3,400],[437.5,380],[440,377.1],[460,376.7],[478.2,380],[480,380.4],[500,384],[520,390],[539.1,400],[540,400.3],[560,400.5],[580,400.6],[600,400.5],[600.4,400],[600.3,380],[600,379.1],[580.9,360],[580,358.2],[573.3,340],[580,321.8],[580.5,320],[590,300],[600,295],[620,284],[630,280],[640,276.7],[656.7,260],[660,256],[671.4,240],[680,225],[683.3,220],[691.4,200],[693.3,180],[686.7,160],[680.5,140],[680,139.8],[660,128.6],[640,125.7],[620,123.3],[601.8,120],[600,119.7],[598.2,120],[580,124],[560,130],[540,136],[520,139.6],[518.2,140],[500,143.3],[480,143.3],[460,144],[440,144],[420,148],[400,148.6],[380,147.5],[368,140],[360,136],[340,130],[325,120],[320,116],[304,100],[300,96],[280.5,80],[280,79.6],[260,64],[241.8,60],[240,59.5],[239.4,60],[224,80],[220,85],[210,100],[200,110],[191.4,120],[180,128.9],[160,136],[152,140],[140,144.6],[120,159.8],[119.8,160],[108.2,180],[100,197.5],[98.6,200],[93.3,220],[88.6,240],[83.5,260],[80,272],[76.7,280],[60,299.8]]]}" }, { "height": 150, "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[80,296],[76.7,300],[77.8,320],[79.8,340],[80,340.9],[83.3,360],[90,380],[100,396],[101.7,400],[113.3,420],[120,426],[135.6,440],[140,445],[155,460],[160,465.7],[174.3,480],[180,484.4],[200,496],[210,500],[220,504],[240,510.8],[260,516.7],[280,516.9],[300,513.8],[320,504.6],[328.6,500],[340,492],[357.1,480],[360,475],[366,460],[372.3,440],[375.6,420],[380,400.9],[380.4,400],[400,387.5],[410,380],[420,368],[426.7,360],[440,346.7],[460,343.3],[480,344],[490,340],[500,337.1],[520,320.6],[520.5,320],[532,300],[540,290],[550,280],[560,266.7],[580,260.5],[600,260.3],[600.6,260],[620,240.6],[640,240.4],[640.3,240],[640.6,220],[641.8,200],[641.8,180],[640.6,160],[640,159.6],[620,159.6],[618.2,160],[600,178.2],[580,179.4],[560,179.5],[540,179.5],[520,173.3],[500,174.3],[480,179.6],[478.2,180],[460,182.5],[440,186.7],[420,185.7],[400,182.5],[393.3,180],[380,174.3],[360,170],[340,166.7],[320.9,160],[320,159.5],[304,140],[300,136],[280,122.5],[275,120],[260,105],[240,104],[220,119.7],[219.7,120],[200,132.7],[191.1,140],[180,148.3],[160,154.5],[140,159.8],[139.8,160],[120,179.8],[119.9,180],[112.9,200],[108.3,220],[103.1,240],[100,246.7],[95.3,260],[89.4,280],[80,296]]]}" } ] }, "layers": [ { "geom": "sf", "stat": "sf", "position": "identity", "aes": { "fill": { "field": "height" } }, "params": { "alpha": 0.55, "linewidth": 0.9 } } ], "coord": { "type": "fixed" }, "scales": { "fill": { "type": "sequential", "scheme": "viridis" } }, "labs": { "title": "Simple features as filled polygons", "subtitle": "Elevation bands on a hill drawn from simple-feature rings", "fill": "Metres" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSf, GGPlot, Inspect, Labs, ScaleFillContinuous, ThemeClassic, } from "@ggsvelte/svelte"; import { heightRings } from "./data.js"; </script> <GGPlot data={heightRings} aes={{ fill: "height" }} width={640} height={400}> <GeomSf alpha={0.55} linewidth={0.9} /> <CoordFixed /> <ScaleFillContinuous scheme="viridis" /> <ThemeClassic /> <Labs title="Simple features as filled polygons" subtitle="Elevation bands on a hill drawn from simple-feature rings" fill="Metres" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Pump names on measured boxes (sf/boxed-labels) Thirteen Soho neighbourhoods with each pump name on a measured box. The box keeps the name readable over a coloured fill. Tags: sf, sf_label, label, box, geojson, centroid, map Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pump": "Oxford Market", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,16.32],[10.3,16.42],[9.68,18.73],[3.39,18.73],[3.39,16.32]]]}" }, { "pump": "Castle St E", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[10.3,16.42],[11.19,15.85],[12.54,18.73],[9.68,18.73],[10.3,16.42]]]}" }, { "pump": "Oxford St #1", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[11.19,15.85],[11.79,14.73],[15.05,14.27],[13.82,18.73],[12.54,18.73],[11.19,15.85]]]}" }, { "pump": "Oxford St #2", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[15.05,14.27],[16.56,13.69],[16.84,13.75],[19.91,15.28],[19.91,18.73],[13.82,18.73],[15.05,14.27]]]}" }, { "pump": "Gt Marlborough", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,13.4],[11.21,14.02],[11.79,14.73],[11.19,15.85],[10.3,16.42],[3.39,16.32],[3.39,13.4]]]}" }, { "pump": "Crown Chapel", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,8.83],[6.17,8.88],[10.16,10.23],[11.21,14.02],[3.39,13.4],[3.39,8.83]]]}" }, { "pump": "Broad St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[10.16,10.23],[11.75,9.52],[14.29,10.16],[16.56,13.69],[15.05,14.27],[11.79,14.73],[11.21,14.02],[10.16,10.23]]]}" }, { "pump": "Warwick", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.5,4.36],[12.65,4.7],[11.75,9.52],[10.16,10.23],[6.17,8.88],[12.5,4.36]]]}" }, { "pump": "Briddle St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.65,4.7],[15.6,7.21],[14.29,10.16],[11.75,9.52],[12.65,4.7]]]}" }, { "pump": "So Soho", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[15.6,7.21],[18.17,6.95],[16.84,13.75],[16.56,13.69],[14.29,10.16],[15.6,7.21]]]}" }, { "pump": "Dean St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[18.17,6.95],[19.91,5.87],[19.91,15.28],[16.84,13.75],[18.17,6.95]]]}" }, { "pump": "Coventry St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.65,4.7],[12.5,4.36],[12.49,3.23],[19.91,3.23],[19.91,5.87],[18.17,6.95],[15.6,7.21],[12.65,4.7]]]}" }, { "pump": "Vigo St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,3.23],[12.49,3.23],[12.5,4.36],[6.17,8.88],[3.39,8.83],[3.39,3.23]]]}" } ] }, "layers": [ { "geom": "sf", "stat": "sf", "position": "identity", "aes": { "fill": { "field": "pump" }, "label": { "field": "pump" } }, "params": { "alpha": 0.75, "linewidth": 0.8 } }, { "geom": "sf_label", "stat": "sf_coordinates", "position": "identity", "aes": { "fill": { "field": "pump" }, "label": { "field": "pump" } }, "params": { "padding": 3, "radius": 2, "size": 11 } } ], "coord": { "type": "fixed" }, "guides": { "fill": { "type": "none" } }, "labs": { "title": "Pump names on measured boxes", "subtitle": "Each label sits on a padded box so it stays readable over a busy fill" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSf, GeomSfLabel, GGPlot, GuideNone, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { pumpNeighbourhoods } from "./data.js"; </script> <GGPlot data={pumpNeighbourhoods} aes={{ fill: "pump", label: "pump" }} width={640} height={400} > <GeomSf alpha={0.75} linewidth={0.8} /> <GeomSfLabel padding={3} radius={2} size={11} /> <CoordFixed /> <ThemeClassic /> <GuideNone channel="fill" /> <Labs title="Pump names on measured boxes" subtitle="Each label sits on a padded box so it stays readable over a busy fill" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## One feature with two separate polygons (sf/geometry-collection) Geometry collections when one logical feature spans disconnected pieces. Tags: sf, geojson, geometrycollection, polygon, fill, map, geom Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "ground": "Above 180 m", "geometry": "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Polygon\",\"coordinates\":[[[140,299.4],[139.8,300],[134.5,320],[134.5,340],[139.8,360],[140,360.6],[152,380],[160,385.7],[171.1,400],[180,411.4],[188.6,420],[200,431.4],[215,440],[220,444],[240,441.8],[258.2,460],[260,460.4],[260.9,460],[280,440.9],[300,440.5],[300.5,440],[300,438.2],[281.8,420],[280,419.7],[279.1,420],[260,433.3],[240.9,420],[240,419.5],[226.7,400],[220,386.7],[218.2,380],[214.7,360],[212.5,340],[211.4,320],[214.3,300],[220,288.6],[226.7,280],[240,260.3],[240.4,260],[260,240.4],[280,240.2],[281.8,240],[280.9,220],[280,219.4],[260.6,200],[260,199.7],[240,196.7],[220,197.5],[215,200],[200,206.7],[180,216.7],[173.3,220],[160,230],[156,240],[152,260],[147.5,280],[140,299.4]]]},{\"type\":\"Polygon\",\"coordinates\":[[[360,259.1],[359.1,260],[360,261.8],[378.2,280],[380,280.9],[380.3,280],[380.4,260],[380,259.5],[360,259.1]]]}]}" } ] }, "layers": [ { "geom": "sf", "stat": "sf", "position": "identity", "aes": { "fill": { "field": "ground" } }, "params": { "alpha": 0.85, "linewidth": 0.9 } } ], "coord": { "type": "fixed" }, "labs": { "title": "One feature with two separate polygons", "subtitle": "Land above 180 m as a single collection: the summit ridge and a rise east of it", "fill": "" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSf, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { aboveOneEighty } from "./data.js"; </script> <GGPlot data={aboveOneEighty} aes={{ fill: "ground" }} width={640} height={400}> <GeomSf alpha={0.85} linewidth={0.9} /> <CoordFixed /> <ThemeClassic /> <Labs title="One feature with two separate polygons" subtitle="Land above 180 m as a single collection: the summit ridge and a rise east of it" fill="" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Polygons with holes (sf/holes) Holes when an inner ring should cut out of an outer polygon. Tags: sf, geojson, polygon, hole, evenodd, fill, map, geom Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "band": "130 to 140 m", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[40,330],[38.3,340],[36.4,360],[38.2,380],[40,386.7],[44.4,400],[60,417.5],[62,420],[78,440],[80,442.2],[100,457.5],[102.5,460],[117.1,480],[120,483.3],[139.6,500],[140,500.2],[160,511.7],[176.7,520],[180,521.7],[200,533.3],[212.5,540],[220,545],[240,546.2],[260,546.2],[280,548.3],[300,549.1],[320,543.6],[330,540],[340,535],[353.3,520],[360,515],[380,500.3],[380.5,500],[400,484],[404,480],[416.7,460],[420,453.3],[433.3,440],[440,430],[460,420.6],[480,428],[500,430],[520,425.7],[540,427.5],[560,432],[580,437.5],[598.2,440],[600,440.2],[600.3,440],[620,422.9],[622,420],[631.1,400],[633.3,380],[633.3,360],[633.3,340],[640,330],[645,320],[656,300],[660,295],[672,280],[680,272],[695,260],[700,253.3],[708,240],[714.3,220],[717.5,200],[712.3,180],[707.3,160],[710,140],[700,122.9],[693.3,120],[680,110],[660.9,100],[660,99.5],[640,96.7],[620,95.6],[600,94],[580,96],[560,98],[540,99.8],[538.2,100],[520,103.3],[500,106.7],[480,112],[460,112],[440,119.6],[438.2,120],[420,122.2],[400,124.4],[380,124.4],[370,120],[360,114.3],[343.3,100],[340,97.5],[320,82.9],[317.1,80],[302.2,60],[300,57.8],[280,43.3],[276,40],[260,26.7],[246.7,20],[240,17.5],[230,20],[220,25],[200,39.4],[199.5,40],[188,60],[180,79.1],[179.6,80],[172,100],[160,112],[150,120],[140,124.4],[122.5,140],[120,141.8],[104.6,160],[100,170],[94,180],[84.3,200],[80,219.4],[79.8,220],[72.7,240],[66,260],[60,279.4],[59.8,280],[43.3,300],[41.5,320],[40,330]],[[60,299.8],[59.8,300],[56.9,320],[58,340],[59.8,360],[59.8,380],[60,380.6],[70,400],[80,415],[84,420],[100,431.4],[110,440],[120,450],[128.6,460],[140,473.3],[145.7,480],[160,492.5],[170,500],[180,505],[199.8,520],[200,520.1],[220,524.4],[240,528],[260,531.4],[280,532.3],[300,530.9],[320,520.2],[320.5,520],[340,512],[357.1,500],[360,498],[380,480.2],[380.2,480],[387.5,460],[392.5,440],[397.8,420],[400,416.7],[420,403.3],[423.3,400],[437.5,380],[440,377.1],[460,376.7],[478.2,380],[480,380.4],[500,384],[520,390],[539.1,400],[540,400.3],[560,400.5],[580,400.6],[600,400.5],[600.4,400],[600.3,380],[600,379.1],[580.9,360],[580,358.2],[573.3,340],[580,321.8],[580.5,320],[590,300],[600,295],[620,284],[630,280],[640,276.7],[656.7,260],[660,256],[671.4,240],[680,225],[683.3,220],[691.4,200],[693.3,180],[686.7,160],[680.5,140],[680,139.8],[660,128.6],[640,125.7],[620,123.3],[601.8,120],[600,119.7],[598.2,120],[580,124],[560,130],[540,136],[520,139.6],[518.2,140],[500,143.3],[480,143.3],[460,144],[440,144],[420,148],[400,148.6],[380,147.5],[368,140],[360,136],[340,130],[325,120],[320,116],[304,100],[300,96],[280.5,80],[280,79.6],[260,64],[241.8,60],[240,59.5],[239.4,60],[224,80],[220,85],[210,100],[200,110],[191.4,120],[180,128.9],[160,136],[152,140],[140,144.6],[120,159.8],[119.8,160],[108.2,180],[100,197.5],[98.6,200],[93.3,220],[88.6,240],[83.5,260],[80,272],[76.7,280],[60,299.8]]]}" }, { "band": "140 to 150 m", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[60,299.8],[59.8,300],[56.9,320],[58,340],[59.8,360],[59.8,380],[60,380.6],[70,400],[80,415],[84,420],[100,431.4],[110,440],[120,450],[128.6,460],[140,473.3],[145.7,480],[160,492.5],[170,500],[180,505],[199.8,520],[200,520.1],[220,524.4],[240,528],[260,531.4],[280,532.3],[300,530.9],[320,520.2],[320.5,520],[340,512],[357.1,500],[360,498],[380,480.2],[380.2,480],[387.5,460],[392.5,440],[397.8,420],[400,416.7],[420,403.3],[423.3,400],[437.5,380],[440,377.1],[460,376.7],[478.2,380],[480,380.4],[500,384],[520,390],[539.1,400],[540,400.3],[560,400.5],[580,400.6],[600,400.5],[600.4,400],[600.3,380],[600,379.1],[580.9,360],[580,358.2],[573.3,340],[580,321.8],[580.5,320],[590,300],[600,295],[620,284],[630,280],[640,276.7],[656.7,260],[660,256],[671.4,240],[680,225],[683.3,220],[691.4,200],[693.3,180],[686.7,160],[680.5,140],[680,139.8],[660,128.6],[640,125.7],[620,123.3],[601.8,120],[600,119.7],[598.2,120],[580,124],[560,130],[540,136],[520,139.6],[518.2,140],[500,143.3],[480,143.3],[460,144],[440,144],[420,148],[400,148.6],[380,147.5],[368,140],[360,136],[340,130],[325,120],[320,116],[304,100],[300,96],[280.5,80],[280,79.6],[260,64],[241.8,60],[240,59.5],[239.4,60],[224,80],[220,85],[210,100],[200,110],[191.4,120],[180,128.9],[160,136],[152,140],[140,144.6],[120,159.8],[119.8,160],[108.2,180],[100,197.5],[98.6,200],[93.3,220],[88.6,240],[83.5,260],[80,272],[76.7,280],[60,299.8]],[[80,296],[76.7,300],[77.8,320],[79.8,340],[80,340.9],[83.3,360],[90,380],[100,396],[101.7,400],[113.3,420],[120,426],[135.6,440],[140,445],[155,460],[160,465.7],[174.3,480],[180,484.4],[200,496],[210,500],[220,504],[240,510.8],[260,516.7],[280,516.9],[300,513.8],[320,504.6],[328.6,500],[340,492],[357.1,480],[360,475],[366,460],[372.3,440],[375.6,420],[380,400.9],[380.4,400],[400,387.5],[410,380],[420,368],[426.7,360],[440,346.7],[460,343.3],[480,344],[490,340],[500,337.1],[520,320.6],[520.5,320],[532,300],[540,290],[550,280],[560,266.7],[580,260.5],[600,260.3],[600.6,260],[620,240.6],[640,240.4],[640.3,240],[640.6,220],[641.8,200],[641.8,180],[640.6,160],[640,159.6],[620,159.6],[618.2,160],[600,178.2],[580,179.4],[560,179.5],[540,179.5],[520,173.3],[500,174.3],[480,179.6],[478.2,180],[460,182.5],[440,186.7],[420,185.7],[400,182.5],[393.3,180],[380,174.3],[360,170],[340,166.7],[320.9,160],[320,159.5],[304,140],[300,136],[280,122.5],[275,120],[260,105],[240,104],[220,119.7],[219.7,120],[200,132.7],[191.1,140],[180,148.3],[160,154.5],[140,159.8],[139.8,160],[120,179.8],[119.9,180],[112.9,200],[108.3,220],[103.1,240],[100,246.7],[95.3,260],[89.4,280],[80,296]]]}" } ] }, "layers": [ { "geom": "sf", "stat": "sf", "position": "identity", "aes": { "fill": { "field": "band" } }, "params": { "alpha": 0.75, "linewidth": 0.9 } } ], "coord": { "type": "fixed" }, "labs": { "title": "Polygons with holes", "subtitle": "Two elevation bands where the higher ring is a hole in the lower", "fill": "Height band" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSf, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { slopeBands } from "./data.js"; </script> <GGPlot data={slopeBands} aes={{ fill: "band" }} width={640} height={400}> <GeomSf alpha={0.75} linewidth={0.9} /> <CoordFixed /> <ThemeClassic /> <Labs title="Polygons with holes" subtitle="Two elevation bands where the higher ring is a hole in the lower" fill="Height band" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Snow's pumps, named in place (sf/labels) The thirteen Soho neighbourhoods with each pump's street name set at the centre of the area it served, positioned from the geometry rather than from x and y. Tags: sf, sf_text, label, geojson, centroid, map Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "pump": "Oxford Market", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,16.32],[10.3,16.42],[9.68,18.73],[3.39,18.73],[3.39,16.32]]]}" }, { "pump": "Castle St E", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[10.3,16.42],[11.19,15.85],[12.54,18.73],[9.68,18.73],[10.3,16.42]]]}" }, { "pump": "Oxford St #1", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[11.19,15.85],[11.79,14.73],[15.05,14.27],[13.82,18.73],[12.54,18.73],[11.19,15.85]]]}" }, { "pump": "Oxford St #2", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[15.05,14.27],[16.56,13.69],[16.84,13.75],[19.91,15.28],[19.91,18.73],[13.82,18.73],[15.05,14.27]]]}" }, { "pump": "Gt Marlborough", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,13.4],[11.21,14.02],[11.79,14.73],[11.19,15.85],[10.3,16.42],[3.39,16.32],[3.39,13.4]]]}" }, { "pump": "Crown Chapel", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,8.83],[6.17,8.88],[10.16,10.23],[11.21,14.02],[3.39,13.4],[3.39,8.83]]]}" }, { "pump": "Broad St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[10.16,10.23],[11.75,9.52],[14.29,10.16],[16.56,13.69],[15.05,14.27],[11.79,14.73],[11.21,14.02],[10.16,10.23]]]}" }, { "pump": "Warwick", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.5,4.36],[12.65,4.7],[11.75,9.52],[10.16,10.23],[6.17,8.88],[12.5,4.36]]]}" }, { "pump": "Briddle St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.65,4.7],[15.6,7.21],[14.29,10.16],[11.75,9.52],[12.65,4.7]]]}" }, { "pump": "So Soho", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[15.6,7.21],[18.17,6.95],[16.84,13.75],[16.56,13.69],[14.29,10.16],[15.6,7.21]]]}" }, { "pump": "Dean St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[18.17,6.95],[19.91,5.87],[19.91,15.28],[16.84,13.75],[18.17,6.95]]]}" }, { "pump": "Coventry St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[12.65,4.7],[12.5,4.36],[12.49,3.23],[19.91,3.23],[19.91,5.87],[18.17,6.95],[15.6,7.21],[12.65,4.7]]]}" }, { "pump": "Vigo St", "geometry": "{\"type\":\"Polygon\",\"coordinates\":[[[3.39,3.23],[12.49,3.23],[12.5,4.36],[6.17,8.88],[3.39,8.83],[3.39,3.23]]]}" } ] }, "layers": [ { "geom": "sf", "stat": "sf", "position": "identity", "aes": { "fill": { "field": "pump" }, "label": { "field": "pump" } }, "params": { "alpha": 0.35, "linewidth": 0.8 } }, { "geom": "sf_text", "stat": "sf_coordinates", "position": "identity", "aes": { "fill": { "field": "pump" }, "label": { "field": "pump" } }, "params": { "size": 11 } } ], "coord": { "type": "fixed" }, "guides": { "fill": { "type": "none" } }, "labs": { "title": "Every pump in Snow's Soho, named", "subtitle": "Plain labels at the centre of the area each pump served" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSf, GeomSfText, GGPlot, GuideNone, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { pumpNeighbourhoods } from "./data.js"; </script> <GGPlot data={pumpNeighbourhoods} aes={{ fill: "pump", label: "pump" }} width={640} height={400} > <GeomSf alpha={0.35} linewidth={0.8} /> <GeomSfText size={11} /> <CoordFixed /> <ThemeClassic /> <GuideNone channel="fill" /> <Labs title="Every pump in Snow's Soho, named" subtitle="Plain labels at the centre of the area each pump served" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## Kyoto cherry blossoms, 812–2026 (showcase/kyoto-sakura) Tufte's full-bloom chart at full fidelity: 30-year rolling median, epoch bands, labeled baseline, and circled records. Tags: line, point, time, annotations, theme, case study Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "year": 812, "bloomDate": "0812-04-01", "bloomDoy": 92 }, { "year": 815, "bloomDate": "0815-04-15", "bloomDoy": 105 }, { "year": 831, "bloomDate": "0831-04-06", "bloomDoy": 96 }, { "year": 851, "bloomDate": "0851-04-18", "bloomDoy": 108 }, { "year": 853, "bloomDate": "0853-04-14", "bloomDoy": 104 }, { "year": 864, "bloomDate": "0864-04-09", "bloomDoy": 100 }, { "year": 866, "bloomDate": "0866-04-16", "bloomDoy": 106 }, { "year": 869, "bloomDate": "0869-04-05", "bloomDoy": 95 }, { "year": 889, "bloomDate": "0889-04-14", "bloomDoy": 104 }, { "year": 891, "bloomDate": "0891-04-19", "bloomDoy": 109 }, { "year": 892, "bloomDate": "0892-04-17", "bloomDoy": 108 }, { "year": 894, "bloomDate": "0894-04-16", "bloomDoy": 106 }, { "year": 895, "bloomDate": "0895-04-14", "bloomDoy": 104 }, { "year": 896, "bloomDate": "0896-04-13", "bloomDoy": 104 }, { "year": 902, "bloomDate": "0902-04-12", "bloomDoy": 102 }, { "year": 908, "bloomDate": "0908-04-07", "bloomDoy": 98 }, { "year": 912, "bloomDate": "0912-04-04", "bloomDoy": 95 }, { "year": 913, "bloomDate": "0913-04-20", "bloomDoy": 110 }, { "year": 917, "bloomDate": "0917-04-05", "bloomDoy": 95 }, { "year": 923, "bloomDate": "0923-04-14", "bloomDoy": 104 } ] }, "layers": [ { "geom": "rect", "stat": "identity", "position": "identity", "aes": { "fill": { "field": "epoch" }, "ymin": { "field": "top" }, "ymax": { "field": "bottom" }, "xmin": { "field": "year" }, "xmax": { "field": "until" } }, "data": { "values": [ { "epoch": "Medieval warm period", "year": 950, "until": 1250, "top": "03-18", "bottom": "05-10" }, { "epoch": "Little Ice Age", "year": 1300, "until": 1850, "top": "03-18", "bottom": "05-10" }, { "epoch": "Industrial era", "year": 1850, "until": 2026, "top": "03-18", "bottom": "05-10" } ] }, "inspect": false, "params": { "alpha": 0.55 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "midYear" }, "y": { "field": "nameDate" }, "color": { "value": "#6b7075" }, "label": { "field": "epoch" } }, "data": { "values": [ { "epoch": "Medieval warm period", "midYear": 1100, "nameDate": "03-14" }, { "epoch": "Little Ice Age", "midYear": 1575, "nameDate": "03-14" }, { "epoch": "Industrial era", "midYear": 1938, "nameDate": "03-14" } ] }, "inspect": false, "params": { "size": 11 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#b7c1cd" }, "linetype": { "value": "dotted" } }, "inspect": false, "params": { "yintercept": "04-05", "linewidth": 0.75 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#b7c1cd" }, "linetype": { "value": "dotted" } }, "inspect": false, "params": { "yintercept": "04-25", "linewidth": 0.75 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#4a5568" } }, "params": { "alpha": 0.55, "size": 1.4 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#6b7075" } }, "inspect": false, "params": { "yintercept": "04-15", "linewidth": 1 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#6b7075" }, "label": { "field": "label" } }, "data": { "values": [ { "year": 812, "bloomDate": "04-15", "label": "median" } ] }, "inspect": false, "params": { "size": 9, "anchor": "start", "dy": 22 } }, { "geom": "line", "stat": "summary_rolling", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#262626" } }, "params": { "fun": "median", "window": 30, "curve": "linear", "linewidth": 1.8 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#2c5282" } }, "data": { "values": [ { "year": 1323, "bloomDate": "05-04" } ] }, "params": { "shape": "circle-open", "size": 3.5 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#c53030" } }, "data": { "values": [ { "year": 1409, "bloomDate": "03-27" } ] }, "params": { "shape": "circle-open", "size": 3.5 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "year" }, "y": { "field": "bloomDate" }, "color": { "value": "#c53030" } }, "data": { "values": [ { "year": 2023, "bloomDate": "03-25" } ] }, "params": { "size": 3 } }, { "geom": "segment", "stat": "identity", "position": "identity", "aes": { "x": { "field": "labelYear" }, "y": { "field": "labelDate" }, "color": { "value": "#b3452f" }, "xend": { "field": "year" }, "yend": { "field": "bloomDate" } }, "data": { "values": [ { "year": 1323, "bloomDate": "05-04", "label": "1323 · May 4, latest on record", "labelYear": 1305, "labelDate": "05-07" }, { "year": 1409, "bloomDate": "03-27", "label": "1409 · March 27, earliest for six centuries", "labelYear": 1400, "labelDate": "03-22" }, { "year": 2023, "bloomDate": "03-25", "label": "2023 · March 25, earliest in 1,200 years", "labelYear": 2014, "labelDate": "03-20" } ] }, "params": { "linewidth": 0.7, "alpha": 0.9 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "labelYear" }, "y": { "field": "labelDate" }, "color": { "value": "#b3452f" }, "label": { "field": "label" } }, "data": { "values": [ { "year": 1323, "bloomDate": "05-04", "label": "1323 · May 4, latest on record", "labelYear": 1305, "labelDate": "05-07" }, { "year": 1409, "bloomDate": "03-27", "label": "1409 · March 27, earliest for six centuries", "labelYear": 1400, "labelDate": "03-22" }, { "year": 2023, "bloomDate": "03-25", "label": "2023 · March 25, earliest in 1,200 years", "labelYear": 2014, "labelDate": "03-20" } ] }, "params": { "size": 11, "anchor": "end", "dx": -4 } } ], "scales": { "x": { "type": "linear", "domain": [ 800, 2030 ], "labels": "d" }, "y": { "type": "time", "temporalKind": "monthDay", "reverse": true, "breaks": [ "04-05", "04-15", "04-25" ], "dateLabels": "%b %e", "domain": [ "05-10", "03-10" ] }, "fill": { "type": "manual", "domain": [ "Medieval warm period", "Little Ice Age", "Industrial era" ], "range": [ "#f5edc4", "#dce8f2", "#f3dcda" ] } }, "guides": { "fill": { "type": "none" } }, "labs": { "x": "Year", "y": "Bloom date (earlier ↑)" }, "theme": "tufte" } // note: inline data truncated (818 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomLine, GeomPoint, GeomRect, GeomRule, GeomSegment, GeomText, GGPlot, GuideNone, Inspect, Labs, registerSummaryRolling, ScaleFillManual, ScaleXContinuous, ScaleYMonthDay, ThemeTufte, } from "@ggsvelte/svelte"; import { kyotoSakura } from "@ggsvelte/svelte/data"; // stat="summary_rolling" on the basic GeomLine shell: opt into the family // (the shell registers only its default stat, #1420). registerSummaryRolling(); // Bands cover every observation; a strip above holds the epoch names. const span = { top: "03-18", bottom: "05-10" }; const epochs = [ { epoch: "Medieval warm period", year: 950, until: 1250, ...span }, { epoch: "Little Ice Age", year: 1300, until: 1850, ...span }, { epoch: "Industrial era", year: 1850, until: 2026, ...span }, ]; const epochNames = epochs.map((band) => ({ epoch: band.epoch, midYear: Math.round((band.year + band.until) / 2), nameDate: "03-14", })); const records = [ { year: 1323, bloomDate: "05-04", label: "1323 · May 4, latest on record", labelYear: 1305, labelDate: "05-07", }, { year: 1409, bloomDate: "03-27", label: "1409 · March 27, earliest for six centuries", labelYear: 1400, labelDate: "03-22", }, { year: 2023, bloomDate: "03-25", label: "2023 · March 25, earliest in 1,200 years", labelYear: 2014, labelDate: "03-20", }, ]; const baselineLabel = [{ year: 812, bloomDate: "04-15", label: "median" }]; // Ring treatment from the reference: an open blue ring on the latest bloom, // an open red ring on the earliest, a filled red dot on the modern record. const ringLatest = [{ year: 1323, bloomDate: "05-04" }]; const ringEarliest = [{ year: 1409, bloomDate: "03-27" }]; const recordRecent = [{ year: 2023, bloomDate: "03-25" }]; </script> <GGPlot data={kyotoSakura} aes={{ x: "year", y: "bloomDate" }} width={768} height={420} > <GeomRect data={epochs} aes={{ x: null, y: null, xmin: "year", xmax: "until", ymin: "top", ymax: "bottom", fill: "epoch", }} alpha={0.55} inspect={false} /> <GeomText data={epochNames} aes={{ x: "midYear", y: "nameDate", label: "epoch", color: { value: "#6b7075" }, }} size={11} inspect={false} /> <GeomRule yintercept="04-05" linewidth={0.75} aes={{ color: { value: "#b7c1cd" }, linetype: { value: "dotted" } }} inspect={false} /> <GeomRule yintercept="04-25" linewidth={0.75} aes={{ color: { value: "#b7c1cd" }, linetype: { value: "dotted" } }} inspect={false} /> <GeomPoint alpha={0.55} size={1.4} aes={{ color: { value: "#4a5568" } }} /> <GeomRule yintercept="04-15" linewidth={1} aes={{ color: { value: "#6b7075" } }} inspect={false} /> <GeomText data={baselineLabel} aes={{ x: "year", y: "bloomDate", label: "label", color: { value: "#6b7075" }, }} size={9} anchor="start" dy={22} inspect={false} /> <GeomLine stat="summary_rolling" fun="median" window={30} curve="linear" linewidth={1.8} aes={{ color: { value: "#262626" } }} /> <GeomPoint data={ringLatest} shape="circle-open" size={3.5} aes={{ x: "year", y: "bloomDate", color: { value: "#2c5282" } }} /> <GeomPoint data={ringEarliest} shape="circle-open" size={3.5} aes={{ x: "year", y: "bloomDate", color: { value: "#c53030" } }} /> <GeomPoint data={recordRecent} size={3} aes={{ x: "year", y: "bloomDate", color: { value: "#c53030" } }} /> <GeomSegment data={records} aes={{ x: "labelYear", y: "labelDate", xend: "year", yend: "bloomDate", color: { value: "#b3452f" }, }} linewidth={0.7} alpha={0.9} /> <GeomText data={records} aes={{ x: "labelYear", y: "labelDate", label: "label", color: { value: "#b3452f" }, }} size={11} anchor="end" dx={-4} /> <ScaleXContinuous labels="d" domain={[800, 2030]} /> <ScaleYMonthDay reverse breaks={["04-05", "04-15", "04-25"]} dateLabels="%b %e" domain={["05-10", "03-10"]} /> <ScaleFillManual domain={["Medieval warm period", "Little Ice Age", "Industrial era"]} values={["#f5edc4", "#dce8f2", "#f3dcda"]} /> <ThemeTufte /> <GuideNone channel="fill" /> <Labs x="Year" y="Bloom date (earlier ↑)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Cocoa percent against bar rating (smooth/loess-scatter) Loess through a sample of chocolate reviews, with a confidence band. Copy this pattern when you want a smooth trend on a dense scatter. Tags: smooth, loess, ribbon, scatter Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "id": "bar-0001", "company": "5150", "companyLocation": "U.S.A.", "beanOrigin": "Tanzania", "cocoaPercent": 76, "rating": 3.25 }, { "id": "bar-0007", "company": "5150", "companyLocation": "U.S.A.", "beanOrigin": "India", "cocoaPercent": 68, "rating": 3.5 }, { "id": "bar-0013", "company": "A. Morin", "companyLocation": "France", "beanOrigin": "Burma", "cocoaPercent": 70, "rating": 3 }, { "id": "bar-0019", "company": "A. Morin", "companyLocation": "France", "beanOrigin": "Venezuela", "cocoaPercent": 70, "rating": 4 }, { "id": "bar-0025", "company": "A. Morin", "companyLocation": "France", "beanOrigin": "Peru", "cocoaPercent": 70, "rating": 4 }, { "id": "bar-0031", "company": "A. Morin", "companyLocation": "France", "beanOrigin": "Venezuela", "cocoaPercent": 70, "rating": 3.5 }, { "id": "bar-0037", "company": "Acalli", "companyLocation": "U.S.A.", "beanOrigin": "Peru", "cocoaPercent": 70, "rating": 3.5 }, { "id": "bar-0043", "company": "Aelan", "companyLocation": "Vanuatu", "beanOrigin": "Vanuatu", "cocoaPercent": 70, "rating": 2.75 }, { "id": "bar-0049", "company": "Akesson's (Pralus)", "companyLocation": "U.K.", "beanOrigin": "Madagascar", "cocoaPercent": 75, "rating": 2.75 }, { "id": "bar-0055", "company": "Alain Ducasse", "companyLocation": "France", "beanOrigin": "Vietnam", "cocoaPercent": 75, "rating": 2.75 }, { "id": "bar-0061", "company": "Alexandre", "companyLocation": "Netherlands", "beanOrigin": "Vietnam", "cocoaPercent": 70, "rating": 3.5 }, { "id": "bar-0067", "company": "Altus aka Cao Artisan", "companyLocation": "U.S.A.", "beanOrigin": "Bolivia", "cocoaPercent": 60, "rating": 2.5 }, { "id": "bar-0073", "company": "Amano", "companyLocation": "U.S.A.", "beanOrigin": "Venezuela", "cocoaPercent": 70, "rating": 3 }, { "id": "bar-0079", "company": "Amano", "companyLocation": "U.S.A.", "beanOrigin": "Dominican Republic", "cocoaPercent": 70, "rating": 3.75 }, { "id": "bar-0085", "company": "Amatller (Simon Coll)", "companyLocation": "Spain", "beanOrigin": "Ghana", "cocoaPercent": 85, "rating": 3 }, { "id": "bar-0091", "company": "Ambrosia", "companyLocation": "Canada", "beanOrigin": "Madagascar", "cocoaPercent": 66, "rating": 3.25 }, { "id": "bar-0097", "company": "Amedei", "companyLocation": "Italy", "beanOrigin": "Ecuador", "cocoaPercent": 70, "rating": 3 }, { "id": "bar-0103", "company": "Amedei", "companyLocation": "Italy", "beanOrigin": "Peru", "cocoaPercent": 70, "rating": 3.75 }, { "id": "bar-0109", "company": "AMMA", "companyLocation": "Brazil", "beanOrigin": "Brazil", "cocoaPercent": 85, "rating": 2.75 }, { "id": "bar-0115", "company": "Animas", "companyLocation": "U.S.A.", "beanOrigin": "Bolivia", "cocoaPercent": 75, "rating": 3.5 } ] }, "layers": [ { "geom": "smooth", "stat": "smooth", "position": "identity", "aes": { "x": { "field": "cocoaPercent" }, "y": { "field": "rating" } }, "params": { "method": "loess", "span": 0.75 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "cocoaPercent" }, "y": { "field": "rating" } }, "params": { "alpha": 0.25, "size": 2 } } ], "labs": { "title": "Cocoa percent against bar rating", "subtitle": "Loess through a sample of chocolate reviews, with a confidence band", "x": "Cocoa (%)", "y": "Rating (1–4)" }, "theme": "tufte" } // note: inline data truncated (402 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomSmooth, GGPlot, Inspect, Labs, ThemeTufte, } from "@ggsvelte/svelte"; import { chocolateBarsSample } from "./data.js"; </script> <GGPlot data={chocolateBarsSample} aes={{ x: "cocoaPercent", y: "rating" }} width={640} height={400} > <GeomSmooth method="loess" span={0.75} /> <GeomPoint alpha={0.25} size={2} /> <ThemeTufte /> <Labs title="Cocoa percent against bar rating" subtitle="Loess through a sample of chocolate reviews, with a confidence band" x="Cocoa (%)" y="Rating (1–4)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Eight rays from one origin (spoke/rays) Eight long thick spokes at equal angles — the mark shape that reads at index thumbnail size. Tags: spoke, vector, angle, radius Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 0, "y": 0, "angle": 0, "radius": 4 }, { "x": 0, "y": 0, "angle": 0.7853981633974483, "radius": 4 }, { "x": 0, "y": 0, "angle": 1.5707963267948966, "radius": 4 }, { "x": 0, "y": 0, "angle": 2.356194490192345, "radius": 4 }, { "x": 0, "y": 0, "angle": 3.141592653589793, "radius": 4 }, { "x": 0, "y": 0, "angle": 3.9269908169872414, "radius": 4 }, { "x": 0, "y": 0, "angle": 4.71238898038469, "radius": 4 }, { "x": 0, "y": 0, "angle": 5.497787143782138, "radius": 4 } ] }, "layers": [ { "geom": "spoke", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "#1a202c" }, "angle": { "field": "angle" }, "radius": { "field": "radius" } }, "params": { "linewidth": 3.2, "lineend": "round", "alpha": 1 } } ], "coord": { "type": "fixed" }, "scales": { "x": { "type": "linear", "domain": [ -5, 5 ] }, "y": { "type": "linear", "domain": [ -5, 5 ] } }, "labs": { "title": "Eight rays from one origin", "subtitle": "Radius is length in data units", "x": "x", "y": "y" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomSpoke, GGPlot, Inspect, Labs, ScaleXContinuous, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { rays } from "./data.js"; </script> <GGPlot data={rays} aes={{ x: "x", y: "y", angle: "angle", radius: "radius" }} width={640} height={400} > <GeomSpoke linewidth={3.2} lineend="round" alpha={1} aes={{ color: { value: "#1a202c" } }} /> <CoordFixed /> <ScaleXContinuous limits={[-5, 5]} /> <ScaleYContinuous limits={[-5, 5]} /> <ThemeMinimal /> <Labs title="Eight rays from one origin" subtitle="Radius is length in data units" x="x" y="y" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Direction and length from each point (spoke/vector-field) Spokes for vector fields with origin, angle, and radius. Tags: spoke, vector, angle, radius, segment, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "east": 30, "north": 30, "angle": -3.141592653589793, "fall": 10.4 }, { "east": 30, "north": 90, "angle": -3.141592653589793, "fall": 5.2 }, { "east": 30, "north": 150, "angle": -2.6773350725593015, "fall": 11.7 }, { "east": 30, "north": 210, "angle": -2.761110876655029, "fall": 28.1 }, { "east": 30, "north": 270, "angle": -3.141592653589793, "fall": 46.9 }, { "east": 30, "north": 330, "angle": -2.897246558310587, "fall": 64.5 }, { "east": 30, "north": 390, "angle": 2.8204520712228365, "fall": 65.9 }, { "east": 30, "north": 450, "angle": 2.4975661596038856, "fall": 26.1 }, { "east": 30, "north": 510, "angle": -3.141592653589793, "fall": 5.2 }, { "east": 30, "north": 570, "angle": 2.356194490192345, "fall": 7.4 }, { "east": 90, "north": 30, "angle": -2.6773350725593015, "fall": 11.7 }, { "east": 90, "north": 90, "angle": -2.8204520712228365, "fall": 16.5 }, { "east": 90, "north": 150, "angle": -2.722713633111154, "fall": 51.3 }, { "east": 90, "north": 210, "angle": -2.7977627909469103, "fall": 77.5 }, { "east": 90, "north": 270, "angle": -2.9670597283903604, "fall": 90 }, { "east": 90, "north": 330, "angle": -3.141592653589793, "fall": 62.6 }, { "east": 90, "north": 390, "angle": 2.6773350725593015, "fall": 35 }, { "east": 90, "north": 450, "angle": 2.158972284716986, "fall": 56.4 }, { "east": 90, "north": 510, "angle": 2.5534166956677042, "fall": 18.8 }, { "east": 90, "north": 570, "angle": 1.5707963267948966, "fall": 10.4 } ] }, "layers": [ { "geom": "spoke", "stat": "identity", "position": "identity", "aes": { "x": { "field": "east" }, "y": { "field": "north" }, "angle": { "field": "angle" }, "radius": { "field": "fall" } }, "params": { "linewidth": 1.6, "lineend": "round", "alpha": 0.9 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "east" }, "y": { "field": "north" }, "angle": { "field": "angle" }, "radius": { "field": "fall" } }, "params": { "size": 2, "alpha": 0.7 } } ], "coord": { "type": "fixed" }, "labs": { "title": "Direction and length from each point", "subtitle": "Flow off a hillside as angle and radius from each cell", "x": "Metres east", "y": "Metres north" }, "theme": "classic" } // note: inline data truncated (120 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomPoint, GeomSpoke, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { maungaWhauSlope } from "./data.js"; </script> <GGPlot data={maungaWhauSlope} aes={{ x: "east", y: "north", angle: "angle", radius: "fall" }} width={640} height={400} > <GeomSpoke linewidth={1.6} lineend="round" alpha={0.9} /> <GeomPoint size={2} alpha={0.7} /> <CoordFixed /> <ThemeClassic /> <Labs title="Direction and length from each point" subtitle="Flow off a hillside as angle and radius from each cell" x="Metres east" y="Metres north" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Step ECDF of paired differences (step/ecdf) Step ECDFs for cumulative distributions of differences or measurements. Tags: step, ecdf, line, geom Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "difference": -8.375, "share": 0.0667 }, { "difference": -6, "share": 0.1333 }, { "difference": 0.75, "share": 0.2 }, { "difference": 1, "share": 0.2667 }, { "difference": 1.75, "share": 0.3333 }, { "difference": 2, "share": 0.4 }, { "difference": 2.875, "share": 0.4667 }, { "difference": 3, "share": 0.5333 }, { "difference": 3.5, "share": 0.6 }, { "difference": 3.625, "share": 0.6667 }, { "difference": 5.125, "share": 0.7333 }, { "difference": 6.125, "share": 0.8 }, { "difference": 7, "share": 0.8667 }, { "difference": 7.5, "share": 0.9333 }, { "difference": 9.375, "share": 1 } ] }, "layers": [ { "geom": "step", "stat": "identity", "position": "identity", "aes": { "x": { "field": "difference" }, "y": { "field": "share" } }, "params": { "direction": "hv", "linewidth": 1.8 } }, { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "difference" }, "y": { "field": "share" } }, "params": { "size": 2.6, "alpha": 0.85 } } ], "labs": { "title": "Step ECDF of paired differences", "subtitle": "Cross-fertilised height minus self-fertilised", "x": "Cross-fertilised height less self-fertilised (inches)", "y": "Share of pairs at or below" }, "theme": "classic" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomStep, GGPlot, Inspect, Labs, ThemeClassic, } from "@ggsvelte/svelte"; import { maizeDifferences } from "./data.js"; </script> <GGPlot data={maizeDifferences} aes={{ x: "difference", y: "share" }} width={640} height={400} > <GeomStep direction="hv" linewidth={1.8} /> <GeomPoint size={2.6} alpha={0.85} /> <ThemeClassic /> <Labs title="Step ECDF of paired differences" subtitle="Cross-fertilised height minus self-fertilised" x="Cross-fertilised height less self-fertilised (inches)" y="Share of pairs at or below" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Thick staircase (step/stairs) Five vertices joined with hv steps — the stair mark that reads at index thumbnail size. Tags: step, line, stairs Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "x": 1, "y": 1 }, { "x": 2, "y": 2 }, { "x": 3, "y": 2 }, { "x": 4, "y": 4 }, { "x": 5, "y": 5 } ] }, "layers": [ { "geom": "step", "stat": "identity", "position": "identity", "aes": { "x": { "field": "x" }, "y": { "field": "y" }, "color": { "value": "#1a202c" } }, "params": { "direction": "hv", "linewidth": 3.4 } } ], "scales": { "x": { "type": "linear", "domain": [ 0.5, 5.5 ] }, "y": { "type": "linear", "domain": [ 0, 6 ] } }, "labs": { "title": "Thick staircase", "subtitle": "Five vertices joined horizontal-then-vertical", "x": "x", "y": "y" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomStep, GGPlot, Inspect, Labs, ScaleXContinuous, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { stairVertices } from "./data.js"; </script> <GGPlot data={stairVertices} aes={{ x: "x", y: "y" }} width={640} height={400}> <GeomStep direction="hv" linewidth={3.4} aes={{ color: { value: "#1a202c" } }} /> <ScaleXContinuous limits={[0.5, 5.5]} /> <ScaleYContinuous limits={[0, 6]} /> <ThemeMinimal /> <Labs title="Thick staircase" subtitle="Five vertices joined horizontal-then-vertical" x="x" y="y" /> <Inspect mode="x" pin /> </GGPlot> ``` ## Bare text labels (text/labels) A few large bare text marks — short names so the geom reads at index thumbnail size. Tags: text, point, labels Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "rank": 1, "name": "Brahe", "longitude": 20 }, { "rank": 2, "name": "Clavius", "longitude": 24 }, { "rank": 3, "name": "Maginus", "longitude": 28 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "longitude" }, "y": { "field": "rank" }, "color": { "value": "#4a5568" } }, "params": { "size": 4, "alpha": 0.5 } }, { "geom": "text", "stat": "identity", "position": "identity", "aes": { "x": { "field": "longitude" }, "y": { "field": "rank" }, "color": { "value": "#1a202c" }, "label": { "field": "name" } }, "params": { "anchor": "middle", "dy": -18, "size": 20 } } ], "scales": { "x": { "type": "linear", "domain": [ 18, 30 ] }, "y": { "type": "linear", "domain": [ 0.2, 3.8 ] } }, "labs": { "title": "Bare text labels", "subtitle": "Three short names — ink only, no box", "x": "Estimated longitude (°)", "y": "Order" }, "theme": "minimal" } ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomText, GGPlot, Inspect, Labs, ScaleXContinuous, ScaleYContinuous, ThemeMinimal, } from "@ggsvelte/svelte"; import { langrenLabels } from "./data.js"; </script> <GGPlot data={langrenLabels} aes={{ x: "longitude", y: "rank" }} width={640} height={400} > <GeomPoint size={4} alpha={0.5} aes={{ color: { value: "#4a5568" } }} /> <GeomText aes={{ label: "name", color: { value: "#1a202c" } }} anchor="middle" dy={-18} size={20} /> <ScaleXContinuous limits={[18, 30]} /> <ScaleYContinuous limits={[0.2, 3.8]} /> <ThemeMinimal /> <Labs title="Bare text labels" subtitle="Three short names — ink only, no box" x="Estimated longitude (°)" y="Order" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ``` ## Cholera in England and Wales, 1849 (tile/heatmap) Every day of the epidemic from Farr's weekly returns, laid out as a calendar of square tiles so the summer peak reads as a block. Tags: tile, heatmap, categorical, calendar Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "week": 0, "weekday": "Mon", "deaths": 13 }, { "week": 0, "weekday": "Tue", "deaths": 19 }, { "week": 0, "weekday": "Wed", "deaths": 28 }, { "week": 0, "weekday": "Thu", "deaths": 24 }, { "week": 0, "weekday": "Fri", "deaths": 23 }, { "week": 0, "weekday": "Sat", "deaths": 39 }, { "week": 1, "weekday": "Sun", "deaths": 25 }, { "week": 1, "weekday": "Mon", "deaths": 35 }, { "week": 1, "weekday": "Tue", "deaths": 26 }, { "week": 1, "weekday": "Wed", "deaths": 23 }, { "week": 1, "weekday": "Thu", "deaths": 22 }, { "week": 1, "weekday": "Fri", "deaths": 26 }, { "week": 1, "weekday": "Sat", "deaths": 21 }, { "week": 2, "weekday": "Sun", "deaths": 17 }, { "week": 2, "weekday": "Mon", "deaths": 17 }, { "week": 2, "weekday": "Tue", "deaths": 23 }, { "week": 2, "weekday": "Wed", "deaths": 14 }, { "week": 2, "weekday": "Thu", "deaths": 9 }, { "week": 2, "weekday": "Fri", "deaths": 16 }, { "week": 2, "weekday": "Sat", "deaths": 12 } ] }, "layers": [ { "geom": "tile", "stat": "identity", "position": "identity", "aes": { "x": { "field": "week" }, "y": { "field": "weekday" }, "fill": { "field": "deaths" } } } ], "coord": { "type": "fixed" }, "scales": { "x": { "type": "band" }, "y": { "type": "band", "domain": [ "Sat", "Fri", "Thu", "Wed", "Tue", "Mon", "Sun" ], "breaks": [ "Mon", "Wed", "Fri" ] }, "fill": { "type": "sequential", "scheme": "viridis" } }, "guides": { "fill": { "type": "colorbar", "position": "bottom", "direction": "horizontal" } }, "labs": { "title": "Cholera in England and Wales, 1849", "subtitle": "53,293 deaths. Peak 1,121 on 6 September", "x": "Week of 1849", "y": "", "fill": "Deaths" }, "theme": { "name": "light", "grid": "none", "gridX": false, "gridY": false } } // note: inline data truncated (345 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { CoordFixed, GeomTile, GGPlot, GuideColorbar, Inspect, Labs, ScaleFillContinuous, ScaleXDiscrete, ScaleYDiscrete, ThemeLight, } from "@ggsvelte/svelte"; import { cholera1849 } from "./data.js"; </script> <!-- 1200×380 (not the default 640×400): a 53-week × 7-day calendar needs a wide, short frame so CoordFixed can keep each band cell square and large enough to read. Theme turns grid off so incomplete weeks are real holes. --> <GGPlot data={cholera1849} aes={{ x: "week", y: "weekday", fill: "deaths" }} width={1200} height={380} > <GeomTile /> <CoordFixed /> <ScaleFillContinuous scheme="viridis" /> <!-- Weeks are numeric but read as a band — one column per week. --> <ScaleXDiscrete /> <!-- Weekday domain reversed so Sunday sits at the top of a calendar. --> <ScaleYDiscrete domain={["Sat", "Fri", "Thu", "Wed", "Tue", "Mon", "Sun"]} breaks={["Mon", "Wed", "Fri"]} /> <ThemeLight grid="none" gridX={false} gridY={false} /> <GuideColorbar channel="fill" position="bottom" direction="horizontal" /> <Labs title="Cholera in England and Wales, 1849" subtitle="53,293 deaths. Peak 1,121 on 6 September" x="Week of 1849" y="" fill="Deaths" /> <Inspect mode="exact" pin /> </GGPlot> ``` ## One vertical cutoff (vline/cutoff) Vertical rules for cutoffs along a sequence or continuous x across a panel. Tags: vline, rule, annotation, line, theme Spec (canonical PortableSpec JSON): ```json { "edition": 2, "data": { "values": [ { "trial": 1, "density": 5.5 }, { "trial": 2, "density": 5.61 }, { "trial": 3, "density": 4.88 }, { "trial": 4, "density": 5.07 }, { "trial": 5, "density": 5.26 }, { "trial": 6, "density": 5.55 }, { "trial": 7, "density": 5.36 }, { "trial": 8, "density": 5.29 }, { "trial": 9, "density": 5.58 }, { "trial": 10, "density": 5.65 }, { "trial": 11, "density": 5.57 }, { "trial": 12, "density": 5.53 }, { "trial": 13, "density": 5.62 }, { "trial": 14, "density": 5.29 }, { "trial": 15, "density": 5.44 }, { "trial": 16, "density": 5.34 }, { "trial": 17, "density": 5.79 }, { "trial": 18, "density": 5.1 }, { "trial": 19, "density": 5.27 }, { "trial": 20, "density": 5.39 } ] }, "layers": [ { "geom": "point", "stat": "identity", "position": "identity", "aes": { "x": { "field": "trial" }, "y": { "field": "density" } }, "params": { "size": 2.2, "alpha": 0.55 } }, { "geom": "rule", "stat": "identity", "position": "identity", "aes": { "color": { "value": "#d4615c" } }, "params": { "xintercept": 6.5, "linewidth": 2.6 } } ], "labs": { "title": "One vertical cutoff", "subtitle": "Wire changed after this trial", "x": "Determination", "y": "Density of the earth (water = 1)" }, "theme": "minimal" } // note: inline data truncated (9 more rows) ``` Svelte usage: ```svelte <script lang="ts"> import { GeomPoint, GeomVline, GGPlot, Inspect, Labs, ThemeMinimal, } from "@ggsvelte/svelte"; import { earthDensity } from "./data.js"; </script> <GGPlot data={earthDensity} aes={{ x: "trial", y: "density" }} width={640} height={400} > <GeomPoint size={2.2} alpha={0.55} /> <GeomVline xintercept={6.5} linewidth={2.6} aes={{ color: { value: "#d4615c" } }} /> <ThemeMinimal /> <Labs title="One vertical cutoff" subtitle="Wire changed after this trial" x="Determination" y="Density of the earth (water = 1)" /> <Inspect mode="xy" pin maxDistance={24} /> </GGPlot> ```