@fasl-work/caos-app-shell 0.18.0 → 0.19.1
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/dist/chart.d.ts +43 -10
- package/dist/chart.js +597 -85
- package/dist/chunk-FXPSZD6X.js +686 -0
- package/dist/index.d.ts +36 -5
- package/dist/index.js +499 -66
- package/dist/{text-DyQ1A8oo.d.ts → tokens-BvU9bFPw.d.ts} +25 -22
- package/gate/inpage.mjs +169 -0
- package/gate/run.mjs +12 -0
- package/package.json +1 -1
- package/reserved-classes.json +22 -0
- package/styles.css +41 -0
- package/dist/chunk-DBCMAL2G.js +0 -289
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, NOT_AVAILABLE, widestLabel, fitLabel, formatTicks, niceTicks, textWidth,
|
|
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-
|
|
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-FXPSZD6X.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-FXPSZD6X.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';
|
|
@@ -3142,23 +3142,87 @@ var TONE = {
|
|
|
3142
3142
|
accent: "var(--color-accent)",
|
|
3143
3143
|
neutral: "var(--color-fg-faint)"
|
|
3144
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
|
+
};
|
|
3145
3149
|
function Lines({ x, y, lines, anchor, fill }) {
|
|
3146
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)) });
|
|
3147
3151
|
}
|
|
3148
|
-
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 }) {
|
|
3149
3153
|
const lang = useShellLang();
|
|
3150
|
-
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
|
+
);
|
|
3151
3163
|
const fill = height === "fill";
|
|
3152
3164
|
const name = pick(title, lang);
|
|
3153
|
-
const
|
|
3165
|
+
const unit = axis.unit ? pick(axis.unit, lang) : "";
|
|
3166
|
+
const axisTitle = unit ? `${pick(axis.label, lang)} (${unit})` : pick(axis.label, lang);
|
|
3154
3167
|
const fmt = axis.format ?? { digits: 3 };
|
|
3168
|
+
const n = data.length;
|
|
3155
3169
|
const finite = data.map((d) => d.value).filter((v) => v !== null && v !== void 0 && Number.isFinite(v));
|
|
3156
|
-
const
|
|
3157
|
-
|
|
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;
|
|
3158
3182
|
const anyHighlight = data.some((d) => d.highlight);
|
|
3159
3183
|
const colour = (d) => d.tone ? TONE[d.tone] : !anyHighlight || d.highlight ? "var(--color-accent)" : "var(--color-fg-faint)";
|
|
3160
|
-
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));
|
|
3161
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]);
|
|
3162
3226
|
const select = (id) => onSelect?.(id);
|
|
3163
3227
|
const keyed = (id) => (e) => {
|
|
3164
3228
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -3167,53 +3231,103 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
|
|
|
3167
3231
|
}
|
|
3168
3232
|
};
|
|
3169
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);
|
|
3170
3235
|
let drawing = null;
|
|
3171
3236
|
let drawnHeight = 0;
|
|
3172
|
-
|
|
3173
|
-
|
|
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;
|
|
3174
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);
|
|
3175
3261
|
if (orientation === "horizontal") {
|
|
3176
|
-
const
|
|
3177
|
-
const
|
|
3178
|
-
const
|
|
3179
|
-
const
|
|
3180
|
-
const
|
|
3181
|
-
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);
|
|
3182
3281
|
const tickProbe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
|
|
3183
|
-
const ticks = niceTicks(lo, hi, x1 - x0, tickProbe);
|
|
3282
|
+
const ticks = niceTicks(lo, hi, x1 - x0, tickProbe, 6, resolution);
|
|
3184
3283
|
const t0 = ticks[0];
|
|
3185
3284
|
const t1 = ticks[ticks.length - 1];
|
|
3186
|
-
|
|
3285
|
+
tickLabels = formatTicks(ticks, lang, fmt);
|
|
3187
3286
|
const sx = (v) => x0 + (v - t0) / (t1 - t0 || 1) * (x1 - x0);
|
|
3188
|
-
const rowH = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / data.length) : need;
|
|
3189
3287
|
const top = 4;
|
|
3190
|
-
const plotBottom = top +
|
|
3288
|
+
const plotBottom = top + nb * rowFull;
|
|
3191
3289
|
drawnHeight = fill && box.height > 0 ? Math.max(box.height, plotBottom + axisH) : plotBottom + axisH;
|
|
3192
|
-
const
|
|
3193
|
-
|
|
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: [
|
|
3194
3295
|
/* @__PURE__ */ jsx("title", { children: name }),
|
|
3195
3296
|
ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
3196
3297
|
/* @__PURE__ */ jsx("line", { x1: sx(t2), x2: sx(t2), y1: top, y2: plotBottom, stroke: "var(--color-border)", strokeWidth: t2 === 0 ? 1.4 : 1 }),
|
|
3197
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] })
|
|
3198
3299
|
] }, `t${i}`)),
|
|
3199
|
-
/* @__PURE__ */ jsx(
|
|
3200
|
-
data.
|
|
3201
|
-
|
|
3202
|
-
|
|
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];
|
|
3203
3313
|
const v = d.value;
|
|
3204
3314
|
const ok = v !== null && v !== void 0 && Number.isFinite(v);
|
|
3205
|
-
const
|
|
3206
|
-
const
|
|
3315
|
+
const ex = extents[i];
|
|
3316
|
+
const xa = ex ? sx(ex.a) : sx(0);
|
|
3317
|
+
const xb = ex ? sx(ex.b) : xa;
|
|
3207
3318
|
const labelText = valueLabels[i];
|
|
3208
|
-
const negative = ok && v < 0;
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
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) }),
|
|
3213
3327
|
values && /* @__PURE__ */ jsx(
|
|
3214
3328
|
"text",
|
|
3215
3329
|
{
|
|
3216
|
-
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,
|
|
3217
3331
|
y: cy + 4,
|
|
3218
3332
|
textAnchor: negative ? "end" : "start",
|
|
3219
3333
|
fontSize: PX,
|
|
@@ -3222,63 +3336,382 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
|
|
|
3222
3336
|
style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" },
|
|
3223
3337
|
children: labelText
|
|
3224
3338
|
}
|
|
3225
|
-
)
|
|
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" })
|
|
3226
3342
|
] }, d.id);
|
|
3227
|
-
})
|
|
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) })
|
|
3228
3345
|
] });
|
|
3229
3346
|
} else {
|
|
3230
3347
|
const h = fill && box.height > 0 ? box.height : typeof height === "number" ? height : 240;
|
|
3231
|
-
const
|
|
3348
|
+
const titleLines = wrapLines(axisTitle, width - 4, PX);
|
|
3349
|
+
const titleRow = titleLines.length * LINE + 6;
|
|
3232
3350
|
const valueRow = values ? LINE + 4 : 4;
|
|
3233
3351
|
const probe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
|
|
3234
3352
|
const left = probe + 12;
|
|
3235
3353
|
const right = 8;
|
|
3236
|
-
const band = (width - left - right) /
|
|
3237
|
-
const fitted =
|
|
3238
|
-
const
|
|
3239
|
-
const
|
|
3240
|
-
const
|
|
3241
|
-
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);
|
|
3242
3365
|
const t0 = ticks[0];
|
|
3243
3366
|
const t1 = ticks[ticks.length - 1];
|
|
3244
|
-
|
|
3367
|
+
tickLabels = formatTicks(ticks, lang, fmt);
|
|
3245
3368
|
const sy = (v) => y1 - (v - t0) / (t1 - t0 || 1) * (y1 - y0);
|
|
3246
|
-
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);
|
|
3247
3371
|
drawnHeight = h;
|
|
3248
|
-
|
|
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: [
|
|
3249
3375
|
/* @__PURE__ */ jsx("title", { children: name }),
|
|
3250
|
-
/* @__PURE__ */ jsx(
|
|
3376
|
+
/* @__PURE__ */ jsx(Lines, { x: 0, y: LINE - 2, lines: titleLines, anchor: "start", fill: "var(--color-fg-subtle)" }),
|
|
3251
3377
|
ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
3252
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 }),
|
|
3253
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] })
|
|
3254
3380
|
] }, `t${i}`)),
|
|
3255
|
-
data.
|
|
3256
|
-
|
|
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);
|
|
3257
3392
|
const v = d.value;
|
|
3258
3393
|
const ok = v !== null && v !== void 0 && Number.isFinite(v);
|
|
3259
|
-
const
|
|
3260
|
-
const
|
|
3261
|
-
const
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
/* @__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" })
|
|
3267
3407
|
] }, d.id);
|
|
3268
|
-
})
|
|
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 })
|
|
3269
3410
|
] });
|
|
3270
3411
|
}
|
|
3271
3412
|
}
|
|
3272
|
-
|
|
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(
|
|
3273
3659
|
"div",
|
|
3274
3660
|
{
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
"data-
|
|
3279
|
-
"data-
|
|
3280
|
-
"data-
|
|
3281
|
-
|
|
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
|
+
]
|
|
3282
3715
|
}
|
|
3283
3716
|
);
|
|
3284
3717
|
}
|