@sipemu/ggplot-rs 0.35.1 → 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 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()`. That covers point, line, path, step, area, ribbon, col, bar, histogram, freqpoly, density, boxplot, violin, dotplot, smooth, text, label, errorbar, linerange, pointrange, crossbar, segment, rect, tile, raster, polygon, rug, count, bin2d, hex, density2d, contour, qq, qq_line, hline, vline, abline and blank, plus the chart types waterfall, pareto, sankey, chord, treemap, sunburst, icicle, funnel, stream, lollipop, dumbbell, candlestick, ohlc and calendar. |
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
@@ -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" | "col" | "bar"
19
- | "histogram" | "freqpoly" | "density" | "boxplot" | "violin" | "dotplot" | "smooth"
20
- | "text" | "label" | "errorbar" | "linerange" | "pointrange" | "crossbar" | "segment"
21
- | "rect" | "tile" | "raster" | "polygon" | "rug" | "count" | "bin2d" | "hex"
22
- | "density2d" | "contour" | "qq" | "qq_line" | "hline" | "vline" | "abline" | "blank"
23
- | "waterfall" | "pareto" | "sankey" | "chord" | "treemap" | "sunburst" | "icicle"
24
- | "funnel" | "stream" | "lollipop" | "dumbbell" | "candlestick" | "ohlc" | "calendar";
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] };
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sipemu/ggplot-rs",
3
- "version": "0.35.1",
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.