@danielsimonjr/mathts-plot 0.4.10 → 0.4.12

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.
Files changed (3) hide show
  1. package/dist/index.js +1235 -1126
  2. package/dist/render-file.js +140 -154
  3. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -1,1235 +1,1344 @@
1
- // src/plot.ts
2
- import { parse, evaluate, range } from "@danielsimonjr/mathts-functions";
3
-
4
- // src/coerce.ts
5
- import {
6
- isMatrix,
7
- isComplex,
8
- isBigNumber,
9
- isFraction,
10
- number as toNumber
11
- } from "@danielsimonjr/mathts-core";
1
+ import { evaluate, histogram as histogram$1, parse, range } from "@danielsimonjr/mathts-functions";
2
+ import { isBigNumber, isComplex, isFraction, isMatrix, number } from "@danielsimonjr/mathts-core";
3
+ //#region src/coerce.ts
12
4
  function scalar(v) {
13
- if (typeof v === "number") return v;
14
- if (typeof v === "bigint") return Number(v);
15
- if (isComplex(v)) return v.re;
16
- if (isBigNumber(v) || isFraction(v)) {
17
- try {
18
- return toNumber(v);
19
- } catch {
20
- return NaN;
21
- }
22
- }
23
- return Number(v);
5
+ if (typeof v === "number") return v;
6
+ if (typeof v === "bigint") return Number(v);
7
+ if (isComplex(v)) return v.re;
8
+ if (isBigNumber(v) || isFraction(v)) try {
9
+ return number(v);
10
+ } catch {
11
+ return NaN;
12
+ }
13
+ return Number(v);
24
14
  }
25
15
  function unwrap(raw) {
26
- if (isMatrix(raw)) {
27
- try {
28
- return raw.toArray();
29
- } catch {
30
- return raw;
31
- }
32
- }
33
- return raw;
16
+ if (isMatrix(raw)) try {
17
+ return raw.toArray();
18
+ } catch {
19
+ return raw;
20
+ }
21
+ return raw;
34
22
  }
35
23
  function coerce1d(raw) {
36
- const u = unwrap(raw);
37
- const arr = Array.isArray(u) ? u.flat(Infinity) : u instanceof Float64Array ? Array.from(u) : [];
38
- const out = [];
39
- for (const v of arr) {
40
- const n = scalar(v);
41
- if (Number.isFinite(n)) out.push(n);
42
- }
43
- return out;
24
+ const u = unwrap(raw);
25
+ const arr = Array.isArray(u) ? u.flat(Infinity) : u instanceof Float64Array ? Array.from(u) : [];
26
+ const out = [];
27
+ for (const v of arr) {
28
+ const n = scalar(v);
29
+ if (Number.isFinite(n)) out.push(n);
30
+ }
31
+ return out;
44
32
  }
45
33
  function coerce1dPositional(raw) {
46
- const u = unwrap(raw);
47
- const arr = Array.isArray(u) ? u.flat(Infinity) : u instanceof Float64Array ? Array.from(u) : [];
48
- return arr.map((v) => scalar(v));
34
+ const u = unwrap(raw);
35
+ return (Array.isArray(u) ? u.flat(Infinity) : u instanceof Float64Array ? Array.from(u) : []).map((v) => scalar(v));
49
36
  }
50
37
  function coerce2d(raw) {
51
- const u = unwrap(raw);
52
- if (!Array.isArray(u)) return [];
53
- return u.map((row) => {
54
- const r = row instanceof Float64Array ? Array.from(row) : Array.isArray(row) ? row : [row];
55
- return r.map((v) => {
56
- const n = scalar(v);
57
- return Number.isFinite(n) ? n : NaN;
58
- });
59
- });
60
- }
61
-
62
- // src/scale.ts
38
+ const u = unwrap(raw);
39
+ if (!Array.isArray(u)) return [];
40
+ return u.map((row) => {
41
+ return (row instanceof Float64Array ? Array.from(row) : Array.isArray(row) ? row : [row]).map((v) => {
42
+ const n = scalar(v);
43
+ return Number.isFinite(n) ? n : NaN;
44
+ });
45
+ });
46
+ }
47
+ //#endregion
48
+ //#region src/scale.ts
63
49
  function extent(xs) {
64
- if (xs.length === 0) return [0, 1];
65
- let lo = xs[0];
66
- let hi = xs[0];
67
- for (const v of xs) {
68
- if (v < lo) lo = v;
69
- if (v > hi) hi = v;
70
- }
71
- if (lo === hi) return [lo - 1, hi + 1];
72
- return [lo, hi];
73
- }
74
- function linearScale(dom, range2) {
75
- const [d0, d1] = dom;
76
- const [r0, r1] = range2;
77
- const m = (r1 - r0) / (d1 - d0);
78
- return (v) => r0 + (v - d0) * m;
79
- }
80
- function logScale(dom, range2) {
81
- const d0 = Math.log10(Math.max(dom[0], Number.MIN_VALUE));
82
- const d1 = Math.log10(Math.max(dom[1], Number.MIN_VALUE));
83
- const [r0, r1] = range2;
84
- const m = (r1 - r0) / (d1 - d0);
85
- return (v) => r0 + (Math.log10(Math.max(v, Number.MIN_VALUE)) - d0) * m;
50
+ if (xs.length === 0) return [0, 1];
51
+ let lo = xs[0];
52
+ let hi = xs[0];
53
+ for (const v of xs) {
54
+ if (v < lo) lo = v;
55
+ if (v > hi) hi = v;
56
+ }
57
+ if (lo === hi) return [lo - 1, hi + 1];
58
+ return [lo, hi];
59
+ }
60
+ function linearScale(dom, range) {
61
+ const [d0, d1] = dom;
62
+ const [r0, r1] = range;
63
+ const m = (r1 - r0) / (d1 - d0);
64
+ return (v) => r0 + (v - d0) * m;
65
+ }
66
+ function logScale(dom, range) {
67
+ const d0 = Math.log10(Math.max(dom[0], Number.MIN_VALUE));
68
+ const d1 = Math.log10(Math.max(dom[1], Number.MIN_VALUE));
69
+ const [r0, r1] = range;
70
+ const m = (r1 - r0) / (d1 - d0);
71
+ return (v) => r0 + (Math.log10(Math.max(v, Number.MIN_VALUE)) - d0) * m;
86
72
  }
87
73
  function niceTicks(min, max, count = 5) {
88
- if (!(max > min)) return [min];
89
- const span = max - min;
90
- const raw = span / count;
91
- const mag = Math.pow(10, Math.floor(Math.log10(raw)));
92
- const norm = raw / mag;
93
- const step2 = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
94
- const start = Math.floor(min / step2) * step2;
95
- const ticks = [];
96
- for (let t = start; t <= max + step2 * 0.5; t += step2) ticks.push(Math.round(t / step2) * step2);
97
- return ticks;
98
- }
99
-
100
- // src/svg.ts
101
- var PALETTE = [
102
- "#2a4d8f",
103
- "#c0392b",
104
- "#27ae60",
105
- "#8e44ad",
106
- "#d68910",
107
- "#16a085",
108
- "#7f8c8d",
109
- "#2c3e50"
74
+ if (!(max > min)) return [min];
75
+ const raw = (max - min) / count;
76
+ const mag = Math.pow(10, Math.floor(Math.log10(raw)));
77
+ const norm = raw / mag;
78
+ const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
79
+ const start = Math.floor(min / step) * step;
80
+ const ticks = [];
81
+ for (let t = start; t <= max + step * .5; t += step) ticks.push(Math.round(t / step) * step);
82
+ return ticks;
83
+ }
84
+ //#endregion
85
+ //#region src/svg.ts
86
+ const PALETTE = [
87
+ "#2a4d8f",
88
+ "#c0392b",
89
+ "#27ae60",
90
+ "#8e44ad",
91
+ "#d68910",
92
+ "#16a085",
93
+ "#7f8c8d",
94
+ "#2c3e50"
110
95
  ];
111
- var THEMES = {
112
- light: {
113
- fg: "#1a1a2e",
114
- muted: "#5a5a72",
115
- grid: "#e6e6ee",
116
- axis: "#5a5a72",
117
- bg: "#ffffff",
118
- series: PALETTE
119
- },
120
- dark: {
121
- fg: "#e8e8f0",
122
- muted: "#a0a0b0",
123
- grid: "#2c2c3a",
124
- axis: "#a0a0b0",
125
- bg: "#14141c",
126
- series: PALETTE
127
- }
96
+ const THEMES = {
97
+ light: {
98
+ fg: "#1a1a2e",
99
+ muted: "#5a5a72",
100
+ grid: "#e6e6ee",
101
+ axis: "#5a5a72",
102
+ bg: "#ffffff",
103
+ series: PALETTE
104
+ },
105
+ dark: {
106
+ fg: "#e8e8f0",
107
+ muted: "#a0a0b0",
108
+ grid: "#2c2c3a",
109
+ axis: "#a0a0b0",
110
+ bg: "#14141c",
111
+ series: PALETTE
112
+ }
128
113
  };
129
114
  function esc(s) {
130
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
115
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
131
116
  }
132
117
  function fmt(n) {
133
- if (!Number.isFinite(n)) return "";
134
- const a = Math.abs(n);
135
- if (a !== 0 && (a < 1e-3 || a >= 1e6)) return n.toExponential(2);
136
- return String(Math.round(n * 1e6) / 1e6);
137
- }
138
- var r2 = (n) => Math.round(n * 100) / 100;
139
- var ATTR_ENTITIES = {
140
- "&": "&amp;",
141
- "<": "&lt;",
142
- ">": "&gt;",
143
- '"': "&quot;",
144
- "'": "&#39;"
118
+ if (!Number.isFinite(n)) return "";
119
+ const a = Math.abs(n);
120
+ if (a !== 0 && (a < .001 || a >= 1e6)) return n.toExponential(2);
121
+ return String(Math.round(n * 1e6) / 1e6);
122
+ }
123
+ const r2 = (n) => Math.round(n * 100) / 100;
124
+ const ATTR_ENTITIES = {
125
+ "&": "&amp;",
126
+ "<": "&lt;",
127
+ ">": "&gt;",
128
+ "\"": "&quot;",
129
+ "'": "&#39;"
145
130
  };
146
131
  function escAttr(v) {
147
- return String(v).replace(/[&<>"']/g, (c) => ATTR_ENTITIES[c]);
132
+ return String(v).replace(/[&<>"']/g, (c) => ATTR_ENTITIES[c]);
148
133
  }
149
134
  function el(tag, attrs, body) {
150
- let s = `<${tag}`;
151
- for (const [name, value] of attrs) if (value !== void 0) s += ` ${name}="${escAttr(value)}"`;
152
- return body === void 0 ? `${s}/>` : `${s}>${body}</${tag}>`;
135
+ let s = `<${tag}`;
136
+ for (const [name, value] of attrs) if (value !== void 0) s += ` ${name}="${escAttr(value)}"`;
137
+ return body === void 0 ? `${s}/>` : `${s}>${body}</${tag}>`;
153
138
  }
154
- var opacityAttr = (opacity) => opacity !== void 0 && opacity < 1 ? Math.round(opacity * 1e3) / 1e3 : void 0;
155
- var points = (pts) => pts.map(([x, y]) => `${r2(x)},${r2(y)}`).join(" ");
139
+ const opacityAttr = (opacity) => opacity !== void 0 && opacity < 1 ? Math.round(opacity * 1e3) / 1e3 : void 0;
140
+ const points = (pts) => pts.map(([x, y]) => `${r2(x)},${r2(y)}`).join(" ");
156
141
  function svgDoc(width, height, body, bg) {
157
- return el(
158
- "svg",
159
- [
160
- ["xmlns", "http://www.w3.org/2000/svg"],
161
- ["viewBox", `0 0 ${width} ${height}`],
162
- ["width", "100%"],
163
- ["role", "img"]
164
- ],
165
- el("rect", [
166
- ["x", 0],
167
- ["y", 0],
168
- ["width", width],
169
- ["height", height],
170
- ["fill", bg]
171
- ]) + body
172
- );
173
- }
174
- function line(x1, y1, x2, y2, stroke, w = 1, opacity) {
175
- return el("line", [
176
- ["x1", r2(x1)],
177
- ["y1", r2(y1)],
178
- ["x2", r2(x2)],
179
- ["y2", r2(y2)],
180
- ["stroke", stroke],
181
- ["stroke-width", w],
182
- ["stroke-opacity", opacityAttr(opacity)]
183
- ]);
142
+ return el("svg", [
143
+ ["xmlns", "http://www.w3.org/2000/svg"],
144
+ ["viewBox", `0 0 ${width} ${height}`],
145
+ ["width", "100%"],
146
+ ["role", "img"]
147
+ ], el("rect", [
148
+ ["x", 0],
149
+ ["y", 0],
150
+ ["width", width],
151
+ ["height", height],
152
+ ["fill", bg]
153
+ ]) + body);
154
+ }
155
+ function line$1(x1, y1, x2, y2, stroke, w = 1, opacity) {
156
+ return el("line", [
157
+ ["x1", r2(x1)],
158
+ ["y1", r2(y1)],
159
+ ["x2", r2(x2)],
160
+ ["y2", r2(y2)],
161
+ ["stroke", stroke],
162
+ ["stroke-width", w],
163
+ ["stroke-opacity", opacityAttr(opacity)]
164
+ ]);
184
165
  }
185
166
  function circle(cx, cy, r, fill, opacity) {
186
- return el("circle", [
187
- ["cx", r2(cx)],
188
- ["cy", r2(cy)],
189
- ["r", r],
190
- ["fill", fill],
191
- ["fill-opacity", opacityAttr(opacity)]
192
- ]);
167
+ return el("circle", [
168
+ ["cx", r2(cx)],
169
+ ["cy", r2(cy)],
170
+ ["r", r],
171
+ ["fill", fill],
172
+ ["fill-opacity", opacityAttr(opacity)]
173
+ ]);
193
174
  }
194
175
  function rect(x, y, w, h, fill) {
195
- return el("rect", [
196
- ["x", r2(x)],
197
- ["y", r2(y)],
198
- ["width", r2(w)],
199
- ["height", r2(h)],
200
- ["fill", fill]
201
- ]);
176
+ return el("rect", [
177
+ ["x", r2(x)],
178
+ ["y", r2(y)],
179
+ ["width", r2(w)],
180
+ ["height", r2(h)],
181
+ ["fill", fill]
182
+ ]);
202
183
  }
203
184
  function polyline(pts, stroke, w = 2) {
204
- return el("polyline", [
205
- ["points", points(pts)],
206
- ["fill", "none"],
207
- ["stroke", stroke],
208
- ["stroke-width", w]
209
- ]);
185
+ return el("polyline", [
186
+ ["points", points(pts)],
187
+ ["fill", "none"],
188
+ ["stroke", stroke],
189
+ ["stroke-width", w]
190
+ ]);
210
191
  }
211
192
  function polygon(pts, fill, stroke = "none") {
212
- return el("polygon", [
213
- ["points", points(pts)],
214
- ["fill", fill],
215
- ["stroke", stroke]
216
- ]);
193
+ return el("polygon", [
194
+ ["points", points(pts)],
195
+ ["fill", fill],
196
+ ["stroke", stroke]
197
+ ]);
217
198
  }
218
199
  function text(x, y, s, fill, anchor = "start", size = 12, transform) {
219
- const pos = transform === void 0 ? [
220
- ["x", r2(x)],
221
- ["y", r2(y)]
222
- ] : [["transform", transform]];
223
- return el(
224
- "text",
225
- [
226
- ...pos,
227
- ["text-anchor", anchor],
228
- ["font-family", "system-ui,sans-serif"],
229
- ["font-size", size],
230
- ["fill", fill]
231
- ],
232
- esc(s)
233
- );
200
+ return el("text", [
201
+ ...transform === void 0 ? [["x", r2(x)], ["y", r2(y)]] : [["transform", transform]],
202
+ ["text-anchor", anchor],
203
+ ["font-family", "system-ui,sans-serif"],
204
+ ["font-size", size],
205
+ ["fill", fill]
206
+ ], esc(s));
234
207
  }
235
208
  function primSVG(p) {
236
- switch (p.k) {
237
- case "line":
238
- return line(p.x1, p.y1, p.x2, p.y2, p.stroke, p.w, p.opacity);
239
- case "circle":
240
- return circle(p.cx, p.cy, p.r, p.fill, p.opacity);
241
- case "rect":
242
- return rect(p.x, p.y, p.w, p.h, p.fill);
243
- case "polyline":
244
- return polyline(p.pts, p.stroke, p.w);
245
- case "polygon":
246
- return polygon(p.pts, p.fill, p.stroke);
247
- case "text":
248
- return p.rotate !== void 0 ? text(
249
- p.x,
250
- p.y,
251
- p.s,
252
- p.fill,
253
- p.anchor,
254
- p.size,
255
- `translate(${p.x},${p.y}) rotate(${p.rotate})`
256
- ) : text(p.x, p.y, p.s, p.fill, p.anchor, p.size);
257
- }
209
+ switch (p.k) {
210
+ case "line": return line$1(p.x1, p.y1, p.x2, p.y2, p.stroke, p.w, p.opacity);
211
+ case "circle": return circle(p.cx, p.cy, p.r, p.fill, p.opacity);
212
+ case "rect": return rect(p.x, p.y, p.w, p.h, p.fill);
213
+ case "polyline": return polyline(p.pts, p.stroke, p.w);
214
+ case "polygon": return polygon(p.pts, p.fill, p.stroke);
215
+ case "text": return p.rotate !== void 0 ? text(p.x, p.y, p.s, p.fill, p.anchor, p.size, `translate(${p.x},${p.y}) rotate(${p.rotate})`) : text(p.x, p.y, p.s, p.fill, p.anchor, p.size);
216
+ }
258
217
  }
259
218
  function emitSVG(scene) {
260
- return svgDoc(scene.width, scene.height, scene.prims.map(primSVG).join(""), scene.bg);
219
+ return svgDoc(scene.width, scene.height, scene.prims.map(primSVG).join(""), scene.bg);
261
220
  }
262
-
263
- // src/render-core.ts
221
+ //#endregion
222
+ //#region src/render-core.ts
264
223
  function xy(layer) {
265
- const ys = coerce1dPositional(layer.y);
266
- const xs = layer.x ? coerce1dPositional(layer.x) : ys.map((_, i) => i);
267
- const n = Math.min(xs.length, ys.length);
268
- const pts = [];
269
- for (let i = 0; i < n; i++) {
270
- if (Number.isFinite(xs[i]) && Number.isFinite(ys[i])) pts.push([xs[i], ys[i]]);
271
- }
272
- return pts;
224
+ const ys = coerce1dPositional(layer.y);
225
+ const xs = layer.x ? coerce1dPositional(layer.x) : ys.map((_, i) => i);
226
+ const n = Math.min(xs.length, ys.length);
227
+ const pts = [];
228
+ for (let i = 0; i < n; i++) if (Number.isFinite(xs[i]) && Number.isFinite(ys[i])) pts.push([xs[i], ys[i]]);
229
+ return pts;
273
230
  }
274
231
  function renderLine(layer, f, i) {
275
- const color = layer.color ?? f.color(i);
276
- const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
277
- return [
278
- { k: "polyline", pts, stroke: color, w: 2 },
279
- ...pts.map((p) => ({ k: "circle", cx: p[0], cy: p[1], r: 2.5, fill: color }))
280
- ];
232
+ const color = layer.color ?? f.color(i);
233
+ const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
234
+ return [{
235
+ k: "polyline",
236
+ pts,
237
+ stroke: color,
238
+ w: 2
239
+ }, ...pts.map((p) => ({
240
+ k: "circle",
241
+ cx: p[0],
242
+ cy: p[1],
243
+ r: 2.5,
244
+ fill: color
245
+ }))];
281
246
  }
282
247
  function renderScatter(layer, f, i) {
283
- const color = layer.color ?? f.color(i);
284
- return xy(layer).map(([x, y]) => ({
285
- k: "circle",
286
- cx: f.px(x),
287
- cy: f.py(y),
288
- r: 3,
289
- fill: color
290
- }));
248
+ const color = layer.color ?? f.color(i);
249
+ return xy(layer).map(([x, y]) => ({
250
+ k: "circle",
251
+ cx: f.px(x),
252
+ cy: f.py(y),
253
+ r: 3,
254
+ fill: color
255
+ }));
291
256
  }
292
257
  function renderBar(layer, f, i) {
293
- const color = layer.color ?? f.color(i);
294
- const pts = xy(layer);
295
- const base = f.py(Math.max(0, f.ydom[0]));
296
- const bw = pts.length > 1 ? Math.abs(f.px(pts[1][0]) - f.px(pts[0][0])) * 0.7 : 20;
297
- return pts.map(([x, y]) => {
298
- const yp = f.py(y);
299
- return {
300
- k: "rect",
301
- x: f.px(x) - bw / 2,
302
- y: Math.min(yp, base),
303
- w: bw,
304
- h: Math.abs(base - yp),
305
- fill: color
306
- };
307
- });
258
+ const color = layer.color ?? f.color(i);
259
+ const pts = xy(layer);
260
+ const base = f.py(Math.max(0, f.ydom[0]));
261
+ const bw = pts.length > 1 ? Math.abs(f.px(pts[1][0]) - f.px(pts[0][0])) * .7 : 20;
262
+ return pts.map(([x, y]) => {
263
+ const yp = f.py(y);
264
+ return {
265
+ k: "rect",
266
+ x: f.px(x) - bw / 2,
267
+ y: Math.min(yp, base),
268
+ w: bw,
269
+ h: Math.abs(base - yp),
270
+ fill: color
271
+ };
272
+ });
308
273
  }
309
274
  function renderArea(layer, f, i) {
310
- const color = layer.color ?? f.color(i);
311
- const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
312
- if (pts.length === 0) return [];
313
- const base = f.py(Math.max(0, f.ydom[0]));
314
- const poly = [[pts[0][0], base], ...pts, [pts[pts.length - 1][0], base]];
315
- return [
316
- { k: "polygon", pts: poly, fill: color + "55", stroke: "none" },
317
- { k: "polyline", pts, stroke: color, w: 2 }
318
- ];
275
+ const color = layer.color ?? f.color(i);
276
+ const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
277
+ if (pts.length === 0) return [];
278
+ const base = f.py(Math.max(0, f.ydom[0]));
279
+ return [{
280
+ k: "polygon",
281
+ pts: [
282
+ [pts[0][0], base],
283
+ ...pts,
284
+ [pts[pts.length - 1][0], base]
285
+ ],
286
+ fill: color + "55",
287
+ stroke: "none"
288
+ }, {
289
+ k: "polyline",
290
+ pts,
291
+ stroke: color,
292
+ w: 2
293
+ }];
319
294
  }
320
295
  function renderStep(layer, f, i) {
321
- const color = layer.color ?? f.color(i);
322
- const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
323
- const stepped = [];
324
- for (let k = 0; k < pts.length; k++) {
325
- stepped.push(pts[k]);
326
- if (k < pts.length - 1) stepped.push([pts[k + 1][0], pts[k][1]]);
327
- }
328
- return [{ k: "polyline", pts: stepped, stroke: color, w: 2 }];
296
+ const color = layer.color ?? f.color(i);
297
+ const pts = xy(layer).map(([x, y]) => [f.px(x), f.py(y)]);
298
+ const stepped = [];
299
+ for (let k = 0; k < pts.length; k++) {
300
+ stepped.push(pts[k]);
301
+ if (k < pts.length - 1) stepped.push([pts[k + 1][0], pts[k][1]]);
302
+ }
303
+ return [{
304
+ k: "polyline",
305
+ pts: stepped,
306
+ stroke: color,
307
+ w: 2
308
+ }];
329
309
  }
330
310
  function renderErrorbar(layer, f, i) {
331
- const color = layer.color ?? f.color(i);
332
- const ys = coerce1dPositional(layer.y);
333
- const xs = layer.x ? coerce1dPositional(layer.x) : ys.map((_, k) => k);
334
- const errs = coerce1dPositional(layer.yerr ?? []);
335
- const n = Math.min(xs.length, ys.length);
336
- const out = [];
337
- for (let k = 0; k < n; k++) {
338
- if (!Number.isFinite(xs[k]) || !Number.isFinite(ys[k])) continue;
339
- const e = Number.isFinite(errs[k]) ? errs[k] : 0;
340
- const cx = f.px(xs[k]);
341
- const top = f.py(ys[k] + e);
342
- const bot = f.py(ys[k] - e);
343
- out.push(
344
- { k: "line", x1: cx, y1: top, x2: cx, y2: bot, stroke: color, w: 1.5 },
345
- { k: "line", x1: cx - 3, y1: top, x2: cx + 3, y2: top, stroke: color, w: 1.5 },
346
- { k: "line", x1: cx - 3, y1: bot, x2: cx + 3, y2: bot, stroke: color, w: 1.5 },
347
- { k: "circle", cx, cy: f.py(ys[k]), r: 3, fill: color }
348
- );
349
- }
350
- return out;
311
+ const color = layer.color ?? f.color(i);
312
+ const ys = coerce1dPositional(layer.y);
313
+ const xs = layer.x ? coerce1dPositional(layer.x) : ys.map((_, k) => k);
314
+ const errs = coerce1dPositional(layer.yerr ?? []);
315
+ const n = Math.min(xs.length, ys.length);
316
+ const out = [];
317
+ for (let k = 0; k < n; k++) {
318
+ if (!Number.isFinite(xs[k]) || !Number.isFinite(ys[k])) continue;
319
+ const e = Number.isFinite(errs[k]) ? errs[k] : 0;
320
+ const cx = f.px(xs[k]);
321
+ const top = f.py(ys[k] + e);
322
+ const bot = f.py(ys[k] - e);
323
+ out.push({
324
+ k: "line",
325
+ x1: cx,
326
+ y1: top,
327
+ x2: cx,
328
+ y2: bot,
329
+ stroke: color,
330
+ w: 1.5
331
+ }, {
332
+ k: "line",
333
+ x1: cx - 3,
334
+ y1: top,
335
+ x2: cx + 3,
336
+ y2: top,
337
+ stroke: color,
338
+ w: 1.5
339
+ }, {
340
+ k: "line",
341
+ x1: cx - 3,
342
+ y1: bot,
343
+ x2: cx + 3,
344
+ y2: bot,
345
+ stroke: color,
346
+ w: 1.5
347
+ }, {
348
+ k: "circle",
349
+ cx,
350
+ cy: f.py(ys[k]),
351
+ r: 3,
352
+ fill: color
353
+ });
354
+ }
355
+ return out;
351
356
  }
352
357
  function renderQuiver(layer, f, i) {
353
- const color = layer.color ?? f.color(i);
354
- const xs = layer.x ? coerce1dPositional(layer.x) : [];
355
- const ys = coerce1dPositional(layer.y);
356
- const us = coerce1dPositional(layer.u ?? []);
357
- const vs = coerce1dPositional(layer.v ?? []);
358
- const n = Math.min(xs.length, ys.length, us.length, vs.length);
359
- const out = [];
360
- for (let k = 0; k < n; k++) {
361
- if (![xs[k], ys[k], us[k], vs[k]].every(Number.isFinite)) continue;
362
- const x0 = f.px(xs[k]);
363
- const y0 = f.py(ys[k]);
364
- const x1 = f.px(xs[k] + us[k]);
365
- const y1 = f.py(ys[k] + vs[k]);
366
- const ang = Math.atan2(y1 - y0, x1 - x0);
367
- const ah = 5;
368
- out.push(
369
- { k: "line", x1: x0, y1: y0, x2: x1, y2: y1, stroke: color, w: 1.5 },
370
- {
371
- k: "line",
372
- x1,
373
- y1,
374
- x2: x1 - ah * Math.cos(ang - 0.4),
375
- y2: y1 - ah * Math.sin(ang - 0.4),
376
- stroke: color,
377
- w: 1.5
378
- },
379
- {
380
- k: "line",
381
- x1,
382
- y1,
383
- x2: x1 - ah * Math.cos(ang + 0.4),
384
- y2: y1 - ah * Math.sin(ang + 0.4),
385
- stroke: color,
386
- w: 1.5
387
- }
388
- );
389
- }
390
- return out;
358
+ const color = layer.color ?? f.color(i);
359
+ const xs = layer.x ? coerce1dPositional(layer.x) : [];
360
+ const ys = coerce1dPositional(layer.y);
361
+ const us = coerce1dPositional(layer.u ?? []);
362
+ const vs = coerce1dPositional(layer.v ?? []);
363
+ const n = Math.min(xs.length, ys.length, us.length, vs.length);
364
+ const out = [];
365
+ for (let k = 0; k < n; k++) {
366
+ if (![
367
+ xs[k],
368
+ ys[k],
369
+ us[k],
370
+ vs[k]
371
+ ].every(Number.isFinite)) continue;
372
+ const x0 = f.px(xs[k]);
373
+ const y0 = f.py(ys[k]);
374
+ const x1 = f.px(xs[k] + us[k]);
375
+ const y1 = f.py(ys[k] + vs[k]);
376
+ const ang = Math.atan2(y1 - y0, x1 - x0);
377
+ const ah = 5;
378
+ out.push({
379
+ k: "line",
380
+ x1: x0,
381
+ y1: y0,
382
+ x2: x1,
383
+ y2: y1,
384
+ stroke: color,
385
+ w: 1.5
386
+ }, {
387
+ k: "line",
388
+ x1,
389
+ y1,
390
+ x2: x1 - ah * Math.cos(ang - .4),
391
+ y2: y1 - ah * Math.sin(ang - .4),
392
+ stroke: color,
393
+ w: 1.5
394
+ }, {
395
+ k: "line",
396
+ x1,
397
+ y1,
398
+ x2: x1 - ah * Math.cos(ang + .4),
399
+ y2: y1 - ah * Math.sin(ang + .4),
400
+ stroke: color,
401
+ w: 1.5
402
+ });
403
+ }
404
+ return out;
391
405
  }
392
406
  function renderLayer(layer, f, i) {
393
- switch (layer.type) {
394
- case "line":
395
- return renderLine(layer, f, i);
396
- case "scatter":
397
- return renderScatter(layer, f, i);
398
- case "bar":
399
- return renderBar(layer, f, i);
400
- case "area":
401
- return renderArea(layer, f, i);
402
- case "step":
403
- return renderStep(layer, f, i);
404
- case "errorbar":
405
- return renderErrorbar(layer, f, i);
406
- case "quiver":
407
- return renderQuiver(layer, f, i);
408
- default:
409
- return renderLine(layer, f, i);
410
- }
411
- }
412
-
413
- // src/tikz.ts
407
+ switch (layer.type) {
408
+ case "line": return renderLine(layer, f, i);
409
+ case "scatter": return renderScatter(layer, f, i);
410
+ case "bar": return renderBar(layer, f, i);
411
+ case "area": return renderArea(layer, f, i);
412
+ case "step": return renderStep(layer, f, i);
413
+ case "errorbar": return renderErrorbar(layer, f, i);
414
+ case "quiver": return renderQuiver(layer, f, i);
415
+ default: return renderLine(layer, f, i);
416
+ }
417
+ }
418
+ //#endregion
419
+ //#region src/tikz.ts
414
420
  function texEsc(s) {
415
- return s.replace(/[\\&%$#_{}~^]/g, (c) => {
416
- switch (c) {
417
- case "\\":
418
- return "\\textbackslash{}";
419
- case "~":
420
- return "\\textasciitilde{}";
421
- case "^":
422
- return "\\textasciicircum{}";
423
- default:
424
- return `\\${c}`;
425
- }
426
- });
427
- }
428
- var TIKZ_COLOR_NAME = /^[A-Za-z][A-Za-z0-9!.]*$/;
421
+ return s.replace(/[\\&%$#_{}~^]/g, (c) => {
422
+ switch (c) {
423
+ case "\\": return "\\textbackslash{}";
424
+ case "~": return "\\textasciitilde{}";
425
+ case "^": return "\\textasciicircum{}";
426
+ default: return `\\${c}`;
427
+ }
428
+ });
429
+ }
430
+ const TIKZ_COLOR_NAME = /^[A-Za-z][A-Za-z0-9!.]*$/;
429
431
  function tikzColor(c) {
430
- if (c === "none") return { color: "none", alpha: 1 };
431
- if (c.startsWith("#")) {
432
- const h = c.slice(1);
433
- const r = parseInt(h.slice(0, 2), 16);
434
- const g = parseInt(h.slice(2, 4), 16);
435
- const b = parseInt(h.slice(4, 6), 16);
436
- const alpha = h.length >= 8 ? Math.round(parseInt(h.slice(6, 8), 16) / 255 * 100) / 100 : 1;
437
- return { color: `{rgb,255:red,${r};green,${g};blue,${b}}`, alpha };
438
- }
439
- return { color: TIKZ_COLOR_NAME.test(c) ? c : "black", alpha: 1 };
440
- }
441
- var round = (n) => Math.round(n * 100) / 100;
442
- var ANCHOR = {
443
- start: "base west",
444
- middle: "base",
445
- end: "base east"
432
+ if (c === "none") return {
433
+ color: "none",
434
+ alpha: 1
435
+ };
436
+ if (c.startsWith("#")) {
437
+ const h = c.slice(1);
438
+ const r = parseInt(h.slice(0, 2), 16);
439
+ const g = parseInt(h.slice(2, 4), 16);
440
+ const b = parseInt(h.slice(4, 6), 16);
441
+ const alpha = h.length >= 8 ? Math.round(parseInt(h.slice(6, 8), 16) / 255 * 100) / 100 : 1;
442
+ return {
443
+ color: `{rgb,255:red,${r};green,${g};blue,${b}}`,
444
+ alpha
445
+ };
446
+ }
447
+ return {
448
+ color: TIKZ_COLOR_NAME.test(c) ? c : "black",
449
+ alpha: 1
450
+ };
451
+ }
452
+ const round = (n) => Math.round(n * 100) / 100;
453
+ const ANCHOR = {
454
+ start: "base west",
455
+ middle: "base",
456
+ end: "base east"
446
457
  };
447
458
  function primTikZ(p, X, Y, s) {
448
- const pt = (x, y) => `(${X(x)},${Y(y)})`;
449
- const path = (pts) => pts.map(([x, y]) => pt(x, y)).join(" -- ");
450
- switch (p.k) {
451
- case "line": {
452
- const { color } = tikzColor(p.stroke);
453
- const op = p.opacity !== void 0 && p.opacity < 1 ? `,draw opacity=${round(p.opacity)}` : "";
454
- return `\\draw[color=${color},line width=${round(p.w * s)}pt${op}] ${pt(p.x1, p.y1)} -- ${pt(p.x2, p.y2)};`;
455
- }
456
- case "polyline": {
457
- const { color } = tikzColor(p.stroke);
458
- return `\\draw[color=${color},line width=${round(p.w * s)}pt] ${path(p.pts)};`;
459
- }
460
- case "polygon": {
461
- const f = tikzColor(p.fill);
462
- const st = tikzColor(p.stroke);
463
- const drawOpt = p.stroke === "none" ? "draw=none" : `draw=${st.color}`;
464
- const op = f.alpha < 1 ? `,fill opacity=${f.alpha}` : "";
465
- return `\\filldraw[fill=${f.color},${drawOpt}${op}] ${path(p.pts)} -- cycle;`;
466
- }
467
- case "rect": {
468
- const f = tikzColor(p.fill);
469
- const op = f.alpha < 1 ? `[fill opacity=${f.alpha}]` : "";
470
- return `\\fill${op}[color=${f.color}] ${pt(p.x, p.y)} rectangle ${pt(p.x + p.w, p.y + p.h)};`;
471
- }
472
- case "circle": {
473
- const f = tikzColor(p.fill);
474
- const a = (p.opacity ?? 1) * f.alpha;
475
- const op = a < 1 ? `,fill opacity=${round(a)}` : "";
476
- return `\\filldraw[fill=${f.color},draw=none${op}] ${pt(p.cx, p.cy)} circle (${round(p.r * s)}pt);`;
477
- }
478
- case "text": {
479
- const { color } = tikzColor(p.fill);
480
- const rot = p.rotate !== void 0 ? `,rotate=${p.rotate}` : "";
481
- return `\\node[anchor=${ANCHOR[p.anchor]},text=${color},font=\\fontsize{${round(p.size * s)}}{${round(p.size * s * 1.2)}}\\selectfont${rot}] at ${pt(p.x, p.y)} {${texEsc(p.s)}};`;
482
- }
483
- }
459
+ const pt = (x, y) => `(${X(x)},${Y(y)})`;
460
+ const path = (pts) => pts.map(([x, y]) => pt(x, y)).join(" -- ");
461
+ switch (p.k) {
462
+ case "line": {
463
+ const { color } = tikzColor(p.stroke);
464
+ const op = p.opacity !== void 0 && p.opacity < 1 ? `,draw opacity=${round(p.opacity)}` : "";
465
+ return `\\draw[color=${color},line width=${round(p.w * s)}pt${op}] ${pt(p.x1, p.y1)} -- ${pt(p.x2, p.y2)};`;
466
+ }
467
+ case "polyline": {
468
+ const { color } = tikzColor(p.stroke);
469
+ return `\\draw[color=${color},line width=${round(p.w * s)}pt] ${path(p.pts)};`;
470
+ }
471
+ case "polygon": {
472
+ const f = tikzColor(p.fill);
473
+ const st = tikzColor(p.stroke);
474
+ const drawOpt = p.stroke === "none" ? "draw=none" : `draw=${st.color}`;
475
+ const op = f.alpha < 1 ? `,fill opacity=${f.alpha}` : "";
476
+ return `\\filldraw[fill=${f.color},${drawOpt}${op}] ${path(p.pts)} -- cycle;`;
477
+ }
478
+ case "rect": {
479
+ const f = tikzColor(p.fill);
480
+ return `\\fill${f.alpha < 1 ? `[fill opacity=${f.alpha}]` : ""}[color=${f.color}] ${pt(p.x, p.y)} rectangle ${pt(p.x + p.w, p.y + p.h)};`;
481
+ }
482
+ case "circle": {
483
+ const f = tikzColor(p.fill);
484
+ const a = (p.opacity ?? 1) * f.alpha;
485
+ const op = a < 1 ? `,fill opacity=${round(a)}` : "";
486
+ return `\\filldraw[fill=${f.color},draw=none${op}] ${pt(p.cx, p.cy)} circle (${round(p.r * s)}pt);`;
487
+ }
488
+ case "text": {
489
+ const { color } = tikzColor(p.fill);
490
+ const rot = p.rotate !== void 0 ? `,rotate=${p.rotate}` : "";
491
+ return `\\node[anchor=${ANCHOR[p.anchor]},text=${color},font=\\fontsize{${round(p.size * s)}}{${round(p.size * s * 1.2)}}\\selectfont${rot}] at ${pt(p.x, p.y)} {${texEsc(p.s)}};`;
492
+ }
493
+ }
484
494
  }
485
495
  function emitTikZ(scene, opts = {}) {
486
- const s = opts.tikz?.scale ?? 0.75;
487
- const X = (x) => round(x * s);
488
- const Y = (y) => round((scene.height - y) * s);
489
- const bg = tikzColor(scene.bg);
490
- const body = [
491
- `\\fill[color=${bg.color}] (${X(0)},${Y(scene.height)}) rectangle (${X(scene.width)},${Y(0)});`,
492
- ...scene.prims.map((p) => primTikZ(p, X, Y, s))
493
- ];
494
- const pic = `\\begin{tikzpicture}[x=1pt,y=1pt]
495
- ${body.join("\n")}
496
- \\end{tikzpicture}`;
497
- if (opts.tikz?.standalone === false) return pic;
498
- return `\\documentclass[tikz,border=2pt]{standalone}
499
- \\begin{document}
500
- ${pic}
501
- \\end{document}`;
502
- }
503
-
504
- // src/emit.ts
496
+ const s = opts.tikz?.scale ?? .75;
497
+ const X = (x) => round(x * s);
498
+ const Y = (y) => round((scene.height - y) * s);
499
+ const pic = `\\begin{tikzpicture}[x=1pt,y=1pt]\n${[`\\fill[color=${tikzColor(scene.bg).color}] (${X(0)},${Y(scene.height)}) rectangle (${X(scene.width)},${Y(0)});`, ...scene.prims.map((p) => primTikZ(p, X, Y, s))].join("\n")}\n\\end{tikzpicture}`;
500
+ if (opts.tikz?.standalone === false) return pic;
501
+ return `\\documentclass[tikz,border=2pt]{standalone}\n\\begin{document}\n${pic}\n\\end{document}`;
502
+ }
503
+ //#endregion
504
+ //#region src/emit.ts
505
505
  function emit(scene, opts = {}) {
506
- return opts.format === "tikz" ? emitTikZ(scene, opts) : emitSVG(scene);
507
- }
508
-
509
- // src/frame.ts
510
- var MARGIN = { top: 34, right: 20, bottom: 46, left: 64 };
506
+ return opts.format === "tikz" ? emitTikZ(scene, opts) : emitSVG(scene);
507
+ }
508
+ //#endregion
509
+ //#region src/frame.ts
510
+ const MARGIN$4 = {
511
+ top: 34,
512
+ right: 20,
513
+ bottom: 46,
514
+ left: 64
515
+ };
511
516
  function combinedExtent(layers, pick) {
512
- const all = [];
513
- for (const l of layers) all.push(...pick(l));
514
- return extent(all);
517
+ const all = [];
518
+ for (const l of layers) all.push(...pick(l));
519
+ return extent(all);
515
520
  }
516
521
  function noDataScene(width, height, theme) {
517
- return {
518
- width,
519
- height,
520
- bg: theme.bg,
521
- prims: [
522
- {
523
- k: "text",
524
- x: width / 2,
525
- y: height / 2,
526
- s: "no data",
527
- fill: theme.muted,
528
- anchor: "middle",
529
- size: 13
530
- }
531
- ]
532
- };
522
+ return {
523
+ width,
524
+ height,
525
+ bg: theme.bg,
526
+ prims: [{
527
+ k: "text",
528
+ x: width / 2,
529
+ y: height / 2,
530
+ s: "no data",
531
+ fill: theme.muted,
532
+ anchor: "middle",
533
+ size: 13
534
+ }]
535
+ };
533
536
  }
534
537
  function draw2D(layers, opts = {}) {
535
- const width = opts.width ?? 520;
536
- const height = opts.height ?? 320;
537
- const theme = THEMES[opts.theme ?? "light"];
538
- try {
539
- const yAll = layers.flatMap((l) => coerce1d(l.y));
540
- if (yAll.length === 0) return emit(noDataScene(width, height, theme), opts);
541
- const xdom = combinedExtent(
542
- layers,
543
- (l) => l.x ? coerce1d(l.x) : coerce1d(l.y).map((_, i) => i)
544
- );
545
- const ydom = combinedExtent(layers, (l) => coerce1d(l.y));
546
- if (!Number.isFinite(xdom[0]) || !Number.isFinite(ydom[0]))
547
- return emit(noDataScene(width, height, theme), opts);
548
- const innerX = [MARGIN.left, width - MARGIN.right];
549
- const innerY = [height - MARGIN.bottom, MARGIN.top];
550
- const xScaleFn = opts.x?.scale === "log" ? logScale : linearScale;
551
- const yScaleFn = opts.y?.scale === "log" ? logScale : linearScale;
552
- const px = xScaleFn(xdom, innerX);
553
- const py = yScaleFn(ydom, innerY);
554
- const frame = {
555
- px,
556
- py,
557
- xdom,
558
- ydom,
559
- theme,
560
- width,
561
- height,
562
- color: (i) => theme.series[i % theme.series.length]
563
- };
564
- const prims = [];
565
- const xticks = niceTicks(xdom[0], xdom[1]).filter((t) => t >= xdom[0] && t <= xdom[1]);
566
- const yticks = niceTicks(ydom[0], ydom[1]).filter((t) => t >= ydom[0] && t <= ydom[1]);
567
- for (const t of xticks)
568
- prims.push({
569
- k: "line",
570
- x1: px(t),
571
- y1: innerY[0],
572
- x2: px(t),
573
- y2: innerY[1],
574
- stroke: theme.grid,
575
- w: 1
576
- });
577
- for (const t of yticks)
578
- prims.push({
579
- k: "line",
580
- x1: innerX[0],
581
- y1: py(t),
582
- x2: innerX[1],
583
- y2: py(t),
584
- stroke: theme.grid,
585
- w: 1
586
- });
587
- prims.push({
588
- k: "line",
589
- x1: MARGIN.left,
590
- y1: height - MARGIN.bottom,
591
- x2: width - MARGIN.right,
592
- y2: height - MARGIN.bottom,
593
- stroke: theme.axis,
594
- w: 1
595
- });
596
- prims.push({
597
- k: "line",
598
- x1: MARGIN.left,
599
- y1: MARGIN.top,
600
- x2: MARGIN.left,
601
- y2: height - MARGIN.bottom,
602
- stroke: theme.axis,
603
- w: 1
604
- });
605
- for (const t of xticks)
606
- prims.push({
607
- k: "text",
608
- x: px(t),
609
- y: height - MARGIN.bottom + 16,
610
- s: fmt(t),
611
- fill: theme.muted,
612
- anchor: "middle",
613
- size: 11
614
- });
615
- for (const t of yticks)
616
- prims.push({
617
- k: "text",
618
- x: MARGIN.left - 8,
619
- y: py(t) + 4,
620
- s: fmt(t),
621
- fill: theme.muted,
622
- anchor: "end",
623
- size: 11
624
- });
625
- if (opts.title)
626
- prims.push({
627
- k: "text",
628
- x: width / 2,
629
- y: 20,
630
- s: opts.title,
631
- fill: theme.fg,
632
- anchor: "middle",
633
- size: 14
634
- });
635
- if (opts.xLabel)
636
- prims.push({
637
- k: "text",
638
- x: MARGIN.left + (width - MARGIN.left - MARGIN.right) / 2,
639
- y: height - 8,
640
- s: opts.xLabel,
641
- fill: theme.fg,
642
- anchor: "middle",
643
- size: 12
644
- });
645
- if (opts.yLabel)
646
- prims.push({
647
- k: "text",
648
- x: 16,
649
- y: MARGIN.top + (height - MARGIN.top - MARGIN.bottom) / 2,
650
- s: opts.yLabel,
651
- fill: theme.fg,
652
- anchor: "middle",
653
- size: 12,
654
- rotate: -90
655
- });
656
- layers.forEach((l, i) => prims.push(...renderLayer(l, frame, i)));
657
- if (opts.legend && layers.some((l) => l.label)) {
658
- layers.forEach((l, i) => {
659
- if (!l.label) return;
660
- const ly = MARGIN.top + 4 + i * 16;
661
- const c = l.color ?? frame.color(i);
662
- prims.push({ k: "rect", x: width - MARGIN.right - 120, y: ly - 8, w: 10, h: 10, fill: c });
663
- prims.push({
664
- k: "text",
665
- x: width - MARGIN.right - 106,
666
- y: ly + 1,
667
- s: l.label,
668
- fill: theme.fg,
669
- anchor: "start",
670
- size: 11
671
- });
672
- });
673
- }
674
- return emit({ width, height, bg: theme.bg, prims }, opts);
675
- } catch {
676
- return emit(noDataScene(width, height, theme), opts);
677
- }
678
- }
679
-
680
- // src/marks2d.ts
681
- function line2(x, y, opts) {
682
- return draw2D([{ type: "line", x, y }], opts);
538
+ const width = opts.width ?? 520;
539
+ const height = opts.height ?? 320;
540
+ const theme = THEMES[opts.theme ?? "light"];
541
+ try {
542
+ if (layers.flatMap((l) => coerce1d(l.y)).length === 0) return emit(noDataScene(width, height, theme), opts);
543
+ const xdom = combinedExtent(layers, (l) => l.x ? coerce1d(l.x) : coerce1d(l.y).map((_, i) => i));
544
+ const ydom = combinedExtent(layers, (l) => coerce1d(l.y));
545
+ if (!Number.isFinite(xdom[0]) || !Number.isFinite(ydom[0])) return emit(noDataScene(width, height, theme), opts);
546
+ const innerX = [MARGIN$4.left, width - MARGIN$4.right];
547
+ const innerY = [height - MARGIN$4.bottom, MARGIN$4.top];
548
+ const xScaleFn = opts.x?.scale === "log" ? logScale : linearScale;
549
+ const yScaleFn = opts.y?.scale === "log" ? logScale : linearScale;
550
+ const px = xScaleFn(xdom, innerX);
551
+ const py = yScaleFn(ydom, innerY);
552
+ const frame = {
553
+ px,
554
+ py,
555
+ xdom,
556
+ ydom,
557
+ theme,
558
+ width,
559
+ height,
560
+ color: (i) => theme.series[i % theme.series.length]
561
+ };
562
+ const prims = [];
563
+ const xticks = niceTicks(xdom[0], xdom[1]).filter((t) => t >= xdom[0] && t <= xdom[1]);
564
+ const yticks = niceTicks(ydom[0], ydom[1]).filter((t) => t >= ydom[0] && t <= ydom[1]);
565
+ for (const t of xticks) prims.push({
566
+ k: "line",
567
+ x1: px(t),
568
+ y1: innerY[0],
569
+ x2: px(t),
570
+ y2: innerY[1],
571
+ stroke: theme.grid,
572
+ w: 1
573
+ });
574
+ for (const t of yticks) prims.push({
575
+ k: "line",
576
+ x1: innerX[0],
577
+ y1: py(t),
578
+ x2: innerX[1],
579
+ y2: py(t),
580
+ stroke: theme.grid,
581
+ w: 1
582
+ });
583
+ prims.push({
584
+ k: "line",
585
+ x1: MARGIN$4.left,
586
+ y1: height - MARGIN$4.bottom,
587
+ x2: width - MARGIN$4.right,
588
+ y2: height - MARGIN$4.bottom,
589
+ stroke: theme.axis,
590
+ w: 1
591
+ });
592
+ prims.push({
593
+ k: "line",
594
+ x1: MARGIN$4.left,
595
+ y1: MARGIN$4.top,
596
+ x2: MARGIN$4.left,
597
+ y2: height - MARGIN$4.bottom,
598
+ stroke: theme.axis,
599
+ w: 1
600
+ });
601
+ for (const t of xticks) prims.push({
602
+ k: "text",
603
+ x: px(t),
604
+ y: height - MARGIN$4.bottom + 16,
605
+ s: fmt(t),
606
+ fill: theme.muted,
607
+ anchor: "middle",
608
+ size: 11
609
+ });
610
+ for (const t of yticks) prims.push({
611
+ k: "text",
612
+ x: MARGIN$4.left - 8,
613
+ y: py(t) + 4,
614
+ s: fmt(t),
615
+ fill: theme.muted,
616
+ anchor: "end",
617
+ size: 11
618
+ });
619
+ if (opts.title) prims.push({
620
+ k: "text",
621
+ x: width / 2,
622
+ y: 20,
623
+ s: opts.title,
624
+ fill: theme.fg,
625
+ anchor: "middle",
626
+ size: 14
627
+ });
628
+ if (opts.xLabel) prims.push({
629
+ k: "text",
630
+ x: MARGIN$4.left + (width - MARGIN$4.left - MARGIN$4.right) / 2,
631
+ y: height - 8,
632
+ s: opts.xLabel,
633
+ fill: theme.fg,
634
+ anchor: "middle",
635
+ size: 12
636
+ });
637
+ if (opts.yLabel) prims.push({
638
+ k: "text",
639
+ x: 16,
640
+ y: MARGIN$4.top + (height - MARGIN$4.top - MARGIN$4.bottom) / 2,
641
+ s: opts.yLabel,
642
+ fill: theme.fg,
643
+ anchor: "middle",
644
+ size: 12,
645
+ rotate: -90
646
+ });
647
+ layers.forEach((l, i) => prims.push(...renderLayer(l, frame, i)));
648
+ if (opts.legend && layers.some((l) => l.label)) layers.forEach((l, i) => {
649
+ if (!l.label) return;
650
+ const ly = MARGIN$4.top + 4 + i * 16;
651
+ const c = l.color ?? frame.color(i);
652
+ prims.push({
653
+ k: "rect",
654
+ x: width - MARGIN$4.right - 120,
655
+ y: ly - 8,
656
+ w: 10,
657
+ h: 10,
658
+ fill: c
659
+ });
660
+ prims.push({
661
+ k: "text",
662
+ x: width - MARGIN$4.right - 106,
663
+ y: ly + 1,
664
+ s: l.label,
665
+ fill: theme.fg,
666
+ anchor: "start",
667
+ size: 11
668
+ });
669
+ });
670
+ return emit({
671
+ width,
672
+ height,
673
+ bg: theme.bg,
674
+ prims
675
+ }, opts);
676
+ } catch {
677
+ return emit(noDataScene(width, height, theme), opts);
678
+ }
679
+ }
680
+ //#endregion
681
+ //#region src/marks2d.ts
682
+ function line(x, y, opts) {
683
+ return draw2D([{
684
+ type: "line",
685
+ x,
686
+ y
687
+ }], opts);
683
688
  }
684
689
  function scatter(x, y, opts) {
685
- return draw2D([{ type: "scatter", x, y }], opts);
690
+ return draw2D([{
691
+ type: "scatter",
692
+ x,
693
+ y
694
+ }], opts);
686
695
  }
687
696
  function bar(x, y, opts) {
688
- return draw2D([{ type: "bar", x, y }], opts);
697
+ return draw2D([{
698
+ type: "bar",
699
+ x,
700
+ y
701
+ }], opts);
689
702
  }
690
703
  function area(x, y, opts) {
691
- return draw2D([{ type: "area", x, y }], opts);
704
+ return draw2D([{
705
+ type: "area",
706
+ x,
707
+ y
708
+ }], opts);
692
709
  }
693
710
  function step(x, y, opts) {
694
- return draw2D([{ type: "step", x, y }], opts);
711
+ return draw2D([{
712
+ type: "step",
713
+ x,
714
+ y
715
+ }], opts);
695
716
  }
696
717
  function errorbar(x, y, yerr, opts) {
697
- return draw2D([{ type: "errorbar", x, y, yerr }], opts);
718
+ return draw2D([{
719
+ type: "errorbar",
720
+ x,
721
+ y,
722
+ yerr
723
+ }], opts);
698
724
  }
699
725
  function quiver(x, y, u, v, opts) {
700
- return draw2D([{ type: "quiver", x, y, u, v }], opts);
701
- }
702
-
703
- // src/overlay.ts
726
+ return draw2D([{
727
+ type: "quiver",
728
+ x,
729
+ y,
730
+ u,
731
+ v
732
+ }], opts);
733
+ }
734
+ //#endregion
735
+ //#region src/overlay.ts
704
736
  function overlay(layers, opts) {
705
- return draw2D(layers, opts);
706
- }
707
-
708
- // src/palette.ts
709
- var STOPS = [
710
- [68, 1, 84],
711
- [72, 40, 120],
712
- [62, 74, 137],
713
- [49, 104, 142],
714
- [38, 130, 142],
715
- [31, 158, 137],
716
- [53, 183, 121],
717
- [110, 206, 88],
718
- [253, 231, 37]
737
+ return draw2D(layers, opts);
738
+ }
739
+ //#endregion
740
+ //#region src/palette.ts
741
+ const STOPS = [
742
+ [
743
+ 68,
744
+ 1,
745
+ 84
746
+ ],
747
+ [
748
+ 72,
749
+ 40,
750
+ 120
751
+ ],
752
+ [
753
+ 62,
754
+ 74,
755
+ 137
756
+ ],
757
+ [
758
+ 49,
759
+ 104,
760
+ 142
761
+ ],
762
+ [
763
+ 38,
764
+ 130,
765
+ 142
766
+ ],
767
+ [
768
+ 31,
769
+ 158,
770
+ 137
771
+ ],
772
+ [
773
+ 53,
774
+ 183,
775
+ 121
776
+ ],
777
+ [
778
+ 110,
779
+ 206,
780
+ 88
781
+ ],
782
+ [
783
+ 253,
784
+ 231,
785
+ 37
786
+ ]
719
787
  ];
720
- var hex = (n) => Math.round(n).toString(16).padStart(2, "0");
788
+ const hex = (n) => Math.round(n).toString(16).padStart(2, "0");
721
789
  function viridis(t) {
722
- const c = Math.max(0, Math.min(1, t));
723
- const s = c * (STOPS.length - 1);
724
- const i = Math.min(STOPS.length - 2, Math.floor(s));
725
- const f = s - i;
726
- const a = STOPS[i];
727
- const b = STOPS[i + 1];
728
- const r = a[0] + (b[0] - a[0]) * f;
729
- const g = a[1] + (b[1] - a[1]) * f;
730
- const bl = a[2] + (b[2] - a[2]) * f;
731
- return `#${hex(r)}${hex(g)}${hex(bl)}`;
732
- }
733
-
734
- // src/contour.ts
735
- var MARGIN2 = { top: 34, right: 22, bottom: 30, left: 40 };
790
+ const s = Math.max(0, Math.min(1, t)) * (STOPS.length - 1);
791
+ const i = Math.min(STOPS.length - 2, Math.floor(s));
792
+ const f = s - i;
793
+ const a = STOPS[i];
794
+ const b = STOPS[i + 1];
795
+ const r = a[0] + (b[0] - a[0]) * f;
796
+ const g = a[1] + (b[1] - a[1]) * f;
797
+ const bl = a[2] + (b[2] - a[2]) * f;
798
+ return `#${hex(r)}${hex(g)}${hex(bl)}`;
799
+ }
800
+ //#endregion
801
+ //#region src/contour.ts
802
+ const MARGIN$3 = {
803
+ top: 34,
804
+ right: 22,
805
+ bottom: 30,
806
+ left: 40
807
+ };
736
808
  function contour(z, opts = {}) {
737
- const width = opts.width ?? 520;
738
- const height = opts.height ?? 320;
739
- const theme = THEMES[opts.theme ?? "light"];
740
- const grid = coerce2d(z);
741
- const rows = grid.length;
742
- const cols = rows ? grid[0].length : 0;
743
- if (rows < 2 || cols < 2) {
744
- const scene = {
745
- width,
746
- height,
747
- bg: theme.bg,
748
- prims: [
749
- {
750
- k: "text",
751
- x: width / 2,
752
- y: height / 2,
753
- s: "no data",
754
- fill: theme.muted,
755
- anchor: "middle",
756
- size: 13
757
- }
758
- ]
759
- };
760
- return emit(scene, opts);
761
- }
762
- let lo = Infinity;
763
- let hi = -Infinity;
764
- for (const row of grid)
765
- for (const v of row)
766
- if (Number.isFinite(v)) {
767
- if (v < lo) lo = v;
768
- if (v > hi) hi = v;
769
- }
770
- const nLevels = opts.levels ?? 10;
771
- const px = linearScale([0, cols - 1], [MARGIN2.left, width - MARGIN2.right]);
772
- const py = linearScale([0, rows - 1], [height - MARGIN2.bottom, MARGIN2.top]);
773
- const prims = [];
774
- const span = hi > lo ? hi - lo : 1;
775
- for (let li = 1; li <= nLevels; li++) {
776
- const level = lo + span * li / (nLevels + 1);
777
- const color = viridis(li / (nLevels + 1));
778
- const interp = (va, vb) => (level - va) / (vb - va);
779
- for (let r = 0; r < rows - 1; r++) {
780
- for (let c = 0; c < cols - 1; c++) {
781
- const tl = grid[r][c];
782
- const tr = grid[r][c + 1];
783
- const br = grid[r + 1][c + 1];
784
- const bl = grid[r + 1][c];
785
- if (![tl, tr, br, bl].every(Number.isFinite)) continue;
786
- const cross = [];
787
- if (tl < level !== tr < level) cross.push([c + interp(tl, tr), r]);
788
- if (tr < level !== br < level) cross.push([c + 1, r + interp(tr, br)]);
789
- if (bl < level !== br < level) cross.push([c + interp(bl, br), r + 1]);
790
- if (tl < level !== bl < level) cross.push([c, r + interp(tl, bl)]);
791
- for (let k = 0; k + 1 < cross.length; k += 2) {
792
- const [ax, ay] = cross[k];
793
- const [bx, by] = cross[k + 1];
794
- prims.push({
795
- k: "line",
796
- x1: px(ax),
797
- y1: py(ay),
798
- x2: px(bx),
799
- y2: py(by),
800
- stroke: color,
801
- w: 1.5
802
- });
803
- }
804
- }
805
- }
806
- }
807
- if (opts.title)
808
- prims.push({
809
- k: "text",
810
- x: width / 2,
811
- y: 20,
812
- s: opts.title,
813
- fill: theme.fg,
814
- anchor: "middle",
815
- size: 14
816
- });
817
- return emit({ width, height, bg: theme.bg, prims }, opts);
818
- }
819
-
820
- // src/three/project.ts
809
+ const width = opts.width ?? 520;
810
+ const height = opts.height ?? 320;
811
+ const theme = THEMES[opts.theme ?? "light"];
812
+ const grid = coerce2d(z);
813
+ const rows = grid.length;
814
+ const cols = rows ? grid[0].length : 0;
815
+ if (rows < 2 || cols < 2) return emit({
816
+ width,
817
+ height,
818
+ bg: theme.bg,
819
+ prims: [{
820
+ k: "text",
821
+ x: width / 2,
822
+ y: height / 2,
823
+ s: "no data",
824
+ fill: theme.muted,
825
+ anchor: "middle",
826
+ size: 13
827
+ }]
828
+ }, opts);
829
+ let lo = Infinity;
830
+ let hi = -Infinity;
831
+ for (const row of grid) for (const v of row) if (Number.isFinite(v)) {
832
+ if (v < lo) lo = v;
833
+ if (v > hi) hi = v;
834
+ }
835
+ const nLevels = opts.levels ?? 10;
836
+ const px = linearScale([0, cols - 1], [MARGIN$3.left, width - MARGIN$3.right]);
837
+ const py = linearScale([0, rows - 1], [height - MARGIN$3.bottom, MARGIN$3.top]);
838
+ const prims = [];
839
+ const span = hi > lo ? hi - lo : 1;
840
+ for (let li = 1; li <= nLevels; li++) {
841
+ const level = lo + span * li / (nLevels + 1);
842
+ const color = viridis(li / (nLevels + 1));
843
+ const interp = (va, vb) => (level - va) / (vb - va);
844
+ for (let r = 0; r < rows - 1; r++) for (let c = 0; c < cols - 1; c++) {
845
+ const tl = grid[r][c];
846
+ const tr = grid[r][c + 1];
847
+ const br = grid[r + 1][c + 1];
848
+ const bl = grid[r + 1][c];
849
+ if (![
850
+ tl,
851
+ tr,
852
+ br,
853
+ bl
854
+ ].every(Number.isFinite)) continue;
855
+ const cross = [];
856
+ if (tl < level !== tr < level) cross.push([c + interp(tl, tr), r]);
857
+ if (tr < level !== br < level) cross.push([c + 1, r + interp(tr, br)]);
858
+ if (bl < level !== br < level) cross.push([c + interp(bl, br), r + 1]);
859
+ if (tl < level !== bl < level) cross.push([c, r + interp(tl, bl)]);
860
+ for (let k = 0; k + 1 < cross.length; k += 2) {
861
+ const [ax, ay] = cross[k];
862
+ const [bx, by] = cross[k + 1];
863
+ prims.push({
864
+ k: "line",
865
+ x1: px(ax),
866
+ y1: py(ay),
867
+ x2: px(bx),
868
+ y2: py(by),
869
+ stroke: color,
870
+ w: 1.5
871
+ });
872
+ }
873
+ }
874
+ }
875
+ if (opts.title) prims.push({
876
+ k: "text",
877
+ x: width / 2,
878
+ y: 20,
879
+ s: opts.title,
880
+ fill: theme.fg,
881
+ anchor: "middle",
882
+ size: 14
883
+ });
884
+ return emit({
885
+ width,
886
+ height,
887
+ bg: theme.bg,
888
+ prims
889
+ }, opts);
890
+ }
891
+ //#endregion
892
+ //#region src/three/project.ts
821
893
  function project(p, cam) {
822
- const az = cam.azim * Math.PI / 180;
823
- const el2 = cam.elev * Math.PI / 180;
824
- const [x, y, z] = p;
825
- const xa = x * Math.cos(az) - y * Math.sin(az);
826
- const ya = x * Math.sin(az) + y * Math.cos(az);
827
- const sx = xa;
828
- const sy = z * Math.cos(el2) - ya * Math.sin(el2);
829
- const depth = z * Math.sin(el2) + ya * Math.cos(el2);
830
- return [sx, sy, depth];
831
- }
832
-
833
- // src/three/surface.ts
834
- var MARGIN3 = 40;
835
- var DEPTH_EPS = 1e-9;
894
+ const az = cam.azim * Math.PI / 180;
895
+ const el = cam.elev * Math.PI / 180;
896
+ const [x, y, z] = p;
897
+ const xa = x * Math.cos(az) - y * Math.sin(az);
898
+ const ya = x * Math.sin(az) + y * Math.cos(az);
899
+ return [
900
+ xa,
901
+ z * Math.cos(el) - ya * Math.sin(el),
902
+ z * Math.sin(el) + ya * Math.cos(el)
903
+ ];
904
+ }
905
+ //#endregion
906
+ //#region src/three/surface.ts
907
+ const MARGIN$2 = 40;
836
908
  function compareDepth(a, b) {
837
- const d = b.depth - a.depth;
838
- if (Math.abs(d) > DEPTH_EPS) return d;
839
- return a.index - b.index;
909
+ const d = b.depth - a.depth;
910
+ if (Math.abs(d) > 1e-9) return d;
911
+ return a.index - b.index;
840
912
  }
841
913
  function surface(z, opts = {}) {
842
- const width = opts.width ?? 520;
843
- const height = opts.height ?? 320;
844
- const theme = THEMES[opts.theme ?? "light"];
845
- const grid = coerce2d(z);
846
- const rows = grid.length;
847
- const cols = rows ? grid[0].length : 0;
848
- if (rows < 2 || cols < 2) {
849
- const scene = {
850
- width,
851
- height,
852
- bg: theme.bg,
853
- prims: [
854
- {
855
- k: "text",
856
- x: width / 2,
857
- y: height / 2,
858
- s: "no data",
859
- fill: theme.muted,
860
- anchor: "middle",
861
- size: 13
862
- }
863
- ]
864
- };
865
- return emit(scene, opts);
866
- }
867
- const cam = { azim: opts.azim ?? 45, elev: opts.elev ?? 25 };
868
- let zlo = Infinity;
869
- let zhi = -Infinity;
870
- for (const row of grid)
871
- for (const v of row)
872
- if (Number.isFinite(v)) {
873
- if (v < zlo) zlo = v;
874
- if (v > zhi) zhi = v;
875
- }
876
- const zspan = zhi > zlo ? zhi - zlo : 1;
877
- const proj = [];
878
- let sxlo = Infinity, sxhi = -Infinity, sylo = Infinity, syhi = -Infinity;
879
- for (let r = 0; r < rows; r++) {
880
- const prow = [];
881
- for (let c = 0; c < cols; c++) {
882
- const wx = c / (cols - 1) * 2 - 1;
883
- const wy = r / (rows - 1) * 2 - 1;
884
- const wz = (grid[r][c] - zlo) / zspan * 2 - 1;
885
- const p = project([wx, wy, wz], cam);
886
- prow.push(p);
887
- if (p[0] < sxlo) sxlo = p[0];
888
- if (p[0] > sxhi) sxhi = p[0];
889
- if (p[1] < sylo) sylo = p[1];
890
- if (p[1] > syhi) syhi = p[1];
891
- }
892
- proj.push(prow);
893
- }
894
- const sxSpan = sxhi > sxlo ? sxhi - sxlo : 1;
895
- const sySpan = syhi > sylo ? syhi - sylo : 1;
896
- const toScreen = (p) => [
897
- MARGIN3 + (p[0] - sxlo) / sxSpan * (width - 2 * MARGIN3),
898
- height - MARGIN3 - (p[1] - sylo) / sySpan * (height - 2 * MARGIN3)
899
- ];
900
- const kind = opts.kind ?? "filled";
901
- const prims = [];
902
- if (kind === "wireframe") {
903
- for (let r = 0; r < rows; r++)
904
- prims.push({ k: "polyline", pts: proj[r].map(toScreen), stroke: theme.series[0], w: 1 });
905
- for (let c = 0; c < cols; c++)
906
- prims.push({
907
- k: "polyline",
908
- pts: proj.map((row) => toScreen(row[c])),
909
- stroke: theme.series[0],
910
- w: 1
911
- });
912
- } else {
913
- const quads = [];
914
- for (let r = 0; r < rows - 1; r++) {
915
- for (let c = 0; c < cols - 1; c++) {
916
- const corners = [proj[r][c], proj[r][c + 1], proj[r + 1][c + 1], proj[r + 1][c]];
917
- const depth = (corners[0][2] + corners[1][2] + corners[2][2] + corners[3][2]) / 4;
918
- const meanZ = (grid[r][c] + grid[r][c + 1] + grid[r + 1][c + 1] + grid[r + 1][c]) / 4;
919
- quads.push({
920
- depth,
921
- index: quads.length,
922
- pts: corners.map(toScreen),
923
- shade: (meanZ - zlo) / zspan
924
- });
925
- }
926
- }
927
- quads.sort(compareDepth);
928
- for (const q of quads)
929
- prims.push({ k: "polygon", pts: q.pts, fill: viridis(q.shade), stroke: theme.grid });
930
- }
931
- if (opts.title)
932
- prims.push({
933
- k: "text",
934
- x: width / 2,
935
- y: 20,
936
- s: opts.title,
937
- fill: theme.fg,
938
- anchor: "middle",
939
- size: 14
940
- });
941
- return emit({ width, height, bg: theme.bg, prims }, opts);
942
- }
943
-
944
- // src/plot.ts
914
+ const width = opts.width ?? 520;
915
+ const height = opts.height ?? 320;
916
+ const theme = THEMES[opts.theme ?? "light"];
917
+ const grid = coerce2d(z);
918
+ const rows = grid.length;
919
+ const cols = rows ? grid[0].length : 0;
920
+ if (rows < 2 || cols < 2) return emit({
921
+ width,
922
+ height,
923
+ bg: theme.bg,
924
+ prims: [{
925
+ k: "text",
926
+ x: width / 2,
927
+ y: height / 2,
928
+ s: "no data",
929
+ fill: theme.muted,
930
+ anchor: "middle",
931
+ size: 13
932
+ }]
933
+ }, opts);
934
+ const cam = {
935
+ azim: opts.azim ?? 45,
936
+ elev: opts.elev ?? 25
937
+ };
938
+ let zlo = Infinity;
939
+ let zhi = -Infinity;
940
+ for (const row of grid) for (const v of row) if (Number.isFinite(v)) {
941
+ if (v < zlo) zlo = v;
942
+ if (v > zhi) zhi = v;
943
+ }
944
+ const zspan = zhi > zlo ? zhi - zlo : 1;
945
+ const proj = [];
946
+ let sxlo = Infinity, sxhi = -Infinity, sylo = Infinity, syhi = -Infinity;
947
+ for (let r = 0; r < rows; r++) {
948
+ const prow = [];
949
+ for (let c = 0; c < cols; c++) {
950
+ const p = project([
951
+ c / (cols - 1) * 2 - 1,
952
+ r / (rows - 1) * 2 - 1,
953
+ (grid[r][c] - zlo) / zspan * 2 - 1
954
+ ], cam);
955
+ prow.push(p);
956
+ if (p[0] < sxlo) sxlo = p[0];
957
+ if (p[0] > sxhi) sxhi = p[0];
958
+ if (p[1] < sylo) sylo = p[1];
959
+ if (p[1] > syhi) syhi = p[1];
960
+ }
961
+ proj.push(prow);
962
+ }
963
+ const sxSpan = sxhi > sxlo ? sxhi - sxlo : 1;
964
+ const sySpan = syhi > sylo ? syhi - sylo : 1;
965
+ const toScreen = (p) => [MARGIN$2 + (p[0] - sxlo) / sxSpan * (width - 80), height - MARGIN$2 - (p[1] - sylo) / sySpan * (height - 80)];
966
+ const kind = opts.kind ?? "filled";
967
+ const prims = [];
968
+ if (kind === "wireframe") {
969
+ for (let r = 0; r < rows; r++) prims.push({
970
+ k: "polyline",
971
+ pts: proj[r].map(toScreen),
972
+ stroke: theme.series[0],
973
+ w: 1
974
+ });
975
+ for (let c = 0; c < cols; c++) prims.push({
976
+ k: "polyline",
977
+ pts: proj.map((row) => toScreen(row[c])),
978
+ stroke: theme.series[0],
979
+ w: 1
980
+ });
981
+ } else {
982
+ const quads = [];
983
+ for (let r = 0; r < rows - 1; r++) for (let c = 0; c < cols - 1; c++) {
984
+ const corners = [
985
+ proj[r][c],
986
+ proj[r][c + 1],
987
+ proj[r + 1][c + 1],
988
+ proj[r + 1][c]
989
+ ];
990
+ const depth = (corners[0][2] + corners[1][2] + corners[2][2] + corners[3][2]) / 4;
991
+ const meanZ = (grid[r][c] + grid[r][c + 1] + grid[r + 1][c + 1] + grid[r + 1][c]) / 4;
992
+ quads.push({
993
+ depth,
994
+ index: quads.length,
995
+ pts: corners.map(toScreen),
996
+ shade: (meanZ - zlo) / zspan
997
+ });
998
+ }
999
+ quads.sort(compareDepth);
1000
+ for (const q of quads) prims.push({
1001
+ k: "polygon",
1002
+ pts: q.pts,
1003
+ fill: viridis(q.shade),
1004
+ stroke: theme.grid
1005
+ });
1006
+ }
1007
+ if (opts.title) prims.push({
1008
+ k: "text",
1009
+ x: width / 2,
1010
+ y: 20,
1011
+ s: opts.title,
1012
+ fill: theme.fg,
1013
+ anchor: "middle",
1014
+ size: 14
1015
+ });
1016
+ return emit({
1017
+ width,
1018
+ height,
1019
+ bg: theme.bg,
1020
+ prims
1021
+ }, opts);
1022
+ }
1023
+ //#endregion
1024
+ //#region src/plot.ts
945
1025
  function isLayerArray(v) {
946
- return Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && "type" in v[0];
1026
+ return Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && "type" in v[0];
947
1027
  }
948
1028
  function freeVars(source) {
949
- const names = /* @__PURE__ */ new Set();
950
- const funcs = /* @__PURE__ */ new Set();
951
- try {
952
- const node = parse(source);
953
- if (typeof node.traverse === "function") {
954
- node.traverse((n) => {
955
- const nn = n;
956
- if (nn.type === "SymbolNode" && nn.name) names.add(nn.name);
957
- if (nn.type === "FunctionNode" && nn.name) funcs.add(nn.name);
958
- });
959
- }
960
- } catch {
961
- }
962
- funcs.forEach((f) => names.delete(f));
963
- ["pi", "e", "tau"].forEach((c) => names.delete(c));
964
- return [...names];
1029
+ const names = /* @__PURE__ */ new Set();
1030
+ const funcs = /* @__PURE__ */ new Set();
1031
+ try {
1032
+ const node = parse(source);
1033
+ if (typeof node.traverse === "function") node.traverse((n) => {
1034
+ const nn = n;
1035
+ if (nn.type === "SymbolNode" && nn.name) names.add(nn.name);
1036
+ if (nn.type === "FunctionNode" && nn.name) funcs.add(nn.name);
1037
+ });
1038
+ } catch {}
1039
+ funcs.forEach((f) => names.delete(f));
1040
+ [
1041
+ "pi",
1042
+ "e",
1043
+ "tau"
1044
+ ].forEach((c) => names.delete(c));
1045
+ return [...names];
965
1046
  }
966
1047
  function plotExpr(source, opts) {
967
- const { kind, ...rest } = opts;
968
- const from = opts.from ?? -10;
969
- const to = opts.to ?? 10;
970
- const n = opts.samples ?? 200;
971
- const scope = opts.scope ?? {};
972
- const rangeFn = range;
973
- const grid = rangeFn(from, to, (to - from) / (n - 1), true).toArray?.() ?? Array.from({ length: n }, (_, i) => from + (to - from) * i / (n - 1));
974
- const vars = freeVars(source);
975
- if (vars.length <= 1) {
976
- const v = vars[0] ?? "x";
977
- const ys = grid.map((val) => {
978
- try {
979
- return Number(evaluate(source, { ...scope, [v]: val }));
980
- } catch {
981
- return NaN;
982
- }
983
- });
984
- return line2(grid, ys, { xLabel: v, title: source, ...rest });
985
- }
986
- const [vx, vy] = vars;
987
- const g2 = grid.map(
988
- (yv) => grid.map((xv) => {
989
- try {
990
- return Number(evaluate(source, { ...scope, [vx]: xv, [vy]: yv }));
991
- } catch {
992
- return NaN;
993
- }
994
- })
995
- );
996
- return kind === "3d" || kind === "surface" ? surface(g2, { title: source, ...rest }) : contour(g2, { title: source, ...rest });
1048
+ const { kind, ...rest } = opts;
1049
+ const from = opts.from ?? -10;
1050
+ const to = opts.to ?? 10;
1051
+ const n = opts.samples ?? 200;
1052
+ const scope = opts.scope ?? {};
1053
+ const grid = range(from, to, (to - from) / (n - 1), true).toArray?.() ?? Array.from({ length: n }, (_, i) => from + (to - from) * i / (n - 1));
1054
+ const vars = freeVars(source);
1055
+ if (vars.length <= 1) {
1056
+ const v = vars[0] ?? "x";
1057
+ return line(grid, grid.map((val) => {
1058
+ try {
1059
+ return Number(evaluate(source, {
1060
+ ...scope,
1061
+ [v]: val
1062
+ }));
1063
+ } catch {
1064
+ return NaN;
1065
+ }
1066
+ }), {
1067
+ xLabel: v,
1068
+ title: source,
1069
+ ...rest
1070
+ });
1071
+ }
1072
+ const [vx, vy] = vars;
1073
+ const g2 = grid.map((yv) => grid.map((xv) => {
1074
+ try {
1075
+ return Number(evaluate(source, {
1076
+ ...scope,
1077
+ [vx]: xv,
1078
+ [vy]: yv
1079
+ }));
1080
+ } catch {
1081
+ return NaN;
1082
+ }
1083
+ }));
1084
+ return kind === "3d" || kind === "surface" ? surface(g2, {
1085
+ title: source,
1086
+ ...rest
1087
+ }) : contour(g2, {
1088
+ title: source,
1089
+ ...rest
1090
+ });
997
1091
  }
998
1092
  function plot(a, b, c) {
999
- if (typeof a === "string") return plotExpr(a, b ?? {});
1000
- if (isLayerArray(a)) return overlay(a, b ?? {});
1001
- if (Array.isArray(b) || b instanceof Float64Array) return line2(a, b, c);
1002
- return line2(void 0, a, b ?? {});
1093
+ if (typeof a === "string") return plotExpr(a, b ?? {});
1094
+ if (isLayerArray(a)) return overlay(a, b ?? {});
1095
+ if (Array.isArray(b) || b instanceof Float64Array) return line(a, b, c);
1096
+ return line(void 0, a, b ?? {});
1003
1097
  }
1004
1098
  function toTikZ(a, b, c) {
1005
- if (typeof a === "string") return plot(a, { ...b ?? {}, format: "tikz" });
1006
- if (Array.isArray(a) && a.length > 0 && typeof a[0] === "object" && a[0] !== null && "type" in a[0])
1007
- return plot(a, { ...b ?? {}, format: "tikz" });
1008
- if (Array.isArray(b) || b instanceof Float64Array)
1009
- return plot(a, b, { ...c ?? {}, format: "tikz" });
1010
- return plot(a, { ...b ?? {}, format: "tikz" });
1011
- }
1012
-
1013
- // src/histogram.ts
1014
- import { histogram as histBins } from "@danielsimonjr/mathts-functions";
1099
+ if (typeof a === "string") return plot(a, {
1100
+ ...b ?? {},
1101
+ format: "tikz"
1102
+ });
1103
+ if (Array.isArray(a) && a.length > 0 && typeof a[0] === "object" && a[0] !== null && "type" in a[0]) return plot(a, {
1104
+ ...b ?? {},
1105
+ format: "tikz"
1106
+ });
1107
+ if (Array.isArray(b) || b instanceof Float64Array) return plot(a, b, {
1108
+ ...c ?? {},
1109
+ format: "tikz"
1110
+ });
1111
+ return plot(a, {
1112
+ ...b ?? {},
1113
+ format: "tikz"
1114
+ });
1115
+ }
1116
+ //#endregion
1117
+ //#region src/histogram.ts
1015
1118
  function histogram(data, opts = {}) {
1016
- const nums = coerce1d(data);
1017
- if (nums.length === 0) return draw2D([], opts);
1018
- const { counts, edges } = histBins(nums, opts.bins ?? 10);
1019
- const centers = counts.map((_, i) => (edges[i] + edges[i + 1]) / 2);
1020
- return draw2D([{ type: "bar", x: centers, y: counts, color: opts.palette?.[0] }], {
1021
- yLabel: "count",
1022
- ...opts
1023
- });
1024
- }
1025
-
1026
- // src/heatmap.ts
1027
- var MARGIN4 = { top: 34, right: 22, bottom: 30, left: 40 };
1119
+ const nums = coerce1d(data);
1120
+ if (nums.length === 0) return draw2D([], opts);
1121
+ const { counts, edges } = histogram$1(nums, opts.bins ?? 10);
1122
+ return draw2D([{
1123
+ type: "bar",
1124
+ x: counts.map((_, i) => (edges[i] + edges[i + 1]) / 2),
1125
+ y: counts,
1126
+ color: opts.palette?.[0]
1127
+ }], {
1128
+ yLabel: "count",
1129
+ ...opts
1130
+ });
1131
+ }
1132
+ //#endregion
1133
+ //#region src/heatmap.ts
1134
+ const MARGIN$1 = {
1135
+ top: 34,
1136
+ right: 22,
1137
+ bottom: 30,
1138
+ left: 40
1139
+ };
1028
1140
  function heatmap(z, opts = {}) {
1029
- const width = opts.width ?? 520;
1030
- const height = opts.height ?? 320;
1031
- const theme = THEMES[opts.theme ?? "light"];
1032
- const grid = coerce2d(z);
1033
- const rows = grid.length;
1034
- const cols = rows ? grid[0].length : 0;
1035
- if (rows === 0 || cols === 0) {
1036
- const scene = {
1037
- width,
1038
- height,
1039
- bg: theme.bg,
1040
- prims: [
1041
- {
1042
- k: "text",
1043
- x: width / 2,
1044
- y: height / 2,
1045
- s: "no data",
1046
- fill: theme.muted,
1047
- anchor: "middle",
1048
- size: 13
1049
- }
1050
- ]
1051
- };
1052
- return emit(scene, opts);
1053
- }
1054
- let lo = Infinity;
1055
- let hi = -Infinity;
1056
- for (const row of grid)
1057
- for (const v of row)
1058
- if (Number.isFinite(v)) {
1059
- if (v < lo) lo = v;
1060
- if (v > hi) hi = v;
1061
- }
1062
- const span = hi > lo ? hi - lo : 1;
1063
- const cw = (width - MARGIN4.left - MARGIN4.right) / cols;
1064
- const ch = (height - MARGIN4.top - MARGIN4.bottom) / rows;
1065
- const prims = [];
1066
- for (let r = 0; r < rows; r++) {
1067
- for (let c = 0; c < cols; c++) {
1068
- const v = grid[r][c];
1069
- const color = Number.isFinite(v) ? viridis((v - lo) / span) : theme.grid;
1070
- prims.push({
1071
- k: "rect",
1072
- x: MARGIN4.left + c * cw,
1073
- y: MARGIN4.top + r * ch,
1074
- w: cw + 0.5,
1075
- h: ch + 0.5,
1076
- fill: color
1077
- });
1078
- }
1079
- }
1080
- if (opts.title)
1081
- prims.push({
1082
- k: "text",
1083
- x: width / 2,
1084
- y: 20,
1085
- s: opts.title,
1086
- fill: theme.fg,
1087
- anchor: "middle",
1088
- size: 14
1089
- });
1090
- return emit({ width, height, bg: theme.bg, prims }, opts);
1091
- }
1092
-
1093
- // src/three/points3d.ts
1094
- var MARGIN5 = 40;
1141
+ const width = opts.width ?? 520;
1142
+ const height = opts.height ?? 320;
1143
+ const theme = THEMES[opts.theme ?? "light"];
1144
+ const grid = coerce2d(z);
1145
+ const rows = grid.length;
1146
+ const cols = rows ? grid[0].length : 0;
1147
+ if (rows === 0 || cols === 0) return emit({
1148
+ width,
1149
+ height,
1150
+ bg: theme.bg,
1151
+ prims: [{
1152
+ k: "text",
1153
+ x: width / 2,
1154
+ y: height / 2,
1155
+ s: "no data",
1156
+ fill: theme.muted,
1157
+ anchor: "middle",
1158
+ size: 13
1159
+ }]
1160
+ }, opts);
1161
+ let lo = Infinity;
1162
+ let hi = -Infinity;
1163
+ for (const row of grid) for (const v of row) if (Number.isFinite(v)) {
1164
+ if (v < lo) lo = v;
1165
+ if (v > hi) hi = v;
1166
+ }
1167
+ const span = hi > lo ? hi - lo : 1;
1168
+ const cw = (width - MARGIN$1.left - MARGIN$1.right) / cols;
1169
+ const ch = (height - MARGIN$1.top - MARGIN$1.bottom) / rows;
1170
+ const prims = [];
1171
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
1172
+ const v = grid[r][c];
1173
+ const color = Number.isFinite(v) ? viridis((v - lo) / span) : theme.grid;
1174
+ prims.push({
1175
+ k: "rect",
1176
+ x: MARGIN$1.left + c * cw,
1177
+ y: MARGIN$1.top + r * ch,
1178
+ w: cw + .5,
1179
+ h: ch + .5,
1180
+ fill: color
1181
+ });
1182
+ }
1183
+ if (opts.title) prims.push({
1184
+ k: "text",
1185
+ x: width / 2,
1186
+ y: 20,
1187
+ s: opts.title,
1188
+ fill: theme.fg,
1189
+ anchor: "middle",
1190
+ size: 14
1191
+ });
1192
+ return emit({
1193
+ width,
1194
+ height,
1195
+ bg: theme.bg,
1196
+ prims
1197
+ }, opts);
1198
+ }
1199
+ //#endregion
1200
+ //#region src/three/points3d.ts
1201
+ const MARGIN = 40;
1095
1202
  function minOf(a) {
1096
- let m = Infinity;
1097
- for (const v of a) if (v < m) m = v;
1098
- return m;
1203
+ let m = Infinity;
1204
+ for (const v of a) if (v < m) m = v;
1205
+ return m;
1099
1206
  }
1100
1207
  function maxOf(a) {
1101
- let m = -Infinity;
1102
- for (const v of a) if (v > m) m = v;
1103
- return m;
1208
+ let m = -Infinity;
1209
+ for (const v of a) if (v > m) m = v;
1210
+ return m;
1104
1211
  }
1105
1212
  function projectAll(x, y, z, cam) {
1106
- const xs = coerce1d(x);
1107
- const ys = coerce1d(y);
1108
- const zs = coerce1d(z);
1109
- const n = Math.min(xs.length, ys.length, zs.length);
1110
- const nx = (a) => {
1111
- const lo = minOf(a);
1112
- const hi = maxOf(a);
1113
- const s = hi > lo ? hi - lo : 1;
1114
- return (v) => (v - lo) / s * 2 - 1;
1115
- };
1116
- const fx = nx(xs.slice(0, n));
1117
- const fy = nx(ys.slice(0, n));
1118
- const fz = nx(zs.slice(0, n));
1119
- const screen = [];
1120
- for (let i = 0; i < n; i++) screen.push(project([fx(xs[i]), fy(ys[i]), fz(zs[i])], cam));
1121
- return { screen };
1213
+ const xs = coerce1d(x);
1214
+ const ys = coerce1d(y);
1215
+ const zs = coerce1d(z);
1216
+ const n = Math.min(xs.length, ys.length, zs.length);
1217
+ const nx = (a) => {
1218
+ const lo = minOf(a);
1219
+ const hi = maxOf(a);
1220
+ const s = hi > lo ? hi - lo : 1;
1221
+ return (v) => (v - lo) / s * 2 - 1;
1222
+ };
1223
+ const fx = nx(xs.slice(0, n));
1224
+ const fy = nx(ys.slice(0, n));
1225
+ const fz = nx(zs.slice(0, n));
1226
+ const screen = [];
1227
+ for (let i = 0; i < n; i++) screen.push(project([
1228
+ fx(xs[i]),
1229
+ fy(ys[i]),
1230
+ fz(zs[i])
1231
+ ], cam));
1232
+ return { screen };
1122
1233
  }
1123
1234
  function render(x, y, z, opts, mode) {
1124
- const width = opts.width ?? 520;
1125
- const height = opts.height ?? 320;
1126
- const theme = THEMES[opts.theme ?? "light"];
1127
- const cam = { azim: opts.azim ?? 45, elev: opts.elev ?? 25 };
1128
- const { screen } = projectAll(x, y, z, cam);
1129
- if (screen.length === 0) {
1130
- const scene = {
1131
- width,
1132
- height,
1133
- bg: theme.bg,
1134
- prims: [
1135
- {
1136
- k: "text",
1137
- x: width / 2,
1138
- y: height / 2,
1139
- s: "no data",
1140
- fill: theme.muted,
1141
- anchor: "middle",
1142
- size: 13
1143
- }
1144
- ]
1145
- };
1146
- return emit(scene, opts);
1147
- }
1148
- let sxlo = Infinity, sxhi = -Infinity, sylo = Infinity, syhi = -Infinity;
1149
- for (const p of screen) {
1150
- if (p[0] < sxlo) sxlo = p[0];
1151
- if (p[0] > sxhi) sxhi = p[0];
1152
- if (p[1] < sylo) sylo = p[1];
1153
- if (p[1] > syhi) syhi = p[1];
1154
- }
1155
- const sxSpan = sxhi > sxlo ? sxhi - sxlo : 1;
1156
- const sySpan = syhi > sylo ? syhi - sylo : 1;
1157
- const toScreen = (p) => [
1158
- MARGIN5 + (p[0] - sxlo) / sxSpan * (width - 2 * MARGIN5),
1159
- height - MARGIN5 - (p[1] - sylo) / sySpan * (height - 2 * MARGIN5)
1160
- ];
1161
- const color = theme.series[0];
1162
- const prims = [];
1163
- if (mode === "line") {
1164
- const depths = screen.map((p) => p[2]);
1165
- const dlo = minOf(depths);
1166
- const dhi = maxOf(depths);
1167
- const dspan = dhi > dlo ? dhi - dlo : 1;
1168
- const segs = [];
1169
- for (let i = 0; i + 1 < screen.length; i++) {
1170
- const a = screen[i];
1171
- const b = screen[i + 1];
1172
- segs.push({ a, b, depth: (a[2] + b[2]) / 2 });
1173
- }
1174
- segs.sort((s1, s2) => s2.depth - s1.depth);
1175
- for (const sg of segs) {
1176
- const near = 1 - (sg.depth - dlo) / dspan;
1177
- const opacity = 0.4 + 0.6 * near;
1178
- const [x1, y1] = toScreen(sg.a);
1179
- const [x2, y2] = toScreen(sg.b);
1180
- prims.push({ k: "line", x1, y1, x2, y2, stroke: color, w: 2, opacity });
1181
- }
1182
- } else {
1183
- const depths = screen.map((p) => p[2]);
1184
- const dlo = minOf(depths);
1185
- const dhi = maxOf(depths);
1186
- const dspan = dhi > dlo ? dhi - dlo : 1;
1187
- const sorted = screen.slice().sort((a, b) => b[2] - a[2]);
1188
- for (const p of sorted) {
1189
- const [sx, sy] = toScreen(p);
1190
- const near = 1 - (p[2] - dlo) / dspan;
1191
- const opacity = 0.4 + 0.6 * near;
1192
- prims.push({ k: "circle", cx: sx, cy: sy, r: 3, fill: color, opacity });
1193
- }
1194
- }
1195
- if (opts.title)
1196
- prims.push({
1197
- k: "text",
1198
- x: width / 2,
1199
- y: 20,
1200
- s: opts.title,
1201
- fill: theme.fg,
1202
- anchor: "middle",
1203
- size: 14
1204
- });
1205
- return emit({ width, height, bg: theme.bg, prims }, opts);
1235
+ const width = opts.width ?? 520;
1236
+ const height = opts.height ?? 320;
1237
+ const theme = THEMES[opts.theme ?? "light"];
1238
+ const { screen } = projectAll(x, y, z, {
1239
+ azim: opts.azim ?? 45,
1240
+ elev: opts.elev ?? 25
1241
+ });
1242
+ if (screen.length === 0) return emit({
1243
+ width,
1244
+ height,
1245
+ bg: theme.bg,
1246
+ prims: [{
1247
+ k: "text",
1248
+ x: width / 2,
1249
+ y: height / 2,
1250
+ s: "no data",
1251
+ fill: theme.muted,
1252
+ anchor: "middle",
1253
+ size: 13
1254
+ }]
1255
+ }, opts);
1256
+ let sxlo = Infinity, sxhi = -Infinity, sylo = Infinity, syhi = -Infinity;
1257
+ for (const p of screen) {
1258
+ if (p[0] < sxlo) sxlo = p[0];
1259
+ if (p[0] > sxhi) sxhi = p[0];
1260
+ if (p[1] < sylo) sylo = p[1];
1261
+ if (p[1] > syhi) syhi = p[1];
1262
+ }
1263
+ const sxSpan = sxhi > sxlo ? sxhi - sxlo : 1;
1264
+ const sySpan = syhi > sylo ? syhi - sylo : 1;
1265
+ const toScreen = (p) => [MARGIN + (p[0] - sxlo) / sxSpan * (width - 80), height - MARGIN - (p[1] - sylo) / sySpan * (height - 80)];
1266
+ const color = theme.series[0];
1267
+ const prims = [];
1268
+ if (mode === "line") {
1269
+ const depths = screen.map((p) => p[2]);
1270
+ const dlo = minOf(depths);
1271
+ const dhi = maxOf(depths);
1272
+ const dspan = dhi > dlo ? dhi - dlo : 1;
1273
+ const segs = [];
1274
+ for (let i = 0; i + 1 < screen.length; i++) {
1275
+ const a = screen[i];
1276
+ const b = screen[i + 1];
1277
+ segs.push({
1278
+ a,
1279
+ b,
1280
+ depth: (a[2] + b[2]) / 2
1281
+ });
1282
+ }
1283
+ segs.sort((s1, s2) => s2.depth - s1.depth);
1284
+ for (const sg of segs) {
1285
+ const opacity = .4 + .6 * (1 - (sg.depth - dlo) / dspan);
1286
+ const [x1, y1] = toScreen(sg.a);
1287
+ const [x2, y2] = toScreen(sg.b);
1288
+ prims.push({
1289
+ k: "line",
1290
+ x1,
1291
+ y1,
1292
+ x2,
1293
+ y2,
1294
+ stroke: color,
1295
+ w: 2,
1296
+ opacity
1297
+ });
1298
+ }
1299
+ } else {
1300
+ const depths = screen.map((p) => p[2]);
1301
+ const dlo = minOf(depths);
1302
+ const dhi = maxOf(depths);
1303
+ const dspan = dhi > dlo ? dhi - dlo : 1;
1304
+ const sorted = screen.slice().sort((a, b) => b[2] - a[2]);
1305
+ for (const p of sorted) {
1306
+ const [sx, sy] = toScreen(p);
1307
+ const opacity = .4 + .6 * (1 - (p[2] - dlo) / dspan);
1308
+ prims.push({
1309
+ k: "circle",
1310
+ cx: sx,
1311
+ cy: sy,
1312
+ r: 3,
1313
+ fill: color,
1314
+ opacity
1315
+ });
1316
+ }
1317
+ }
1318
+ if (opts.title) prims.push({
1319
+ k: "text",
1320
+ x: width / 2,
1321
+ y: 20,
1322
+ s: opts.title,
1323
+ fill: theme.fg,
1324
+ anchor: "middle",
1325
+ size: 14
1326
+ });
1327
+ return emit({
1328
+ width,
1329
+ height,
1330
+ bg: theme.bg,
1331
+ prims
1332
+ }, opts);
1206
1333
  }
1207
1334
  function scatter3d(x, y, z, opts = {}) {
1208
- return render(x, y, z, opts, "points");
1335
+ return render(x, y, z, opts, "points");
1209
1336
  }
1210
1337
  function curve3d(x, y, z, opts = {}) {
1211
- return render(x, y, z, opts, "line");
1212
- }
1213
-
1214
- // src/index.ts
1215
- var VERSION = "0.4.10";
1216
- export {
1217
- VERSION,
1218
- area,
1219
- bar,
1220
- contour,
1221
- curve3d,
1222
- errorbar,
1223
- heatmap,
1224
- histogram,
1225
- line2 as line,
1226
- overlay,
1227
- plot,
1228
- quiver,
1229
- scatter,
1230
- scatter3d,
1231
- step,
1232
- surface,
1233
- toTikZ,
1234
- viridis
1235
- };
1338
+ return render(x, y, z, opts, "line");
1339
+ }
1340
+ //#endregion
1341
+ //#region src/index.ts
1342
+ const VERSION = "0.4.12";
1343
+ //#endregion
1344
+ export { VERSION, area, bar, contour, curve3d, errorbar, heatmap, histogram, line, overlay, plot, quiver, scatter, scatter3d, step, surface, toTikZ, viridis };