@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, NOT_AVAILABLE, formatTicks,
|
|
2
|
-
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, formatNumber, formatTicks, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-
|
|
1
|
+
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, barExtent, clipExtent, allIntegers, bandsOf, windowRange, windowZoomed, unusedLegendTones, NOT_AVAILABLE, formatNumber, widestLabel, fitLabel, GROUP_MARGIN, axisTitleFits, wrapLines, formatTicks, niceTicks, textWidth, repeatedLabels, chartSummary, NBSP, CHART_ROLE, ChartSummary, ChartDataTable, ResetZoomButton, memberSlots, wheelFactor, pinchState, DRAG_THRESHOLD, panRange, brushWindow, keyAction, KEY_ZOOM, stepIndex, KEY_PAN, stepWindow, roundWindow, zoomRange, categoryAt, memberAt } from './chunk-3LONJM5B.js';
|
|
2
|
+
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, fitLabel, fontFamily, formatNumber, formatTicks, niceStep, niceTicks, pick, readTheme, resolveToken, textWidth, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens, widestLabel } from './chunk-3LONJM5B.js';
|
|
3
3
|
import { APP_ROUTE, LANDING_ROUTE } from './chunk-33WTUQ7Q.js';
|
|
4
4
|
export { APP_ROUTE, LANDING_ROUTE, LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-33WTUQ7Q.js';
|
|
5
5
|
import { createContext, Component, useState, useRef, useId, useEffect, useLayoutEffect, useCallback, useContext, useMemo, Children, isValidElement } from 'react';
|
|
@@ -2092,7 +2092,7 @@ function executiveTextFields(d) {
|
|
|
2092
2092
|
add("title", d.title, 1);
|
|
2093
2093
|
add("key_statement", d.key_statement, 2);
|
|
2094
2094
|
d.headline_figures.forEach((f, i) => figure(`headline_figures[${i}]`, f));
|
|
2095
|
-
const { pain, context
|
|
2095
|
+
const { pain, context, situation, trends, proposal } = d;
|
|
2096
2096
|
add("pain.key_message", pain.key_message, 1);
|
|
2097
2097
|
pain.paragraphs.forEach((p, i) => add(`pain.paragraphs[${i}]`, p, void 0, EXEC_LIMITS.minParagraphWords));
|
|
2098
2098
|
pain.bearers.forEach((b, i) => {
|
|
@@ -2102,11 +2102,11 @@ function executiveTextFields(d) {
|
|
|
2102
2102
|
});
|
|
2103
2103
|
pain.figures.forEach((f, i) => figure(`pain.figures[${i}]`, f));
|
|
2104
2104
|
pain.causes.forEach((c, i) => add(`pain.causes[${i}].cause`, c.cause));
|
|
2105
|
-
add("context.key_message",
|
|
2106
|
-
|
|
2107
|
-
add("context.setting",
|
|
2108
|
-
|
|
2109
|
-
|
|
2105
|
+
add("context.key_message", context.key_message, 1);
|
|
2106
|
+
context.paragraphs.forEach((p, i) => add(`context.paragraphs[${i}]`, p, void 0, EXEC_LIMITS.minParagraphWords));
|
|
2107
|
+
add("context.setting", context.setting);
|
|
2108
|
+
context.forces.forEach((f, i) => add(`context.forces[${i}].force`, f.force));
|
|
2109
|
+
context.evidence.forEach((e, i) => add(`context.evidence[${i}].statement`, e.statement, 1));
|
|
2110
2110
|
add("situation.key_message", situation.key_message, 1);
|
|
2111
2111
|
situation.roles.forEach((r, i) => {
|
|
2112
2112
|
add(`situation.roles[${i}].role`, r.role);
|
|
@@ -3133,74 +3133,6 @@ var BREAKPOINTS = {
|
|
|
3133
3133
|
/** The gate's large screen: the viewport rules of ADR-0071 are measured from here up. */
|
|
3134
3134
|
large: 1280
|
|
3135
3135
|
};
|
|
3136
|
-
|
|
3137
|
-
// src/chart/text.ts
|
|
3138
|
-
var context;
|
|
3139
|
-
function fontFamily() {
|
|
3140
|
-
if (typeof document === "undefined") return "sans-serif";
|
|
3141
|
-
return getComputedStyle(document.documentElement).getPropertyValue("--font-sans").trim() || "sans-serif";
|
|
3142
|
-
}
|
|
3143
|
-
function textWidth(text, px, weight = 400) {
|
|
3144
|
-
if (context === void 0) {
|
|
3145
|
-
try {
|
|
3146
|
-
context = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
|
|
3147
|
-
} catch {
|
|
3148
|
-
context = null;
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
if (!context) return text.length * px * 0.62;
|
|
3152
|
-
context.font = `${weight} ${px}px ${fontFamily()}`;
|
|
3153
|
-
return context.measureText(text).width;
|
|
3154
|
-
}
|
|
3155
|
-
function widestLabel(labels, px, weight = 400) {
|
|
3156
|
-
return Math.max(0, ...labels.map((label) => textWidth(label, px, weight)));
|
|
3157
|
-
}
|
|
3158
|
-
function shorten(line, width, px) {
|
|
3159
|
-
if (textWidth(line, px) <= width) return line;
|
|
3160
|
-
let cut = line;
|
|
3161
|
-
while (cut.length > 1 && textWidth(`${cut}\u2026`, px) > width) cut = cut.slice(0, -1);
|
|
3162
|
-
return `${cut.trimEnd()}\u2026`;
|
|
3163
|
-
}
|
|
3164
|
-
function fitLabel(label, width, px, maxLines = 2) {
|
|
3165
|
-
if (textWidth(label, px) <= width) return { lines: [label], shortened: false };
|
|
3166
|
-
const words = label.split(" ");
|
|
3167
|
-
if (maxLines === 2 && words.length > 1) {
|
|
3168
|
-
let best = null;
|
|
3169
|
-
let bestWidth = Infinity;
|
|
3170
|
-
for (let k = 1; k < words.length; k += 1) {
|
|
3171
|
-
const first = words.slice(0, k).join(" ");
|
|
3172
|
-
const second = words.slice(k).join(" ");
|
|
3173
|
-
const widest = Math.max(textWidth(first, px), textWidth(second, px));
|
|
3174
|
-
if (widest < bestWidth) {
|
|
3175
|
-
bestWidth = widest;
|
|
3176
|
-
best = [first, second];
|
|
3177
|
-
}
|
|
3178
|
-
}
|
|
3179
|
-
if (best && bestWidth <= width) return { lines: best, shortened: false };
|
|
3180
|
-
if (best) return { lines: [shorten(best[0], width, px), shorten(best[1], width, px)], shortened: true };
|
|
3181
|
-
}
|
|
3182
|
-
return { lines: [shorten(label, width, px)], shortened: true };
|
|
3183
|
-
}
|
|
3184
|
-
function niceStep(x) {
|
|
3185
|
-
if (!(x > 0) || !Number.isFinite(x)) return 1;
|
|
3186
|
-
const magnitude = 10 ** Math.floor(Math.log10(x));
|
|
3187
|
-
return ([1, 2, 2.5, 5, 10].find((m) => m * magnitude >= x * (1 - 1e-12)) ?? 10) * magnitude;
|
|
3188
|
-
}
|
|
3189
|
-
function niceTicks(lo, hi, room, labelSize, most = 6) {
|
|
3190
|
-
if (!Number.isFinite(lo) || !Number.isFinite(hi)) return [0];
|
|
3191
|
-
if (hi < lo) [lo, hi] = [hi, lo];
|
|
3192
|
-
if (hi === lo) hi = lo === 0 ? 1 : lo + Math.abs(lo);
|
|
3193
|
-
const count = Math.max(2, Math.min(most, Math.floor(room / (labelSize + 16)) + 1));
|
|
3194
|
-
const step = niceStep((hi - lo) / (count - 1));
|
|
3195
|
-
const first = Math.floor(lo / step + 1e-9) * step;
|
|
3196
|
-
const ticks = [];
|
|
3197
|
-
for (let k = 0; first + k * step <= hi + step * 1e-9 || ticks.length < 2; k += 1) {
|
|
3198
|
-
ticks.push(Number((first + k * step).toPrecision(12)));
|
|
3199
|
-
if (ticks.length > 50) break;
|
|
3200
|
-
}
|
|
3201
|
-
if (ticks[ticks.length - 1] < hi - step * 1e-9) ticks.push(Number((ticks[ticks.length - 1] + step).toPrecision(12)));
|
|
3202
|
-
return ticks;
|
|
3203
|
-
}
|
|
3204
3136
|
var PX = 11;
|
|
3205
3137
|
var LINE = 13;
|
|
3206
3138
|
var TONE = {
|
|
@@ -3210,23 +3142,87 @@ var TONE = {
|
|
|
3210
3142
|
accent: "var(--color-accent)",
|
|
3211
3143
|
neutral: "var(--color-fg-faint)"
|
|
3212
3144
|
};
|
|
3145
|
+
var REST = {
|
|
3146
|
+
en: "Point at a bar, or use the arrow keys, to read its value",
|
|
3147
|
+
es: "Apunte a una barra, o use las flechas, para leer su valor"
|
|
3148
|
+
};
|
|
3213
3149
|
function Lines({ x, y, lines, anchor, fill }) {
|
|
3214
3150
|
return /* @__PURE__ */ jsx("text", { x, y, textAnchor: anchor, fontSize: PX, fill, style: { fontFamily: "var(--font-sans)" }, children: lines.map((line, i) => /* @__PURE__ */ jsx("tspan", { x, dy: i === 0 ? 0 : LINE, children: line }, i)) });
|
|
3215
3151
|
}
|
|
3216
|
-
function BarChart({ title, data, axis, orientation = "horizontal", values = true, height, onSelect }) {
|
|
3152
|
+
function BarChart({ title, data, axis, orientation = "horizontal", values = true, height, onSelect, zoomOff, legend }) {
|
|
3217
3153
|
const lang = useShellLang();
|
|
3218
|
-
const [
|
|
3154
|
+
const [measureRef, box] = useStageSize();
|
|
3155
|
+
const innerRef = useRef(null);
|
|
3156
|
+
const setInner = useCallback(
|
|
3157
|
+
(el) => {
|
|
3158
|
+
innerRef.current = el;
|
|
3159
|
+
measureRef(el);
|
|
3160
|
+
},
|
|
3161
|
+
[measureRef]
|
|
3162
|
+
);
|
|
3219
3163
|
const fill = height === "fill";
|
|
3220
3164
|
const name = pick(title, lang);
|
|
3221
|
-
const
|
|
3165
|
+
const unit = axis.unit ? pick(axis.unit, lang) : "";
|
|
3166
|
+
const axisTitle = unit ? `${pick(axis.label, lang)} (${unit})` : pick(axis.label, lang);
|
|
3222
3167
|
const fmt = axis.format ?? { digits: 3 };
|
|
3168
|
+
const n = data.length;
|
|
3223
3169
|
const finite = data.map((d) => d.value).filter((v) => v !== null && v !== void 0 && Number.isFinite(v));
|
|
3224
|
-
const
|
|
3225
|
-
|
|
3170
|
+
const spanEnds = data.flatMap((d) => {
|
|
3171
|
+
const e = barExtent(d.value, d.span);
|
|
3172
|
+
return e && e.given ? [e.a, e.b] : [];
|
|
3173
|
+
});
|
|
3174
|
+
const floating = data.some((d) => d.span !== void 0 && d.span !== null);
|
|
3175
|
+
const reach = [...finite, ...spanEnds];
|
|
3176
|
+
const lo = floating && axis.range ? Math.min(axis.range[0], axis.range[1]) : Math.min(0, axis.range ? axis.range[0] : Math.min(0, ...reach));
|
|
3177
|
+
const hi = floating && axis.range ? Math.max(axis.range[0], axis.range[1]) : Math.max(0, axis.range ? axis.range[1] : Math.max(0, ...reach));
|
|
3178
|
+
const extents = data.map((d) => clipExtent(barExtent(d.value, d.span), lo, hi, floating));
|
|
3179
|
+
const anyDot = extents.some((e) => e !== null && e.dot);
|
|
3180
|
+
const countAxis = fmt.decimals === void 0 && reach.length > 0 && allIntegers(reach) && allIntegers(axis.range ?? []);
|
|
3181
|
+
const resolution = fmt.decimals !== void 0 ? 10 ** -fmt.decimals / (fmt.percent ? 100 : 1) : countAxis ? 1 : 0;
|
|
3226
3182
|
const anyHighlight = data.some((d) => d.highlight);
|
|
3227
3183
|
const colour = (d) => d.tone ? TONE[d.tone] : !anyHighlight || d.highlight ? "var(--color-accent)" : "var(--color-fg-faint)";
|
|
3228
|
-
const
|
|
3184
|
+
const outline = (d) => d.tone && d.highlight ? { stroke: "var(--color-fg)", strokeWidth: 2 } : {};
|
|
3185
|
+
const fmtOf = (d) => d.format ?? fmt;
|
|
3186
|
+
const shown = (d) => d.value === null || d.value === void 0 || !Number.isFinite(d.value) ? NOT_AVAILABLE[lang] : formatNumber(d.value, lang, fmtOf(d));
|
|
3229
3187
|
const width = box.width;
|
|
3188
|
+
const ids = useId();
|
|
3189
|
+
const tableId = `${ids}-table`;
|
|
3190
|
+
const summaryId = `${ids}-summary`;
|
|
3191
|
+
const [win, setWin] = useState(null);
|
|
3192
|
+
const [cursor, setCursor] = useState(null);
|
|
3193
|
+
const [brush, setBrush] = useState(null);
|
|
3194
|
+
const bands = useMemo(() => bandsOf(data.map((d) => d.group?.id)), [data]);
|
|
3195
|
+
const nb = bands.length;
|
|
3196
|
+
const grouped = data.some((d) => d.group !== void 0 && d.group !== null);
|
|
3197
|
+
const most = Math.max(1, ...bands.map((b) => b.to - b.from));
|
|
3198
|
+
const bandOf = (i) => bands.findIndex((b) => i >= b.from && i < b.to);
|
|
3199
|
+
const bandLabel = (b) => pick(b.group !== void 0 ? data[b.from].group.label : data[b.from].label, lang);
|
|
3200
|
+
const [s0, e0] = windowRange(nb, win);
|
|
3201
|
+
const zoomed = windowZoomed(nb, win);
|
|
3202
|
+
const zoomIsOff = zoomOff !== void 0 && zoomOff !== null;
|
|
3203
|
+
const zoomable = !zoomIsOff && nb >= 3;
|
|
3204
|
+
const zoomWhy = zoomIsOff ? pick(zoomOff, lang) : "";
|
|
3205
|
+
const kbRef = useRef(false);
|
|
3206
|
+
const geoRef = useRef(null);
|
|
3207
|
+
const live = useRef({ win, n: nb, zoomable, cursor });
|
|
3208
|
+
live.current = { win, n: nb, zoomable, cursor };
|
|
3209
|
+
const idKey = data.map((d) => d.group ? `${d.id}${d.group.id}` : d.id).join("");
|
|
3210
|
+
useEffect(() => {
|
|
3211
|
+
setWin(null);
|
|
3212
|
+
setCursor(null);
|
|
3213
|
+
setBrush(null);
|
|
3214
|
+
}, [idKey]);
|
|
3215
|
+
const unusedTones = legend ? unusedLegendTones(data.map((d) => d.tone ?? (anyHighlight && !d.highlight ? "neutral" : "accent")), legend) : [];
|
|
3216
|
+
const unusedKey = unusedTones.join(",");
|
|
3217
|
+
useEffect(() => {
|
|
3218
|
+
let dev = false;
|
|
3219
|
+
try {
|
|
3220
|
+
dev = process.env.NODE_ENV !== "production";
|
|
3221
|
+
} catch {
|
|
3222
|
+
dev = false;
|
|
3223
|
+
}
|
|
3224
|
+
if (dev && unusedKey) console.warn(`BarChart "${name}": the legend names the tone(s) ${unusedKey}, which no bar uses`);
|
|
3225
|
+
}, [unusedKey, name]);
|
|
3230
3226
|
const select = (id) => onSelect?.(id);
|
|
3231
3227
|
const keyed = (id) => (e) => {
|
|
3232
3228
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -3235,53 +3231,103 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
|
|
|
3235
3231
|
}
|
|
3236
3232
|
};
|
|
3237
3233
|
const interactive = (d) => onSelect ? { role: "button", tabIndex: 0, onClick: () => select(d.id), onKeyDown: keyed(d.id), style: { cursor: "pointer" } } : {};
|
|
3234
|
+
const hitArea = Boolean(onSelect) && (floating || grouped);
|
|
3238
3235
|
let drawing = null;
|
|
3239
3236
|
let drawnHeight = 0;
|
|
3240
|
-
|
|
3241
|
-
|
|
3237
|
+
let tickLabels = [];
|
|
3238
|
+
const valueLabels = data.map(shown);
|
|
3239
|
+
const endsText = data.map((d) => {
|
|
3240
|
+
const e = barExtent(d.value, d.span);
|
|
3241
|
+
if (!e || !e.given && !floating) return "";
|
|
3242
|
+
const [a, b] = [formatNumber(e.a, lang, fmtOf(d)), formatNumber(e.b, lang, fmtOf(d))];
|
|
3243
|
+
return lang === "es" ? `de ${a} a ${b}` : `from ${a} to ${b}`;
|
|
3244
|
+
});
|
|
3245
|
+
const memberName = (d, room) => {
|
|
3246
|
+
if (!d.group) return "";
|
|
3247
|
+
const full = pick(d.label, lang);
|
|
3248
|
+
for (let k = full.length; k >= 1; k--) {
|
|
3249
|
+
const t2 = full.slice(0, k).trimEnd();
|
|
3250
|
+
if (t2 && textWidth(t2, PX) <= room) return t2;
|
|
3251
|
+
}
|
|
3252
|
+
return "";
|
|
3253
|
+
};
|
|
3254
|
+
const fullName = (d) => d.group ? `${pick(d.group.label, lang)}, ${pick(d.label, lang)}` : pick(d.label, lang);
|
|
3255
|
+
if (width > 0 && n > 0) {
|
|
3256
|
+
const count = e0 - s0;
|
|
3242
3257
|
const valueCol = values ? widestLabel(valueLabels, PX) + 8 : 0;
|
|
3258
|
+
const vis = bands.slice(s0, e0);
|
|
3259
|
+
const items = vis.flatMap((b, kb) => Array.from({ length: b.to - b.from }, (_, j) => ({ b, kb, j, i: b.from + j, c: b.to - b.from })));
|
|
3260
|
+
const slots = (c) => memberSlots(c, most);
|
|
3243
3261
|
if (orientation === "horizontal") {
|
|
3244
|
-
const
|
|
3245
|
-
const
|
|
3246
|
-
const
|
|
3247
|
-
const
|
|
3248
|
-
const
|
|
3249
|
-
const
|
|
3262
|
+
const bandTexts = bands.map(bandLabel);
|
|
3263
|
+
const labelCol = Math.min(widestLabel(bandTexts, PX), Math.max(80, 0.38 * width));
|
|
3264
|
+
const fittedAll = bandTexts.map((t2) => fitLabel(t2, labelCol, PX, 2));
|
|
3265
|
+
const fitted = fittedAll.slice(s0, e0);
|
|
3266
|
+
const need = Math.max(22, Math.max(...fittedAll.map((f) => f.lines.length)) * LINE + 8, grouped ? most * 18 / (1 - 2 * GROUP_MARGIN) : 0);
|
|
3267
|
+
const memberRoom = grouped ? Math.min(widestLabel(data.map((d) => d.group ? pick(d.label, lang) : ""), PX), Math.max(24, 0.2 * width)) : 0;
|
|
3268
|
+
const memberCol = memberRoom > 0 ? memberRoom + 8 : 0;
|
|
3269
|
+
const p0 = labelCol + 10 + memberCol;
|
|
3270
|
+
const p1 = Math.max(p0 + 40, width - valueCol - 6);
|
|
3271
|
+
const cx = p0 + (p1 - p0) / 2;
|
|
3272
|
+
const titleFits = axisTitleFits(axisTitle, 2 * Math.min(cx, width - cx) - 8, PX);
|
|
3273
|
+
const titleLines = titleFits ? [axisTitle] : wrapLines(axisTitle, width - 8, PX);
|
|
3274
|
+
const axisH = 6 + LINE + 4 + titleLines.length * LINE + 4;
|
|
3275
|
+
const rowFull = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / nb) : need;
|
|
3276
|
+
const rowH = nb * rowFull / count;
|
|
3277
|
+
const barH = grouped ? Math.min(rowH * slots(most).size * 0.8, 40) : Math.min(rowH * 0.5, 40);
|
|
3278
|
+
const inset = anyDot ? barH / 2 : 0;
|
|
3279
|
+
const x0 = p0 + inset;
|
|
3280
|
+
const x1 = Math.max(x0 + 40, p1 - inset);
|
|
3250
3281
|
const tickProbe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
|
|
3251
|
-
const ticks = niceTicks(lo, hi, x1 - x0, tickProbe);
|
|
3282
|
+
const ticks = niceTicks(lo, hi, x1 - x0, tickProbe, 6, resolution);
|
|
3252
3283
|
const t0 = ticks[0];
|
|
3253
3284
|
const t1 = ticks[ticks.length - 1];
|
|
3254
|
-
|
|
3285
|
+
tickLabels = formatTicks(ticks, lang, fmt);
|
|
3255
3286
|
const sx = (v) => x0 + (v - t0) / (t1 - t0 || 1) * (x1 - x0);
|
|
3256
|
-
const rowH = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / data.length) : need;
|
|
3257
3287
|
const top = 4;
|
|
3258
|
-
const plotBottom = top +
|
|
3288
|
+
const plotBottom = top + nb * rowFull;
|
|
3259
3289
|
drawnHeight = fill && box.height > 0 ? Math.max(box.height, plotBottom + axisH) : plotBottom + axisH;
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3290
|
+
const slotH = grouped ? rowH * slots(1).size : rowH;
|
|
3291
|
+
const centre = (it) => top + it.kb * rowH + (grouped ? rowH * (slots(it.c).start + (it.j + 0.5) * slots(it.c).size) : rowH / 2);
|
|
3292
|
+
geoRef.current = { horizontal: true, start: top, end: plotBottom };
|
|
3293
|
+
const cb = cursor !== null ? bandOf(cursor) : -1;
|
|
3294
|
+
drawing = /* @__PURE__ */ jsxs("svg", { width, height: drawnHeight, role: "presentation", "data-chart": "", "data-chart-kind": "bars", children: [
|
|
3262
3295
|
/* @__PURE__ */ jsx("title", { children: name }),
|
|
3263
3296
|
ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
3264
3297
|
/* @__PURE__ */ jsx("line", { x1: sx(t2), x2: sx(t2), y1: top, y2: plotBottom, stroke: "var(--color-border)", strokeWidth: t2 === 0 ? 1.4 : 1 }),
|
|
3265
3298
|
/* @__PURE__ */ jsx("text", { x: sx(t2), y: plotBottom + 6 + LINE - 2, textAnchor: i === 0 && sx(t2) - textWidth(tickLabels[i], PX) / 2 < 0 ? "start" : i === ticks.length - 1 && sx(t2) + textWidth(tickLabels[i], PX) / 2 > width ? "end" : "middle", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: tickLabels[i] })
|
|
3266
3299
|
] }, `t${i}`)),
|
|
3267
|
-
/* @__PURE__ */ jsx(
|
|
3268
|
-
data.
|
|
3269
|
-
|
|
3270
|
-
|
|
3300
|
+
/* @__PURE__ */ jsx(Lines, { x: titleFits ? cx : width / 2, y: plotBottom + 6 + LINE + 4 + LINE - 2, lines: titleLines, anchor: "middle", fill: "var(--color-fg-subtle)" }),
|
|
3301
|
+
cursor !== null && cb >= s0 && cb < e0 && (grouped ? /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: 0, y: top + (cb - s0) * rowH + rowH * (slots(bands[cb].to - bands[cb].from).start + (cursor - bands[cb].from) * slots(1).size), width, height: rowH * slots(1).size }) : /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: 0, y: top + (cursor - s0) * rowH, width, height: rowH })),
|
|
3302
|
+
vis.map(
|
|
3303
|
+
(b, kb) => b.group !== void 0 ? /* @__PURE__ */ jsxs("g", { "data-group": b.group, children: [
|
|
3304
|
+
/* @__PURE__ */ jsx("title", { children: bandTexts[s0 + kb] }),
|
|
3305
|
+
/* @__PURE__ */ jsx(Lines, { x: labelCol + 2, y: top + kb * rowH + rowH / 2 - (fitted[kb].lines.length - 1) * LINE / 2 + 4, lines: fitted[kb].lines, anchor: "end", fill: "var(--color-fg)" })
|
|
3306
|
+
] }, `grp${s0 + kb}`) : null
|
|
3307
|
+
),
|
|
3308
|
+
items.map((it) => {
|
|
3309
|
+
const { i, kb } = it;
|
|
3310
|
+
const d = data[i];
|
|
3311
|
+
const cy = centre(it);
|
|
3312
|
+
const f = fitted[kb];
|
|
3271
3313
|
const v = d.value;
|
|
3272
3314
|
const ok = v !== null && v !== void 0 && Number.isFinite(v);
|
|
3273
|
-
const
|
|
3274
|
-
const
|
|
3315
|
+
const ex = extents[i];
|
|
3316
|
+
const xa = ex ? sx(ex.a) : sx(0);
|
|
3317
|
+
const xb = ex ? sx(ex.b) : xa;
|
|
3275
3318
|
const labelText = valueLabels[i];
|
|
3276
|
-
const negative = ok && v < 0;
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3319
|
+
const negative = ok && !ex?.given && v < 0;
|
|
3320
|
+
const far = Math.max(xa, xb);
|
|
3321
|
+
const member = memberName(d, memberRoom);
|
|
3322
|
+
return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, "data-cursor": cursor === i ? "1" : void 0, ...interactive(d), children: [
|
|
3323
|
+
/* @__PURE__ */ jsx("title", { children: [fullName(d), labelText, pick(d.hint, lang), endsText[i]].filter(Boolean).join(": ") }),
|
|
3324
|
+
!d.group && /* @__PURE__ */ jsx(Lines, { x: labelCol + 2, y: cy - (f.lines.length - 1) * LINE / 2 + 4, lines: f.lines, anchor: "end", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" }),
|
|
3325
|
+
ex && ex.dot && /* @__PURE__ */ jsx("circle", { cx: xa, cy, r: barH / 2, fill: colour(d), ...outline(d) }),
|
|
3326
|
+
ex && !ex.dot && /* @__PURE__ */ jsx("rect", { x: Math.min(xa, xb), y: cy - barH / 2, width: Math.max(1, Math.abs(xb - xa)), height: barH, rx: 2, fill: colour(d), ...outline(d) }),
|
|
3281
3327
|
values && /* @__PURE__ */ jsx(
|
|
3282
3328
|
"text",
|
|
3283
3329
|
{
|
|
3284
|
-
x: ok ? negative ? Math.max(x0 + textWidth(labelText, PX) + 2, xb - 4) : xb + 4 : xa + 4,
|
|
3330
|
+
x: ok ? negative ? Math.max(x0 + textWidth(labelText, PX) + 2, xb - 4) : (ex?.given ? far : xb) + 4 + (ex?.dot ? barH / 2 : 0) : xa + 4,
|
|
3285
3331
|
y: cy + 4,
|
|
3286
3332
|
textAnchor: negative ? "end" : "start",
|
|
3287
3333
|
fontSize: PX,
|
|
@@ -3290,63 +3336,382 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
|
|
|
3290
3336
|
style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" },
|
|
3291
3337
|
children: labelText
|
|
3292
3338
|
}
|
|
3293
|
-
)
|
|
3339
|
+
),
|
|
3340
|
+
member && /* @__PURE__ */ jsx("text", { "data-member": "", x: p0 - 8, y: cy + 4, textAnchor: "end", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: member }),
|
|
3341
|
+
hitArea && /* @__PURE__ */ jsx("rect", { "data-bar-hit": "", x: 0, y: cy - slotH / 2, width, height: slotH, fill: "transparent" })
|
|
3294
3342
|
] }, d.id);
|
|
3295
|
-
})
|
|
3343
|
+
}),
|
|
3344
|
+
brush && /* @__PURE__ */ jsx("rect", { className: "caos-bars-brush", "data-bar-brush": "", x: 0, y: Math.min(brush.a, brush.b), width, height: Math.abs(brush.b - brush.a) })
|
|
3296
3345
|
] });
|
|
3297
3346
|
} else {
|
|
3298
3347
|
const h = fill && box.height > 0 ? box.height : typeof height === "number" ? height : 240;
|
|
3299
|
-
const
|
|
3348
|
+
const titleLines = wrapLines(axisTitle, width - 4, PX);
|
|
3349
|
+
const titleRow = titleLines.length * LINE + 6;
|
|
3300
3350
|
const valueRow = values ? LINE + 4 : 4;
|
|
3301
3351
|
const probe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
|
|
3302
3352
|
const left = probe + 12;
|
|
3303
3353
|
const right = 8;
|
|
3304
|
-
const band = (width - left - right) /
|
|
3305
|
-
const fitted =
|
|
3306
|
-
const
|
|
3307
|
-
const
|
|
3308
|
-
const
|
|
3309
|
-
const
|
|
3354
|
+
const band = Math.max(0, (width - left - right) / count);
|
|
3355
|
+
const fitted = vis.map((b) => fitLabel(bandLabel(b), Math.max(24, band - 6), PX, 2));
|
|
3356
|
+
const slotW = band * slots(most).size;
|
|
3357
|
+
const barW = grouped ? Math.max(0, Math.min(slotW * 0.84, 56)) : Math.max(0, Math.min(band * 0.62, 56));
|
|
3358
|
+
const memberRow = grouped && data.some((d) => memberName(d, slotW - 2) !== "") ? LINE + 3 : 0;
|
|
3359
|
+
const bottom = Math.max(...fitted.map((f) => f.lines.length)) * LINE + 10 + memberRow;
|
|
3360
|
+
const inset = anyDot ? barW / 2 : 0;
|
|
3361
|
+
const y0 = titleRow + valueRow + inset;
|
|
3362
|
+
const y1 = Math.max(y0 + 40, h - bottom - inset);
|
|
3363
|
+
const yl = y1 + inset;
|
|
3364
|
+
const ticks = niceTicks(lo, hi, y1 - y0, LINE, 6, resolution);
|
|
3310
3365
|
const t0 = ticks[0];
|
|
3311
3366
|
const t1 = ticks[ticks.length - 1];
|
|
3312
|
-
|
|
3367
|
+
tickLabels = formatTicks(ticks, lang, fmt);
|
|
3313
3368
|
const sy = (v) => y1 - (v - t0) / (t1 - t0 || 1) * (y1 - y0);
|
|
3314
|
-
const
|
|
3369
|
+
const crowded = grouped && values && widestLabel(valueLabels, PX) > slotW - 2;
|
|
3370
|
+
const centre = (it) => left + band * it.kb + (grouped ? band * (slots(it.c).start + (it.j + 0.5) * slots(it.c).size) : band / 2);
|
|
3315
3371
|
drawnHeight = h;
|
|
3316
|
-
|
|
3372
|
+
geoRef.current = { horizontal: false, start: left, end: width - right };
|
|
3373
|
+
const cb = cursor !== null ? bandOf(cursor) : -1;
|
|
3374
|
+
drawing = /* @__PURE__ */ jsxs("svg", { width, height: h, role: "presentation", "data-chart": "", "data-chart-kind": "bars", children: [
|
|
3317
3375
|
/* @__PURE__ */ jsx("title", { children: name }),
|
|
3318
|
-
/* @__PURE__ */ jsx(
|
|
3376
|
+
/* @__PURE__ */ jsx(Lines, { x: 0, y: LINE - 2, lines: titleLines, anchor: "start", fill: "var(--color-fg-subtle)" }),
|
|
3319
3377
|
ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
3320
3378
|
/* @__PURE__ */ jsx("line", { x1: left, x2: width - right, y1: sy(t2), y2: sy(t2), stroke: "var(--color-border)", strokeWidth: t2 === 0 ? 1.4 : 1 }),
|
|
3321
3379
|
/* @__PURE__ */ jsx("text", { x: left - 6, y: sy(t2) + 4, textAnchor: "end", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: tickLabels[i] })
|
|
3322
3380
|
] }, `t${i}`)),
|
|
3323
|
-
data.
|
|
3324
|
-
|
|
3381
|
+
cursor !== null && cb >= s0 && cb < e0 && (grouped ? /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: left + band * (cb - s0) + band * (slots(bands[cb].to - bands[cb].from).start + (cursor - bands[cb].from) * slots(1).size), y: y0, width: band * slots(1).size, height: h - y0 }) : /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: left + band * (cursor - s0), y: y0, width: band, height: h - y0 })),
|
|
3382
|
+
vis.map(
|
|
3383
|
+
(b, kb) => b.group !== void 0 ? /* @__PURE__ */ jsxs("g", { "data-group": b.group, children: [
|
|
3384
|
+
/* @__PURE__ */ jsx("title", { children: bandLabel(b) }),
|
|
3385
|
+
/* @__PURE__ */ jsx(Lines, { x: left + band * kb + band / 2, y: yl + LINE + 2 + memberRow, lines: fitted[kb].lines, anchor: "middle", fill: "var(--color-fg)" })
|
|
3386
|
+
] }, `grp${s0 + kb}`) : null
|
|
3387
|
+
),
|
|
3388
|
+
items.map((it) => {
|
|
3389
|
+
const { i, kb } = it;
|
|
3390
|
+
const d = data[i];
|
|
3391
|
+
const cx = centre(it);
|
|
3325
3392
|
const v = d.value;
|
|
3326
3393
|
const ok = v !== null && v !== void 0 && Number.isFinite(v);
|
|
3327
|
-
const
|
|
3328
|
-
const
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
/* @__PURE__ */ jsx(
|
|
3394
|
+
const ex = extents[i];
|
|
3395
|
+
const ya = ex ? sy(ex.a) : sy(0);
|
|
3396
|
+
const yb = ex ? sy(ex.b) : ya;
|
|
3397
|
+
const f = fitted[kb];
|
|
3398
|
+
const member = memberName(d, slotW - 2);
|
|
3399
|
+
return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, "data-cursor": cursor === i ? "1" : void 0, ...interactive(d), children: [
|
|
3400
|
+
/* @__PURE__ */ jsx("title", { children: [fullName(d), valueLabels[i], pick(d.hint, lang), endsText[i]].filter(Boolean).join(": ") }),
|
|
3401
|
+
ex && ex.dot && /* @__PURE__ */ jsx("circle", { cx, cy: ya, r: barW / 2, fill: colour(d), ...outline(d) }),
|
|
3402
|
+
ex && !ex.dot && /* @__PURE__ */ jsx("rect", { x: cx - barW / 2, y: Math.min(ya, yb), width: barW, height: Math.max(1, Math.abs(yb - ya)), rx: 2, fill: colour(d), ...outline(d) }),
|
|
3403
|
+
values && /* @__PURE__ */ jsx("text", { x: cx, y: Math.min(ya, yb) - 5 - (ex?.dot ? barW / 2 : 0) - (crowded && it.j % 2 === 1 ? LINE : 0), textAnchor: "middle", fontSize: PX, fill: ok ? "var(--color-fg)" : "var(--color-fg-faint)", fontStyle: ok ? void 0 : "italic", style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" }, children: valueLabels[i] }),
|
|
3404
|
+
!d.group && /* @__PURE__ */ jsx(Lines, { x: cx, y: yl + LINE + 2, lines: f.lines, anchor: "middle", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" }),
|
|
3405
|
+
member && /* @__PURE__ */ jsx("text", { "data-member": "", x: cx, y: yl + LINE + 2, textAnchor: "middle", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: member }),
|
|
3406
|
+
hitArea && /* @__PURE__ */ jsx("rect", { "data-bar-hit": "", x: cx - (grouped ? slotW : band) / 2, y: 0, width: grouped ? slotW : band, height: h, fill: "transparent" })
|
|
3335
3407
|
] }, d.id);
|
|
3336
|
-
})
|
|
3408
|
+
}),
|
|
3409
|
+
brush && /* @__PURE__ */ jsx("rect", { className: "caos-bars-brush", "data-bar-brush": "", x: Math.min(brush.a, brush.b), y: y0, width: Math.abs(brush.b - brush.a), height: h - y0 })
|
|
3337
3410
|
] });
|
|
3338
3411
|
}
|
|
3339
3412
|
}
|
|
3340
|
-
|
|
3413
|
+
const drawn = width > 0 && n > 0;
|
|
3414
|
+
const repeats = repeatedLabels(tickLabels);
|
|
3415
|
+
const a11y = useMemo(() => {
|
|
3416
|
+
if (!drawn) return null;
|
|
3417
|
+
const head = [
|
|
3418
|
+
...grouped ? [lang === "es" ? "Grupo" : "Group"] : [],
|
|
3419
|
+
lang === "es" ? "Categor\xEDa" : "Category",
|
|
3420
|
+
unit ? `${pick(axis.label, lang)} (${unit})` : pick(axis.label, lang),
|
|
3421
|
+
...floating ? lang === "es" ? ["Desde", "Hasta"] : ["From", "To"] : [],
|
|
3422
|
+
lang === "es" ? "Nota" : "Note"
|
|
3423
|
+
];
|
|
3424
|
+
const rows = data.map((d, i) => {
|
|
3425
|
+
const e = barExtent(d.value, d.span);
|
|
3426
|
+
const end = (v) => e && v !== void 0 ? formatNumber(v, lang, fmtOf(d)) : "";
|
|
3427
|
+
return [...grouped ? [d.group ? pick(d.group.label, lang) : ""] : [], pick(d.label, lang), valueLabels[i], ...floating ? [end(e?.a), end(e?.b)] : [], pick(d.hint, lang)];
|
|
3428
|
+
});
|
|
3429
|
+
let low = -1;
|
|
3430
|
+
let high = -1;
|
|
3431
|
+
data.forEach((d, i) => {
|
|
3432
|
+
if (d.value === null || d.value === void 0 || !Number.isFinite(d.value)) return;
|
|
3433
|
+
if (low < 0 || d.value < data[low].value) low = i;
|
|
3434
|
+
if (high < 0 || d.value > data[high].value) high = i;
|
|
3435
|
+
});
|
|
3436
|
+
const withUnit = (i) => `${valueLabels[i]}${unit ? `${NBSP}${unit}` : ""}`;
|
|
3437
|
+
const drawnExtents = data.map((d) => barExtent(d.value, d.span));
|
|
3438
|
+
const firstAt = drawnExtents.findIndex((e) => e !== null);
|
|
3439
|
+
const lastAt = drawnExtents.length - 1 - [...drawnExtents].reverse().findIndex((e) => e !== null);
|
|
3440
|
+
const endOf = (i, v) => ({ text: `${formatNumber(v, lang, fmtOf(data[i]))}${unit ? `${NBSP}${unit}` : ""}`, at: fullName(data[i]) });
|
|
3441
|
+
const ends = floating && firstAt >= 0 ? { first: endOf(firstAt, drawnExtents[firstAt].a), last: endOf(lastAt, drawnExtents[lastAt].b) } : {};
|
|
3442
|
+
const summary = chartSummary(lang, {
|
|
3443
|
+
...ends,
|
|
3444
|
+
kind: "bars",
|
|
3445
|
+
name,
|
|
3446
|
+
count: n,
|
|
3447
|
+
yLabel: axisTitle,
|
|
3448
|
+
low: low >= 0 ? { text: withUnit(low), at: fullName(data[low]) } : void 0,
|
|
3449
|
+
high: high >= 0 ? { text: withUnit(high), at: fullName(data[high]) } : void 0
|
|
3450
|
+
});
|
|
3451
|
+
return { head, rows, summary };
|
|
3452
|
+
}, [drawn, data, lang, unit, name, axisTitle, axis.label, floating, grouped]);
|
|
3453
|
+
const rest = lang === "es" ? REST.es : REST.en;
|
|
3454
|
+
const read = cursor !== null && cursor < n ? [`${fullName(data[cursor])}: ${valueLabels[cursor]}${data[cursor].value === null || data[cursor].value === void 0 || !unit ? "" : `${NBSP}${unit}`}`, pick(data[cursor].hint, lang), endsText[cursor]].filter(Boolean).join(" \xB7 ") : rest;
|
|
3455
|
+
const along = (e) => {
|
|
3456
|
+
const g = geoRef.current;
|
|
3457
|
+
const svg = innerRef.current?.querySelector("svg");
|
|
3458
|
+
if (!g || !svg) return null;
|
|
3459
|
+
const r = svg.getBoundingClientRect();
|
|
3460
|
+
return g.horizontal ? e.clientY - r.top : e.clientX - r.left;
|
|
3461
|
+
};
|
|
3462
|
+
const setWindow = (r) => {
|
|
3463
|
+
if (!r || r[0] <= 0 && r[1] >= nb) setWin(null);
|
|
3464
|
+
else setWin(roundWindow(r, nb));
|
|
3465
|
+
};
|
|
3466
|
+
const fine = useRef(null);
|
|
3467
|
+
const zoomAround = (factor, anchorBar, discrete = false) => {
|
|
3468
|
+
const cur = windowRange(nb, live.current.win);
|
|
3469
|
+
if (discrete) {
|
|
3470
|
+
fine.current = null;
|
|
3471
|
+
const next2 = stepWindow(cur, nb, anchorBar, factor);
|
|
3472
|
+
setWindow(next2);
|
|
3473
|
+
return next2[0] !== cur[0] || next2[1] !== cur[1];
|
|
3474
|
+
}
|
|
3475
|
+
const held = fine.current;
|
|
3476
|
+
const base = held && held[0] > cur[0] - 1 && held[1] < cur[1] + 1 && roundWindow(held, nb)[0] === cur[0] && roundWindow(held, nb)[1] === cur[1] ? held : cur;
|
|
3477
|
+
const next = zoomRange(base, [0, nb], anchorBar, factor, { minSpan: Math.min(2, nb) });
|
|
3478
|
+
fine.current = next;
|
|
3479
|
+
setWindow(next);
|
|
3480
|
+
return Math.abs(next[0] - base[0]) > 1e-9 || Math.abs(next[1] - base[1]) > 1e-9;
|
|
3481
|
+
};
|
|
3482
|
+
const barAt = (pos) => {
|
|
3483
|
+
const g = geoRef.current;
|
|
3484
|
+
if (pos === null || !g) return null;
|
|
3485
|
+
const win2 = windowRange(nb, live.current.win);
|
|
3486
|
+
const bi = categoryAt(pos, g.start, g.end, win2, nb);
|
|
3487
|
+
if (bi === null) return null;
|
|
3488
|
+
const b = bands[bi];
|
|
3489
|
+
if (b.to - b.from === 1) return b.from;
|
|
3490
|
+
const scaled = (pos - g.start) / (g.end - g.start || 1) * (win2[1] - win2[0]);
|
|
3491
|
+
return b.from + memberAt(scaled - Math.floor(scaled), b.to - b.from, most);
|
|
3492
|
+
};
|
|
3493
|
+
const barFloat = (pos) => {
|
|
3494
|
+
const g = geoRef.current;
|
|
3495
|
+
const [s, e] = windowRange(nb, live.current.win);
|
|
3496
|
+
return g ? s + (pos - g.start) / (g.end - g.start || 1) * (e - s) : s;
|
|
3497
|
+
};
|
|
3498
|
+
const wheelOn = drawn && zoomable;
|
|
3499
|
+
useEffect(() => {
|
|
3500
|
+
const el = innerRef.current;
|
|
3501
|
+
if (!el || !wheelOn) return;
|
|
3502
|
+
const onWheel = (e) => {
|
|
3503
|
+
if (!live.current.zoomable) return;
|
|
3504
|
+
if (!el.contains(document.activeElement)) return;
|
|
3505
|
+
const pos = along(e);
|
|
3506
|
+
if (pos === null) return;
|
|
3507
|
+
if (zoomAround(wheelFactor(e.deltaY, e.deltaMode), barFloat(pos))) e.preventDefault();
|
|
3508
|
+
};
|
|
3509
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
3510
|
+
return () => el.removeEventListener("wheel", onWheel);
|
|
3511
|
+
}, [wheelOn, nb]);
|
|
3512
|
+
const pointers = useRef(/* @__PURE__ */ new Map());
|
|
3513
|
+
const gesture = useRef({ kind: null, id: -1, start: 0, last: 0, moved: false, f: [0, 0], dist: 0 });
|
|
3514
|
+
const onPointerDown = (e) => {
|
|
3515
|
+
e.currentTarget.focus({ preventScroll: true });
|
|
3516
|
+
kbRef.current = false;
|
|
3517
|
+
if (!live.current.zoomable) return;
|
|
3518
|
+
pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
3519
|
+
const pos = along(e);
|
|
3520
|
+
const g = gesture.current;
|
|
3521
|
+
if (e.pointerType === "mouse") {
|
|
3522
|
+
if (e.button !== 0 || pos === null) return;
|
|
3523
|
+
Object.assign(g, { kind: e.shiftKey ? "pan" : "brush", id: e.pointerId, start: pos, last: pos, moved: false, f: windowRange(nb, live.current.win) });
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
if (pointers.current.size === 2) {
|
|
3527
|
+
const [a, b] = [...pointers.current.values()];
|
|
3528
|
+
Object.assign(g, { kind: "pinch", dist: pinchState(a, b).dist });
|
|
3529
|
+
} else if (pointers.current.size === 1 && windowZoomed(nb, live.current.win) && pos !== null) {
|
|
3530
|
+
Object.assign(g, { kind: "pan", id: e.pointerId, start: pos, last: pos, moved: false, f: windowRange(nb, live.current.win) });
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
const onPointerMove = (e) => {
|
|
3534
|
+
const g = gesture.current;
|
|
3535
|
+
const pos = along(e);
|
|
3536
|
+
if (pointers.current.has(e.pointerId)) pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
3537
|
+
if (g.kind === "pinch" && pointers.current.size === 2) {
|
|
3538
|
+
const [a, b] = [...pointers.current.values()];
|
|
3539
|
+
const now = pinchState(a, b);
|
|
3540
|
+
const mid = along({ clientX: now.mx, clientY: now.my });
|
|
3541
|
+
if (g.dist > 0 && now.dist > 0 && mid !== null) {
|
|
3542
|
+
zoomAround(g.dist / now.dist, barFloat(mid));
|
|
3543
|
+
g.dist = now.dist;
|
|
3544
|
+
}
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
if (g.kind && g.id === e.pointerId && pos !== null) {
|
|
3548
|
+
const gap = geoRef.current ? geoRef.current.end - geoRef.current.start : 1;
|
|
3549
|
+
if (!g.moved && Math.abs(pos - g.start) < DRAG_THRESHOLD) return;
|
|
3550
|
+
if (!g.moved) {
|
|
3551
|
+
g.moved = true;
|
|
3552
|
+
try {
|
|
3553
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
3554
|
+
} catch {
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
if (g.kind === "brush") setBrush({ a: g.start, b: pos });
|
|
3558
|
+
else {
|
|
3559
|
+
const [s, en] = windowRange(nb, live.current.win);
|
|
3560
|
+
const bars = (g.last - pos) / (gap || 1) * (en - s);
|
|
3561
|
+
g.last = pos;
|
|
3562
|
+
g.f = panRange(g.f, [0, nb], bars / (g.f[1] - g.f[0] || 1));
|
|
3563
|
+
setWindow(g.f);
|
|
3564
|
+
}
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
if (e.pointerType === "mouse" || e.pointerType === "pen") {
|
|
3568
|
+
const bar = barAt(pos);
|
|
3569
|
+
kbRef.current = false;
|
|
3570
|
+
setCursor(bar);
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
const endPointer = (e) => {
|
|
3574
|
+
const g = gesture.current;
|
|
3575
|
+
pointers.current.delete(e.pointerId);
|
|
3576
|
+
if (e.pointerType === "touch" && e.type === "pointerup" && !g.moved && g.kind !== "pinch") {
|
|
3577
|
+
const bar = barAt(along(e));
|
|
3578
|
+
if (bar !== null) {
|
|
3579
|
+
kbRef.current = false;
|
|
3580
|
+
setCursor(bar);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
if (g.kind === "brush" && g.id === e.pointerId && g.moved) {
|
|
3584
|
+
const geo = geoRef.current;
|
|
3585
|
+
const pos = along(e);
|
|
3586
|
+
if (geo && pos !== null) {
|
|
3587
|
+
const w = brushWindow(g.start, pos, geo.start, geo.end, windowRange(nb, live.current.win), nb);
|
|
3588
|
+
if (w) setWindow(w);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
|
|
3592
|
+
setBrush(null);
|
|
3593
|
+
if (g.kind !== "pinch" || pointers.current.size < 2) gesture.current = { ...g, kind: null, moved: false };
|
|
3594
|
+
};
|
|
3595
|
+
const onPointerLeave = (e) => {
|
|
3596
|
+
if (e.pointerType !== "touch" && !kbRef.current) setCursor(null);
|
|
3597
|
+
};
|
|
3598
|
+
const onKeyDown = (e) => {
|
|
3599
|
+
const onBar = e.target !== e.currentTarget && e.target instanceof Element && e.target.closest("[data-bar]") !== null;
|
|
3600
|
+
if (e.target !== e.currentTarget && !onBar || nb === 0) return;
|
|
3601
|
+
const act = keyAction(e.key, { shift: e.shiftKey, ctrl: e.ctrlKey, meta: e.metaKey, alt: e.altKey });
|
|
3602
|
+
if (!act || onBar && act === "activate") return;
|
|
3603
|
+
const [s, en] = windowRange(nb, win);
|
|
3604
|
+
const stand = cursor;
|
|
3605
|
+
switch (act) {
|
|
3606
|
+
case "prev":
|
|
3607
|
+
case "next":
|
|
3608
|
+
case "first":
|
|
3609
|
+
case "last": {
|
|
3610
|
+
const standBand = stand !== null ? bandOf(stand) : -1;
|
|
3611
|
+
const from = stand !== null && standBand >= s && standBand < en ? stand : null;
|
|
3612
|
+
let idx;
|
|
3613
|
+
if (from === null && (act === "next" || act === "prev") && windowZoomed(nb, win)) idx = act === "next" ? bands[s].from : bands[en - 1].to - 1;
|
|
3614
|
+
else idx = stepIndex(from, act, n);
|
|
3615
|
+
if (idx === null) return;
|
|
3616
|
+
kbRef.current = true;
|
|
3617
|
+
setCursor(idx);
|
|
3618
|
+
const into = bandOf(idx);
|
|
3619
|
+
if (into < s) setWindow([into, into + (en - s)]);
|
|
3620
|
+
else if (into >= en) setWindow([into + 1 - (en - s), into + 1]);
|
|
3621
|
+
break;
|
|
3622
|
+
}
|
|
3623
|
+
case "zoomIn":
|
|
3624
|
+
case "zoomOut":
|
|
3625
|
+
if (!zoomable) return;
|
|
3626
|
+
zoomAround(act === "zoomIn" ? KEY_ZOOM : 1 / KEY_ZOOM, stand !== null ? bandOf(stand) + 0.5 : (s + en) / 2, true);
|
|
3627
|
+
break;
|
|
3628
|
+
case "panPrev":
|
|
3629
|
+
case "panNext": {
|
|
3630
|
+
if (!zoomable || !windowZoomed(nb, win)) return;
|
|
3631
|
+
const bars = Math.max(1, Math.round((en - s) * KEY_PAN));
|
|
3632
|
+
setWindow(panRange([s, en], [0, nb], (act === "panNext" ? 1 : -1) * bars / (en - s)));
|
|
3633
|
+
break;
|
|
3634
|
+
}
|
|
3635
|
+
case "reset":
|
|
3636
|
+
if (e.key === "Escape" && !windowZoomed(nb, win)) return;
|
|
3637
|
+
setWin(null);
|
|
3638
|
+
e.stopPropagation();
|
|
3639
|
+
break;
|
|
3640
|
+
case "activate":
|
|
3641
|
+
if (stand === null || !onSelect) return;
|
|
3642
|
+
select(data[stand].id);
|
|
3643
|
+
break;
|
|
3644
|
+
}
|
|
3645
|
+
e.preventDefault();
|
|
3646
|
+
};
|
|
3647
|
+
const onBlur = (e) => {
|
|
3648
|
+
if (e.relatedTarget instanceof Node && e.currentTarget.contains(e.relatedTarget)) return;
|
|
3649
|
+
if (kbRef.current) {
|
|
3650
|
+
kbRef.current = false;
|
|
3651
|
+
setCursor(null);
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
const resetZoom = () => {
|
|
3655
|
+
setWin(null);
|
|
3656
|
+
innerRef.current?.focus({ preventScroll: true });
|
|
3657
|
+
};
|
|
3658
|
+
return /* @__PURE__ */ jsxs(
|
|
3341
3659
|
"div",
|
|
3342
3660
|
{
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
"data-
|
|
3347
|
-
"data-
|
|
3348
|
-
"data-
|
|
3349
|
-
|
|
3661
|
+
className: fill ? "caos-bars-host fill" : "caos-bars-host",
|
|
3662
|
+
"data-chart-host": drawn ? "bars" : void 0,
|
|
3663
|
+
"data-points": drawn ? String(n) : void 0,
|
|
3664
|
+
"data-groups": drawn && grouped ? String(nb) : void 0,
|
|
3665
|
+
"data-legend": drawn && legend && legend.length > 0 ? String(legend.length) : void 0,
|
|
3666
|
+
"data-spans": drawn && floating ? String(data.filter((d) => d.span !== void 0 && d.span !== null).length) : void 0,
|
|
3667
|
+
"data-zoomable": drawn ? zoomable ? "1" : "0" : void 0,
|
|
3668
|
+
"data-zoomed": drawn && zoomed ? "1" : void 0,
|
|
3669
|
+
"data-zoom-off": drawn && zoomIsOff ? zoomWhy : void 0,
|
|
3670
|
+
"data-window": drawn ? `${s0},${e0}` : void 0,
|
|
3671
|
+
"data-orientation": drawn ? orientation : void 0,
|
|
3672
|
+
children: [
|
|
3673
|
+
/* @__PURE__ */ jsx(
|
|
3674
|
+
"div",
|
|
3675
|
+
{
|
|
3676
|
+
ref: setInner,
|
|
3677
|
+
className: fill ? "caos-bars fill" : "caos-bars",
|
|
3678
|
+
style: !fill && orientation === "vertical" ? { height: typeof height === "number" ? height : 240 } : void 0,
|
|
3679
|
+
"data-drawn": width > 0 ? "1" : "0",
|
|
3680
|
+
"data-width": width,
|
|
3681
|
+
"data-height": drawnHeight,
|
|
3682
|
+
"data-ticks-repeat": drawn ? String(repeats) : void 0,
|
|
3683
|
+
...drawn ? {
|
|
3684
|
+
tabIndex: 0,
|
|
3685
|
+
role: "group",
|
|
3686
|
+
"aria-roledescription": CHART_ROLE[lang],
|
|
3687
|
+
"aria-label": name,
|
|
3688
|
+
"aria-describedby": summaryId,
|
|
3689
|
+
"aria-details": tableId,
|
|
3690
|
+
"data-chart-focus": "",
|
|
3691
|
+
"data-zoomed": zoomed ? "1" : void 0,
|
|
3692
|
+
onKeyDown,
|
|
3693
|
+
onBlur,
|
|
3694
|
+
onPointerDown,
|
|
3695
|
+
onPointerMove,
|
|
3696
|
+
onPointerUp: endPointer,
|
|
3697
|
+
onPointerCancel: endPointer,
|
|
3698
|
+
onPointerLeave,
|
|
3699
|
+
onDoubleClick: () => setWin(null)
|
|
3700
|
+
} : {},
|
|
3701
|
+
children: drawing
|
|
3702
|
+
}
|
|
3703
|
+
),
|
|
3704
|
+
drawn && legend && legend.length > 0 && /* @__PURE__ */ jsx("ul", { className: "caos-bars-legend", "aria-label": lang === "es" ? "Leyenda" : "Legend", children: legend.map((l, k) => /* @__PURE__ */ jsxs("li", { className: "caos-bars-key", "data-tone": l.tone, children: [
|
|
3705
|
+
/* @__PURE__ */ jsx("span", { className: "caos-bars-swatch", style: { "--swatch": TONE[l.tone] }, "aria-hidden": "true" }),
|
|
3706
|
+
pick(l.label, lang)
|
|
3707
|
+
] }, k)) }),
|
|
3708
|
+
drawn && a11y && /* @__PURE__ */ jsx(ChartSummary, { id: summaryId, text: a11y.summary }),
|
|
3709
|
+
drawn && a11y && /* @__PURE__ */ jsx(ChartDataTable, { id: tableId, caption: name, head: a11y.head, rows: a11y.rows }),
|
|
3710
|
+
drawn && /* @__PURE__ */ jsxs("div", { className: "caos-bars-foot", children: [
|
|
3711
|
+
/* @__PURE__ */ jsx("p", { className: "caos-bars-readout", "aria-live": "polite", title: read, children: read }),
|
|
3712
|
+
zoomed && zoomable && /* @__PURE__ */ jsx(ResetZoomButton, { lang, onReset: resetZoom })
|
|
3713
|
+
] })
|
|
3714
|
+
]
|
|
3350
3715
|
}
|
|
3351
3716
|
);
|
|
3352
3717
|
}
|
|
@@ -3489,7 +3854,7 @@ function panel(id, lane, provenance, content) {
|
|
|
3489
3854
|
return /* @__PURE__ */ jsx("div", { className: "caos-cw-panel", "data-group": id, "data-lane": lane, "data-provenance": provenance, children: content });
|
|
3490
3855
|
}
|
|
3491
3856
|
function CaseWorkbench(props) {
|
|
3492
|
-
const { caseId, cases, source, controls, replayOnly, variants, rail, railLabel, groups, compare, context
|
|
3857
|
+
const { caseId, cases, source, controls, replayOnly, variants, rail, railLabel, groups, compare, context, group, onGroupChange, deepLinkView = true, ariaLabel, focus } = props;
|
|
3493
3858
|
const lang = useShellLang();
|
|
3494
3859
|
const focusCfg = focus === true ? {} : focus || null;
|
|
3495
3860
|
const { show: showFocus, setFocus, enterRef, memory } = useFocusMode(focusCfg);
|
|
@@ -3509,8 +3874,8 @@ function CaseWorkbench(props) {
|
|
|
3509
3874
|
}
|
|
3510
3875
|
tabs.push({
|
|
3511
3876
|
id: "context",
|
|
3512
|
-
label: pick(
|
|
3513
|
-
content: /* @__PURE__ */ jsx("div", { className: "prose caos-cw-context", children:
|
|
3877
|
+
label: pick(context.label ?? { en: "Context", es: "Contexto" }, lang),
|
|
3878
|
+
content: /* @__PURE__ */ jsx("div", { className: "prose caos-cw-context", children: context.content })
|
|
3514
3879
|
});
|
|
3515
3880
|
if (tabs.length > MAX_WORKBENCH_GROUPS) {
|
|
3516
3881
|
throw new Error(
|
|
@@ -3778,4 +4143,4 @@ function ViewsRow({ children, shares }) {
|
|
|
3778
4143
|
}) });
|
|
3779
4144
|
}
|
|
3780
4145
|
|
|
3781
|
-
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Stat, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, deckPosition, deckWindow, firstAppearance,
|
|
4146
|
+
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Stat, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, deckPosition, deckWindow, firstAppearance, groupByCategory, isStale, makeStateKey, perPageFor, readCaseParam, removeQueryParam, replaceQueryParam, sourcesPresent, useFocusMode, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, validateExecutive, viewParamOf, withCaseParam };
|