Getting started
Install
Node.js 22 or newer. In an existing SvelteKit (or Svelte) app:
bun add @ggsvelte/svelteStart with a basic plot
Svelte
<script lang="ts">
import { GeomPoint, GGPlot, Labs, ScaleXContinuous, ScaleYMonthDay } from "@ggsvelte/svelte";
import { kyotoSakura } from "@ggsvelte/svelte/data";
</script>
<GGPlot
data={kyotoSakura}
aes={{ x: "year", y: "bloomDate" }}
>
<ScaleYMonthDay reverse />
<ScaleXContinuous labels="d" domain={[800, 2030]} />
<Labs x="Year" y="Bloom date" />
<GeomPoint />
</GGPlot>Output
Add layers
Pick a minimal theme and add a rolling median line
<ThemeTufte />
<ScaleYMonthDay
reverse
breaks={["04-05", "04-15", "04-25"]}
dateLabels="%b %e"
domain={["05-10", "03-10"]}
/>
<GeomPoint alpha={0.5} size={1.6}
aes={{ color: { value: "#777777" } }} />
<GeomLine stat="summary_bin" fun="median" binwidth={25}
curve="step-hv" linewidth={1.8}
aes={{ color: { value: "#262626" } }} />Add epochs
<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} />
<ScaleFillManual
domain={["Medieval warm period", "Little Ice Age", "Industrial era"]}
values={["#f5edc4", "#dce8f2", "#f3dcda"]}
/>
<GuideNone channel="fill" />Annotate record years
<GeomRule yintercept="04-15" linewidth={1}
aes={{ color: { value: "#6b7075" }, linetype: { value: "dashed" } }} />
<GeomSegment data={records}
aes={{ x: "labelYear", y: "labelDate", xend: "year",
yend: "bloomDate", color: { value: "#b3452f" } }} linewidth={0.7} />
<GeomText data={records}
aes={{ x: "labelYear", y: "labelDate", label: "label",
color: { value: "#b3452f" } }} size={11} anchor="end" dx={-4} />Make it interactive
key="year"
inspect={{ mode: "exact", pin: true }}The finished file
<script lang="ts">
import {
GeomLine,
GeomPoint,
GeomRect,
GeomRule,
GeomSegment,
GeomText,
GGPlot,
GuideNone,
Labs,
ScaleFillManual,
ScaleXContinuous,
ScaleYMonthDay,
ThemeTufte,
} from "@ggsvelte/svelte";
import { kyotoSakura } from "@ggsvelte/svelte/data";
// 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 = [
{ 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" },
];
const records = [
{
year: 1323, bloomDate: "05-04",
labelYear: 1250, labelDate: "05-08",
label: "1323 · May 4, latest on record",
},
{
year: 1409, bloomDate: "03-27",
labelYear: 1310, labelDate: "03-24",
label: "1409 · March 27, earliest for six centuries",
},
{
year: 2023, bloomDate: "03-25",
labelYear: 2010, labelDate: "03-24",
label: "2023 · March 25, earliest in 1,200 years",
},
];
</script>
<GGPlot
data={kyotoSakura}
aes={{ x: "year", y: "bloomDate" }}
key="year"
inspect={{ mode: "exact", pin: true }}
>
<ThemeTufte />
<ScaleYMonthDay
reverse
breaks={["04-05", "04-15", "04-25"]}
dateLabels="%b %e"
domain={["05-10", "03-10"]}
/>
<ScaleXContinuous labels="d" domain={[800, 2030]} />
<ScaleFillManual
domain={["Medieval warm period", "Little Ice Age", "Industrial era"]}
values={["#f5edc4", "#dce8f2", "#f3dcda"]}
/>
<GuideNone channel="fill" />
<Labs x="Year" y="Bloom date" />
<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}
/>
<GeomPoint
alpha={0.5}
size={1.6}
aes={{ color: { value: "#777777" } }}
/>
<GeomRule
yintercept="04-15"
linewidth={1}
aes={{ color: { value: "#6b7075" }, linetype: { value: "dashed" } }}
/>
<GeomLine
stat="summary_bin"
fun="median"
binwidth={25}
curve="step-hv"
linewidth={1.8}
aes={{ color: { value: "#262626" } }}
/>
<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}
/>
</GGPlot>Agent JSON spec
{
"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": "point",
"aes": {
"color": {
"value": "#777777"
}
},
"params": {
"alpha": 0.5,
"size": 1.6
}
},
{
"geom": "rule",
"aes": {
"color": {
"value": "#6b7075"
},
"linetype": {
"value": "dashed"
}
},
"params": {
"yintercept": "04-15",
"linewidth": 1
}
},
{
"geom": "line",
"stat": "summary_bin",
"aes": {
"color": {
"value": "#262626"
}
},
"params": {
"fun": "median",
"binwidth": 25,
"curve": "step-hv",
"linewidth": 1.8
}
},
{
"geom": "segment",
"data": {
"values": [
{
"year": 1323,
"bloomDate": "05-04",
"label": "1323 · May 4, latest on record",
"labelYear": 1250,
"labelDate": "05-08"
},
{
"year": 1409,
"bloomDate": "03-27",
"label": "1409 · March 27, earliest for six centuries",
"labelYear": 1310,
"labelDate": "03-24"
},
{
"year": 2023,
"bloomDate": "03-25",
"label": "2023 · March 25, earliest in 1,200 years",
"labelYear": 2010,
"labelDate": "03-24"
}
]
},
"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": 1250,
"labelDate": "05-08"
},
{
"year": 1409,
"bloomDate": "03-27",
"label": "1409 · March 27, earliest for six centuries",
"labelYear": 1310,
"labelDate": "03-24"
},
{
"year": 2023,
"bloomDate": "03-25",
"label": "2023 · March 25, earliest in 1,200 years",
"labelYear": 2010,
"labelDate": "03-24"
}
]
},
"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"
},
"theme": "tufte"
}