@fasl-work/caos-app-shell 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chart.css +5 -0
- package/dist/chart.d.ts +106 -14
- package/dist/chart.js +715 -111
- package/dist/chunk-3LONJM5B.js +680 -0
- package/dist/index.d.ts +36 -39
- package/dist/index.js +509 -144
- package/dist/{tokens-BTOzar-U.d.ts → tokens-BvU9bFPw.d.ts} +42 -1
- package/gate/inpage.mjs +170 -1
- package/gate/run.mjs +15 -1
- package/package.json +1 -1
- package/reserved-classes.json +24 -0
- package/styles.css +32 -0
- package/dist/chunk-YECZXGVG.js +0 -217
package/dist/chart.js
CHANGED
|
@@ -1,9 +1,210 @@
|
|
|
1
|
-
import { useShellLang, useThemeStore, useStageSize,
|
|
1
|
+
import { pick, placeXLabel, placeYLabel, useShellLang, useThemeStore, useStageSize, defaultChartName, axisTitleFits, resolveToken, DRAG_THRESHOLD, sameData, NBSP, formatNumber, thinRows, chartSummary, referenceLineName, ZOOM_HINT, CHART_ROLE, ChartSummary, ChartDataTable, ResetZoomButton, formatTicks, countOverlaps, isZoomed, formatTime, keyAction, KEY_PAN, KEY_ZOOM, firstVisible, stepIndex, wheelFactor, pinchState, repeatedLabels, panRange, zoomRange, revealValue, brushRange, leastLabelSpan, MIN_SPAN_SHARE } from './chunk-3LONJM5B.js';
|
|
2
|
+
export { referenceLineName, repeatedLabels } from './chunk-3LONJM5B.js';
|
|
2
3
|
import './chunk-33WTUQ7Q.js';
|
|
3
|
-
import { useRef, useCallback, useState,
|
|
4
|
+
import { useRef, useCallback, useState, useId, useEffect, useMemo } from 'react';
|
|
4
5
|
import uPlot from 'uplot';
|
|
5
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
|
|
8
|
+
// src/chart/uplotZoom.ts
|
|
9
|
+
var rangeOf = (u, key) => [u.scales[key].min ?? 0, u.scales[key].max ?? 1];
|
|
10
|
+
function createZoom(u, ctx) {
|
|
11
|
+
const differs = (a, b, full) => {
|
|
12
|
+
const tol = Math.abs(full[1] - full[0]) * 1e-9;
|
|
13
|
+
return Math.abs(a[0] - b[0]) > tol || Math.abs(a[1] - b[1]) > tol;
|
|
14
|
+
};
|
|
15
|
+
const apply = (nx, ny) => {
|
|
16
|
+
const full = ctx.full();
|
|
17
|
+
const cx = rangeOf(u, "x");
|
|
18
|
+
const cy = rangeOf(u, "y");
|
|
19
|
+
const moved = Boolean(nx && differs(nx, cx, full ? full.x : cx) || ny && differs(ny, cy, full ? full.y : cy));
|
|
20
|
+
if (!moved) return false;
|
|
21
|
+
ctx.explicit.current = true;
|
|
22
|
+
try {
|
|
23
|
+
u.batch(() => {
|
|
24
|
+
if (nx) u.setScale("x", { min: nx[0], max: nx[1] });
|
|
25
|
+
if (ny) u.setScale("y", { min: ny[0], max: ny[1] });
|
|
26
|
+
});
|
|
27
|
+
} finally {
|
|
28
|
+
ctx.explicit.current = false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
const isNow = () => {
|
|
33
|
+
const full = ctx.full();
|
|
34
|
+
if (!full) return false;
|
|
35
|
+
return isZoomed(rangeOf(u, "x"), full.x) || ctx.both && isZoomed(rangeOf(u, "y"), full.y);
|
|
36
|
+
};
|
|
37
|
+
const leastOf = (axis, full) => {
|
|
38
|
+
if (ctx.log[axis]) return void 0;
|
|
39
|
+
return Math.max(Math.abs(full[1] - full[0]) * MIN_SPAN_SHARE, ctx.least?.(axis) ?? 0);
|
|
40
|
+
};
|
|
41
|
+
const zoomBy = (factor, ax, ay) => {
|
|
42
|
+
const full = ctx.full();
|
|
43
|
+
if (!full || !ctx.enabled()) return false;
|
|
44
|
+
const nx = zoomRange(rangeOf(u, "x"), full.x, ax, factor, { log: ctx.log.x, minSpan: leastOf("x", full.x) });
|
|
45
|
+
const ny = ctx.both && ay !== void 0 ? zoomRange(rangeOf(u, "y"), full.y, ay, factor, { log: ctx.log.y, minSpan: leastOf("y", full.y) }) : null;
|
|
46
|
+
return apply(nx, ny);
|
|
47
|
+
};
|
|
48
|
+
const panBy = (shareX, shareY = 0) => {
|
|
49
|
+
const full = ctx.full();
|
|
50
|
+
if (!full || !ctx.enabled()) return false;
|
|
51
|
+
const nx = shareX ? panRange(rangeOf(u, "x"), full.x, shareX, ctx.log.x) : null;
|
|
52
|
+
const ny = ctx.both && shareY ? panRange(rangeOf(u, "y"), full.y, shareY, ctx.log.y) : null;
|
|
53
|
+
return apply(nx, ny);
|
|
54
|
+
};
|
|
55
|
+
const brush = (sx, sy) => {
|
|
56
|
+
const full = ctx.full();
|
|
57
|
+
if (!full || !ctx.enabled()) return false;
|
|
58
|
+
const nx = sx ? brushRange(sx, full.x, leastOf("x", full.x) ?? 0) : null;
|
|
59
|
+
const ny = ctx.both && sy ? brushRange(sy, full.y, leastOf("y", full.y) ?? 0) : null;
|
|
60
|
+
return apply(nx, ny);
|
|
61
|
+
};
|
|
62
|
+
const reveal = (x) => {
|
|
63
|
+
const full = ctx.full();
|
|
64
|
+
if (!full || !isNow()) return;
|
|
65
|
+
const cur = rangeOf(u, "x");
|
|
66
|
+
const next = revealValue(cur, full.x, x, ctx.log.x);
|
|
67
|
+
if (next[0] !== cur[0] || next[1] !== cur[1]) apply(next, null);
|
|
68
|
+
};
|
|
69
|
+
const reset = () => {
|
|
70
|
+
const full = ctx.full();
|
|
71
|
+
if (!full) return;
|
|
72
|
+
apply(full.x, ctx.both ? full.y : null);
|
|
73
|
+
};
|
|
74
|
+
const place = (idx) => {
|
|
75
|
+
const d = u.data;
|
|
76
|
+
const xv = d[0][idx];
|
|
77
|
+
if (xv === null || xv === void 0) return;
|
|
78
|
+
const left = u.valToPos(xv, "x");
|
|
79
|
+
let top = u.bbox.height / (window.devicePixelRatio || 1) / 2;
|
|
80
|
+
for (let k = 1; k < d.length; k++) {
|
|
81
|
+
const v = d[k][idx];
|
|
82
|
+
if (v !== null && v !== void 0 && Number.isFinite(v)) {
|
|
83
|
+
top = u.valToPos(v, "y");
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
u.setCursor({ left, top }, true);
|
|
88
|
+
};
|
|
89
|
+
const clear = () => u.setCursor({ left: -10, top: -10 }, true);
|
|
90
|
+
const over = u.over;
|
|
91
|
+
const onHandle = (e) => e.target instanceof Element && e.target.closest("[data-y-handle]") !== null;
|
|
92
|
+
const ac = new AbortController();
|
|
93
|
+
const opt = { signal: ac.signal };
|
|
94
|
+
const sizeOf = () => {
|
|
95
|
+
const r = over.getBoundingClientRect();
|
|
96
|
+
return { w: Math.max(1, r.width), h: Math.max(1, r.height), left: r.left, top: r.top };
|
|
97
|
+
};
|
|
98
|
+
over.addEventListener(
|
|
99
|
+
"wheel",
|
|
100
|
+
(e) => {
|
|
101
|
+
if (!ctx.enabled()) return;
|
|
102
|
+
if (!ctx.plot.contains(document.activeElement)) return;
|
|
103
|
+
const s = sizeOf();
|
|
104
|
+
const changed = Math.abs(e.deltaX) > Math.abs(e.deltaY) && isNow() ? panBy(e.deltaX / s.w) : zoomBy(wheelFactor(e.deltaY, e.deltaMode), u.posToVal(e.clientX - s.left, "x"), ctx.both ? u.posToVal(e.clientY - s.top, "y") : void 0);
|
|
105
|
+
if (changed) e.preventDefault();
|
|
106
|
+
},
|
|
107
|
+
{ ...opt, passive: false }
|
|
108
|
+
);
|
|
109
|
+
u.root.addEventListener(
|
|
110
|
+
"mousedown",
|
|
111
|
+
(e) => {
|
|
112
|
+
if (e.shiftKey && e.button === 0 && ctx.enabled() && !onHandle(e)) {
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{ ...opt, capture: true }
|
|
118
|
+
);
|
|
119
|
+
const pts = /* @__PURE__ */ new Map();
|
|
120
|
+
let pan = null;
|
|
121
|
+
let pinch = null;
|
|
122
|
+
const startPan = (id, x, y) => {
|
|
123
|
+
pan = { id, x, y };
|
|
124
|
+
try {
|
|
125
|
+
over.setPointerCapture(id);
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
over.addEventListener(
|
|
130
|
+
"pointerdown",
|
|
131
|
+
(e) => {
|
|
132
|
+
if (onHandle(e)) return;
|
|
133
|
+
ctx.plot.focus({ preventScroll: true });
|
|
134
|
+
if (!ctx.enabled()) return;
|
|
135
|
+
pts.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
136
|
+
if (e.pointerType === "mouse") {
|
|
137
|
+
if (e.button === 0 && e.shiftKey) startPan(e.pointerId, e.clientX, e.clientY);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (pts.size === 2) {
|
|
141
|
+
const [a, b] = [...pts.values()];
|
|
142
|
+
pinch = { dist: pinchState(a, b).dist };
|
|
143
|
+
pan = null;
|
|
144
|
+
} else if (pts.size === 1 && isNow()) startPan(e.pointerId, e.clientX, e.clientY);
|
|
145
|
+
},
|
|
146
|
+
opt
|
|
147
|
+
);
|
|
148
|
+
over.addEventListener(
|
|
149
|
+
"pointermove",
|
|
150
|
+
(e) => {
|
|
151
|
+
if (!pts.has(e.pointerId)) return;
|
|
152
|
+
const before = pts.get(e.pointerId);
|
|
153
|
+
pts.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
154
|
+
const s = sizeOf();
|
|
155
|
+
if (pinch && pts.size === 2) {
|
|
156
|
+
const [a, b] = [...pts.values()];
|
|
157
|
+
const now = pinchState(a, b);
|
|
158
|
+
if (pinch.dist > 0 && now.dist > 0) {
|
|
159
|
+
zoomBy(pinch.dist / now.dist, u.posToVal(now.mx - s.left, "x"), ctx.both ? u.posToVal(now.my - s.top, "y") : void 0);
|
|
160
|
+
pinch.dist = now.dist;
|
|
161
|
+
}
|
|
162
|
+
panBy(-(e.clientX - before.x) / 2 / s.w, ctx.both ? (e.clientY - before.y) / 2 / s.h : 0);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (pan && pan.id === e.pointerId) {
|
|
166
|
+
panBy(-(e.clientX - pan.x) / s.w, ctx.both ? (e.clientY - pan.y) / s.h : 0);
|
|
167
|
+
pan.x = e.clientX;
|
|
168
|
+
pan.y = e.clientY;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
opt
|
|
172
|
+
);
|
|
173
|
+
const end = (e) => {
|
|
174
|
+
pts.delete(e.pointerId);
|
|
175
|
+
if (pan && pan.id === e.pointerId) {
|
|
176
|
+
pan = null;
|
|
177
|
+
if (over.hasPointerCapture?.(e.pointerId)) over.releasePointerCapture(e.pointerId);
|
|
178
|
+
}
|
|
179
|
+
if (pts.size < 2) pinch = null;
|
|
180
|
+
if (pts.size === 1 && !pan && isNow()) {
|
|
181
|
+
const [[id, p]] = [...pts.entries()];
|
|
182
|
+
startPan(id, p.x, p.y);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
over.addEventListener("pointerup", end, opt);
|
|
186
|
+
over.addEventListener("pointercancel", end, opt);
|
|
187
|
+
over.addEventListener(
|
|
188
|
+
"dblclick",
|
|
189
|
+
(e) => {
|
|
190
|
+
if (onHandle(e) || !ctx.enabled()) return;
|
|
191
|
+
reset();
|
|
192
|
+
},
|
|
193
|
+
opt
|
|
194
|
+
);
|
|
195
|
+
return {
|
|
196
|
+
zoomBy,
|
|
197
|
+
panBy,
|
|
198
|
+
brush,
|
|
199
|
+
reveal,
|
|
200
|
+
reset,
|
|
201
|
+
view: () => ({ x: rangeOf(u, "x"), y: rangeOf(u, "y") }),
|
|
202
|
+
place,
|
|
203
|
+
clear,
|
|
204
|
+
zoomed: isNow,
|
|
205
|
+
detach: () => ac.abort()
|
|
206
|
+
};
|
|
207
|
+
}
|
|
7
208
|
var YMARK_TONE_TOKENS = {
|
|
8
209
|
warn: "--color-warn",
|
|
9
210
|
bad: "--color-bad",
|
|
@@ -11,6 +212,52 @@ var YMARK_TONE_TOKENS = {
|
|
|
11
212
|
neutral: "--color-fg-subtle"
|
|
12
213
|
};
|
|
13
214
|
var ROTATION = ["--color-accent", "--color-magenta", "--color-accent-2", "--color-good", "--color-warn", "--color-bad"];
|
|
215
|
+
var SOLO_DIM_ALPHA = 0.12;
|
|
216
|
+
function soloStyle(solo, i, width) {
|
|
217
|
+
return solo !== null && solo !== i ? { alpha: SOLO_DIM_ALPHA, width: 1 } : { alpha: 1, width };
|
|
218
|
+
}
|
|
219
|
+
function tickSplits(ticks, min, max, log = false) {
|
|
220
|
+
const eps = Math.abs(max - min) * 1e-9;
|
|
221
|
+
const values = ticks.map((t) => t.value).filter((v) => Number.isFinite(v) && (!log || v > 0) && v >= min - eps && v <= max + eps);
|
|
222
|
+
return [...new Set(values)].sort((a, b) => a - b);
|
|
223
|
+
}
|
|
224
|
+
function tickLabelAt(ticks, x, lang, span = 0) {
|
|
225
|
+
const eps = Math.abs(span) * 1e-9;
|
|
226
|
+
const t = ticks?.find((one) => Math.abs(one.value - x) <= eps);
|
|
227
|
+
return t ? pick(t.label, lang) : null;
|
|
228
|
+
}
|
|
229
|
+
function namedRepeats(labels) {
|
|
230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
231
|
+
const names = [];
|
|
232
|
+
let n = 0;
|
|
233
|
+
for (const l of labels) {
|
|
234
|
+
if (!l) continue;
|
|
235
|
+
if (seen.has(l)) {
|
|
236
|
+
n++;
|
|
237
|
+
if (!names.includes(l)) names.push(l);
|
|
238
|
+
} else seen.add(l);
|
|
239
|
+
}
|
|
240
|
+
return { n, names };
|
|
241
|
+
}
|
|
242
|
+
function soloKey(s) {
|
|
243
|
+
return s.id ?? pick(s.label, "en");
|
|
244
|
+
}
|
|
245
|
+
function xScaleRange(xs, ext, range, log) {
|
|
246
|
+
const given = range && !(log && Math.min(range[0], range[1]) <= 0) ? range : void 0;
|
|
247
|
+
if (!ext && !given || xs.length === 0) return null;
|
|
248
|
+
let lo = xs[0];
|
|
249
|
+
let hi = xs[xs.length - 1];
|
|
250
|
+
if (ext) {
|
|
251
|
+
lo = Math.min(lo, ext.x[0]);
|
|
252
|
+
hi = Math.max(hi, ext.x[1]);
|
|
253
|
+
}
|
|
254
|
+
if (given) {
|
|
255
|
+
lo = Math.min(lo, given[0], given[1]);
|
|
256
|
+
hi = Math.max(hi, given[0], given[1]);
|
|
257
|
+
}
|
|
258
|
+
if (log && !(lo > 0)) return null;
|
|
259
|
+
return hi > lo ? [lo, hi] : log ? [lo / 10, hi * 10] : [lo - 1, hi + 1];
|
|
260
|
+
}
|
|
14
261
|
function seriesStyle(s, i) {
|
|
15
262
|
const color = s.color ?? ROTATION[i % ROTATION.length];
|
|
16
263
|
const dash = s.dash ?? (!s.color && i >= ROTATION.length ? [6, 4] : void 0);
|
|
@@ -150,19 +397,16 @@ var DECADE_AXIS = {
|
|
|
150
397
|
};
|
|
151
398
|
var PARITY_TICK_SPACE = 40;
|
|
152
399
|
var HANDLE_HEIGHT = 14;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
|
|
156
|
-
return n;
|
|
157
|
-
}
|
|
158
|
-
function drawMarks(u, marks, style) {
|
|
400
|
+
var CUT_SEP = String.fromCharCode(1);
|
|
401
|
+
function drawMarks(u, marks, style, boxes = []) {
|
|
159
402
|
const ctx = u.ctx;
|
|
160
403
|
const pr = uPlot.pxRatio || 1;
|
|
161
404
|
const left = u.bbox.left;
|
|
162
405
|
const right = u.bbox.left + u.bbox.width;
|
|
163
406
|
const gap = 4 * pr;
|
|
164
407
|
const rowH = 14 * pr;
|
|
165
|
-
const
|
|
408
|
+
const textH = 11 * pr;
|
|
409
|
+
const cut = [];
|
|
166
410
|
ctx.save();
|
|
167
411
|
ctx.font = `${Math.round(11 * pr)}px ${style.family}`;
|
|
168
412
|
ctx.textBaseline = "alphabetic";
|
|
@@ -178,24 +422,25 @@ function drawMarks(u, marks, style) {
|
|
|
178
422
|
ctx.lineTo(px, u.bbox.top + u.bbox.height);
|
|
179
423
|
ctx.stroke();
|
|
180
424
|
ctx.setLineDash([]);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
425
|
+
if (!m.label) continue;
|
|
426
|
+
const at = placeXLabel(m.label, { px, left, right, bottom: u.bbox.top + u.bbox.height, gap, rowH, textH, first: u.bbox.top + 12 * pr, boxes, measure: (t) => ctx.measureText(t).width });
|
|
427
|
+
if (!at) {
|
|
428
|
+
cut.push(m.label);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (at.cut) cut.push(m.label);
|
|
432
|
+
boxes.push({ kind: "x", text: at.text, full: m.label, x0: at.x0, x1: at.x0 + at.w, y0: at.y - textH, y1: at.y });
|
|
189
433
|
ctx.textAlign = "left";
|
|
190
434
|
ctx.lineWidth = 3 * pr;
|
|
191
435
|
ctx.strokeStyle = style.halo;
|
|
192
|
-
ctx.strokeText(
|
|
436
|
+
ctx.strokeText(at.text, at.x0, at.y);
|
|
193
437
|
ctx.fillStyle = style.color;
|
|
194
|
-
ctx.fillText(
|
|
438
|
+
ctx.fillText(at.text, at.x0, at.y);
|
|
195
439
|
}
|
|
196
440
|
ctx.restore();
|
|
441
|
+
return cut;
|
|
197
442
|
}
|
|
198
|
-
function drawYMarks(u, marks, style) {
|
|
443
|
+
function drawYMarks(u, marks, style, boxes) {
|
|
199
444
|
const ctx = u.ctx;
|
|
200
445
|
const pr = uPlot.pxRatio || 1;
|
|
201
446
|
const left = u.bbox.left;
|
|
@@ -203,8 +448,10 @@ function drawYMarks(u, marks, style) {
|
|
|
203
448
|
const top = u.bbox.top;
|
|
204
449
|
const bottom = u.bbox.top + u.bbox.height;
|
|
205
450
|
const gap = 4 * pr;
|
|
451
|
+
const rowH = 14 * pr;
|
|
206
452
|
const textH = 11 * pr;
|
|
207
453
|
const placed = [];
|
|
454
|
+
const cut = [];
|
|
208
455
|
ctx.save();
|
|
209
456
|
ctx.font = `${Math.round(11 * pr)}px ${style.family}`;
|
|
210
457
|
ctx.textBaseline = "alphabetic";
|
|
@@ -221,22 +468,24 @@ function drawYMarks(u, marks, style) {
|
|
|
221
468
|
ctx.lineTo(right, py);
|
|
222
469
|
ctx.stroke();
|
|
223
470
|
ctx.setLineDash([]);
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
471
|
+
if (!m.label) continue;
|
|
472
|
+
const at = placeYLabel(m.label, { py, left, right, top, bottom, gap, rowH, textH, boxes: [...placed, ...boxes ?? []], measure: (t) => ctx.measureText(t).width });
|
|
473
|
+
if (!at) {
|
|
474
|
+
cut.push(m.label);
|
|
475
|
+
continue;
|
|
229
476
|
}
|
|
230
|
-
|
|
231
|
-
placed.push({ x0, x1: x0 + w, y0:
|
|
477
|
+
if (at.cut) cut.push(m.label);
|
|
478
|
+
placed.push({ x0: at.x0, x1: at.x0 + at.w, y0: at.y - textH, y1: at.y });
|
|
479
|
+
boxes?.push({ kind: "y", text: at.text, full: m.label, x0: at.x0, x1: at.x0 + at.w, y0: at.y - textH, y1: at.y });
|
|
232
480
|
ctx.textAlign = "left";
|
|
233
481
|
ctx.lineWidth = 3 * pr;
|
|
234
482
|
ctx.strokeStyle = style.halo;
|
|
235
|
-
ctx.strokeText(
|
|
483
|
+
ctx.strokeText(at.text, at.x0, at.y);
|
|
236
484
|
ctx.fillStyle = color;
|
|
237
|
-
ctx.fillText(
|
|
485
|
+
ctx.fillText(at.text, at.x0, at.y);
|
|
238
486
|
}
|
|
239
487
|
ctx.restore();
|
|
488
|
+
return cut;
|
|
240
489
|
}
|
|
241
490
|
function nearestPoint(xs, ys, left, top) {
|
|
242
491
|
let best = null;
|
|
@@ -255,7 +504,7 @@ function nearestPoint(xs, ys, left, top) {
|
|
|
255
504
|
}
|
|
256
505
|
return best;
|
|
257
506
|
}
|
|
258
|
-
function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280, onCursor, onPick, parity, readout, hint }) {
|
|
507
|
+
function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280, onCursor, onPick, parity, readout, hint, solo, title, zoomY, zoomOff }) {
|
|
259
508
|
const lang = useShellLang();
|
|
260
509
|
const theme = useThemeStore((s) => s.theme);
|
|
261
510
|
const fill = height === "fill";
|
|
@@ -271,8 +520,29 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
271
520
|
const width = parity ? parityBox(box.width, box.height, typeof height === "number" ? height : 0, fill) : box.width;
|
|
272
521
|
const h = parity ? width : fill ? box.height : height;
|
|
273
522
|
const uRef = useRef(null);
|
|
523
|
+
const ctlRef = useRef(null);
|
|
524
|
+
const explicitRef = useRef(false);
|
|
274
525
|
const hostRef = useRef(null);
|
|
275
526
|
const [cursor, setCursor] = useState(null);
|
|
527
|
+
const ids = useId();
|
|
528
|
+
const tableId = `${ids}-table`;
|
|
529
|
+
const summaryId = `${ids}-summary`;
|
|
530
|
+
const [zoomed, setZoomed] = useState(false);
|
|
531
|
+
const fullRef = useRef(null);
|
|
532
|
+
const zoomRef = useRef({ x: null, y: null });
|
|
533
|
+
const zoomedRef = useRef(false);
|
|
534
|
+
const kbRef = useRef(null);
|
|
535
|
+
const dataRef = useRef(null);
|
|
536
|
+
const [forced, setForced] = useState("");
|
|
537
|
+
const [cutKey, setCutKey] = useState("");
|
|
538
|
+
const [soloPick, setSoloPick] = useState(null);
|
|
539
|
+
const soloFound = solo && soloPick !== null ? series.findIndex((s) => soloKey(s) === soloPick) : -1;
|
|
540
|
+
const soloIdx = soloFound >= 0 ? soloFound : null;
|
|
541
|
+
const soloRef = useRef(soloIdx);
|
|
542
|
+
soloRef.current = soloIdx;
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
if (soloPick !== null && soloIdx === null) setSoloPick(null);
|
|
545
|
+
}, [soloPick, soloIdx]);
|
|
276
546
|
const onCursorRef = useRef(onCursor);
|
|
277
547
|
onCursorRef.current = onCursor;
|
|
278
548
|
const onPickRef = useRef(onPick);
|
|
@@ -290,19 +560,15 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
290
560
|
const levels = useMemo(() => (yMarks ?? []).flatMap((m) => m.drag ? [m.drag.min, m.drag.max] : [m.y]), [yMarks]);
|
|
291
561
|
const range = useMemo(() => parity ? parityRange(xs, ...ys, levels, ext ? [...ext.x, ...ext.y] : []) : null, [parity, xs, ys, levels, ext]);
|
|
292
562
|
const yLevels = !parity && !y.range && (levels.length > 0 || ext) ? [...levels, ...ext ? ext.y : []] : null;
|
|
293
|
-
const xExt = useMemo(() =>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const hi = Math.max(xs[xs.length - 1], ext.x[1]);
|
|
297
|
-
if (x.log && !(lo > 0)) return null;
|
|
298
|
-
return hi > lo ? [lo, hi] : x.log ? [lo / 10, hi * 10] : [lo - 1, hi + 1];
|
|
299
|
-
}, [ext, range, xs, x.log]);
|
|
563
|
+
const xExt = useMemo(() => range ? null : xScaleRange(xs, ext, x.range, Boolean(x.log)), [ext, range, xs, x.log, x.range]);
|
|
564
|
+
const xSpan = xs.length > 0 ? Math.max(Math.abs(xs[xs.length - 1] - xs[0]), x.range ? Math.abs(x.range[1] - x.range[0]) : 0) : 0;
|
|
565
|
+
const ticks = !x.time && x.ticks && x.ticks.length > 0 ? x.ticks : void 0;
|
|
300
566
|
const tiny = useMemo(
|
|
301
567
|
() => ({
|
|
302
|
-
x: Boolean(x.log) && smallestPositive(ext ? [xs, ext.x] : [xs], range) < LOG_TABLE_FLOOR,
|
|
568
|
+
x: !ticks && Boolean(x.log) && smallestPositive(ext ? [xs, ext.x] : [xs], range) < LOG_TABLE_FLOOR,
|
|
303
569
|
y: Boolean(y.log) && smallestPositive(yLevels ? [...ys, yLevels] : ys, range ?? y.range) < LOG_TABLE_FLOOR
|
|
304
570
|
}),
|
|
305
|
-
[x.log, y.log, xs, ys, range, y.range, yLevels, ext]
|
|
571
|
+
[x.log, y.log, xs, ys, range, y.range, yLevels, ext, ticks]
|
|
306
572
|
);
|
|
307
573
|
const xLabel = pick(x.label, lang);
|
|
308
574
|
const yLabel = pick(y.label, lang);
|
|
@@ -317,21 +583,45 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
317
583
|
const dragCount = (yMarks ?? []).filter((m) => m.drag).length;
|
|
318
584
|
const xUnit = x.unit ? pick(x.unit, lang) : "";
|
|
319
585
|
const yUnit = y.unit ? pick(y.unit, lang) : "";
|
|
586
|
+
const drawn = width > 0 && h > 0;
|
|
587
|
+
const name = title ? pick(title, lang) : defaultChartName(lang, yLabel, xLabel);
|
|
588
|
+
const zoomIsOff = zoomOff !== void 0 && zoomOff !== null;
|
|
589
|
+
const zoomWhy = zoomIsOff ? pick(zoomOff, lang) : "";
|
|
590
|
+
const zoomable = !zoomIsOff && xs.length >= 2 && xs[xs.length - 1] > xs[0];
|
|
591
|
+
const both = Boolean(zoomY || parity);
|
|
592
|
+
const zoomableRef = useRef(zoomable);
|
|
593
|
+
zoomableRef.current = zoomable;
|
|
594
|
+
const bothRef = useRef(both);
|
|
595
|
+
bothRef.current = both;
|
|
596
|
+
const xTitle = xUnit ? `${xLabel} (${xUnit})` : xLabel;
|
|
597
|
+
const yTitle = yUnit ? `${yLabel} (${yUnit})` : yLabel;
|
|
598
|
+
const yAbove = !axisTitleFits(yTitle, h - 44) || forced === yTitle;
|
|
599
|
+
const xBelow = !axisTitleFits(xTitle, width - 16);
|
|
320
600
|
const structure = JSON.stringify({
|
|
321
|
-
s: series.map((s, i) => [pick(s.label, lang), seriesStyle(s, i), s.width ?? 2, s.mode ?? "line", s.size ?? 9, isOverlay(s)]),
|
|
322
|
-
x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null, tiny.x, xExt],
|
|
601
|
+
s: series.map((s, i) => [pick(s.label, lang), seriesStyle(s, i), s.width ?? 2, s.mode ?? "line", s.size ?? 9, isOverlay(s), Boolean(s.dots)]),
|
|
602
|
+
x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null, tiny.x, xExt, ticks ? ticks.map((t) => [t.value, pick(t.label, lang)]) : null],
|
|
323
603
|
oe: ext,
|
|
324
604
|
p: range,
|
|
325
605
|
pick: Boolean(onPick),
|
|
326
606
|
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null, tiny.y],
|
|
327
607
|
m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
|
|
328
|
-
ym: (yMarks ?? []).map((m) => m.drag ? ["drag", m.drag] : [m.y, pick(m.label, lang), m.tone ?? "warn"]),
|
|
608
|
+
ym: (yMarks ?? []).map((m) => m.drag ? ["drag", m.drag, Boolean(m.label)] : [m.y, m.label ? pick(m.label, lang) : null, m.tone ?? "warn"]),
|
|
329
609
|
theme,
|
|
330
610
|
lang,
|
|
331
611
|
h,
|
|
332
|
-
w: width
|
|
612
|
+
w: width,
|
|
613
|
+
ta: [yAbove, xBelow, zoomable, both]
|
|
333
614
|
});
|
|
334
615
|
const data = useMemo(() => [xs, ...ys], [xs, ys]);
|
|
616
|
+
const leastSpan = (axis) => {
|
|
617
|
+
const u = uRef.current;
|
|
618
|
+
const f = fullRef.current;
|
|
619
|
+
if (!u || !f) return void 0;
|
|
620
|
+
if (axis === "x" ? x.time || x.log || Boolean(ticks) : Boolean(y.log)) return void 0;
|
|
621
|
+
const pr = uPlot.pxRatio || 1;
|
|
622
|
+
const room = (axis === "x" ? u.bbox.width : u.bbox.height) / pr;
|
|
623
|
+
return leastLabelSpan(axis === "x" ? x.format : y.format, f[axis], room, range ? PARITY_TICK_SPACE : axis === "x" ? 50 : 30);
|
|
624
|
+
};
|
|
335
625
|
const scatter = baseEntries.length > 0 && baseEntries.every(({ s }) => s.mode === "points");
|
|
336
626
|
useEffect(() => {
|
|
337
627
|
const el = plotRef.current;
|
|
@@ -342,22 +632,34 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
342
632
|
const font = `11px ${family}`;
|
|
343
633
|
const surface = resolveToken("--color-surface", "#fff");
|
|
344
634
|
const repeats = { x: 0, y: 0 };
|
|
635
|
+
const declareTicks = (axis, labels, repeated = repeatedLabels(labels)) => {
|
|
636
|
+
repeats[axis] = repeated;
|
|
637
|
+
hostRef.current?.setAttribute("data-ticks-repeat", String(repeats.x + repeats.y));
|
|
638
|
+
hostRef.current?.setAttribute(`data-ticks-${axis}`, String(labels.filter((l) => l).length));
|
|
639
|
+
};
|
|
345
640
|
const tick = (fmt, axis, pre) => (_u, vals) => {
|
|
346
641
|
const labels = formatTicks(pre ? pre(vals) : vals, lang, fmt ?? { digits: 4 });
|
|
347
642
|
if (axis === "x" && pre) hostRef.current?.setAttribute("data-log-labels", labels.filter((l) => l).join("|"));
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
643
|
+
declareTicks(axis, labels);
|
|
644
|
+
return labels;
|
|
645
|
+
};
|
|
646
|
+
const namedTicks = (_u, vals) => {
|
|
647
|
+
const labels = vals.map((v) => v === null || v === void 0 ? "" : tickLabelAt(ticks, v, lang) ?? "");
|
|
648
|
+
hostRef.current?.setAttribute("data-x-tick-labels", labels.filter((l) => l).join("|"));
|
|
649
|
+
const dup = namedRepeats(labels);
|
|
650
|
+
if (dup.n > 0) hostRef.current?.setAttribute("data-ticks-repeated", dup.names.join("|"));
|
|
651
|
+
else hostRef.current?.removeAttribute("data-ticks-repeated");
|
|
652
|
+
declareTicks("x", labels, dup.n);
|
|
351
653
|
return labels;
|
|
352
654
|
};
|
|
353
655
|
const near = { left: NaN, top: NaN, idx: null };
|
|
354
|
-
const nearest = (
|
|
355
|
-
const { left, top } =
|
|
656
|
+
const nearest = (u2) => {
|
|
657
|
+
const { left, top } = u2.cursor;
|
|
356
658
|
if (left === void 0 || top === void 0 || left < 0 || top < 0) return null;
|
|
357
659
|
if (left !== near.left || top !== near.top) {
|
|
358
|
-
const d =
|
|
359
|
-
const px = d[0].map((v) => v === null ? NaN :
|
|
360
|
-
const py = d.slice(1).map((col) => col.map((v) => v === null ? null :
|
|
660
|
+
const d = u2.data;
|
|
661
|
+
const px = d[0].map((v) => v === null ? NaN : u2.valToPos(v, "x"));
|
|
662
|
+
const py = d.slice(1).map((col) => col.map((v) => v === null ? null : u2.valToPos(v, "y")));
|
|
361
663
|
near.left = left;
|
|
362
664
|
near.top = top;
|
|
363
665
|
near.idx = nearestPoint(px, py, left, top);
|
|
@@ -366,11 +668,18 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
366
668
|
};
|
|
367
669
|
const xSteps = x.time || x.log ? void 0 : tickSteps(x.format);
|
|
368
670
|
const ySteps = y.log ? void 0 : tickSteps(y.format);
|
|
671
|
+
const fixedRange = (r) => (_u, min, max) => explicitRef.current && min !== null && max !== null ? [min, max] : [r[0], r[1]];
|
|
369
672
|
const opts = {
|
|
370
673
|
width,
|
|
371
674
|
height: h,
|
|
372
675
|
legend: { show: false },
|
|
373
|
-
|
|
676
|
+
cursor: {
|
|
677
|
+
...scatter ? { dataIdx: (u2, sIdx, closest) => sIdx === 0 ? closest : nearest(u2) ?? closest } : {},
|
|
678
|
+
// the brush: a drag along x (and y where both axes zoom); a drag under DRAG_THRESHOLD px is a click, not a zoom. uPlot's own setScale
|
|
679
|
+
// is off: a fixed range (`range`, `x.range`) turns the scale it sets back into the fixed one, so the selection is applied by the
|
|
680
|
+
// `setSelect` hook below, through the same operation as the wheel and the keys
|
|
681
|
+
drag: zoomable ? { x: true, y: both, dist: DRAG_THRESHOLD, setScale: false } : { x: false, y: false, setScale: false }
|
|
682
|
+
},
|
|
374
683
|
// a log axis labels the ticks uPlot keeps and passes null for the rest, which formatTicks leaves blank
|
|
375
684
|
scales: {
|
|
376
685
|
// a log x axis spans the data, not the decades around it (uPlot rounds out to the next decade, which left an
|
|
@@ -378,16 +687,18 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
378
687
|
x: {
|
|
379
688
|
time: Boolean(x.time),
|
|
380
689
|
distr: x.log ? 3 : 1,
|
|
381
|
-
|
|
690
|
+
// a fixed range is a function that gives way to a zoom: uPlot passes even an explicit range through the scale's range, so a constant
|
|
691
|
+
// (or an array) undid every zoom of a chart with `x.range` or `y.range` (Tier A, 0.19.0)
|
|
692
|
+
...range ? { range: fixedRange(range) } : xExt ? { range: fixedRange(xExt) } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
382
693
|
},
|
|
383
694
|
y: {
|
|
384
695
|
distr: y.log ? 3 : 1,
|
|
385
|
-
...range ? { range } : y.range ? { range: y.range } : yLevels ? {
|
|
696
|
+
...range ? { range: fixedRange(range) } : y.range ? { range: fixedRange(y.range) } : yLevels ? {
|
|
386
697
|
// uPlot's own range of the data and the levels: its padding, its decade snap on a log axis
|
|
387
|
-
range: (
|
|
698
|
+
range: (u2, dmin, dmax) => {
|
|
388
699
|
const w = rangeWithLevels(dmin, dmax, yLevels, Boolean(y.log), 0.05);
|
|
389
700
|
if (!w) return [dmin, dmax];
|
|
390
|
-
return y.log ? uPlot.rangeLog(w[0], w[1],
|
|
701
|
+
return y.log ? uPlot.rangeLog(w[0], w[1], u2.scales.y.log ?? 10, false) : uPlot.rangeNum(w[0], w[1], 0.1, true);
|
|
391
702
|
}
|
|
392
703
|
} : {}
|
|
393
704
|
}
|
|
@@ -398,20 +709,25 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
398
709
|
grid: { stroke: grid, width: 1 },
|
|
399
710
|
ticks: { stroke: grid },
|
|
400
711
|
font,
|
|
401
|
-
label:
|
|
712
|
+
label: xBelow ? void 0 : xTitle,
|
|
402
713
|
labelFont: font,
|
|
403
714
|
// a log x axis labels the decades only; the ticks between stay drawn, unlabelled
|
|
404
|
-
values: x.time ? void 0 : tick(x.format, "x", x.log && !tiny.x ? decadeOnly : void 0),
|
|
405
|
-
...
|
|
406
|
-
|
|
407
|
-
|
|
715
|
+
values: x.time ? void 0 : ticks ? namedTicks : tick(x.format, "x", x.log && !tiny.x ? decadeOnly : void 0),
|
|
716
|
+
...ticks ? (
|
|
717
|
+
// exactly the named values, no other tick
|
|
718
|
+
{ splits: (_u, _i, min, max) => tickSplits(ticks, min, max, Boolean(x.log)), filter: (_u, splits) => splits }
|
|
719
|
+
) : {
|
|
720
|
+
...xSteps ? { incrs: xSteps } : {},
|
|
721
|
+
...range ? { space: PARITY_TICK_SPACE } : {},
|
|
722
|
+
...tiny.x ? DECADE_AXIS : {}
|
|
723
|
+
}
|
|
408
724
|
},
|
|
409
725
|
{
|
|
410
726
|
stroke: fg,
|
|
411
727
|
grid: { stroke: grid, width: 1 },
|
|
412
728
|
ticks: { stroke: grid },
|
|
413
729
|
font,
|
|
414
|
-
label:
|
|
730
|
+
label: yAbove ? void 0 : yTitle,
|
|
415
731
|
labelFont: font,
|
|
416
732
|
values: tick(y.format, "y"),
|
|
417
733
|
...ySteps ? { incrs: ySteps } : {},
|
|
@@ -420,12 +736,12 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
420
736
|
// Sized to the longest tick label. uPlot draws in device pixels with a font scaled by its pixel ratio, so the
|
|
421
737
|
// label is measured in that font and divided back (measuring the unscaled font and dividing by the ratio
|
|
422
738
|
// made the axis too narrow on every high-density screen; the gate runs at ratio 1 and never saw it).
|
|
423
|
-
size: (
|
|
739
|
+
size: (u2, values) => {
|
|
424
740
|
if (!values || values.length === 0) return 48;
|
|
425
741
|
const pr = uPlot.pxRatio || 1;
|
|
426
|
-
|
|
427
|
-
const widest = Math.max(...values.map((v) =>
|
|
428
|
-
return Math.ceil(widest / pr) + 30;
|
|
742
|
+
u2.ctx.font = `${Math.round(11 * pr)}px ${family}`;
|
|
743
|
+
const widest = Math.max(...values.map((v) => u2.ctx.measureText(v ?? "").width));
|
|
744
|
+
return Math.ceil(widest / pr) + (yAbove ? 18 : 30);
|
|
429
745
|
}
|
|
430
746
|
}
|
|
431
747
|
],
|
|
@@ -434,21 +750,22 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
434
750
|
...baseEntries.map(({ s, i }) => {
|
|
435
751
|
const style = seriesStyle(s, i);
|
|
436
752
|
const stroke = resolveToken(style.color, "#4a8");
|
|
437
|
-
|
|
753
|
+
const dim = soloStyle(soloRef.current, i, s.mode === "points" ? 1.5 : s.width ?? 2);
|
|
754
|
+
return s.mode === "points" ? { label: pick(s.label, lang), stroke, width: dim.width, alpha: dim.alpha, paths: () => null, points: { show: true, space: 0, size: s.size ?? 9, fill: stroke } } : { label: pick(s.label, lang), stroke, width: dim.width, alpha: dim.alpha, dash: style.dash, spanGaps: false, points: s.dots ? { show: true, size: 5, fill: stroke } : { show: false } };
|
|
438
755
|
})
|
|
439
756
|
],
|
|
440
757
|
hooks: {
|
|
441
758
|
setCursor: [
|
|
442
|
-
(
|
|
443
|
-
const idx = scatter ? nearest(
|
|
759
|
+
(u2) => {
|
|
760
|
+
const idx = scatter ? nearest(u2) : u2.cursor.idx ?? null;
|
|
444
761
|
pickedRef.current = idx;
|
|
445
762
|
setCursor(idx);
|
|
446
763
|
onCursorRef.current?.(idx === null ? null : orderRef.current ? orderRef.current[idx] : idx);
|
|
447
764
|
}
|
|
448
765
|
],
|
|
449
766
|
ready: [
|
|
450
|
-
(
|
|
451
|
-
|
|
767
|
+
(u2) => {
|
|
768
|
+
u2.over.addEventListener("click", () => {
|
|
452
769
|
const idx = pickedRef.current;
|
|
453
770
|
if (idx === null || idx === void 0 || !onPickRef.current) return;
|
|
454
771
|
onPickRef.current(orderRef.current ? orderRef.current[idx] : idx);
|
|
@@ -473,7 +790,7 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
473
790
|
if (v === liveRef.current[i]) return;
|
|
474
791
|
liveRef.current[i] = v;
|
|
475
792
|
onYMarkChangeRef.current?.(i, v);
|
|
476
|
-
|
|
793
|
+
u2.redraw(false);
|
|
477
794
|
};
|
|
478
795
|
let dragging = false;
|
|
479
796
|
el2.addEventListener("pointerdown", (e) => {
|
|
@@ -485,7 +802,7 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
485
802
|
});
|
|
486
803
|
el2.addEventListener("pointermove", (e) => {
|
|
487
804
|
if (!dragging) return;
|
|
488
|
-
set(
|
|
805
|
+
set(u2.posToVal(e.clientY - u2.over.getBoundingClientRect().top, "y"));
|
|
489
806
|
});
|
|
490
807
|
const end = (e) => {
|
|
491
808
|
dragging = false;
|
|
@@ -499,97 +816,337 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
499
816
|
e.preventDefault();
|
|
500
817
|
set(v);
|
|
501
818
|
});
|
|
502
|
-
|
|
819
|
+
u2.over.appendChild(el2);
|
|
503
820
|
});
|
|
504
|
-
if (dragCount) queueMicrotask(() => uRef.current ===
|
|
821
|
+
if (dragCount) queueMicrotask(() => uRef.current === u2 && u2.redraw(false));
|
|
822
|
+
}
|
|
823
|
+
],
|
|
824
|
+
setSelect: [
|
|
825
|
+
(u2) => {
|
|
826
|
+
const sel = { left: u2.select.left, top: u2.select.top, width: u2.select.width, height: u2.select.height };
|
|
827
|
+
if (!(sel.width > 0 || sel.height > 0)) return;
|
|
828
|
+
const x0 = u2.posToVal(sel.left, "x");
|
|
829
|
+
const x1 = u2.posToVal(sel.left + sel.width, "x");
|
|
830
|
+
const y0 = u2.posToVal(sel.top + sel.height, "y");
|
|
831
|
+
const y1 = u2.posToVal(sel.top, "y");
|
|
832
|
+
u2.setSelect({ left: 0, top: 0, width: 0, height: 0 }, false);
|
|
833
|
+
ctlRef.current?.brush(sel.width > 0 ? [x0, x1] : null, bothRef.current && sel.height > 0 ? [y0, y1] : void 0);
|
|
834
|
+
}
|
|
835
|
+
],
|
|
836
|
+
setScale: [
|
|
837
|
+
(u2, key) => {
|
|
838
|
+
if (key !== "x" && key !== "y") return;
|
|
839
|
+
const f2 = fullRef.current;
|
|
840
|
+
if (!f2) return;
|
|
841
|
+
const rx = [u2.scales.x.min ?? f2.x[0], u2.scales.x.max ?? f2.x[1]];
|
|
842
|
+
const ry = [u2.scales.y.min ?? f2.y[0], u2.scales.y.max ?? f2.y[1]];
|
|
843
|
+
hostRef.current?.setAttribute("data-view-x", `${rx[0]},${rx[1]}`);
|
|
844
|
+
const zx = isZoomed(rx, f2.x);
|
|
845
|
+
const zy = bothRef.current && isZoomed(ry, f2.y);
|
|
846
|
+
zoomRef.current = { x: zx ? rx : null, y: zy ? ry : null };
|
|
847
|
+
const z = zx || zy;
|
|
848
|
+
if (z !== zoomedRef.current) {
|
|
849
|
+
zoomedRef.current = z;
|
|
850
|
+
setZoomed(z);
|
|
851
|
+
}
|
|
852
|
+
u2.over.style.touchAction = zoomableRef.current ? z ? "none" : "pan-y" : "";
|
|
505
853
|
}
|
|
506
854
|
],
|
|
507
855
|
draw: [
|
|
508
|
-
(
|
|
856
|
+
(u2) => {
|
|
509
857
|
if (range) {
|
|
510
|
-
const ctx =
|
|
511
|
-
const
|
|
858
|
+
const ctx = u2.ctx;
|
|
859
|
+
const pr2 = uPlot.pxRatio || 1;
|
|
512
860
|
ctx.save();
|
|
513
861
|
ctx.strokeStyle = resolveToken("--color-fg-faint", "#888");
|
|
514
|
-
ctx.lineWidth = Math.max(1,
|
|
515
|
-
ctx.setLineDash([5 *
|
|
862
|
+
ctx.lineWidth = Math.max(1, pr2);
|
|
863
|
+
ctx.setLineDash([5 * pr2, 4 * pr2]);
|
|
516
864
|
ctx.beginPath();
|
|
517
|
-
ctx.moveTo(
|
|
518
|
-
ctx.lineTo(
|
|
865
|
+
ctx.moveTo(u2.valToPos(range[0], "x", true), u2.valToPos(range[0], "y", true));
|
|
866
|
+
ctx.lineTo(u2.valToPos(range[1], "x", true), u2.valToPos(range[1], "y", true));
|
|
519
867
|
ctx.stroke();
|
|
520
868
|
ctx.restore();
|
|
521
869
|
}
|
|
522
870
|
const dots = overlaysRef.current;
|
|
523
871
|
if (dots.length) {
|
|
524
|
-
const ctx =
|
|
525
|
-
const
|
|
526
|
-
let
|
|
872
|
+
const ctx = u2.ctx;
|
|
873
|
+
const pr2 = uPlot.pxRatio || 1;
|
|
874
|
+
let drawn2 = 0;
|
|
527
875
|
ctx.save();
|
|
528
876
|
ctx.beginPath();
|
|
529
|
-
ctx.rect(
|
|
877
|
+
ctx.rect(u2.bbox.left, u2.bbox.top, u2.bbox.width, u2.bbox.height);
|
|
530
878
|
ctx.clip();
|
|
531
879
|
for (const { s, i } of dots) {
|
|
880
|
+
ctx.globalAlpha = soloStyle(soloRef.current, i, 1).alpha;
|
|
532
881
|
ctx.fillStyle = resolveToken(seriesStyle(s, i).color, "#4a8");
|
|
533
882
|
ctx.strokeStyle = surface;
|
|
534
|
-
ctx.lineWidth = Math.max(1,
|
|
535
|
-
const r = (s.size ?? 9) / 2 *
|
|
883
|
+
ctx.lineWidth = Math.max(1, pr2);
|
|
884
|
+
const r = (s.size ?? 9) / 2 * pr2;
|
|
536
885
|
const ox = s.x ?? [];
|
|
537
886
|
for (let k = 0; k < Math.min(ox.length, s.values.length); k++) {
|
|
538
887
|
const v = s.values[k];
|
|
539
888
|
if (v === null || v === void 0 || !Number.isFinite(v) || !Number.isFinite(ox[k])) continue;
|
|
540
|
-
const px =
|
|
541
|
-
const py =
|
|
542
|
-
if (!Number.isFinite(px) || !Number.isFinite(py) || px <
|
|
889
|
+
const px = u2.valToPos(ox[k], "x", true);
|
|
890
|
+
const py = u2.valToPos(v, "y", true);
|
|
891
|
+
if (!Number.isFinite(px) || !Number.isFinite(py) || px < u2.bbox.left - 0.5 || px > u2.bbox.left + u2.bbox.width + 0.5 || py < u2.bbox.top - 0.5 || py > u2.bbox.top + u2.bbox.height + 0.5) continue;
|
|
543
892
|
ctx.beginPath();
|
|
544
893
|
ctx.arc(px, py, r, 0, Math.PI * 2);
|
|
545
894
|
ctx.fill();
|
|
546
895
|
ctx.stroke();
|
|
547
|
-
|
|
896
|
+
drawn2++;
|
|
548
897
|
}
|
|
549
898
|
}
|
|
550
899
|
ctx.restore();
|
|
551
|
-
hostRef.current?.setAttribute("data-overlay-drawn", String(
|
|
900
|
+
hostRef.current?.setAttribute("data-overlay-drawn", String(drawn2));
|
|
552
901
|
}
|
|
553
902
|
const markStyle = { color: resolveToken("--color-warn", "#c80"), halo: surface, family };
|
|
554
903
|
const lines = yMarksRef.current ?? [];
|
|
555
|
-
|
|
556
|
-
|
|
904
|
+
const boxes = [];
|
|
905
|
+
const ycut = lines.length ? drawYMarks(u2, lines.map((m, i) => ({ y: liveRef.current[i] ?? m.y, label: m.label ? pick(m.label, lang) : void 0, color: resolveToken(YMARK_TONE_TOKENS[m.tone ?? "warn"], "#c80") })), markStyle, boxes) : [];
|
|
906
|
+
u2.over.querySelectorAll("[data-y-handle]").forEach((el2) => {
|
|
557
907
|
const i = Number(el2.getAttribute("data-y-handle"));
|
|
558
908
|
const m = lines[i];
|
|
559
909
|
const v = liveRef.current[i];
|
|
560
910
|
if (!m || v === void 0) return;
|
|
561
|
-
const top =
|
|
562
|
-
el2.style.display = Number.isFinite(top) && top >= 0 && top <=
|
|
911
|
+
const top = u2.valToPos(v, "y");
|
|
912
|
+
el2.style.display = Number.isFinite(top) && top >= 0 && top <= u2.bbox.height / (uPlot.pxRatio || 1) ? "" : "none";
|
|
563
913
|
el2.style.top = `${top - HANDLE_HEIGHT / 2}px`;
|
|
564
|
-
const label = pick(m.label, lang);
|
|
565
|
-
|
|
914
|
+
const label = m.label ? pick(m.label, lang) : "";
|
|
915
|
+
const shown = `${formatNumber(v, lang, y.format ?? { digits: 4 })}${yUnit ? `${NBSP}${yUnit}` : ""}`;
|
|
916
|
+
const unnamed = lines.flatMap((one, k) => one.drag && !one.label ? [k] : []);
|
|
917
|
+
el2.setAttribute("aria-label", label || referenceLineName(lang, unnamed.indexOf(i) + 1, unnamed.length));
|
|
566
918
|
el2.setAttribute("aria-valuenow", String(Number(v.toPrecision(12))));
|
|
567
|
-
el2.setAttribute("aria-valuetext",
|
|
919
|
+
el2.setAttribute("aria-valuetext", label ? `${label}: ${shown}` : shown);
|
|
568
920
|
});
|
|
569
|
-
|
|
921
|
+
const cut = [...ycut, ...marks?.length ? drawMarks(u2, marks.map((m) => ({ x: m.x, label: pick(m.label, lang) })), markStyle, boxes) : []];
|
|
922
|
+
const pr = uPlot.pxRatio || 1;
|
|
923
|
+
const rounded = (v) => Math.round(v / pr * 10) / 10;
|
|
924
|
+
hostRef.current?.setAttribute("data-mark-overlaps", String(countOverlaps(boxes)));
|
|
925
|
+
if (boxes.length) hostRef.current?.setAttribute("data-mark-boxes", JSON.stringify(boxes.map((b) => ({ k: b.kind, t: b.text, x0: rounded(b.x0), y0: rounded(b.y0), x1: rounded(b.x1), y1: rounded(b.y1), ...b.text !== b.full ? { cut: true } : {} }))));
|
|
926
|
+
else hostRef.current?.removeAttribute("data-mark-boxes");
|
|
927
|
+
if (cut.length) hostRef.current?.setAttribute("data-mark-cut", cut.join("|"));
|
|
928
|
+
else hostRef.current?.removeAttribute("data-mark-cut");
|
|
929
|
+
const now = cut.join(CUT_SEP);
|
|
930
|
+
setCutKey((prev) => prev === now ? prev : now);
|
|
570
931
|
}
|
|
571
932
|
]
|
|
572
933
|
}
|
|
573
934
|
};
|
|
935
|
+
if (!ticks) {
|
|
936
|
+
hostRef.current?.removeAttribute("data-x-tick-labels");
|
|
937
|
+
hostRef.current?.removeAttribute("data-ticks-repeated");
|
|
938
|
+
}
|
|
574
939
|
uRef.current?.destroy();
|
|
575
|
-
|
|
940
|
+
fullRef.current = null;
|
|
941
|
+
dataRef.current = data;
|
|
942
|
+
const u = new uPlot(opts, data, el);
|
|
943
|
+
uRef.current = u;
|
|
944
|
+
u.batch(() => void 0);
|
|
945
|
+
const f = { x: [u.scales.x.min ?? 0, u.scales.x.max ?? 1], y: [u.scales.y.min ?? 0, u.scales.y.max ?? 1] };
|
|
946
|
+
fullRef.current = f;
|
|
947
|
+
hostRef.current?.setAttribute("data-view-x", `${f.x[0]},${f.x[1]}`);
|
|
948
|
+
const ctl = createZoom(u, { plot: el, log: { x: Boolean(x.log), y: Boolean(y.log) }, both, enabled: () => zoomableRef.current, full: () => fullRef.current, explicit: explicitRef, least: leastSpan });
|
|
949
|
+
ctlRef.current = ctl;
|
|
950
|
+
u.over.style.touchAction = zoomable ? "pan-y" : "";
|
|
951
|
+
const held = zoomRef.current;
|
|
952
|
+
const meets = (r, full) => r && r[1] > full[0] && r[0] < full[1] ? [Math.max(r[0], full[0]), Math.min(r[1], full[1])] : null;
|
|
953
|
+
const hx = zoomable ? meets(held.x, f.x) : null;
|
|
954
|
+
const hy = zoomable && both ? meets(held.y, f.y) : null;
|
|
955
|
+
if (hx || hy) {
|
|
956
|
+
explicitRef.current = true;
|
|
957
|
+
u.batch(() => {
|
|
958
|
+
if (hx) u.setScale("x", { min: hx[0], max: hx[1] });
|
|
959
|
+
if (hy) u.setScale("y", { min: hy[0], max: hy[1] });
|
|
960
|
+
});
|
|
961
|
+
explicitRef.current = false;
|
|
962
|
+
} else {
|
|
963
|
+
zoomRef.current = { x: null, y: null };
|
|
964
|
+
if (zoomedRef.current) {
|
|
965
|
+
zoomedRef.current = false;
|
|
966
|
+
setZoomed(false);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (!yAbove && yTitle) {
|
|
970
|
+
const pr = uPlot.pxRatio || 1;
|
|
971
|
+
const cy = (u.bbox.top + u.bbox.height / 2) / pr;
|
|
972
|
+
if (!axisTitleFits(yTitle, 2 * Math.min(cy, h - cy) - 4)) setForced(yTitle);
|
|
973
|
+
}
|
|
576
974
|
return () => {
|
|
975
|
+
ctl.detach();
|
|
976
|
+
ctlRef.current = null;
|
|
577
977
|
uRef.current?.destroy();
|
|
578
978
|
uRef.current = null;
|
|
579
979
|
};
|
|
580
980
|
}, [structure]);
|
|
581
981
|
useEffect(() => {
|
|
582
|
-
uRef.current
|
|
982
|
+
const u = uRef.current;
|
|
983
|
+
if (!u) return;
|
|
984
|
+
if (dataRef.current && sameData(dataRef.current, data)) return;
|
|
985
|
+
dataRef.current = data;
|
|
986
|
+
fullRef.current = null;
|
|
987
|
+
zoomRef.current = { x: null, y: null };
|
|
988
|
+
u.batch(() => u.setData(data));
|
|
989
|
+
fullRef.current = { x: [u.scales.x.min ?? 0, u.scales.x.max ?? 1], y: [u.scales.y.min ?? 0, u.scales.y.max ?? 1] };
|
|
990
|
+
if (zoomedRef.current) {
|
|
991
|
+
zoomedRef.current = false;
|
|
992
|
+
setZoomed(false);
|
|
993
|
+
}
|
|
583
994
|
}, [data]);
|
|
584
995
|
const overlaysKey = JSON.stringify(overlays.map(({ s, i }) => [i, s.x, s.values, s.size ?? 9, seriesStyle(s, i).color]));
|
|
585
|
-
const linesKey = JSON.stringify((yMarks ?? []).map((m) => [m.y, pick(m.label, lang), m.tone ?? "warn"]));
|
|
996
|
+
const linesKey = JSON.stringify((yMarks ?? []).map((m) => [m.y, m.label ? pick(m.label, lang) : null, m.tone ?? "warn"]));
|
|
586
997
|
useEffect(() => {
|
|
587
998
|
uRef.current?.redraw(false);
|
|
588
999
|
}, [linesKey, overlaysKey]);
|
|
1000
|
+
useEffect(() => {
|
|
1001
|
+
const u = uRef.current;
|
|
1002
|
+
if (!u) return;
|
|
1003
|
+
baseEntries.forEach(({ s, i }, k) => {
|
|
1004
|
+
const dim = soloStyle(soloIdx, i, s.mode === "points" ? 1.5 : s.width ?? 2);
|
|
1005
|
+
const one = u.series[k + 1];
|
|
1006
|
+
if (!one) return;
|
|
1007
|
+
one.alpha = dim.alpha;
|
|
1008
|
+
one.width = dim.width;
|
|
1009
|
+
});
|
|
1010
|
+
u.redraw(false);
|
|
1011
|
+
}, [soloIdx, baseEntries]);
|
|
1012
|
+
const timeSpan = xs.length > 1 ? Math.abs(xs[xs.length - 1] - xs[0]) : 0;
|
|
1013
|
+
const xValue = (v) => x.time ? formatTime(v, lang, timeSpan) : formatNumber(v, lang, x.format ?? { digits: 4 });
|
|
589
1014
|
const read = cursor !== null && readout ? readout(order ? order[cursor] : cursor) : cursor !== null ? [
|
|
590
|
-
|
|
1015
|
+
// an x that sits on a named tick is named by it, not by its number
|
|
1016
|
+
tickLabelAt(ticks, xs[cursor], lang, xSpan) !== null ? `${xLabel} ${tickLabelAt(ticks, xs[cursor], lang, xSpan)}` : `${xLabel} ${xValue(xs[cursor])}${xUnit && !x.time ? `${NBSP}${xUnit}` : ""}`,
|
|
591
1017
|
...baseEntries.map(({ s }, k) => ({ s, v: ys[k][cursor] ?? null })).filter(({ v }) => !scatter || v !== null).map(({ s, v }) => `${pick(s.label, lang)} ${formatNumber(v, lang, y.format ?? { digits: 4 })}${yUnit ? `${NBSP}${yUnit}` : ""}`)
|
|
592
1018
|
].join(" \xB7 ") : hint ? pick(hint, lang) : lang === "es" ? "Pase el cursor sobre el gr\xE1fico para leer los valores" : "Hover the chart to read the values";
|
|
1019
|
+
const cutTexts = cutKey ? cutKey.split(CUT_SEP) : [];
|
|
1020
|
+
const readFull = cutTexts.length ? [read, ...cutTexts].join(" \xB7 ") : read;
|
|
1021
|
+
const xText = (v) => tickLabelAt(ticks, v, lang, xSpan) ?? xValue(v);
|
|
1022
|
+
const yText = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : `${formatNumber(v, lang, y.format ?? { digits: 4 })}`;
|
|
1023
|
+
const xFormatKey = JSON.stringify(x.format ?? null);
|
|
1024
|
+
const yFormatKey = JSON.stringify(y.format ?? null);
|
|
1025
|
+
const a11y = useMemo(() => {
|
|
1026
|
+
if (!drawn) return null;
|
|
1027
|
+
const head = [xTitle, ...baseEntries.map(({ s: one }) => yUnit ? `${pick(one.label, lang)} (${yUnit})` : pick(one.label, lang)), ...overlays.map(({ s: one }) => yUnit ? `${pick(one.label, lang)} (${yUnit})` : pick(one.label, lang))];
|
|
1028
|
+
const kept = thinRows(xs.length);
|
|
1029
|
+
const blanks = (n) => Array.from({ length: n }, () => "");
|
|
1030
|
+
const rows = kept.map((i) => [xText(xs[i]), ...ys.map((col) => yText(col[i])), ...blanks(overlays.length)]);
|
|
1031
|
+
overlays.forEach(({ s: one }, o) => {
|
|
1032
|
+
const ox = one.x ?? [];
|
|
1033
|
+
for (const k of thinRows(Math.min(ox.length, one.values.length))) {
|
|
1034
|
+
if (!Number.isFinite(ox[k]) || one.values[k] === null || one.values[k] === void 0) continue;
|
|
1035
|
+
const cells = [...blanks(ys.length), ...blanks(overlays.length)];
|
|
1036
|
+
cells[ys.length + o] = yText(one.values[k]);
|
|
1037
|
+
rows.push([xText(ox[k]), ...cells]);
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
const multi = baseEntries.length + overlays.length > 1;
|
|
1041
|
+
let low = null;
|
|
1042
|
+
let high = null;
|
|
1043
|
+
baseEntries.forEach(({ s: one }, k) => {
|
|
1044
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1045
|
+
const v = ys[k][i];
|
|
1046
|
+
if (v === null || v === void 0 || !Number.isFinite(v)) continue;
|
|
1047
|
+
const at = `${multi ? `${pick(one.label, lang)}, ` : ""}${xLabel} ${xText(xs[i])}`;
|
|
1048
|
+
if (!low || v < low.v) low = { v, at };
|
|
1049
|
+
if (!high || v > high.v) high = { v, at };
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
const unit = yUnit ? `${NBSP}${yUnit}` : "";
|
|
1053
|
+
const lo = low;
|
|
1054
|
+
const hi = high;
|
|
1055
|
+
const summary = chartSummary(lang, {
|
|
1056
|
+
kind: "line",
|
|
1057
|
+
name,
|
|
1058
|
+
count: xs.length,
|
|
1059
|
+
series: series.map((one) => pick(one.label, lang)),
|
|
1060
|
+
xLabel,
|
|
1061
|
+
xFrom: xs.length ? xText(xs[0]) : void 0,
|
|
1062
|
+
xTo: xs.length ? xText(xs[xs.length - 1]) : void 0,
|
|
1063
|
+
yLabel: yTitle,
|
|
1064
|
+
low: lo ? { text: `${yText(lo.v)}${unit}`, at: lo.at } : void 0,
|
|
1065
|
+
high: hi ? { text: `${yText(hi.v)}${unit}`, at: hi.at } : void 0
|
|
1066
|
+
});
|
|
1067
|
+
return { head, rows, summary, thinned: kept.length < xs.length };
|
|
1068
|
+
}, [drawn, xs, ys, baseEntries, overlays, series, lang, ticks, xSpan, xTitle, yTitle, xLabel, yUnit, name, xFormatKey, yFormatKey, x.time, timeSpan]);
|
|
1069
|
+
const marksId = `${ids}-marks`;
|
|
1070
|
+
const markRows = useMemo(() => {
|
|
1071
|
+
const unnamed = (yMarks ?? []).flatMap((m, k) => m.label ? [] : [k]);
|
|
1072
|
+
const xs2 = (marks ?? []).map((m) => [pick(m.label, lang), `${xLabel} ${xText(m.x)}${xUnit && !x.time && tickLabelAt(ticks, m.x, lang, xSpan) === null ? `${NBSP}${xUnit}` : ""}`]);
|
|
1073
|
+
const ys2 = (yMarks ?? []).map((m, k) => [m.label ? pick(m.label, lang) : referenceLineName(lang, unnamed.indexOf(k) + 1, unnamed.length), `${yText(m.y)}${yUnit ? `${NBSP}${yUnit}` : ""}`]);
|
|
1074
|
+
return [...xs2, ...ys2];
|
|
1075
|
+
}, [marks, yMarks, lang, xLabel, xUnit, yUnit, ticks, xSpan, xFormatKey, yFormatKey, x.time, timeSpan]);
|
|
1076
|
+
const onKeyDown = (e) => {
|
|
1077
|
+
if (e.target !== e.currentTarget) return;
|
|
1078
|
+
const u = uRef.current;
|
|
1079
|
+
const ctl = ctlRef.current;
|
|
1080
|
+
if (!u || !ctl) return;
|
|
1081
|
+
const act = keyAction(e.key, { shift: e.shiftKey, ctrl: e.ctrlKey, meta: e.metaKey, alt: e.altKey });
|
|
1082
|
+
if (!act) return;
|
|
1083
|
+
const n = xs.length;
|
|
1084
|
+
const stand = kbRef.current;
|
|
1085
|
+
switch (act) {
|
|
1086
|
+
case "prev":
|
|
1087
|
+
case "next":
|
|
1088
|
+
case "first":
|
|
1089
|
+
case "last": {
|
|
1090
|
+
let idx;
|
|
1091
|
+
if (stand === null && zoomedRef.current && (act === "next" || act === "prev")) {
|
|
1092
|
+
const v = ctl.view().x;
|
|
1093
|
+
let last = null;
|
|
1094
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
1095
|
+
if (xs[i] >= v[0] && xs[i] <= v[1]) {
|
|
1096
|
+
last = i;
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
idx = act === "next" ? firstVisible(xs, v[0], v[1]) : last;
|
|
1101
|
+
} else idx = stepIndex(stand, act, n);
|
|
1102
|
+
if (idx === null) return;
|
|
1103
|
+
kbRef.current = idx;
|
|
1104
|
+
ctl.reveal(xs[idx]);
|
|
1105
|
+
ctl.place(idx);
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
case "zoomIn":
|
|
1109
|
+
case "zoomOut": {
|
|
1110
|
+
if (!zoomableRef.current) return;
|
|
1111
|
+
const v = ctl.view();
|
|
1112
|
+
const ax = stand !== null ? xs[stand] : x.log && v.x[0] > 0 ? Math.sqrt(v.x[0] * v.x[1]) : (v.x[0] + v.x[1]) / 2;
|
|
1113
|
+
const firstY = stand !== null ? ys.map((col) => col[stand]).find((val) => val !== null && val !== void 0 && Number.isFinite(val)) : void 0;
|
|
1114
|
+
const ay = firstY ?? (y.log && v.y[0] > 0 ? Math.sqrt(v.y[0] * v.y[1]) : (v.y[0] + v.y[1]) / 2);
|
|
1115
|
+
ctl.zoomBy(act === "zoomIn" ? KEY_ZOOM : 1 / KEY_ZOOM, ax, ay);
|
|
1116
|
+
if (stand !== null) ctl.place(stand);
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
case "panPrev":
|
|
1120
|
+
case "panNext":
|
|
1121
|
+
if (!zoomableRef.current || !zoomedRef.current) return;
|
|
1122
|
+
ctl.panBy(act === "panNext" ? KEY_PAN : -KEY_PAN);
|
|
1123
|
+
break;
|
|
1124
|
+
case "reset":
|
|
1125
|
+
if (e.key === "Escape" && !zoomedRef.current) return;
|
|
1126
|
+
ctl.reset();
|
|
1127
|
+
if (stand !== null) ctl.place(stand);
|
|
1128
|
+
e.stopPropagation();
|
|
1129
|
+
break;
|
|
1130
|
+
case "activate": {
|
|
1131
|
+
if (stand === null || !onPickRef.current) return;
|
|
1132
|
+
onPickRef.current(orderRef.current ? orderRef.current[stand] : stand);
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
e.preventDefault();
|
|
1137
|
+
};
|
|
1138
|
+
const onBlur = (e) => {
|
|
1139
|
+
if (e.relatedTarget instanceof Node && e.currentTarget.contains(e.relatedTarget)) return;
|
|
1140
|
+
if (kbRef.current === null) return;
|
|
1141
|
+
kbRef.current = null;
|
|
1142
|
+
ctlRef.current?.clear();
|
|
1143
|
+
};
|
|
1144
|
+
const resetZoom = () => {
|
|
1145
|
+
ctlRef.current?.reset();
|
|
1146
|
+
plotRef.current?.focus({ preventScroll: true });
|
|
1147
|
+
};
|
|
1148
|
+
const moved = [xBelow ? "x" : "", yAbove ? "y" : ""].filter(Boolean).join("");
|
|
1149
|
+
const readoutEl = /* @__PURE__ */ jsx("p", { className: "caos-chart-readout", "aria-live": "polite", title: readFull, children: readFull });
|
|
593
1150
|
return /* @__PURE__ */ jsxs(
|
|
594
1151
|
"div",
|
|
595
1152
|
{
|
|
@@ -606,16 +1163,63 @@ function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280,
|
|
|
606
1163
|
"data-y-marks": yMarks?.length ? String(yMarks.length) : void 0,
|
|
607
1164
|
"data-y-drag": dragCount ? String(dragCount) : void 0,
|
|
608
1165
|
"data-overlay": overlays.length ? String(overlays.length) : void 0,
|
|
1166
|
+
"data-solo": soloIdx !== null ? String(soloIdx) : void 0,
|
|
1167
|
+
"data-chart-host": drawn ? "line" : void 0,
|
|
1168
|
+
"data-points": drawn ? String(xs.length) : void 0,
|
|
1169
|
+
"data-zoomable": drawn ? zoomable ? "1" : "0" : void 0,
|
|
1170
|
+
"data-zoomed": drawn && zoomed ? "1" : void 0,
|
|
1171
|
+
"data-zoom-off": drawn && zoomIsOff ? zoomWhy : void 0,
|
|
1172
|
+
"data-axis-title-moved": drawn && moved ? moved : void 0,
|
|
609
1173
|
children: [
|
|
610
|
-
/* @__PURE__ */ jsx("
|
|
611
|
-
|
|
1174
|
+
drawn && yAbove && yTitle && /* @__PURE__ */ jsx("p", { className: "caos-chart-axis-title", "data-axis-title": "y", children: yTitle }),
|
|
1175
|
+
/* @__PURE__ */ jsx(
|
|
1176
|
+
"div",
|
|
1177
|
+
{
|
|
1178
|
+
ref: setPlot,
|
|
1179
|
+
className: "caos-chart-plot",
|
|
1180
|
+
title: cutTexts.length ? cutTexts.join("; ") : void 0,
|
|
1181
|
+
...drawn ? {
|
|
1182
|
+
tabIndex: 0,
|
|
1183
|
+
role: "group",
|
|
1184
|
+
"aria-roledescription": CHART_ROLE[lang],
|
|
1185
|
+
"aria-label": name,
|
|
1186
|
+
"aria-describedby": summaryId,
|
|
1187
|
+
"aria-details": markRows.length ? `${tableId} ${marksId}` : tableId,
|
|
1188
|
+
"aria-description": zoomable ? ZOOM_HINT[lang] : void 0,
|
|
1189
|
+
"data-chart-focus": "",
|
|
1190
|
+
onKeyDown,
|
|
1191
|
+
onBlur
|
|
1192
|
+
} : {}
|
|
1193
|
+
}
|
|
1194
|
+
),
|
|
1195
|
+
drawn && xBelow && /* @__PURE__ */ jsx("p", { className: "caos-chart-axis-title", "data-axis-title": "x", children: xTitle }),
|
|
1196
|
+
drawn && a11y && /* @__PURE__ */ jsx(ChartSummary, { id: summaryId, text: a11y.summary }),
|
|
1197
|
+
drawn && markRows.length > 0 && /* @__PURE__ */ jsx("div", { className: "sr-only", "data-chart-marks-box": "", children: /* @__PURE__ */ jsxs("table", { id: marksId, className: "caos-chart-table", "data-chart-marks": "", children: [
|
|
1198
|
+
/* @__PURE__ */ jsx("caption", { children: lang === "es" ? `Marcas de ${name}` : `Marks of ${name}` }),
|
|
1199
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
1200
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: lang === "es" ? "Marca" : "Mark" }),
|
|
1201
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: lang === "es" ? "Posici\xF3n" : "Position" })
|
|
1202
|
+
] }) }),
|
|
1203
|
+
/* @__PURE__ */ jsx("tbody", { children: markRows.map((row, r) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
1204
|
+
/* @__PURE__ */ jsx("th", { scope: "row", children: row[0] }),
|
|
1205
|
+
/* @__PURE__ */ jsx("td", { children: row[1] })
|
|
1206
|
+
] }, r)) })
|
|
1207
|
+
] }) }),
|
|
1208
|
+
drawn && a11y && /* @__PURE__ */ jsx(ChartDataTable, { id: tableId, caption: a11y.thinned ? `${name} (${lang === "es" ? "una de cada varias filas" : "every few rows"})` : name, head: a11y.head, rows: a11y.rows }),
|
|
1209
|
+
(series.length > 1 || solo) && /* @__PURE__ */ jsx("ul", { className: "caos-chart-legend", "aria-label": lang === "es" ? "Series del gr\xE1fico" : "Series of the chart", children: series.map((s, i) => /* @__PURE__ */ jsx("li", { className: "caos-chart-key", "data-mode": s.mode ?? "line", "data-dash": seriesStyle(s, i).dash ? "1" : void 0, children: solo ? /* @__PURE__ */ jsxs("button", { type: "button", className: "caos-chart-key-button", "aria-pressed": soloIdx === i, onClick: () => setSoloPick(soloIdx === i ? null : soloKey(s)), children: [
|
|
1210
|
+
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${seriesStyle(s, i).color})` }, "aria-hidden": "true" }),
|
|
1211
|
+
pick(s.label, lang)
|
|
1212
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
612
1213
|
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${seriesStyle(s, i).color})` }, "aria-hidden": "true" }),
|
|
613
1214
|
pick(s.label, lang)
|
|
614
|
-
] }, i)) }),
|
|
615
|
-
/* @__PURE__ */
|
|
1215
|
+
] }) }, i)) }),
|
|
1216
|
+
drawn ? /* @__PURE__ */ jsxs("div", { className: "caos-chart-foot", children: [
|
|
1217
|
+
readoutEl,
|
|
1218
|
+
zoomed && zoomable && /* @__PURE__ */ jsx(ResetZoomButton, { lang, onReset: resetZoom })
|
|
1219
|
+
] }) : readoutEl
|
|
616
1220
|
]
|
|
617
1221
|
}
|
|
618
1222
|
);
|
|
619
1223
|
}
|
|
620
1224
|
|
|
621
|
-
export { LOG_TABLE_FLOOR, UPlotChart, YMARK_TONE_TOKENS, clampMark, decadeOnly, drawMarks, drawYMarks, logDecades, nearestPoint, nudgeMark, overlayExtent, parityBox, parityRange, rangeWithLevels,
|
|
1225
|
+
export { LOG_TABLE_FLOOR, SOLO_DIM_ALPHA, UPlotChart, YMARK_TONE_TOKENS, clampMark, decadeOnly, drawMarks, drawYMarks, logDecades, namedRepeats, nearestPoint, nudgeMark, overlayExtent, parityBox, parityRange, rangeWithLevels, seriesStyle, smallestPositive, soloKey, soloStyle, sortOrder, tickLabelAt, tickSplits, tickSteps, xScaleRange };
|