@sipemu/ggplot-rs 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/dist/ggplot_rs.d.ts +36 -8
- package/dist/ggplot_rs_bg.wasm +0 -0
- package/package.json +2 -1
- package/skills/ggplot-rs-js/SKILL.md +131 -0
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ const spec: PlotSpec = {
|
|
|
85
85
|
color: { palette: 'Dark2' }, // or { values: { OJ: '#e69f00', VC: '#56b4e9' } }
|
|
86
86
|
},
|
|
87
87
|
facet: { wrap: 'supp' }, // or { rows: 'a', cols: 'b', scales: 'free_y' }
|
|
88
|
-
coord: 'cartesian', // 'flip' | 'polar' | 'fixed' | 'radar' | { type, xlim, ylim, ratio }
|
|
88
|
+
coord: 'cartesian', // 'flip' | 'polar' | 'fixed' | 'radar' | 'sf' | { type, xlim, ylim, ratio }
|
|
89
89
|
labels: { title: 'Tooth growth', subtitle: 'by supplement', y: 'Length' },
|
|
90
90
|
theme: { base: 'bw', title: { size: 16, face: 'bold' } }, // or just 'classic'
|
|
91
91
|
legend: 'bottom', // 'none' | 'inside' | [x, y] …
|
|
@@ -96,10 +96,10 @@ const spec: PlotSpec = {
|
|
|
96
96
|
|
|
97
97
|
| Field | What it does |
|
|
98
98
|
|---|---|
|
|
99
|
-
| `layers[].geom` | One of `geoms()
|
|
99
|
+
| `layers[].geom` | One of `geoms()` — every `geom_*` of the Rust crate (a test enforces this): the basic geoms (point, line, path, step, area, ribbon, col, bar, segment, curve, rect, tile, raster, polygon, text, label, text_repel, …), reference lines (hline, vline, abline), distributions (histogram, freqpoly, density, density_ridges, boxplot, violin, dotplot, smooth, count, bin2d, hex, density2d, contour, contour_filled, qq, qq_line, qq_band), intervals (errorbar, errorbarh, linerange, pointrange, crossbar, xrange), business charts (waterfall, pareto, funnel, pyramid, lollipop, dumbbell, bullet, marimekko, waffle, parliament, tilemap, wordcloud, venn, timeline, calendar, …), hierarchies and networks (treemap, sunburst, icicle, dendrogram, sankey, chord, arcdiagram, network, stream), finance (candlestick, ohlc, hlc, heikin_ashi, renko, point_figure) and spatial (sf, sf_text, sf_label, flow). |
|
|
100
100
|
| `layers[].aes` | A layer-level mapping, merged over the plot `aes`. |
|
|
101
101
|
| `layers[].data` | Layer-specific data. |
|
|
102
|
-
| `layers[].params` | `bins` (histogram), `method`/`span`/`se` (smooth), `yintercept`/`xintercept`/`slope`/`intercept` (reference lines), `levels` (sunburst/icicle). |
|
|
102
|
+
| `layers[].params` | `bins` (histogram), `method`/`span`/`se` (smooth), `yintercept`/`xintercept`/`slope`/`intercept` (reference lines), `levels` (sunburst/icicle), `rows` (waffle/parliament), `box_size`/`reversal` (renko/point_figure), `shape` (tilemap), `speed`/`direction` (windbarb), `censor` (censor_marks), `projection` (sf, sf_text, sf_label, flow), `target`/`ranges` (bullet). |
|
|
103
103
|
| `layers[].position` | `identity`, `stack`, `fill`, `dodge`, `dodge2`, `jitter` or `nudge`. Use an object such as `{ type: 'dodge', width: 0.8 }` to set parameters. |
|
|
104
104
|
| `layers[].stat` | `'identity'`, which plots values as given (e.g. `bar` with precomputed heights). |
|
|
105
105
|
| `theme` | A preset name (`gray`, `bw`, `minimal`, `classic`, `dark`, `light`, `void`, `linedraw`) or a partial overlay. The overlay uses the same schema as the ggplot-rs CLI's `--theme-config` (`base`, `primary`, `palette`, per-element `title`/`axis_text_x`/`panel_grid_major` …). The default is `minimal`. |
|
|
@@ -129,6 +129,22 @@ the package was built from. The lower-level functions used by the live demo
|
|
|
129
129
|
(`render_plot`, `render_geo`, `geo_bounds`, `render_bar`, `render_hist` and
|
|
130
130
|
`scatter_frame`) are exported too.
|
|
131
131
|
|
|
132
|
+
### Same spec as the CLI and Rust
|
|
133
|
+
|
|
134
|
+
The spec is the crate's `ggplot_rs::spec` module, so the `ggplot-rs` CLI
|
|
135
|
+
accepts the same JSON (`ggplot-rs --spec chart.json --parquet data.parquet`)
|
|
136
|
+
and Rust code can call `ggplot_rs::spec::render_json` (feature `spec`).
|
|
137
|
+
|
|
138
|
+
### Agent skill
|
|
139
|
+
|
|
140
|
+
The package ships an agent skill at `skills/ggplot-rs-js/SKILL.md`, a
|
|
141
|
+
how-to for LLM coding agents (Claude Code and others). To use it with Claude
|
|
142
|
+
Code, copy or link it into your project:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
mkdir -p .claude/skills && cp -r node_modules/@sipemu/ggplot-rs/skills/ggplot-rs-js .claude/skills/
|
|
146
|
+
```
|
|
147
|
+
|
|
132
148
|
## Fidelity to ggplot2
|
|
133
149
|
|
|
134
150
|
The statistical layers (binning, density/`bw.nrd0`, loess, boxplot
|
package/dist/ggplot_rs.d.ts
CHANGED
|
@@ -14,14 +14,22 @@ export type Aesthetic =
|
|
|
14
14
|
/** Aesthetic → column name. */
|
|
15
15
|
export type AesMap = Partial<Record<Aesthetic, string>>;
|
|
16
16
|
|
|
17
|
+
/** Every geom `render()` accepts — `geoms()` returns the same list at runtime. */
|
|
17
18
|
export type GeomName =
|
|
18
|
-
| "point" | "jitter" | "line" | "path" | "step" | "area" | "ribbon" | "
|
|
19
|
-
| "
|
|
20
|
-
| "text" | "label" | "
|
|
21
|
-
| "
|
|
22
|
-
| "
|
|
23
|
-
| "
|
|
24
|
-
| "
|
|
19
|
+
| "point" | "jitter" | "line" | "path" | "step" | "area" | "ribbon" | "stepribbon"
|
|
20
|
+
| "col" | "bar" | "segment" | "curve" | "spoke" | "rect" | "tile" | "raster" | "polygon"
|
|
21
|
+
| "text" | "label" | "text_repel" | "label_repel" | "rug" | "blank" | "hline" | "vline"
|
|
22
|
+
| "abline" | "histogram" | "freqpoly" | "density" | "density_ridges" | "boxplot"
|
|
23
|
+
| "violin" | "dotplot" | "smooth" | "count" | "bin2d" | "hex" | "density2d" | "contour"
|
|
24
|
+
| "contour_filled" | "qq" | "qq_line" | "qq_band" | "censor_marks" | "errorbar"
|
|
25
|
+
| "errorbarh" | "linerange" | "pointrange" | "crossbar" | "xrange" | "waterfall"
|
|
26
|
+
| "pareto" | "funnel" | "pyramid" | "lollipop" | "dumbbell" | "bullet"
|
|
27
|
+
| "bullet_vertical" | "marimekko" | "variwide" | "waffle" | "parliament" | "tilemap"
|
|
28
|
+
| "packed_bubble" | "variable_pie" | "wordcloud" | "venn" | "timeline" | "calendar"
|
|
29
|
+
| "flags" | "windbarb" | "treemap" | "sunburst" | "icicle" | "dendrogram" | "tree"
|
|
30
|
+
| "sankey" | "chord" | "arcdiagram" | "network" | "stream" | "candlestick" | "ohlc"
|
|
31
|
+
| "hlc" | "heikin_ashi" | "renko" | "point_figure" | "sf" | "sf_text" | "sf_label"
|
|
32
|
+
| "flow";
|
|
25
33
|
|
|
26
34
|
export interface LayerParams {
|
|
27
35
|
/** histogram: bin count. */
|
|
@@ -41,6 +49,26 @@ export interface LayerParams {
|
|
|
41
49
|
intercept?: number;
|
|
42
50
|
/** sunburst / icicle (required): hierarchy columns, outermost first. */
|
|
43
51
|
levels?: string[];
|
|
52
|
+
/** waffle (default 10) / parliament (default 6): rows. */
|
|
53
|
+
rows?: number;
|
|
54
|
+
/** renko / point_figure (required): price box size. */
|
|
55
|
+
box_size?: number;
|
|
56
|
+
/** point_figure: reversal in boxes (default 3). */
|
|
57
|
+
reversal?: number;
|
|
58
|
+
/** tilemap tile shape (default "square"). */
|
|
59
|
+
shape?: "square" | "hexagon" | "circle" | "diamond";
|
|
60
|
+
/** windbarb (required): speed column (knots). */
|
|
61
|
+
speed?: string;
|
|
62
|
+
/** windbarb (required): direction column (degrees the wind comes from). */
|
|
63
|
+
direction?: string;
|
|
64
|
+
/** censor_marks (required): 0/1 censoring-indicator column. */
|
|
65
|
+
censor?: string;
|
|
66
|
+
/** sf / sf_text / sf_label / flow: map projection (default "platecarree"). */
|
|
67
|
+
projection?: "platecarree" | "mercator" | "equal-earth" | "albers-usa" | "albers-europe" | `utm:${string}`;
|
|
68
|
+
/** bullet / bullet_vertical: target column. */
|
|
69
|
+
target?: string;
|
|
70
|
+
/** bullet / bullet_vertical: qualitative range band upper bounds. */
|
|
71
|
+
ranges?: number[];
|
|
44
72
|
}
|
|
45
73
|
|
|
46
74
|
export type PositionName = "identity" | "stack" | "fill" | "dodge" | "dodge2" | "jitter" | "nudge";
|
|
@@ -85,7 +113,7 @@ export type Facet =
|
|
|
85
113
|
| { wrap: string; ncol?: number; scales?: "fixed" | "free" | "free_x" | "free_y" }
|
|
86
114
|
| { rows?: string; cols?: string; scales?: "fixed" | "free" | "free_x" | "free_y" };
|
|
87
115
|
|
|
88
|
-
export type CoordName = "cartesian" | "flip" | "polar" | "fixed" | "equal" | "radar";
|
|
116
|
+
export type CoordName = "cartesian" | "flip" | "polar" | "fixed" | "equal" | "radar" | "sf";
|
|
89
117
|
export type Coord =
|
|
90
118
|
| CoordName
|
|
91
119
|
| { type: CoordName; ratio?: number; xlim?: [number, number]; ylim?: [number, number] };
|
package/dist/ggplot_rs_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sipemu/ggplot-rs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Grammar-of-graphics charts (ggplot2-style) rendered to SVG — WebAssembly build of the ggplot-rs Rust crate, no runtime dependencies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/ggplot_rs.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"dist/ggplot_rs.d.ts",
|
|
20
20
|
"dist/ggplot_rs_bg.wasm",
|
|
21
21
|
"dist/ggplot_rs_bg.wasm.d.ts",
|
|
22
|
+
"skills/",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE-MIT",
|
|
24
25
|
"LICENSE-APACHE"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ggplot-rs-js
|
|
3
|
+
description: Render ggplot2-style (Grammar of Graphics) charts to SVG in JavaScript/TypeScript — browser or Node — with the `@sipemu/ggplot-rs` npm package (WebAssembly build of the ggplot-rs crate). Use when writing JS/TS that charts data with `@sipemu/ggplot-rs`, when package.json depends on it, or when asked for ggplot-style charts in a web page, Observable, or a Node script without a DOM.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Charts in JS/TS with `@sipemu/ggplot-rs`
|
|
7
|
+
|
|
8
|
+
A typed, layered **plot spec in → SVG string out**. No runtime dependencies,
|
|
9
|
+
no DOM needed (works in Node), the same engine as the `ggplot-rs` Rust crate
|
|
10
|
+
and CLI (the npm version always equals the crate version). Statistical layers
|
|
11
|
+
match R ggplot2 4.x.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i @sipemu/ggplot-rs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Initialise once, then render
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
// Browser / Vite / any bundler
|
|
21
|
+
import init, { render } from "@sipemu/ggplot-rs";
|
|
22
|
+
await init(); // fetches ggplot_rs_bg.wasm next to the module
|
|
23
|
+
document.querySelector("#chart").innerHTML = render(spec);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// Node ≥ 18 (no fetch from file: hand the bytes over)
|
|
28
|
+
import { readFile } from "node:fs/promises";
|
|
29
|
+
import { createRequire } from "node:module";
|
|
30
|
+
import { initSync, render } from "@sipemu/ggplot-rs";
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
initSync({ module: await readFile(require.resolve("@sipemu/ggplot-rs/ggplot_rs_bg.wasm")) });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Exports: `render(spec)` → SVG string (throws `Error` naming the bad field),
|
|
36
|
+
`renderWithWarnings(spec)` → `{ svg, warnings }`, `geoms()` → accepted geom
|
|
37
|
+
names, `version()`. Types: `PlotSpec`, `Layer`, `GeomName`, `LayerParams`, …
|
|
38
|
+
|
|
39
|
+
## The spec
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const svg = render({
|
|
43
|
+
data: { dose: [0.5, 1, 2, 0.5, 1, 2], // column-oriented, equal lengths
|
|
44
|
+
len: [13.2, 22.7, 26.1, 8.0, 16.8, 26.1],
|
|
45
|
+
supp: ["OJ", "OJ", "OJ", "VC", "VC", "VC"] },
|
|
46
|
+
aes: { x: "dose", y: "len", color: "supp" }, // inherited by every layer
|
|
47
|
+
layers: [
|
|
48
|
+
{ geom: "point" },
|
|
49
|
+
{ geom: "line" },
|
|
50
|
+
{ geom: "hline", params: { yintercept: 20 } },
|
|
51
|
+
],
|
|
52
|
+
scales: { x: { trans: "log2", name: "Dose (mg)" }, color: { palette: "Dark2" } },
|
|
53
|
+
facet: { wrap: "supp" },
|
|
54
|
+
labels: { title: "Tooth growth", y: "Length" },
|
|
55
|
+
theme: { base: "bw", title: { size: 16, face: "bold" } },
|
|
56
|
+
legend: "bottom",
|
|
57
|
+
width: 640, height: 400,
|
|
58
|
+
});
|
|
59
|
+
if (!svg.startsWith("<svg")) throw new Error("render failed");
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Field | Values |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `data` | `{ col: (number \| string \| boolean \| null)[] }` — numbers continuous, strings/booleans categorical |
|
|
65
|
+
| `aes` / `layers[].aes` | aesthetic → column: `x y color fill size shape alpha group linetype linewidth label weight xmin xmax ymin ymax xend yend angle radius tooltip from to subgroup open high low close lower middle upper fontface class` |
|
|
66
|
+
| `layers[].geom` | see Geoms |
|
|
67
|
+
| `layers[].params` | `bins` · `method` (`"lm"`/`"loess"`) `span` `se` · `yintercept` `xintercept` `slope` `intercept` · `levels` · `rows` · `box_size` `reversal` · `shape` · `speed` `direction` · `censor` · `projection` · `target` `ranges` |
|
|
68
|
+
| `layers[].position` | `identity stack fill dodge dodge2 jitter nudge`, or `{ type: "dodge", width: 0.8 }` |
|
|
69
|
+
| `layers[].stat` | `"identity"` (plot precomputed values, e.g. bar heights) |
|
|
70
|
+
| `layers[].data` | layer-specific data |
|
|
71
|
+
| `scales.x/y` | `{ trans, limits: [lo, hi], breaks: [...], name }`; trans `log10 log2 ln sqrt reverse logit probit pseudo_log reciprocal exp` |
|
|
72
|
+
| `scales.color/fill` | `{ palette }` (`Set1 Dark2 Paired npg …` categorical; `viridis magma plasma inferno` numeric), `{ values: { level: "#rrggbb" } }`, `{ gradient: ["#lo", "#hi"] }`, `{ levels: [...] }` |
|
|
73
|
+
| `facet` | `{ wrap, ncol?, scales? }` or `{ rows?, cols?, scales? }`; scales `fixed free free_x free_y` |
|
|
74
|
+
| `coord` | `cartesian flip polar fixed radar sf`, or `{ type, xlim, ylim, ratio }` |
|
|
75
|
+
| `labels` | `title subtitle caption tag x y` (axis titles are not auto-filled from column names) |
|
|
76
|
+
| `theme` | preset `gray bw minimal classic dark light void linedraw` (default `minimal`), or an overlay `{ base, primary: [r,g,b], palette, title: {...}, panel_grid_major: {...}, … }` |
|
|
77
|
+
| `legend` | `right left top bottom none inside` or `[x, y]` inside the panel |
|
|
78
|
+
|
|
79
|
+
## Geoms
|
|
80
|
+
|
|
81
|
+
- basic: `point jitter line path step area ribbon stepribbon col bar segment curve
|
|
82
|
+
spoke rect tile raster polygon text label text_repel label_repel rug blank`
|
|
83
|
+
- reference lines: `hline vline abline` (params `yintercept` / `xintercept` / `slope`)
|
|
84
|
+
- distributions: `histogram freqpoly density density_ridges boxplot violin dotplot
|
|
85
|
+
smooth count bin2d hex density2d contour contour_filled qq qq_line qq_band
|
|
86
|
+
censor_marks`
|
|
87
|
+
- intervals: `errorbar errorbarh linerange pointrange crossbar xrange`
|
|
88
|
+
- business: `waterfall pareto funnel pyramid lollipop dumbbell bullet
|
|
89
|
+
bullet_vertical marimekko variwide waffle parliament tilemap packed_bubble
|
|
90
|
+
variable_pie wordcloud venn timeline calendar flags windbarb`
|
|
91
|
+
- hierarchies & networks: `treemap sunburst icicle dendrogram tree sankey chord
|
|
92
|
+
arcdiagram network stream`
|
|
93
|
+
- finance: `candlestick ohlc hlc heikin_ashi renko point_figure`
|
|
94
|
+
- spatial: `sf sf_text sf_label flow` (a WKT `geometry` column + `params.projection`)
|
|
95
|
+
|
|
96
|
+
Special aesthetics: sankey/chord `from`/`to`/`weight`; treemap `weight`
|
|
97
|
+
(+ `subgroup`, `label`); sunburst/icicle `params.levels`; candlestick/ohlc
|
|
98
|
+
`open`/`high`/`low`/`close`; intervals `ymin`/`ymax`.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const svg = render({
|
|
102
|
+
data: { src: ["a", "a", "b"], dst: ["b", "c", "c"], n: [3, 2, 1] },
|
|
103
|
+
aes: { from: "src", to: "dst", weight: "n" },
|
|
104
|
+
layers: [{ geom: "sankey" }],
|
|
105
|
+
});
|
|
106
|
+
if (!svg.includes("<svg")) throw new Error("sankey failed");
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
const { svg, warnings } = renderWithWarnings({
|
|
111
|
+
data: { x: [1, 2, 3, 4], y: [1, null, 3, 4], g: ["a", "a", "b", "b"] },
|
|
112
|
+
aes: { x: "x", y: "y", fill: "g" },
|
|
113
|
+
layers: [{ geom: "col", position: { type: "dodge", width: 0.8 } }],
|
|
114
|
+
scales: { fill: { values: { a: "#e69f00", b: "#56b4e9" } } },
|
|
115
|
+
coord: "flip",
|
|
116
|
+
});
|
|
117
|
+
if (!Array.isArray(warnings) || !svg) throw new Error("bad result");
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Workflow & pitfalls
|
|
121
|
+
|
|
122
|
+
1. Shape data column-wise (`{ col: [...] }`); convert row arrays with
|
|
123
|
+
`Object.fromEntries(keys.map(k => [k, rows.map(r => r[k])]))`.
|
|
124
|
+
2. Use numbers for continuous and strings for categorical columns — a numeric
|
|
125
|
+
`color` column gets a gradient, and a discrete palette name then errors.
|
|
126
|
+
3. Develop with `renderWithWarnings` and read the warnings.
|
|
127
|
+
4. On a thrown `Error`, read the message: it names the field
|
|
128
|
+
(`layers[0].aes.x: column 'wieght' not found (available: …)`) and lists the
|
|
129
|
+
valid choices.
|
|
130
|
+
5. For huge data, aggregate first (e.g. DuckDB-Wasm) — SVG becomes slow past
|
|
131
|
+
~50k marks.
|