@fasl-work/caos-app-shell 0.15.0 → 0.17.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/bin/caos-shell-gate.mjs +9 -3
- package/dist/chart.d.ts +41 -2
- package/dist/chart.js +206 -20
- package/dist/index.d.ts +137 -48
- package/dist/index.js +220 -97
- package/gate/inpage.mjs +77 -3
- package/gate/run.mjs +129 -13
- package/gate/serve.mjs +13 -3
- package/package.json +3 -4
- package/reserved-classes.json +16 -2
- package/styles.css +46 -4
package/bin/caos-shell-gate.mjs
CHANGED
|
@@ -8,20 +8,24 @@
|
|
|
8
8
|
// npx caos-shell-gate --url https://contraste.fasl-work.com --expect-brand "Contraste" the deployed origin
|
|
9
9
|
// Options:
|
|
10
10
|
// --base-path /Repo/ the site's base path with --serve (a project page); default /
|
|
11
|
+
// --spa-fallback with --serve, answer as nginx does for a single-page app (try_files $uri $uri/ /index.html): an extensionless
|
|
12
|
+
// path that matches no file gets index.html with 200; a path with an extension stays 404; default off (Pages)
|
|
11
13
|
// --routes /,/methodology the routes; default: read from the header navigation
|
|
12
|
-
// --workbench /app the routes that carry the case workbench; default /app
|
|
14
|
+
// --workbench /app the routes that carry the case workbench; default /app; '' for a product whose App is a SurfacePage
|
|
15
|
+
// --app-route /app the App route measured as a surface when --workbench is '' (G18 walks its views' Focus mode); default /app
|
|
13
16
|
// --sizes 390x844,...,2560x1440 --themes light,dark --langs en,es
|
|
14
17
|
// --case-sample id1,id2 cases walked outside the primary mode; default the first and the last
|
|
15
18
|
// --single-case the workbench has one case and no case control
|
|
16
19
|
// --idle-ms 3000 --settle-ms 15000 --text-floor 400 --instrument-min 0.5 --stage-fill-min 0.3 --stage-floor 140
|
|
17
20
|
// --wide-font-sizes 390x844,1280x800 the sizes of the wide-font pass (G10); --no-wide-font skips it
|
|
18
|
-
// --require-focus fail an App without a Focus control (G18; ADR-0070 requires one in a product with simulations)
|
|
21
|
+
// --require-focus fail an App without a Focus control (G18; ADR-0070 requires one in a product with simulations); on a surface
|
|
22
|
+
// App, one with no view that offers it
|
|
19
23
|
// --out gate-output report (gate-report.json), screenshots (shots/) and the capture index (index.html)
|
|
20
24
|
// Needs `playwright` in the consuming project (optional peer dependency). Exit 1 on any failure, 2 on misuse.
|
|
21
25
|
|
|
22
26
|
import { runGate } from '../gate/run.mjs';
|
|
23
27
|
|
|
24
|
-
const FLAGS = new Set(['single-case', 'no-wide-font', 'require-focus']);
|
|
28
|
+
const FLAGS = new Set(['single-case', 'no-wide-font', 'require-focus', 'spa-fallback']);
|
|
25
29
|
const args = {};
|
|
26
30
|
const argv = process.argv.slice(2);
|
|
27
31
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -40,12 +44,14 @@ const options = {
|
|
|
40
44
|
expectBrand: args['expect-brand'],
|
|
41
45
|
routes: args.routes,
|
|
42
46
|
workbench: args.workbench,
|
|
47
|
+
appRoute: args['app-route'],
|
|
43
48
|
sizes: args.sizes,
|
|
44
49
|
themes: args.themes,
|
|
45
50
|
langs: args.langs,
|
|
46
51
|
caseSample: args['case-sample'],
|
|
47
52
|
singleCase: args['single-case'] === true,
|
|
48
53
|
requireFocus: args['require-focus'] === true,
|
|
54
|
+
spaFallback: args['spa-fallback'] === true,
|
|
49
55
|
idleMs: num('idle-ms'),
|
|
50
56
|
settleMs: num('settle-ms'),
|
|
51
57
|
textFloor: num('text-floor'),
|
package/dist/chart.d.ts
CHANGED
|
@@ -16,6 +16,17 @@ interface ChartSeries {
|
|
|
16
16
|
mode?: 'line' | 'points';
|
|
17
17
|
/** The marker diameter of a `points` series, in CSS pixels (9 by default): a larger one rings the selected case. */
|
|
18
18
|
size?: number;
|
|
19
|
+
/** A `points` series on x values of its own (measured cases over a modelled curve that has other sizes): one x per
|
|
20
|
+
* value. It is drawn over the plot on its own x, outside the curve's data, so it takes no part in the cursor, the
|
|
21
|
+
* pick or the readout, and the axes take its extent in. */
|
|
22
|
+
x?: number[];
|
|
23
|
+
}
|
|
24
|
+
/** What a reference line may be dragged over: the range it stays inside, and the step it snaps to (a hundredth of the
|
|
25
|
+
* range when not given; on a log axis the step is a factor). */
|
|
26
|
+
interface YMarkDrag {
|
|
27
|
+
min: number;
|
|
28
|
+
max: number;
|
|
29
|
+
step?: number;
|
|
19
30
|
}
|
|
20
31
|
interface ChartAxis {
|
|
21
32
|
label: BiText;
|
|
@@ -52,7 +63,12 @@ interface UPlotChartProps {
|
|
|
52
63
|
y: number;
|
|
53
64
|
label: BiText;
|
|
54
65
|
tone?: YMarkTone;
|
|
66
|
+
drag?: YMarkDrag;
|
|
55
67
|
}[];
|
|
68
|
+
/** Called with the index in `yMarks` and the new y of a draggable reference line (`drag`) every time the reader moves
|
|
69
|
+
* it, by the pointer or the keyboard (a slider on the line: arrows, Page keys, Home and End); the caller sets the new
|
|
70
|
+
* y in `yMarks`. */
|
|
71
|
+
onYMarkChange?: (index: number, y: number) => void;
|
|
56
72
|
/** Height in pixels, or `fill` to take the container's height (inside `PlotCard fill`); the width is always the
|
|
57
73
|
* container's. */
|
|
58
74
|
height?: number | 'fill';
|
|
@@ -110,6 +126,29 @@ declare const LOG_TABLE_FLOOR = 1e-22;
|
|
|
110
126
|
declare function logDecades(min: number, max: number, most?: number): number[];
|
|
111
127
|
/** The smallest positive value among columns of values and the lower end of a fixed range; Infinity if none. */
|
|
112
128
|
declare function smallestPositive(columns: readonly (readonly (number | null)[])[], range?: readonly [number, number] | null): number;
|
|
129
|
+
/**
|
|
130
|
+
* The ticks of a log x axis that get a label: the powers of ten only (null for every other tick, which formatTicks leaves
|
|
131
|
+
* blank). A range with no power of ten in it keeps its ticks, so the axis is never left without a label.
|
|
132
|
+
*/
|
|
133
|
+
declare function decadeOnly(ticks: readonly (number | null)[]): (number | null)[];
|
|
134
|
+
/** The side of the square box of a parity plot: the smaller of the box's width and the height asked for (the box's
|
|
135
|
+
* height when the chart fills it). */
|
|
136
|
+
declare function parityBox(boxWidth: number, boxHeight: number, height: number, fill: boolean): number;
|
|
137
|
+
/** A dragged value kept inside the range of its line and snapped to its step; a value that is not a number falls to the
|
|
138
|
+
* lower end. */
|
|
139
|
+
declare function clampMark(y: number, drag: YMarkDrag): number;
|
|
140
|
+
/**
|
|
141
|
+
* The value a key sets on a draggable line, or null for a key it does not handle: the arrows one step, Page Up and Down
|
|
142
|
+
* ten, Home and End the ends of the range. A step is a hundredth of the range, or `drag.step`; on a log axis it is a factor
|
|
143
|
+
* (a hundredth of the range in decades) and `drag.step` is not used.
|
|
144
|
+
*/
|
|
145
|
+
declare function nudgeMark(y: number, key: string, drag: YMarkDrag, log?: boolean): number | null;
|
|
146
|
+
/** The x and y extent of the series that bring an x of their own, finite points only (on a log x axis, `positiveX`,
|
|
147
|
+
* only the points with x above 0); null when there are none. */
|
|
148
|
+
declare function overlayExtent(series: readonly ChartSeries[], positiveX?: boolean): {
|
|
149
|
+
x: [number, number];
|
|
150
|
+
y: [number, number];
|
|
151
|
+
} | null;
|
|
113
152
|
/** How many tick labels repeat the label before them (empty labels apart); the gate fails a chart with any (G6). */
|
|
114
153
|
declare function repeatedLabels(labels: readonly (string | null | undefined)[]): number;
|
|
115
154
|
/**
|
|
@@ -158,6 +197,6 @@ declare function nearestPoint(xs: readonly number[], ys: readonly (readonly (num
|
|
|
158
197
|
* (known shell defect 17). An axis with fixed decimals ticks only where its labels differ (`tickSteps`). The host
|
|
159
198
|
* declares `data-series`, `data-axis-titles`, `data-ticks-cut`, `data-ticks-repeat` and `data-drawn` for the gate.
|
|
160
199
|
*/
|
|
161
|
-
declare function UPlotChart({ x, y, series, marks, yMarks, height, onCursor, onPick, parity, readout, hint }: UPlotChartProps): react.JSX.Element;
|
|
200
|
+
declare function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height, onCursor, onPick, parity, readout, hint }: UPlotChartProps): react.JSX.Element;
|
|
162
201
|
|
|
163
|
-
export { type ChartAxis, type ChartSeries, LOG_TABLE_FLOOR, UPlotChart, type UPlotChartProps, YMARK_TONE_TOKENS, type YMarkTone, drawMarks, drawYMarks, logDecades, nearestPoint, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
|
202
|
+
export { type ChartAxis, type ChartSeries, LOG_TABLE_FLOOR, UPlotChart, type UPlotChartProps, YMARK_TONE_TOKENS, type YMarkDrag, type YMarkTone, clampMark, decadeOnly, drawMarks, drawYMarks, logDecades, nearestPoint, nudgeMark, overlayExtent, parityBox, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
package/dist/chart.js
CHANGED
|
@@ -83,11 +83,73 @@ function smallestPositive(columns, range) {
|
|
|
83
83
|
for (const c of columns) for (const v of c) if (v !== null && Number.isFinite(v) && v > 0 && v < lo) lo = v;
|
|
84
84
|
return lo;
|
|
85
85
|
}
|
|
86
|
+
function isDecade(t) {
|
|
87
|
+
if (t === null || t === void 0 || !Number.isFinite(t) || t <= 0) return false;
|
|
88
|
+
const d = Math.log10(t);
|
|
89
|
+
return Math.abs(d - Math.round(d)) < 1e-6;
|
|
90
|
+
}
|
|
91
|
+
function decadeOnly(ticks) {
|
|
92
|
+
if (!ticks.some(isDecade)) return [...ticks];
|
|
93
|
+
return ticks.map((t) => isDecade(t) ? t : null);
|
|
94
|
+
}
|
|
95
|
+
function parityBox(boxWidth, boxHeight, height, fill) {
|
|
96
|
+
return Math.min(boxWidth, fill ? boxHeight : height);
|
|
97
|
+
}
|
|
98
|
+
function clampMark(y, drag) {
|
|
99
|
+
if (!Number.isFinite(y)) return drag.min;
|
|
100
|
+
const v = drag.step && drag.step > 0 ? drag.min + Math.round((y - drag.min) / drag.step) * drag.step : y;
|
|
101
|
+
const clamped = Math.min(drag.max, Math.max(drag.min, v));
|
|
102
|
+
return Number(clamped.toPrecision(12));
|
|
103
|
+
}
|
|
104
|
+
function nudgeMark(y, key, drag, log = false) {
|
|
105
|
+
const dir = key === "ArrowUp" || key === "ArrowRight" || key === "PageUp" ? 1 : key === "ArrowDown" || key === "ArrowLeft" || key === "PageDown" ? -1 : 0;
|
|
106
|
+
if (key === "Home") return drag.min;
|
|
107
|
+
if (key === "End") return drag.max;
|
|
108
|
+
if (dir === 0) return null;
|
|
109
|
+
const n = key === "PageUp" || key === "PageDown" ? 10 : 1;
|
|
110
|
+
if (log && drag.min > 0 && drag.max > drag.min) {
|
|
111
|
+
const decades = (Math.log10(drag.max) - Math.log10(drag.min)) / 100;
|
|
112
|
+
return Math.min(drag.max, Math.max(drag.min, y * 10 ** (dir * n * decades)));
|
|
113
|
+
}
|
|
114
|
+
const step = drag.step && drag.step > 0 ? drag.step : (drag.max - drag.min) / 100;
|
|
115
|
+
return clampMark(y + dir * n * step, { ...drag, step: void 0 });
|
|
116
|
+
}
|
|
117
|
+
function isOverlay(s) {
|
|
118
|
+
return Boolean(s.x) && s.mode === "points";
|
|
119
|
+
}
|
|
120
|
+
function dragWithin(drag, range) {
|
|
121
|
+
if (!range) return drag;
|
|
122
|
+
const lo = Math.min(range[0], range[1]);
|
|
123
|
+
const hi = Math.max(range[0], range[1]);
|
|
124
|
+
const min = Math.min(hi, Math.max(lo, drag.min));
|
|
125
|
+
const max = Math.max(lo, Math.min(hi, drag.max));
|
|
126
|
+
return { ...drag, min, max: Math.max(min, max) };
|
|
127
|
+
}
|
|
128
|
+
function overlayExtent(series, positiveX = false) {
|
|
129
|
+
let x0 = Infinity;
|
|
130
|
+
let x1 = -Infinity;
|
|
131
|
+
let y0 = Infinity;
|
|
132
|
+
let y1 = -Infinity;
|
|
133
|
+
for (const s of series) {
|
|
134
|
+
if (!isOverlay(s) || !s.x) continue;
|
|
135
|
+
for (let i = 0; i < Math.min(s.x.length, s.values.length); i++) {
|
|
136
|
+
const px = s.x[i];
|
|
137
|
+
const py = s.values[i];
|
|
138
|
+
if (py === null || py === void 0 || !Number.isFinite(px) || !Number.isFinite(py) || positiveX && !(px > 0)) continue;
|
|
139
|
+
x0 = Math.min(x0, px);
|
|
140
|
+
x1 = Math.max(x1, px);
|
|
141
|
+
y0 = Math.min(y0, py);
|
|
142
|
+
y1 = Math.max(y1, py);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return Number.isFinite(x0) ? { x: [x0, x1], y: [y0, y1] } : null;
|
|
146
|
+
}
|
|
86
147
|
var DECADE_AXIS = {
|
|
87
148
|
splits: (_u, _i, min, max) => logDecades(min, max),
|
|
88
149
|
filter: (_u, splits) => splits
|
|
89
150
|
};
|
|
90
151
|
var PARITY_TICK_SPACE = 40;
|
|
152
|
+
var HANDLE_HEIGHT = 14;
|
|
91
153
|
function repeatedLabels(labels) {
|
|
92
154
|
let n = 0;
|
|
93
155
|
for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
|
|
@@ -193,7 +255,7 @@ function nearestPoint(xs, ys, left, top) {
|
|
|
193
255
|
}
|
|
194
256
|
return best;
|
|
195
257
|
}
|
|
196
|
-
function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPick, parity, readout, hint }) {
|
|
258
|
+
function UPlotChart({ x, y, series, marks, yMarks, onYMarkChange, height = 280, onCursor, onPick, parity, readout, hint }) {
|
|
197
259
|
const lang = useShellLang();
|
|
198
260
|
const theme = useThemeStore((s) => s.theme);
|
|
199
261
|
const fill = height === "fill";
|
|
@@ -206,7 +268,7 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
206
268
|
},
|
|
207
269
|
[measureRef]
|
|
208
270
|
);
|
|
209
|
-
const width = parity ?
|
|
271
|
+
const width = parity ? parityBox(box.width, box.height, typeof height === "number" ? height : 0, fill) : box.width;
|
|
210
272
|
const h = parity ? width : fill ? box.height : height;
|
|
211
273
|
const uRef = useRef(null);
|
|
212
274
|
const hostRef = useRef(null);
|
|
@@ -220,36 +282,57 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
220
282
|
const orderRef = useRef(order);
|
|
221
283
|
orderRef.current = order;
|
|
222
284
|
const xs = useMemo(() => order ? order.map((i) => x.values[i]) : x.values, [order, x.values]);
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
285
|
+
const entries = useMemo(() => series.map((s, i) => ({ s, i })), [series]);
|
|
286
|
+
const baseEntries = useMemo(() => entries.filter(({ s }) => !isOverlay(s)), [entries]);
|
|
287
|
+
const overlays = useMemo(() => entries.filter(({ s }) => isOverlay(s)), [entries]);
|
|
288
|
+
const ext = useMemo(() => overlayExtent(series, Boolean(x.log)), [series, x.log]);
|
|
289
|
+
const ys = useMemo(() => baseEntries.map(({ s }) => order ? order.map((i) => s.values[i] ?? null) : s.values), [order, baseEntries]);
|
|
290
|
+
const levels = useMemo(() => (yMarks ?? []).flatMap((m) => m.drag ? [m.drag.min, m.drag.max] : [m.y]), [yMarks]);
|
|
291
|
+
const range = useMemo(() => parity ? parityRange(xs, ...ys, levels, ext ? [...ext.x, ...ext.y] : []) : null, [parity, xs, ys, levels, ext]);
|
|
292
|
+
const yLevels = !parity && !y.range && (levels.length > 0 || ext) ? [...levels, ...ext ? ext.y : []] : null;
|
|
293
|
+
const xExt = useMemo(() => {
|
|
294
|
+
if (!ext || range || xs.length === 0) return null;
|
|
295
|
+
const lo = Math.min(xs[0], ext.x[0]);
|
|
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]);
|
|
227
300
|
const tiny = useMemo(
|
|
228
301
|
() => ({
|
|
229
|
-
x: Boolean(x.log) && smallestPositive([xs], range) < LOG_TABLE_FLOOR,
|
|
302
|
+
x: Boolean(x.log) && smallestPositive(ext ? [xs, ext.x] : [xs], range) < LOG_TABLE_FLOOR,
|
|
230
303
|
y: Boolean(y.log) && smallestPositive(yLevels ? [...ys, yLevels] : ys, range ?? y.range) < LOG_TABLE_FLOOR
|
|
231
304
|
}),
|
|
232
|
-
[x.log, y.log, xs, ys, range, y.range, yLevels]
|
|
305
|
+
[x.log, y.log, xs, ys, range, y.range, yLevels, ext]
|
|
233
306
|
);
|
|
234
307
|
const xLabel = pick(x.label, lang);
|
|
235
308
|
const yLabel = pick(y.label, lang);
|
|
309
|
+
const yMarksRef = useRef(yMarks);
|
|
310
|
+
yMarksRef.current = yMarks;
|
|
311
|
+
const liveRef = useRef([]);
|
|
312
|
+
liveRef.current = (yMarks ?? []).map((m) => m.y);
|
|
313
|
+
const overlaysRef = useRef(overlays);
|
|
314
|
+
overlaysRef.current = overlays;
|
|
315
|
+
const onYMarkChangeRef = useRef(onYMarkChange);
|
|
316
|
+
onYMarkChangeRef.current = onYMarkChange;
|
|
317
|
+
const dragCount = (yMarks ?? []).filter((m) => m.drag).length;
|
|
236
318
|
const xUnit = x.unit ? pick(x.unit, lang) : "";
|
|
237
319
|
const yUnit = y.unit ? pick(y.unit, lang) : "";
|
|
238
320
|
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],
|
|
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],
|
|
323
|
+
oe: ext,
|
|
241
324
|
p: range,
|
|
242
325
|
pick: Boolean(onPick),
|
|
243
326
|
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null, tiny.y],
|
|
244
327
|
m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
|
|
245
|
-
ym: (yMarks ?? []).map((m) => [m.y, pick(m.label, lang), m.tone ?? "warn"]),
|
|
328
|
+
ym: (yMarks ?? []).map((m) => m.drag ? ["drag", m.drag] : [m.y, pick(m.label, lang), m.tone ?? "warn"]),
|
|
246
329
|
theme,
|
|
247
330
|
lang,
|
|
248
331
|
h,
|
|
249
332
|
w: width
|
|
250
333
|
});
|
|
251
334
|
const data = useMemo(() => [xs, ...ys], [xs, ys]);
|
|
252
|
-
const scatter =
|
|
335
|
+
const scatter = baseEntries.length > 0 && baseEntries.every(({ s }) => s.mode === "points");
|
|
253
336
|
useEffect(() => {
|
|
254
337
|
const el = plotRef.current;
|
|
255
338
|
if (!el || width <= 0 || h <= 0) return;
|
|
@@ -259,8 +342,9 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
259
342
|
const font = `11px ${family}`;
|
|
260
343
|
const surface = resolveToken("--color-surface", "#fff");
|
|
261
344
|
const repeats = { x: 0, y: 0 };
|
|
262
|
-
const tick = (fmt, axis) => (_u, vals) => {
|
|
263
|
-
const labels = formatTicks(vals, lang, fmt ?? { digits: 4 });
|
|
345
|
+
const tick = (fmt, axis, pre) => (_u, vals) => {
|
|
346
|
+
const labels = formatTicks(pre ? pre(vals) : vals, lang, fmt ?? { digits: 4 });
|
|
347
|
+
if (axis === "x" && pre) hostRef.current?.setAttribute("data-log-labels", labels.filter((l) => l).join("|"));
|
|
264
348
|
repeats[axis] = repeatedLabels(labels);
|
|
265
349
|
hostRef.current?.setAttribute("data-ticks-repeat", String(repeats.x + repeats.y));
|
|
266
350
|
hostRef.current?.setAttribute(`data-ticks-${axis}`, String(labels.filter((l) => l).length));
|
|
@@ -294,7 +378,7 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
294
378
|
x: {
|
|
295
379
|
time: Boolean(x.time),
|
|
296
380
|
distr: x.log ? 3 : 1,
|
|
297
|
-
...range ? { range } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
381
|
+
...range ? { range } : xExt ? { range: xExt } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
298
382
|
},
|
|
299
383
|
y: {
|
|
300
384
|
distr: y.log ? 3 : 1,
|
|
@@ -316,7 +400,8 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
316
400
|
font,
|
|
317
401
|
label: xUnit ? `${xLabel} (${xUnit})` : xLabel,
|
|
318
402
|
labelFont: font,
|
|
319
|
-
|
|
403
|
+
// 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),
|
|
320
405
|
...xSteps ? { incrs: xSteps } : {},
|
|
321
406
|
...range ? { space: PARITY_TICK_SPACE } : {},
|
|
322
407
|
...tiny.x ? DECADE_AXIS : {}
|
|
@@ -346,7 +431,7 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
346
431
|
],
|
|
347
432
|
series: [
|
|
348
433
|
{},
|
|
349
|
-
...
|
|
434
|
+
...baseEntries.map(({ s, i }) => {
|
|
350
435
|
const style = seriesStyle(s, i);
|
|
351
436
|
const stroke = resolveToken(style.color, "#4a8");
|
|
352
437
|
return s.mode === "points" ? { label: pick(s.label, lang), stroke, width: 1.5, paths: () => null, points: { show: true, space: 0, size: s.size ?? 9, fill: stroke } } : { label: pick(s.label, lang), stroke, width: s.width ?? 2, dash: style.dash, spanGaps: false, points: { show: false } };
|
|
@@ -368,6 +453,55 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
368
453
|
if (idx === null || idx === void 0 || !onPickRef.current) return;
|
|
369
454
|
onPickRef.current(orderRef.current ? orderRef.current[idx] : idx);
|
|
370
455
|
});
|
|
456
|
+
const accent = resolveToken("--color-accent", "#06c");
|
|
457
|
+
(yMarks ?? []).forEach((m, i) => {
|
|
458
|
+
if (!m.drag) return;
|
|
459
|
+
const drag = dragWithin(m.drag, range ? void 0 : y.range);
|
|
460
|
+
const el2 = document.createElement("div");
|
|
461
|
+
el2.setAttribute("role", "slider");
|
|
462
|
+
el2.setAttribute("tabindex", "0");
|
|
463
|
+
el2.setAttribute("aria-orientation", "vertical");
|
|
464
|
+
el2.setAttribute("aria-valuemin", String(drag.min));
|
|
465
|
+
el2.setAttribute("aria-valuemax", String(drag.max));
|
|
466
|
+
el2.setAttribute("data-y-handle", String(i));
|
|
467
|
+
Object.assign(el2.style, { position: "absolute", left: "0", width: "100%", height: `${HANDLE_HEIGHT}px`, cursor: "ns-resize", touchAction: "none", outlineOffset: "-2px", zIndex: "3" });
|
|
468
|
+
el2.addEventListener("focus", () => el2.style.outline = `2px solid ${accent}`);
|
|
469
|
+
el2.addEventListener("blur", () => el2.style.outline = "none");
|
|
470
|
+
el2.addEventListener("click", (e) => e.stopPropagation());
|
|
471
|
+
const set = (raw) => {
|
|
472
|
+
const v = clampMark(raw, y.log ? { ...drag, step: void 0 } : drag);
|
|
473
|
+
if (v === liveRef.current[i]) return;
|
|
474
|
+
liveRef.current[i] = v;
|
|
475
|
+
onYMarkChangeRef.current?.(i, v);
|
|
476
|
+
u.redraw(false);
|
|
477
|
+
};
|
|
478
|
+
let dragging = false;
|
|
479
|
+
el2.addEventListener("pointerdown", (e) => {
|
|
480
|
+
if (e.button !== 0) return;
|
|
481
|
+
dragging = true;
|
|
482
|
+
el2.setPointerCapture(e.pointerId);
|
|
483
|
+
el2.focus();
|
|
484
|
+
e.preventDefault();
|
|
485
|
+
});
|
|
486
|
+
el2.addEventListener("pointermove", (e) => {
|
|
487
|
+
if (!dragging) return;
|
|
488
|
+
set(u.posToVal(e.clientY - u.over.getBoundingClientRect().top, "y"));
|
|
489
|
+
});
|
|
490
|
+
const end = (e) => {
|
|
491
|
+
dragging = false;
|
|
492
|
+
if (el2.hasPointerCapture(e.pointerId)) el2.releasePointerCapture(e.pointerId);
|
|
493
|
+
};
|
|
494
|
+
el2.addEventListener("pointerup", end);
|
|
495
|
+
el2.addEventListener("pointercancel", end);
|
|
496
|
+
el2.addEventListener("keydown", (e) => {
|
|
497
|
+
const v = nudgeMark(liveRef.current[i], e.key, drag, Boolean(y.log));
|
|
498
|
+
if (v === null) return;
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
set(v);
|
|
501
|
+
});
|
|
502
|
+
u.over.appendChild(el2);
|
|
503
|
+
});
|
|
504
|
+
if (dragCount) queueMicrotask(() => uRef.current === u && u.redraw(false));
|
|
371
505
|
}
|
|
372
506
|
],
|
|
373
507
|
draw: [
|
|
@@ -385,8 +519,53 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
385
519
|
ctx.stroke();
|
|
386
520
|
ctx.restore();
|
|
387
521
|
}
|
|
522
|
+
const dots = overlaysRef.current;
|
|
523
|
+
if (dots.length) {
|
|
524
|
+
const ctx = u.ctx;
|
|
525
|
+
const pr = uPlot.pxRatio || 1;
|
|
526
|
+
let drawn = 0;
|
|
527
|
+
ctx.save();
|
|
528
|
+
ctx.beginPath();
|
|
529
|
+
ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
|
530
|
+
ctx.clip();
|
|
531
|
+
for (const { s, i } of dots) {
|
|
532
|
+
ctx.fillStyle = resolveToken(seriesStyle(s, i).color, "#4a8");
|
|
533
|
+
ctx.strokeStyle = surface;
|
|
534
|
+
ctx.lineWidth = Math.max(1, pr);
|
|
535
|
+
const r = (s.size ?? 9) / 2 * pr;
|
|
536
|
+
const ox = s.x ?? [];
|
|
537
|
+
for (let k = 0; k < Math.min(ox.length, s.values.length); k++) {
|
|
538
|
+
const v = s.values[k];
|
|
539
|
+
if (v === null || v === void 0 || !Number.isFinite(v) || !Number.isFinite(ox[k])) continue;
|
|
540
|
+
const px = u.valToPos(ox[k], "x", true);
|
|
541
|
+
const py = u.valToPos(v, "y", true);
|
|
542
|
+
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;
|
|
543
|
+
ctx.beginPath();
|
|
544
|
+
ctx.arc(px, py, r, 0, Math.PI * 2);
|
|
545
|
+
ctx.fill();
|
|
546
|
+
ctx.stroke();
|
|
547
|
+
drawn++;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
ctx.restore();
|
|
551
|
+
hostRef.current?.setAttribute("data-overlay-drawn", String(drawn));
|
|
552
|
+
}
|
|
388
553
|
const markStyle = { color: resolveToken("--color-warn", "#c80"), halo: surface, family };
|
|
389
|
-
|
|
554
|
+
const lines = yMarksRef.current ?? [];
|
|
555
|
+
if (lines.length) drawYMarks(u, lines.map((m, i) => ({ y: liveRef.current[i] ?? m.y, label: pick(m.label, lang), color: resolveToken(YMARK_TONE_TOKENS[m.tone ?? "warn"], "#c80") })), markStyle);
|
|
556
|
+
u.over.querySelectorAll("[data-y-handle]").forEach((el2) => {
|
|
557
|
+
const i = Number(el2.getAttribute("data-y-handle"));
|
|
558
|
+
const m = lines[i];
|
|
559
|
+
const v = liveRef.current[i];
|
|
560
|
+
if (!m || v === void 0) return;
|
|
561
|
+
const top = u.valToPos(v, "y");
|
|
562
|
+
el2.style.display = Number.isFinite(top) && top >= 0 && top <= u.bbox.height / (uPlot.pxRatio || 1) ? "" : "none";
|
|
563
|
+
el2.style.top = `${top - HANDLE_HEIGHT / 2}px`;
|
|
564
|
+
const label = pick(m.label, lang);
|
|
565
|
+
el2.setAttribute("aria-label", label);
|
|
566
|
+
el2.setAttribute("aria-valuenow", String(Number(v.toPrecision(12))));
|
|
567
|
+
el2.setAttribute("aria-valuetext", `${label}: ${formatNumber(v, lang, y.format ?? { digits: 4 })}${yUnit ? `${NBSP}${yUnit}` : ""}`);
|
|
568
|
+
});
|
|
390
569
|
if (marks?.length) drawMarks(u, marks.map((m) => ({ x: m.x, label: pick(m.label, lang) })), markStyle);
|
|
391
570
|
}
|
|
392
571
|
]
|
|
@@ -402,9 +581,14 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
402
581
|
useEffect(() => {
|
|
403
582
|
uRef.current?.setData(data);
|
|
404
583
|
}, [data]);
|
|
584
|
+
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"]));
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
uRef.current?.redraw(false);
|
|
588
|
+
}, [linesKey, overlaysKey]);
|
|
405
589
|
const read = cursor !== null && readout ? readout(order ? order[cursor] : cursor) : cursor !== null ? [
|
|
406
590
|
`${xLabel} ${formatNumber(xs[cursor], lang, x.format ?? { digits: 4 })}${xUnit ? `${NBSP}${xUnit}` : ""}`,
|
|
407
|
-
...
|
|
591
|
+
...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
592
|
].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
593
|
return /* @__PURE__ */ jsxs(
|
|
410
594
|
"div",
|
|
@@ -420,6 +604,8 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
420
604
|
"data-log-x": x.log ? "1" : void 0,
|
|
421
605
|
"data-pick": onPick ? "1" : void 0,
|
|
422
606
|
"data-y-marks": yMarks?.length ? String(yMarks.length) : void 0,
|
|
607
|
+
"data-y-drag": dragCount ? String(dragCount) : void 0,
|
|
608
|
+
"data-overlay": overlays.length ? String(overlays.length) : void 0,
|
|
423
609
|
children: [
|
|
424
610
|
/* @__PURE__ */ jsx("div", { ref: setPlot, className: "caos-chart-plot" }),
|
|
425
611
|
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__ */ jsxs("li", { className: "caos-chart-key", "data-mode": s.mode ?? "line", "data-dash": seriesStyle(s, i).dash ? "1" : void 0, children: [
|
|
@@ -432,4 +618,4 @@ function UPlotChart({ x, y, series, marks, yMarks, height = 280, onCursor, onPic
|
|
|
432
618
|
);
|
|
433
619
|
}
|
|
434
620
|
|
|
435
|
-
export { LOG_TABLE_FLOOR, UPlotChart, YMARK_TONE_TOKENS, drawMarks, drawYMarks, logDecades, nearestPoint, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
|
621
|
+
export { LOG_TABLE_FLOOR, UPlotChart, YMARK_TONE_TOKENS, clampMark, decadeOnly, drawMarks, drawYMarks, logDecades, nearestPoint, nudgeMark, overlayExtent, parityBox, parityRange, rangeWithLevels, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|