@sipemu/ggplot-rs 0.39.0 → 0.41.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.
@@ -69,12 +69,74 @@ export interface LayerParams {
69
69
  target?: string;
70
70
  /** bullet / bullet_vertical: qualitative range band upper bounds. */
71
71
  ranges?: number[];
72
+ /** boxplot: notched box (`notch = TRUE`). */
73
+ notch?: boolean;
74
+ /** boxplot: box width ∝ √n. */
75
+ varwidth?: boolean;
76
+ /** boxplot: whisker reach in IQRs (default 1.5). */
77
+ coef?: number;
78
+ /** boxplot: draw outliers (default true; false also drops them from the scale). */
79
+ outliers?: boolean;
80
+ /** boxplot: whisker cap width relative to the box (0 = none). */
81
+ staplewidth?: number;
82
+ /** violin: trim to the data range (default true). */
83
+ trim?: boolean;
84
+ /** violin: `"area"` (default), `"count"`, `"width"`. */
85
+ scale?: "area" | "count" | "width";
86
+ /** violin: draw lines at these sample quantiles. */
87
+ quantiles?: number[];
88
+ /** violin: fixed kernel bandwidth (default `bw.nrd0`). */
89
+ bw?: number;
90
+ /** violin: bandwidth multiplier. */
91
+ adjust?: number;
92
+ /** stat "connect": how consecutive points join (default "hv"). */
93
+ connection?: "hv" | "vh" | "mid" | "linear";
94
+ /** stat "summary_hex": summary of the raw `z` column (default "mean"). */
95
+ fun?: "mean" | "median" | "min" | "max" | "sum";
72
96
  }
73
97
 
74
98
  export type PositionName = "identity" | "stack" | "fill" | "dodge" | "dodge2" | "jitter" | "nudge";
75
99
  export type Position =
76
100
  | PositionName
77
- | { type: PositionName; width?: number; height?: number; padding?: number; x?: number; y?: number };
101
+ | {
102
+ type: PositionName; width?: number; height?: number; padding?: number; x?: number; y?: number;
103
+ /** dodge / dodge2: keep one group's width in slots with fewer groups (default "total"). */
104
+ preserve?: "total" | "single";
105
+ };
106
+
107
+ /** A per-aesthetic guide: `"none"`, a colourbar / bins kind, or its options (#409). */
108
+ export type GuideSpec =
109
+ | "none"
110
+ | "legend"
111
+ | "bins"
112
+ | "colourbar"
113
+ | "colorbar"
114
+ | "coloursteps"
115
+ | "colorsteps"
116
+ | {
117
+ title?: string;
118
+ reverse?: boolean;
119
+ type?: "legend" | "colourbar" | "colorbar" | "coloursteps" | "colorsteps" | "bins";
120
+ /** Bar width / height in px. */
121
+ barwidth?: number;
122
+ barheight?: number;
123
+ nbin?: number;
124
+ display?: "gradient" | "rectangles";
125
+ frame?: boolean;
126
+ ticks?: boolean;
127
+ even_steps?: boolean;
128
+ /** coloursteps / bins: label the outer limits too. */
129
+ show_limits?: boolean;
130
+ /** bins: draw the axis line and ticks (default true). */
131
+ axis?: boolean;
132
+ /** Draw this legend at its own side (the others stay at `legend`). */
133
+ position?: "right" | "left" | "top" | "bottom";
134
+ /** Fixed key appearance (ggplot2's override.aes). */
135
+ override_aes?: {
136
+ color?: string; fill?: string; shape?: string | number; linetype?: string;
137
+ size?: number; alpha?: number; linewidth?: number;
138
+ };
139
+ };
78
140
 
79
141
  export interface Layer {
80
142
  geom: GeomName;
@@ -83,8 +145,17 @@ export interface Layer {
83
145
  /** Layer-specific data, replacing the plot's. */
84
146
  data?: Data;
85
147
  params?: LayerParams;
86
- /** Override the geom's default stat; only "identity" is supported. */
87
- stat?: "identity";
148
+ /** Override the geom's default stat: "identity" (plot values as given), "unique"
149
+ * (drop duplicate rows), "connect" (steps, see params.connection), "summary_hex"
150
+ * (hexagons of params.fun over a `z` column), "sf_coordinates" (a point per WKT
151
+ * `geometry`, e.g. text on a map). */
152
+ stat?: "identity" | "unique" | "connect" | "summary_hex" | "sf_coordinates";
153
+ /** Legend key picture (ggplot2's key_glyph); default per geom. */
154
+ key_glyph?: "point" | "path" | "rect" | "polygon" | "boxplot" | "crossbar" | "pointrange"
155
+ | "linerange" | "smooth" | "text" | "label" | "vline" | "vpath" | "abline" | "timeseries"
156
+ | "dotplot" | "blank";
157
+ /** Show this layer in the legends, or per aesthetic: `{ color: true, fill: false }`. */
158
+ show_legend?: boolean | Partial<Record<"color" | "colour" | "fill" | "shape" | "size" | "alpha" | "linetype" | "linewidth", boolean>>;
88
159
  position?: Position;
89
160
  }
90
161
 
@@ -93,8 +164,19 @@ export interface PositionScale {
93
164
  | "probit" | "pseudo_log" | "reciprocal" | "exp";
94
165
  limits?: [number, number];
95
166
  breaks?: number[];
167
+ /** About this many breaks (ggplot2's n.breaks). */
168
+ n_breaks?: number;
169
+ /** Time-of-day axis: values in seconds, HH:MM:SS labels (scale_x_time). */
170
+ time?: boolean;
96
171
  /** Axis title. */
97
172
  name?: string;
173
+ /** Secondary axis (x: top, y: right): `true` repeats the primary
174
+ * (`dup_axis()`), or `secondary = primary * scale + offset`. */
175
+ sec_axis?: boolean | { name?: string; scale?: number; offset?: number };
176
+ /** Binned position scale: about this many breaks (scale_x_binned). */
177
+ binned?: number;
178
+ /** Log10 minor ticks (guide_axis_logticks), with trans "log10". */
179
+ logticks?: boolean;
98
180
  }
99
181
 
100
182
  export interface ColorScale {
@@ -107,16 +189,82 @@ export interface ColorScale {
107
189
  values?: Record<string, string>;
108
190
  /** Two-color gradient `["#low", "#high"]` for a numeric column. */
109
191
  gradient?: [string, string];
192
+ /** Diverging binned `["#low", "#mid", "#high"]` (`scale_*_steps2`), mid at `midpoint` (default 0). */
193
+ steps2?: [string, string, string];
194
+ midpoint?: number;
195
+ /** Bins for `steps2` / `palette: "viridis_b:A"…"viridis_b:H"` (default 5). */
196
+ bins?: number;
110
197
  }
111
198
 
112
- export type Facet =
113
- | { wrap: string; ncol?: number; scales?: "fixed" | "free" | "free_x" | "free_y" }
114
- | { rows?: string; cols?: string; scales?: "fixed" | "free" | "free_x" | "free_y" };
199
+ /** A size / alpha / linewidth / shape scale: exactly one of these (#408). */
200
+ export interface ValueScale {
201
+ /** The column already holds the values (shape: `pch` codes or names). */
202
+ identity?: boolean;
203
+ /** Manual `{ level: number }` (not for shape). */
204
+ values?: Record<string, number>;
205
+ /** Output range `[lo, hi]` (size: radius, ggplot2's `scale_radius`). */
206
+ range?: [number, number];
207
+ /** Binned (scale_*_binned): about this many breaks; with `range`. */
208
+ binned?: number;
209
+ }
115
210
 
116
- export type CoordName = "cartesian" | "flip" | "polar" | "fixed" | "equal" | "radar" | "sf";
211
+ /** A facet strip labeller (#412). */
212
+ export type Labeller =
213
+ | "value" | "both" | "multi_line" | "parsed"
214
+ | { template: string } | { values: Record<string, string> } | { wrap: number }
215
+ | { vars: Record<string, Labeller> };
216
+
217
+ /** Which panels get axes, and whether inner axes are labelled (#412). */
218
+ export interface FacetAxesSpec {
219
+ /** Proportional panels (a wrap needs one row / column). */
220
+ space?: "fixed" | "free_x" | "free_y" | "free";
221
+ axes?: "margins" | "all" | "all_x" | "all_y";
222
+ axis_labels?: "all" | "margins";
223
+ }
224
+
225
+ export type Facet = FacetAxesSpec & (
226
+ | {
227
+ wrap: string; ncol?: number; scales?: "fixed" | "free" | "free_x" | "free_y";
228
+ /** "h" (row by row, default) or "v" (column by column). */
229
+ dir?: "h" | "v";
230
+ /** First row at the top (default true; ggplot2's as.table). */
231
+ as_table?: boolean;
232
+ /** Wrap strip labels at this many characters (label_wrap_gen). */
233
+ label_wrap?: number;
234
+ labeller?: Labeller;
235
+ /** The side of each panel its strip sits on (default "top"). */
236
+ strip_position?: "top" | "bottom" | "left" | "right";
237
+ /** Fixed panel order; listed levels without data still get a panel. */
238
+ levels?: string[];
239
+ }
240
+ | {
241
+ rows?: string; cols?: string; scales?: "fixed" | "free" | "free_x" | "free_y";
242
+ labeller?: Labeller;
243
+ /** Column strips below ("x"), row strips left ("y"), or both. */
244
+ switch?: "x" | "y" | "both";
245
+ /** Fixed row / column order, empty ones kept (drop = FALSE). */
246
+ row_levels?: string[]; col_levels?: string[];
247
+ /** An extra "(all)" row and column (margins = TRUE). */
248
+ margins?: boolean;
249
+ });
250
+
251
+ export type CoordName =
252
+ | "cartesian" | "flip" | "polar" | "radial" | "fixed" | "equal" | "radar" | "quickmap" | "map" | "sf";
117
253
  export type Coord =
118
254
  | CoordName
119
- | { type: CoordName; ratio?: number; xlim?: [number, number]; ylim?: [number, number] };
255
+ | {
256
+ type: CoordName; ratio?: number; xlim?: [number, number]; ylim?: [number, number];
257
+ /** cartesian: mirror the panel; radial: "theta" | "r" | "thetar". */
258
+ reverse?: "x" | "y" | "xy" | "theta" | "r" | "thetar" | "none";
259
+ /** cartesian: keep the scale padding — `false`, or `[top, right, bottom, left]`. */
260
+ expand?: boolean | [boolean, boolean, boolean, boolean];
261
+ /** radial: theta start / end in radians (clockwise from 12 o'clock) and a donut hole (0–1). */
262
+ start?: number; end?: number; inner_radius?: number;
263
+ /** radial: r axis along the start spoke; theta labels follow the circle. */
264
+ r_axis_inside?: boolean; rotate_angle?: boolean;
265
+ /** "on" (default) clips layers to the panel; "off" lets them draw past it. */
266
+ clip?: "on" | "off";
267
+ };
120
268
 
121
269
  export interface Labels {
122
270
  title?: string;
@@ -127,7 +275,7 @@ export interface Labels {
127
275
  y?: string;
128
276
  }
129
277
 
130
- export type ThemePreset = "gray" | "grey" | "bw" | "minimal" | "classic" | "dark" | "light" | "void" | "linedraw";
278
+ export type ThemePreset = "gray" | "grey" | "bw" | "minimal" | "classic" | "dark" | "light" | "void" | "linedraw" | "test";
131
279
  export interface TextElement {
132
280
  family?: string; size?: number; color?: [number, number, number];
133
281
  face?: "plain" | "bold" | "italic"; hjust?: number; vjust?: number; angle?: number; visible?: boolean;
@@ -156,8 +304,31 @@ export interface ThemeConfig {
156
304
  text?: TextElement; title?: TextElement; subtitle?: TextElement; caption?: TextElement;
157
305
  axis_title_x?: TextElement; axis_title_y?: TextElement;
158
306
  axis_text_x?: TextElement; axis_text_y?: TextElement;
307
+ /** A top x axis's / right (secondary) y axis's labels (#413). */
308
+ axis_text_x_top?: TextElement; axis_text_y_right?: TextElement;
309
+ /** Horizontal (x) / turned (y) facet strips only (#413). */
310
+ strip_text_x?: TextElement; strip_text_y?: TextElement;
311
+ strip_background_x?: RectElement; strip_background_y?: RectElement;
312
+ /** Strips below / left of a panel outside its axis (#412), and the gap. */
313
+ strip_placement?: "inside" | "outside"; strip_switch_pad?: number;
314
+ caption_position?: "panel" | "plot";
315
+ legend_location?: "panel" | "plot";
316
+ legend_ticks_length?: number;
317
+ /** Gaps between legend keys and between the panel and the legend, px (#413). */
318
+ legend_key_spacing_x?: number; legend_key_spacing_y?: number; legend_box_spacing?: number;
319
+ /** Line several legends up: "left"/"top" (default), "center", "right"/"bottom", or 0–1. */
320
+ /** Markdown / HTML subset (**bold**, *italic*, ^sup^, ~sub~, <span style='color:…'>) in titles, strips, legends. */
321
+ rich_text?: boolean;
322
+ legend_box_just?: "left" | "top" | "center" | "right" | "bottom" | number;
159
323
  legend_title?: TextElement; legend_text?: TextElement; strip_text?: TextElement;
160
324
  axis_line?: LineElement; axis_ticks?: LineElement;
325
+ /** Per-axis / per-side axis lines and ticks (#413). */
326
+ axis_line_x?: LineElement; axis_line_y?: LineElement;
327
+ axis_ticks_x?: LineElement; axis_ticks_y?: LineElement;
328
+ axis_line_x_top?: LineElement; axis_line_x_bottom?: LineElement;
329
+ axis_line_y_left?: LineElement; axis_line_y_right?: LineElement;
330
+ axis_ticks_x_top?: LineElement; axis_ticks_x_bottom?: LineElement;
331
+ axis_ticks_y_left?: LineElement; axis_ticks_y_right?: LineElement;
161
332
  panel_grid_major?: LineElement; panel_grid_minor?: LineElement; panel_border?: LineElement;
162
333
  panel_background?: RectElement; plot_background?: RectElement;
163
334
  legend_background?: RectElement; strip_background?: RectElement;
@@ -173,16 +344,20 @@ export interface PlotSpec {
173
344
  /** Plot-level mapping inherited by every layer. */
174
345
  aes?: AesMap;
175
346
  layers: Layer[];
176
- scales?: { x?: PositionScale; y?: PositionScale; color?: ColorScale; colour?: ColorScale; fill?: ColorScale };
347
+ scales?: { x?: PositionScale; y?: PositionScale; color?: ColorScale; colour?: ColorScale; fill?: ColorScale; size?: ValueScale; alpha?: ValueScale; linewidth?: ValueScale; shape?: ValueScale };
177
348
  facet?: Facet;
178
349
  coord?: Coord;
179
350
  labels?: Labels;
180
351
  /** A preset name or a theme overlay (default "minimal"). */
181
352
  theme?: ThemePreset | ThemeConfig;
182
353
  legend?: Legend;
354
+ /** Per-aesthetic guides: `"none"` drops a legend, `{ title }` names it. */
355
+ guides?: Partial<Record<"color" | "colour" | "fill" | "shape" | "size" | "alpha" | "linetype" | "linewidth", GuideSpec>>;
183
356
  /** Pixels (default 640 × 400). */
184
357
  width?: number;
185
358
  height?: number;
359
+ /** Tag axes, grid, legend, strips and titles with semantic CSS classes (gg-axis-text, gg-legend-key, …). */
360
+ svg_classes?: boolean;
186
361
  }
187
362
 
188
363
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sipemu/ggplot-rs",
3
- "version": "0.39.0",
3
+ "version": "0.41.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",
@@ -64,17 +64,22 @@ if (!svg.startsWith("<svg")) throw new Error("render failed");
64
64
  | `data` | `{ col: (number \| string \| boolean \| null)[] }` — numbers continuous, strings/booleans categorical |
65
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
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) |
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` · boxplot `notch` `varwidth` `coef` `outliers` `staplewidth` · violin `trim` `scale` (`"area"`/`"count"`/`"width"`) `quantiles` `bw` `adjust` · stat connect `connection` · stat summary_hex `fun` |
68
+ | `layers[].position` | `identity stack fill dodge dodge2 jitter nudge`, or `{ type: "dodge", width: 0.8, preserve: "single" }`. boxplot dodges (`dodge2`) and violin (`dodge`) by default, so grouped boxes sit side by side |
69
+ | `layers[].stat` | `"identity"` (plot precomputed values, e.g. bar heights), `"unique"` (drop duplicate rows), `"connect"` (steps; `params.connection` `hv`/`vh`/`mid`/`linear`), `"summary_hex"` (`params.fun` of a `z` column per hexagon), `"sf_coordinates"` (a point per WKT `geometry` — text on a map) |
70
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 }` |
71
+ | `layers[].key_glyph` | legend key picture: `point path rect polygon boxplot crossbar pointrange linerange smooth text label vline vpath abline timeseries dotplot blank` (default per geom; layers on one mapping share a key) |
72
+ | `layers[].show_legend` | `false` hides the layer from legends; `{ color: false }` per aesthetic |
73
+ | `scales.x/y` | `{ trans, limits: [lo, hi], breaks: [...], logticks: true (log10 minor ticks), binned: n (bin x before the stat: geom bar → histogram), n_breaks, time: true (seconds → HH:MM:SS), name, sec_axis: true | { name, scale, offset } }` (secondary axis on top / right); trans `log10 log2 ln sqrt reverse logit probit pseudo_log reciprocal exp` |
74
+ | `scales.color/fill` | `{ palette }` (`Set1 Dark2 Paired npg …`, `hue` = ggplot2's default hues, `viridis:A`…`viridis:H` for categorical; `viridis magma plasma inferno` or any Brewer name (a distiller gradient) for numeric), `{ values: { level: "#rrggbb" } }`, `{ gradient: ["#lo", "#hi"] }`, `{ steps2: ["#lo", "#mid", "#hi"], midpoint: 0, bins: 5 }`, `{ palette: "viridis_b:C", bins: 6 }` (binned), `{ levels: [...] }` |
75
+ | `scales.size/alpha/linewidth/shape` | exactly one of `{ identity: true }` (the column holds the values; shape: `pch` codes or names), `{ values: { level: n } }` (not shape), `{ range: [lo, hi] }`, or `{ binned: n, range? }` (binned; its legend is `guide_bins`, see `guides`) |
76
+ | `facet` | `{ wrap, ncol?, scales?, dir?: "h"/"v", as_table?, label_wrap?: width, strip_position?: "top"/"bottom"/"left"/"right", levels?: [...] }` or `{ rows?, cols?, scales?, switch?: "x"/"y"/"both", row_levels?, col_levels?, margins?: true }` (grid: column strips on top, row strips on the right); `labeller: "both" | { template: "n = {value}" } | { values: { a: "Alpha" } } | { vars: { var: <labeller> } }`; both take `axes: "all"/"all_x"/"all_y"` + `axis_labels: "margins"` and `space: "free_x"/"free_y"/"free"`; scales `fixed free free_x free_y` |
77
+ | `coord` | `cartesian flip polar radial fixed radar quickmap map sf`, or `{ type, xlim, ylim, ratio, reverse: "x"/"y"/"xy", expand: false | [top, right, bottom, left], start, end, inner_radius, r_axis_inside, rotate_angle, clip: "off" }` (radial `reverse: "theta"/"r"/"thetar"`) (`quickmap` / `map` = plain lon/lat data; `radial` partial circles / donuts) |
75
78
  | `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: {...}, … }` |
79
+ | `theme` | preset `gray bw minimal classic dark light void linedraw test` (default `minimal`), or an overlay `{ base, primary: [r,g,b], palette, title: {...}, panel_grid_major: {...}, axis_text_x_top: {...}, axis_text_y_right: {...}, strip_text_x/y: {...}, strip_background_x/y: {...}, strip_placement: "outside", rich_text: true (Markdown/HTML subset in titles, strips, legends), … }` |
77
80
  | `legend` | `right left top bottom none inside` or `[x, y]` inside the panel |
81
+ | `svg_classes` | `true` tags axes, grid, legend, strips and titles with CSS classes (`gg-axis-text`, `gg-legend-key`, `gg-strip-text`, `gg-title`, …) for restyling; marks always carry `gg-mark gg-<kind>` |
82
+ | `guides` | per aesthetic: `{ fill: "none", color: { title: "Group", reverse: true } }`; continuous colour/fill: `"coloursteps"` or `{ type: "colourbar", barwidth: 20, barheight: 150, nbin: 10, display: "rectangles", frame: false, ticks: false }` (`even_steps`, `show_limits` for steps); binned size/alpha/shape scales: `"bins"` (the default — keys labelled at bin edges) or `{ type: "bins", show_limits: true, axis: false }`, `"legend"` for one key per bin; one legend at its own side: `{ position: "bottom" }`; fixed key looks: `{ override_aes: { size: 4, alpha: 1, shape: "square", color: "#rrggbb" } }` |
78
83
 
79
84
  ## Geoms
80
85