@fasl-work/caos-app-shell 0.16.0 → 0.18.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 +107 -9
- package/dist/chart.js +311 -31
- package/dist/{chunk-YECZXGVG.js → chunk-DBCMAL2G.js} +73 -1
- package/dist/index.d.ts +7 -40
- package/dist/index.js +67 -94
- package/dist/{tokens-BTOzar-U.d.ts → text-DyQ1A8oo.d.ts} +39 -1
- package/gate/inpage.mjs +67 -1
- package/gate/run.mjs +15 -2
- package/package.json +3 -4
- package/reserved-classes.json +10 -2
- package/styles.css +25 -4
package/dist/chart.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useShellLang, useThemeStore, useStageSize,
|
|
1
|
+
import { pick, useShellLang, useThemeStore, useStageSize, resolveToken, NBSP, formatNumber, formatTicks, referenceLineName } from './chunk-DBCMAL2G.js';
|
|
2
|
+
export { referenceLineName } from './chunk-DBCMAL2G.js';
|
|
2
3
|
import './chunk-33WTUQ7Q.js';
|
|
3
|
-
import { useRef, useCallback, useState,
|
|
4
|
+
import { useRef, useCallback, useState, 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
|
|
|
7
8
|
var YMARK_TONE_TOKENS = {
|
|
8
9
|
warn: "--color-warn",
|
|
@@ -11,6 +12,52 @@ var YMARK_TONE_TOKENS = {
|
|
|
11
12
|
neutral: "--color-fg-subtle"
|
|
12
13
|
};
|
|
13
14
|
var ROTATION = ["--color-accent", "--color-magenta", "--color-accent-2", "--color-good", "--color-warn", "--color-bad"];
|
|
15
|
+
var SOLO_DIM_ALPHA = 0.12;
|
|
16
|
+
function soloStyle(solo, i, width) {
|
|
17
|
+
return solo !== null && solo !== i ? { alpha: SOLO_DIM_ALPHA, width: 1 } : { alpha: 1, width };
|
|
18
|
+
}
|
|
19
|
+
function tickSplits(ticks, min, max, log = false) {
|
|
20
|
+
const eps = Math.abs(max - min) * 1e-9;
|
|
21
|
+
const values = ticks.map((t) => t.value).filter((v) => Number.isFinite(v) && (!log || v > 0) && v >= min - eps && v <= max + eps);
|
|
22
|
+
return [...new Set(values)].sort((a, b) => a - b);
|
|
23
|
+
}
|
|
24
|
+
function tickLabelAt(ticks, x, lang, span = 0) {
|
|
25
|
+
const eps = Math.abs(span) * 1e-9;
|
|
26
|
+
const t = ticks?.find((one) => Math.abs(one.value - x) <= eps);
|
|
27
|
+
return t ? pick(t.label, lang) : null;
|
|
28
|
+
}
|
|
29
|
+
function namedRepeats(labels) {
|
|
30
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31
|
+
const names = [];
|
|
32
|
+
let n = 0;
|
|
33
|
+
for (const l of labels) {
|
|
34
|
+
if (!l) continue;
|
|
35
|
+
if (seen.has(l)) {
|
|
36
|
+
n++;
|
|
37
|
+
if (!names.includes(l)) names.push(l);
|
|
38
|
+
} else seen.add(l);
|
|
39
|
+
}
|
|
40
|
+
return { n, names };
|
|
41
|
+
}
|
|
42
|
+
function soloKey(s) {
|
|
43
|
+
return s.id ?? pick(s.label, "en");
|
|
44
|
+
}
|
|
45
|
+
function xScaleRange(xs, ext, range, log) {
|
|
46
|
+
const given = range && !(log && Math.min(range[0], range[1]) <= 0) ? range : void 0;
|
|
47
|
+
if (!ext && !given || xs.length === 0) return null;
|
|
48
|
+
let lo = xs[0];
|
|
49
|
+
let hi = xs[xs.length - 1];
|
|
50
|
+
if (ext) {
|
|
51
|
+
lo = Math.min(lo, ext.x[0]);
|
|
52
|
+
hi = Math.max(hi, ext.x[1]);
|
|
53
|
+
}
|
|
54
|
+
if (given) {
|
|
55
|
+
lo = Math.min(lo, given[0], given[1]);
|
|
56
|
+
hi = Math.max(hi, given[0], given[1]);
|
|
57
|
+
}
|
|
58
|
+
if (log && !(lo > 0)) return null;
|
|
59
|
+
return hi > lo ? [lo, hi] : log ? [lo / 10, hi * 10] : [lo - 1, hi + 1];
|
|
60
|
+
}
|
|
14
61
|
function seriesStyle(s, i) {
|
|
15
62
|
const color = s.color ?? ROTATION[i % ROTATION.length];
|
|
16
63
|
const dash = s.dash ?? (!s.color && i >= ROTATION.length ? [6, 4] : void 0);
|
|
@@ -83,11 +130,73 @@ function smallestPositive(columns, range) {
|
|
|
83
130
|
for (const c of columns) for (const v of c) if (v !== null && Number.isFinite(v) && v > 0 && v < lo) lo = v;
|
|
84
131
|
return lo;
|
|
85
132
|
}
|
|
133
|
+
function isDecade(t) {
|
|
134
|
+
if (t === null || t === void 0 || !Number.isFinite(t) || t <= 0) return false;
|
|
135
|
+
const d = Math.log10(t);
|
|
136
|
+
return Math.abs(d - Math.round(d)) < 1e-6;
|
|
137
|
+
}
|
|
138
|
+
function decadeOnly(ticks) {
|
|
139
|
+
if (!ticks.some(isDecade)) return [...ticks];
|
|
140
|
+
return ticks.map((t) => isDecade(t) ? t : null);
|
|
141
|
+
}
|
|
142
|
+
function parityBox(boxWidth, boxHeight, height, fill) {
|
|
143
|
+
return Math.min(boxWidth, fill ? boxHeight : height);
|
|
144
|
+
}
|
|
145
|
+
function clampMark(y, drag) {
|
|
146
|
+
if (!Number.isFinite(y)) return drag.min;
|
|
147
|
+
const v = drag.step && drag.step > 0 ? drag.min + Math.round((y - drag.min) / drag.step) * drag.step : y;
|
|
148
|
+
const clamped = Math.min(drag.max, Math.max(drag.min, v));
|
|
149
|
+
return Number(clamped.toPrecision(12));
|
|
150
|
+
}
|
|
151
|
+
function nudgeMark(y, key, drag, log = false) {
|
|
152
|
+
const dir = key === "ArrowUp" || key === "ArrowRight" || key === "PageUp" ? 1 : key === "ArrowDown" || key === "ArrowLeft" || key === "PageDown" ? -1 : 0;
|
|
153
|
+
if (key === "Home") return drag.min;
|
|
154
|
+
if (key === "End") return drag.max;
|
|
155
|
+
if (dir === 0) return null;
|
|
156
|
+
const n = key === "PageUp" || key === "PageDown" ? 10 : 1;
|
|
157
|
+
if (log && drag.min > 0 && drag.max > drag.min) {
|
|
158
|
+
const decades = (Math.log10(drag.max) - Math.log10(drag.min)) / 100;
|
|
159
|
+
return Math.min(drag.max, Math.max(drag.min, y * 10 ** (dir * n * decades)));
|
|
160
|
+
}
|
|
161
|
+
const step = drag.step && drag.step > 0 ? drag.step : (drag.max - drag.min) / 100;
|
|
162
|
+
return clampMark(y + dir * n * step, { ...drag, step: void 0 });
|
|
163
|
+
}
|
|
164
|
+
function isOverlay(s) {
|
|
165
|
+
return Boolean(s.x) && s.mode === "points";
|
|
166
|
+
}
|
|
167
|
+
function dragWithin(drag, range) {
|
|
168
|
+
if (!range) return drag;
|
|
169
|
+
const lo = Math.min(range[0], range[1]);
|
|
170
|
+
const hi = Math.max(range[0], range[1]);
|
|
171
|
+
const min = Math.min(hi, Math.max(lo, drag.min));
|
|
172
|
+
const max = Math.max(lo, Math.min(hi, drag.max));
|
|
173
|
+
return { ...drag, min, max: Math.max(min, max) };
|
|
174
|
+
}
|
|
175
|
+
function overlayExtent(series, positiveX = false) {
|
|
176
|
+
let x0 = Infinity;
|
|
177
|
+
let x1 = -Infinity;
|
|
178
|
+
let y0 = Infinity;
|
|
179
|
+
let y1 = -Infinity;
|
|
180
|
+
for (const s of series) {
|
|
181
|
+
if (!isOverlay(s) || !s.x) continue;
|
|
182
|
+
for (let i = 0; i < Math.min(s.x.length, s.values.length); i++) {
|
|
183
|
+
const px = s.x[i];
|
|
184
|
+
const py = s.values[i];
|
|
185
|
+
if (py === null || py === void 0 || !Number.isFinite(px) || !Number.isFinite(py) || positiveX && !(px > 0)) continue;
|
|
186
|
+
x0 = Math.min(x0, px);
|
|
187
|
+
x1 = Math.max(x1, px);
|
|
188
|
+
y0 = Math.min(y0, py);
|
|
189
|
+
y1 = Math.max(y1, py);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return Number.isFinite(x0) ? { x: [x0, x1], y: [y0, y1] } : null;
|
|
193
|
+
}
|
|
86
194
|
var DECADE_AXIS = {
|
|
87
195
|
splits: (_u, _i, min, max) => logDecades(min, max),
|
|
88
196
|
filter: (_u, splits) => splits
|
|
89
197
|
};
|
|
90
198
|
var PARITY_TICK_SPACE = 40;
|
|
199
|
+
var HANDLE_HEIGHT = 14;
|
|
91
200
|
function repeatedLabels(labels) {
|
|
92
201
|
let n = 0;
|
|
93
202
|
for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
|
|
@@ -159,6 +268,7 @@ function drawYMarks(u, marks, style) {
|
|
|
159
268
|
ctx.lineTo(right, py);
|
|
160
269
|
ctx.stroke();
|
|
161
270
|
ctx.setLineDash([]);
|
|
271
|
+
if (!m.label) continue;
|
|
162
272
|
const w = ctx.measureText(m.label).width;
|
|
163
273
|
const base = py - gap - textH < top ? py + gap + textH : py - gap;
|
|
164
274
|
let x1 = right - gap;
|
|
@@ -193,7 +303,7 @@ function nearestPoint(xs, ys, left, top) {
|
|
|
193
303
|
}
|
|
194
304
|
return best;
|
|
195
305
|
}
|
|
196
|
-
function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPick, parity, readout, hint }) {
|
|
306
|
+
function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280, onCursor, onPick, parity, readout, hint, solo }) {
|
|
197
307
|
const lang = useShellLang();
|
|
198
308
|
const theme = useThemeStore((s) => s.theme);
|
|
199
309
|
const fill = height === "fill";
|
|
@@ -206,11 +316,19 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
206
316
|
},
|
|
207
317
|
[measureRef]
|
|
208
318
|
);
|
|
209
|
-
const width = parity ?
|
|
319
|
+
const width = parity ? parityBox(box.width, box.height, typeof height === "number" ? height : 0, fill) : box.width;
|
|
210
320
|
const h = parity ? width : fill ? box.height : height;
|
|
211
321
|
const uRef = useRef(null);
|
|
212
322
|
const hostRef = useRef(null);
|
|
213
323
|
const [cursor, setCursor] = useState(null);
|
|
324
|
+
const [soloPick, setSoloPick] = useState(null);
|
|
325
|
+
const soloFound = solo && soloPick !== null ? series.findIndex((s) => soloKey(s) === soloPick) : -1;
|
|
326
|
+
const soloIdx = soloFound >= 0 ? soloFound : null;
|
|
327
|
+
const soloRef = useRef(soloIdx);
|
|
328
|
+
soloRef.current = soloIdx;
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (soloPick !== null && soloIdx === null) setSoloPick(null);
|
|
331
|
+
}, [soloPick, soloIdx]);
|
|
214
332
|
const onCursorRef = useRef(onCursor);
|
|
215
333
|
onCursorRef.current = onCursor;
|
|
216
334
|
const onPickRef = useRef(onPick);
|
|
@@ -220,36 +338,53 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
220
338
|
const orderRef = useRef(order);
|
|
221
339
|
orderRef.current = order;
|
|
222
340
|
const xs = useMemo(() => order ? order.map((i) => x.values[i]) : x.values, [order, x.values]);
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
341
|
+
const entries = useMemo(() => series.map((s, i) => ({ s, i })), [series]);
|
|
342
|
+
const baseEntries = useMemo(() => entries.filter(({ s }) => !isOverlay(s)), [entries]);
|
|
343
|
+
const overlays = useMemo(() => entries.filter(({ s }) => isOverlay(s)), [entries]);
|
|
344
|
+
const ext = useMemo(() => overlayExtent(series, Boolean(x.log)), [series, x.log]);
|
|
345
|
+
const ys = useMemo(() => baseEntries.map(({ s }) => order ? order.map((i) => s.values[i] ?? null) : s.values), [order, baseEntries]);
|
|
346
|
+
const levels = useMemo(() => (yMarks ?? []).flatMap((m) => m.drag ? [m.drag.min, m.drag.max] : [m.y]), [yMarks]);
|
|
347
|
+
const range = useMemo(() => parity ? parityRange(xs, ...ys, levels, ext ? [...ext.x, ...ext.y] : []) : null, [parity, xs, ys, levels, ext]);
|
|
348
|
+
const yLevels = !parity && !y.range && (levels.length > 0 || ext) ? [...levels, ...ext ? ext.y : []] : null;
|
|
349
|
+
const xExt = useMemo(() => range ? null : xScaleRange(xs, ext, x.range, Boolean(x.log)), [ext, range, xs, x.log, x.range]);
|
|
350
|
+
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;
|
|
351
|
+
const ticks = !x.time && x.ticks && x.ticks.length > 0 ? x.ticks : void 0;
|
|
227
352
|
const tiny = useMemo(
|
|
228
353
|
() => ({
|
|
229
|
-
x: Boolean(x.log) && smallestPositive([xs], range) < LOG_TABLE_FLOOR,
|
|
354
|
+
x: !ticks && Boolean(x.log) && smallestPositive(ext ? [xs, ext.x] : [xs], range) < LOG_TABLE_FLOOR,
|
|
230
355
|
y: Boolean(y.log) && smallestPositive(yLevels ? [...ys, yLevels] : ys, range ?? y.range) < LOG_TABLE_FLOOR
|
|
231
356
|
}),
|
|
232
|
-
[x.log, y.log, xs, ys, range, y.range, yLevels]
|
|
357
|
+
[x.log, y.log, xs, ys, range, y.range, yLevels, ext, ticks]
|
|
233
358
|
);
|
|
234
359
|
const xLabel = pick(x.label, lang);
|
|
235
360
|
const yLabel = pick(y.label, lang);
|
|
361
|
+
const yMarksRef = useRef(yMarks);
|
|
362
|
+
yMarksRef.current = yMarks;
|
|
363
|
+
const liveRef = useRef([]);
|
|
364
|
+
liveRef.current = (yMarks ?? []).map((m) => m.y);
|
|
365
|
+
const overlaysRef = useRef(overlays);
|
|
366
|
+
overlaysRef.current = overlays;
|
|
367
|
+
const onYMarkChangeRef = useRef(onYMarkChange);
|
|
368
|
+
onYMarkChangeRef.current = onYMarkChange;
|
|
369
|
+
const dragCount = (yMarks ?? []).filter((m) => m.drag).length;
|
|
236
370
|
const xUnit = x.unit ? pick(x.unit, lang) : "";
|
|
237
371
|
const yUnit = y.unit ? pick(y.unit, lang) : "";
|
|
238
372
|
const structure = JSON.stringify({
|
|
239
|
-
s: series.map((s, i) => [pick(s.label, lang), seriesStyle(s, i), s.width ?? 2, s.mode ?? "line", s.size ?? 9]),
|
|
240
|
-
x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null, tiny.x],
|
|
373
|
+
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)]),
|
|
374
|
+
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],
|
|
375
|
+
oe: ext,
|
|
241
376
|
p: range,
|
|
242
377
|
pick: Boolean(onPick),
|
|
243
378
|
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null, tiny.y],
|
|
244
379
|
m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
|
|
245
|
-
ym: (yMarks ?? []).map((m) => [m.y, pick(m.label, lang), m.tone ?? "warn"]),
|
|
380
|
+
ym: (yMarks ?? []).map((m) => m.drag ? ["drag", m.drag, Boolean(m.label)] : [m.y, m.label ? pick(m.label, lang) : null, m.tone ?? "warn"]),
|
|
246
381
|
theme,
|
|
247
382
|
lang,
|
|
248
383
|
h,
|
|
249
384
|
w: width
|
|
250
385
|
});
|
|
251
386
|
const data = useMemo(() => [xs, ...ys], [xs, ys]);
|
|
252
|
-
const scatter =
|
|
387
|
+
const scatter = baseEntries.length > 0 && baseEntries.every(({ s }) => s.mode === "points");
|
|
253
388
|
useEffect(() => {
|
|
254
389
|
const el = plotRef.current;
|
|
255
390
|
if (!el || width <= 0 || h <= 0) return;
|
|
@@ -259,11 +394,24 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
259
394
|
const font = `11px ${family}`;
|
|
260
395
|
const surface = resolveToken("--color-surface", "#fff");
|
|
261
396
|
const repeats = { x: 0, y: 0 };
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
repeats[axis] = repeatedLabels(labels);
|
|
397
|
+
const declareTicks = (axis, labels, repeated = repeatedLabels(labels)) => {
|
|
398
|
+
repeats[axis] = repeated;
|
|
265
399
|
hostRef.current?.setAttribute("data-ticks-repeat", String(repeats.x + repeats.y));
|
|
266
400
|
hostRef.current?.setAttribute(`data-ticks-${axis}`, String(labels.filter((l) => l).length));
|
|
401
|
+
};
|
|
402
|
+
const tick = (fmt, axis, pre) => (_u, vals) => {
|
|
403
|
+
const labels = formatTicks(pre ? pre(vals) : vals, lang, fmt ?? { digits: 4 });
|
|
404
|
+
if (axis === "x" && pre) hostRef.current?.setAttribute("data-log-labels", labels.filter((l) => l).join("|"));
|
|
405
|
+
declareTicks(axis, labels);
|
|
406
|
+
return labels;
|
|
407
|
+
};
|
|
408
|
+
const namedTicks = (_u, vals) => {
|
|
409
|
+
const labels = vals.map((v) => v === null || v === void 0 ? "" : tickLabelAt(ticks, v, lang) ?? "");
|
|
410
|
+
hostRef.current?.setAttribute("data-x-tick-labels", labels.filter((l) => l).join("|"));
|
|
411
|
+
const dup = namedRepeats(labels);
|
|
412
|
+
if (dup.n > 0) hostRef.current?.setAttribute("data-ticks-repeated", dup.names.join("|"));
|
|
413
|
+
else hostRef.current?.removeAttribute("data-ticks-repeated");
|
|
414
|
+
declareTicks("x", labels, dup.n);
|
|
267
415
|
return labels;
|
|
268
416
|
};
|
|
269
417
|
const near = { left: NaN, top: NaN, idx: null };
|
|
@@ -294,7 +442,7 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
294
442
|
x: {
|
|
295
443
|
time: Boolean(x.time),
|
|
296
444
|
distr: x.log ? 3 : 1,
|
|
297
|
-
...range ? { range } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
445
|
+
...range ? { range } : xExt ? { range: xExt } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
298
446
|
},
|
|
299
447
|
y: {
|
|
300
448
|
distr: y.log ? 3 : 1,
|
|
@@ -316,10 +464,16 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
316
464
|
font,
|
|
317
465
|
label: xUnit ? `${xLabel} (${xUnit})` : xLabel,
|
|
318
466
|
labelFont: font,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
...
|
|
322
|
-
|
|
467
|
+
// a log x axis labels the decades only; the ticks between stay drawn, unlabelled
|
|
468
|
+
values: x.time ? void 0 : ticks ? namedTicks : tick(x.format, "x", x.log && !tiny.x ? decadeOnly : void 0),
|
|
469
|
+
...ticks ? (
|
|
470
|
+
// exactly the named values, no other tick
|
|
471
|
+
{ splits: (_u, _i, min, max) => tickSplits(ticks, min, max, Boolean(x.log)), filter: (_u, splits) => splits }
|
|
472
|
+
) : {
|
|
473
|
+
...xSteps ? { incrs: xSteps } : {},
|
|
474
|
+
...range ? { space: PARITY_TICK_SPACE } : {},
|
|
475
|
+
...tiny.x ? DECADE_AXIS : {}
|
|
476
|
+
}
|
|
323
477
|
},
|
|
324
478
|
{
|
|
325
479
|
stroke: fg,
|
|
@@ -346,10 +500,11 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
346
500
|
],
|
|
347
501
|
series: [
|
|
348
502
|
{},
|
|
349
|
-
...
|
|
503
|
+
...baseEntries.map(({ s, i }) => {
|
|
350
504
|
const style = seriesStyle(s, i);
|
|
351
505
|
const stroke = resolveToken(style.color, "#4a8");
|
|
352
|
-
|
|
506
|
+
const dim = soloStyle(soloRef.current, i, s.mode === "points" ? 1.5 : s.width ?? 2);
|
|
507
|
+
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 } };
|
|
353
508
|
})
|
|
354
509
|
],
|
|
355
510
|
hooks: {
|
|
@@ -368,6 +523,55 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
368
523
|
if (idx === null || idx === void 0 || !onPickRef.current) return;
|
|
369
524
|
onPickRef.current(orderRef.current ? orderRef.current[idx] : idx);
|
|
370
525
|
});
|
|
526
|
+
const accent = resolveToken("--color-accent", "#06c");
|
|
527
|
+
(yMarks ?? []).forEach((m, i) => {
|
|
528
|
+
if (!m.drag) return;
|
|
529
|
+
const drag = dragWithin(m.drag, range ? void 0 : y.range);
|
|
530
|
+
const el2 = document.createElement("div");
|
|
531
|
+
el2.setAttribute("role", "slider");
|
|
532
|
+
el2.setAttribute("tabindex", "0");
|
|
533
|
+
el2.setAttribute("aria-orientation", "vertical");
|
|
534
|
+
el2.setAttribute("aria-valuemin", String(drag.min));
|
|
535
|
+
el2.setAttribute("aria-valuemax", String(drag.max));
|
|
536
|
+
el2.setAttribute("data-y-handle", String(i));
|
|
537
|
+
Object.assign(el2.style, { position: "absolute", left: "0", width: "100%", height: `${HANDLE_HEIGHT}px`, cursor: "ns-resize", touchAction: "none", outlineOffset: "-2px", zIndex: "3" });
|
|
538
|
+
el2.addEventListener("focus", () => el2.style.outline = `2px solid ${accent}`);
|
|
539
|
+
el2.addEventListener("blur", () => el2.style.outline = "none");
|
|
540
|
+
el2.addEventListener("click", (e) => e.stopPropagation());
|
|
541
|
+
const set = (raw) => {
|
|
542
|
+
const v = clampMark(raw, y.log ? { ...drag, step: void 0 } : drag);
|
|
543
|
+
if (v === liveRef.current[i]) return;
|
|
544
|
+
liveRef.current[i] = v;
|
|
545
|
+
onYMarkChangeRef.current?.(i, v);
|
|
546
|
+
u.redraw(false);
|
|
547
|
+
};
|
|
548
|
+
let dragging = false;
|
|
549
|
+
el2.addEventListener("pointerdown", (e) => {
|
|
550
|
+
if (e.button !== 0) return;
|
|
551
|
+
dragging = true;
|
|
552
|
+
el2.setPointerCapture(e.pointerId);
|
|
553
|
+
el2.focus();
|
|
554
|
+
e.preventDefault();
|
|
555
|
+
});
|
|
556
|
+
el2.addEventListener("pointermove", (e) => {
|
|
557
|
+
if (!dragging) return;
|
|
558
|
+
set(u.posToVal(e.clientY - u.over.getBoundingClientRect().top, "y"));
|
|
559
|
+
});
|
|
560
|
+
const end = (e) => {
|
|
561
|
+
dragging = false;
|
|
562
|
+
if (el2.hasPointerCapture(e.pointerId)) el2.releasePointerCapture(e.pointerId);
|
|
563
|
+
};
|
|
564
|
+
el2.addEventListener("pointerup", end);
|
|
565
|
+
el2.addEventListener("pointercancel", end);
|
|
566
|
+
el2.addEventListener("keydown", (e) => {
|
|
567
|
+
const v = nudgeMark(liveRef.current[i], e.key, drag, Boolean(y.log));
|
|
568
|
+
if (v === null) return;
|
|
569
|
+
e.preventDefault();
|
|
570
|
+
set(v);
|
|
571
|
+
});
|
|
572
|
+
u.over.appendChild(el2);
|
|
573
|
+
});
|
|
574
|
+
if (dragCount) queueMicrotask(() => uRef.current === u && u.redraw(false));
|
|
371
575
|
}
|
|
372
576
|
],
|
|
373
577
|
draw: [
|
|
@@ -385,13 +589,65 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
385
589
|
ctx.stroke();
|
|
386
590
|
ctx.restore();
|
|
387
591
|
}
|
|
592
|
+
const dots = overlaysRef.current;
|
|
593
|
+
if (dots.length) {
|
|
594
|
+
const ctx = u.ctx;
|
|
595
|
+
const pr = uPlot.pxRatio || 1;
|
|
596
|
+
let drawn = 0;
|
|
597
|
+
ctx.save();
|
|
598
|
+
ctx.beginPath();
|
|
599
|
+
ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
|
600
|
+
ctx.clip();
|
|
601
|
+
for (const { s, i } of dots) {
|
|
602
|
+
ctx.globalAlpha = soloStyle(soloRef.current, i, 1).alpha;
|
|
603
|
+
ctx.fillStyle = resolveToken(seriesStyle(s, i).color, "#4a8");
|
|
604
|
+
ctx.strokeStyle = surface;
|
|
605
|
+
ctx.lineWidth = Math.max(1, pr);
|
|
606
|
+
const r = (s.size ?? 9) / 2 * pr;
|
|
607
|
+
const ox = s.x ?? [];
|
|
608
|
+
for (let k = 0; k < Math.min(ox.length, s.values.length); k++) {
|
|
609
|
+
const v = s.values[k];
|
|
610
|
+
if (v === null || v === void 0 || !Number.isFinite(v) || !Number.isFinite(ox[k])) continue;
|
|
611
|
+
const px = u.valToPos(ox[k], "x", true);
|
|
612
|
+
const py = u.valToPos(v, "y", true);
|
|
613
|
+
if (!Number.isFinite(px) || !Number.isFinite(py) || px < u.bbox.left - 0.5 || px > u.bbox.left + u.bbox.width + 0.5 || py < u.bbox.top - 0.5 || py > u.bbox.top + u.bbox.height + 0.5) continue;
|
|
614
|
+
ctx.beginPath();
|
|
615
|
+
ctx.arc(px, py, r, 0, Math.PI * 2);
|
|
616
|
+
ctx.fill();
|
|
617
|
+
ctx.stroke();
|
|
618
|
+
drawn++;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
ctx.restore();
|
|
622
|
+
hostRef.current?.setAttribute("data-overlay-drawn", String(drawn));
|
|
623
|
+
}
|
|
388
624
|
const markStyle = { color: resolveToken("--color-warn", "#c80"), halo: surface, family };
|
|
389
|
-
|
|
625
|
+
const lines = yMarksRef.current ?? [];
|
|
626
|
+
if (lines.length) drawYMarks(u, 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);
|
|
627
|
+
u.over.querySelectorAll("[data-y-handle]").forEach((el2) => {
|
|
628
|
+
const i = Number(el2.getAttribute("data-y-handle"));
|
|
629
|
+
const m = lines[i];
|
|
630
|
+
const v = liveRef.current[i];
|
|
631
|
+
if (!m || v === void 0) return;
|
|
632
|
+
const top = u.valToPos(v, "y");
|
|
633
|
+
el2.style.display = Number.isFinite(top) && top >= 0 && top <= u.bbox.height / (uPlot.pxRatio || 1) ? "" : "none";
|
|
634
|
+
el2.style.top = `${top - HANDLE_HEIGHT / 2}px`;
|
|
635
|
+
const label = m.label ? pick(m.label, lang) : "";
|
|
636
|
+
const shown = `${formatNumber(v, lang, y.format ?? { digits: 4 })}${yUnit ? `${NBSP}${yUnit}` : ""}`;
|
|
637
|
+
const unnamed = lines.flatMap((one, k) => one.drag && !one.label ? [k] : []);
|
|
638
|
+
el2.setAttribute("aria-label", label || referenceLineName(lang, unnamed.indexOf(i) + 1, unnamed.length));
|
|
639
|
+
el2.setAttribute("aria-valuenow", String(Number(v.toPrecision(12))));
|
|
640
|
+
el2.setAttribute("aria-valuetext", label ? `${label}: ${shown}` : shown);
|
|
641
|
+
});
|
|
390
642
|
if (marks?.length) drawMarks(u, marks.map((m) => ({ x: m.x, label: pick(m.label, lang) })), markStyle);
|
|
391
643
|
}
|
|
392
644
|
]
|
|
393
645
|
}
|
|
394
646
|
};
|
|
647
|
+
if (!ticks) {
|
|
648
|
+
hostRef.current?.removeAttribute("data-x-tick-labels");
|
|
649
|
+
hostRef.current?.removeAttribute("data-ticks-repeated");
|
|
650
|
+
}
|
|
395
651
|
uRef.current?.destroy();
|
|
396
652
|
uRef.current = new uPlot(opts, data, el);
|
|
397
653
|
return () => {
|
|
@@ -402,9 +658,27 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
402
658
|
useEffect(() => {
|
|
403
659
|
uRef.current?.setData(data);
|
|
404
660
|
}, [data]);
|
|
661
|
+
const overlaysKey = JSON.stringify(overlays.map(({ s, i }) => [i, s.x, s.values, s.size ?? 9, seriesStyle(s, i).color]));
|
|
662
|
+
const linesKey = JSON.stringify((yMarks ?? []).map((m) => [m.y, m.label ? pick(m.label, lang) : null, m.tone ?? "warn"]));
|
|
663
|
+
useEffect(() => {
|
|
664
|
+
uRef.current?.redraw(false);
|
|
665
|
+
}, [linesKey, overlaysKey]);
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
const u = uRef.current;
|
|
668
|
+
if (!u) return;
|
|
669
|
+
baseEntries.forEach(({ s, i }, k) => {
|
|
670
|
+
const dim = soloStyle(soloIdx, i, s.mode === "points" ? 1.5 : s.width ?? 2);
|
|
671
|
+
const one = u.series[k + 1];
|
|
672
|
+
if (!one) return;
|
|
673
|
+
one.alpha = dim.alpha;
|
|
674
|
+
one.width = dim.width;
|
|
675
|
+
});
|
|
676
|
+
u.redraw(false);
|
|
677
|
+
}, [soloIdx, baseEntries]);
|
|
405
678
|
const read = cursor !== null && readout ? readout(order ? order[cursor] : cursor) : cursor !== null ? [
|
|
406
|
-
|
|
407
|
-
|
|
679
|
+
// an x that sits on a named tick is named by it, not by its number
|
|
680
|
+
tickLabelAt(ticks, xs[cursor], lang, xSpan) !== null ? `${xLabel} ${tickLabelAt(ticks, xs[cursor], lang, xSpan)}` : `${xLabel} ${formatNumber(xs[cursor], lang, x.format ?? { digits: 4 })}${xUnit ? `${NBSP}${xUnit}` : ""}`,
|
|
681
|
+
...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}` : ""}`)
|
|
408
682
|
].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";
|
|
409
683
|
return /* @__PURE__ */ jsxs(
|
|
410
684
|
"div",
|
|
@@ -420,16 +694,22 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
420
694
|
"data-log-x": x.log ? "1" : void 0,
|
|
421
695
|
"data-pick": onPick ? "1" : void 0,
|
|
422
696
|
"data-y-marks": yMarks?.length ? String(yMarks.length) : void 0,
|
|
697
|
+
"data-y-drag": dragCount ? String(dragCount) : void 0,
|
|
698
|
+
"data-overlay": overlays.length ? String(overlays.length) : void 0,
|
|
699
|
+
"data-solo": soloIdx !== null ? String(soloIdx) : void 0,
|
|
423
700
|
children: [
|
|
424
701
|
/* @__PURE__ */ jsx("div", { ref: setPlot, className: "caos-chart-plot" }),
|
|
425
|
-
series.length > 1 && /* @__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__ */
|
|
702
|
+
(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: [
|
|
703
|
+
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${seriesStyle(s, i).color})` }, "aria-hidden": "true" }),
|
|
704
|
+
pick(s.label, lang)
|
|
705
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
426
706
|
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${seriesStyle(s, i).color})` }, "aria-hidden": "true" }),
|
|
427
707
|
pick(s.label, lang)
|
|
428
|
-
] }, i)) }),
|
|
708
|
+
] }) }, i)) }),
|
|
429
709
|
/* @__PURE__ */ jsx("p", { className: "caos-chart-readout", "aria-live": "polite", title: read, children: read })
|
|
430
710
|
]
|
|
431
711
|
}
|
|
432
712
|
);
|
|
433
713
|
}
|
|
434
714
|
|
|
435
|
-
export { LOG_TABLE_FLOOR, UPlotChart, YMARK_TONE_TOKENS, drawMarks, drawYMarks, logDecades, nearestPoint, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
|
715
|
+
export { LOG_TABLE_FLOOR, SOLO_DIM_ALPHA, UPlotChart, YMARK_TONE_TOKENS, clampMark, decadeOnly, drawMarks, drawYMarks, logDecades, namedRepeats, nearestPoint, nudgeMark, overlayExtent, parityBox, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, soloKey, soloStyle, sortOrder, tickLabelAt, tickSplits, tickSteps, xScaleRange };
|
|
@@ -214,4 +214,76 @@ function Stage({ label, children, className }) {
|
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
// src/chart/text.ts
|
|
218
|
+
function referenceLineName(lang, n, of) {
|
|
219
|
+
const base = lang === "es" ? "L\xEDnea de referencia" : "Reference line";
|
|
220
|
+
return of > 1 ? `${base} ${n}` : base;
|
|
221
|
+
}
|
|
222
|
+
var context;
|
|
223
|
+
function fontFamily() {
|
|
224
|
+
if (typeof document === "undefined") return "sans-serif";
|
|
225
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-sans").trim() || "sans-serif";
|
|
226
|
+
}
|
|
227
|
+
function textWidth(text, px, weight = 400) {
|
|
228
|
+
if (context === void 0) {
|
|
229
|
+
try {
|
|
230
|
+
context = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
|
|
231
|
+
} catch {
|
|
232
|
+
context = null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!context) return text.length * px * 0.62;
|
|
236
|
+
context.font = `${weight} ${px}px ${fontFamily()}`;
|
|
237
|
+
return context.measureText(text).width;
|
|
238
|
+
}
|
|
239
|
+
function widestLabel(labels, px, weight = 400) {
|
|
240
|
+
return Math.max(0, ...labels.map((label) => textWidth(label, px, weight)));
|
|
241
|
+
}
|
|
242
|
+
function shorten(line, width, px) {
|
|
243
|
+
if (textWidth(line, px) <= width) return line;
|
|
244
|
+
let cut = line;
|
|
245
|
+
while (cut.length > 1 && textWidth(`${cut}\u2026`, px) > width) cut = cut.slice(0, -1);
|
|
246
|
+
return `${cut.trimEnd()}\u2026`;
|
|
247
|
+
}
|
|
248
|
+
function fitLabel(label, width, px, maxLines = 2) {
|
|
249
|
+
if (textWidth(label, px) <= width) return { lines: [label], shortened: false };
|
|
250
|
+
const words = label.split(" ");
|
|
251
|
+
if (maxLines === 2 && words.length > 1) {
|
|
252
|
+
let best = null;
|
|
253
|
+
let bestWidth = Infinity;
|
|
254
|
+
for (let k = 1; k < words.length; k += 1) {
|
|
255
|
+
const first = words.slice(0, k).join(" ");
|
|
256
|
+
const second = words.slice(k).join(" ");
|
|
257
|
+
const widest = Math.max(textWidth(first, px), textWidth(second, px));
|
|
258
|
+
if (widest < bestWidth) {
|
|
259
|
+
bestWidth = widest;
|
|
260
|
+
best = [first, second];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (best && bestWidth <= width) return { lines: best, shortened: false };
|
|
264
|
+
if (best) return { lines: [shorten(best[0], width, px), shorten(best[1], width, px)], shortened: true };
|
|
265
|
+
}
|
|
266
|
+
return { lines: [shorten(label, width, px)], shortened: true };
|
|
267
|
+
}
|
|
268
|
+
function niceStep(x) {
|
|
269
|
+
if (!(x > 0) || !Number.isFinite(x)) return 1;
|
|
270
|
+
const magnitude = 10 ** Math.floor(Math.log10(x));
|
|
271
|
+
return ([1, 2, 2.5, 5, 10].find((m) => m * magnitude >= x * (1 - 1e-12)) ?? 10) * magnitude;
|
|
272
|
+
}
|
|
273
|
+
function niceTicks(lo, hi, room, labelSize, most = 6) {
|
|
274
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi)) return [0];
|
|
275
|
+
if (hi < lo) [lo, hi] = [hi, lo];
|
|
276
|
+
if (hi === lo) hi = lo === 0 ? 1 : lo + Math.abs(lo);
|
|
277
|
+
const count = Math.max(2, Math.min(most, Math.floor(room / (labelSize + 16)) + 1));
|
|
278
|
+
const step = niceStep((hi - lo) / (count - 1));
|
|
279
|
+
const first = Math.floor(lo / step + 1e-9) * step;
|
|
280
|
+
const ticks = [];
|
|
281
|
+
for (let k = 0; first + k * step <= hi + step * 1e-9 || ticks.length < 2; k += 1) {
|
|
282
|
+
ticks.push(Number((first + k * step).toPrecision(12)));
|
|
283
|
+
if (ticks.length > 50) break;
|
|
284
|
+
}
|
|
285
|
+
if (ticks[ticks.length - 1] < hi - step * 1e-9) ticks.push(Number((ticks[ticks.length - 1] + step).toPrecision(12)));
|
|
286
|
+
return ticks;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, fitLabel, fontFamily, formatNumber, formatTicks, niceStep, niceTicks, pick, readTheme, referenceLineName, resolveToken, textWidth, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens, widestLabel };
|