@sipemu/ggplot-rs 0.40.0 → 0.42.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.
@@ -10,7 +10,7 @@ export type Aesthetic =
10
10
  | "x" | "y" | "color" | "colour" | "fill" | "size" | "shape" | "alpha" | "group"
11
11
  | "linetype" | "linewidth" | "label" | "weight" | "xmin" | "xmax" | "ymin" | "ymax"
12
12
  | "xend" | "yend" | "angle" | "radius" | "tooltip" | "from" | "to" | "subgroup"
13
- | "open" | "high" | "low" | "close" | "lower" | "middle" | "upper" | "fontface" | "class";
13
+ | "open" | "high" | "low" | "close" | "lower" | "middle" | "upper" | "fontface" | "class" | "key";
14
14
  /** Aesthetic → column name. */
15
15
  export type AesMap = Partial<Record<Aesthetic, string>>;
16
16
 
@@ -104,6 +104,40 @@ export type Position =
104
104
  preserve?: "total" | "single";
105
105
  };
106
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
+ };
140
+
107
141
  export interface Layer {
108
142
  geom: GeomName;
109
143
  /** Merged over the plot-level `aes` (layer wins). */
@@ -130,8 +164,19 @@ export interface PositionScale {
130
164
  | "probit" | "pseudo_log" | "reciprocal" | "exp";
131
165
  limits?: [number, number];
132
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;
133
171
  /** Axis title. */
134
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;
135
180
  }
136
181
 
137
182
  export interface ColorScale {
@@ -144,16 +189,82 @@ export interface ColorScale {
144
189
  values?: Record<string, string>;
145
190
  /** Two-color gradient `["#low", "#high"]` for a numeric column. */
146
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;
197
+ }
198
+
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;
147
209
  }
148
210
 
149
- export type Facet =
150
- | { wrap: string; ncol?: number; scales?: "fixed" | "free" | "free_x" | "free_y" }
151
- | { rows?: string; cols?: string; scales?: "fixed" | "free" | "free_x" | "free_y" };
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
+ }
152
224
 
153
- export type CoordName = "cartesian" | "flip" | "polar" | "fixed" | "equal" | "radar" | "sf";
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";
154
253
  export type Coord =
155
254
  | CoordName
156
- | { 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
+ };
157
268
 
158
269
  export interface Labels {
159
270
  title?: string;
@@ -164,7 +275,7 @@ export interface Labels {
164
275
  y?: string;
165
276
  }
166
277
 
167
- 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";
168
279
  export interface TextElement {
169
280
  family?: string; size?: number; color?: [number, number, number];
170
281
  face?: "plain" | "bold" | "italic"; hjust?: number; vjust?: number; angle?: number; visible?: boolean;
@@ -193,8 +304,31 @@ export interface ThemeConfig {
193
304
  text?: TextElement; title?: TextElement; subtitle?: TextElement; caption?: TextElement;
194
305
  axis_title_x?: TextElement; axis_title_y?: TextElement;
195
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;
196
323
  legend_title?: TextElement; legend_text?: TextElement; strip_text?: TextElement;
197
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;
198
332
  panel_grid_major?: LineElement; panel_grid_minor?: LineElement; panel_border?: LineElement;
199
333
  panel_background?: RectElement; plot_background?: RectElement;
200
334
  legend_background?: RectElement; strip_background?: RectElement;
@@ -210,16 +344,20 @@ export interface PlotSpec {
210
344
  /** Plot-level mapping inherited by every layer. */
211
345
  aes?: AesMap;
212
346
  layers: Layer[];
213
- 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 };
214
348
  facet?: Facet;
215
349
  coord?: Coord;
216
350
  labels?: Labels;
217
351
  /** A preset name or a theme overlay (default "minimal"). */
218
352
  theme?: ThemePreset | ThemeConfig;
219
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>>;
220
356
  /** Pixels (default 640 × 400). */
221
357
  width?: number;
222
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;
223
361
  }
224
362
 
225
363
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sipemu/ggplot-rs",
3
- "version": "0.40.0",
3
+ "version": "0.42.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",
@@ -70,13 +70,16 @@ if (!svg.startsWith("<svg")) throw new Error("render failed");
70
70
  | `layers[].data` | layer-specific data |
71
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
72
  | `layers[].show_legend` | `false` hides the layer from legends; `{ color: false }` per aesthetic |
73
- | `scales.x/y` | `{ trans, limits: [lo, hi], breaks: [...], name }`; trans `log10 log2 ln sqrt reverse logit probit pseudo_log reciprocal exp` |
74
- | `scales.color/fill` | `{ palette }` (`Set1 Dark2 Paired npg …` categorical; `viridis magma plasma inferno` numeric), `{ values: { level: "#rrggbb" } }`, `{ gradient: ["#lo", "#hi"] }`, `{ levels: [...] }` |
75
- | `facet` | `{ wrap, ncol?, scales? }` or `{ rows?, cols?, scales? }`; scales `fixed free free_x free_y` |
76
- | `coord` | `cartesian flip polar fixed radar sf`, or `{ type, xlim, ylim, ratio }` |
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) |
77
78
  | `labels` | `title subtitle caption tag x y` (axis titles are not auto-filled from column names) |
78
- | `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), … }` |
79
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" } }` |
80
83
 
81
84
  ## Geoms
82
85