@pixerate/editor 0.165.0 → 0.167.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/THIRD_PARTY_NOTICES.md +11 -0
- package/dist/agent/index.cjs +34 -34
- package/dist/agent/index.js +5 -5
- package/dist/canvas/index.cjs +6 -6
- package/dist/canvas/index.js +2 -2
- package/dist/chart/controller.d.cts +29 -0
- package/dist/chart/controller.d.ts +29 -0
- package/dist/chart/index.cjs +352 -179
- package/dist/chart/index.d.cts +2 -0
- package/dist/chart/index.d.ts +2 -0
- package/dist/chart/index.js +183 -10
- package/dist/chart/layout-families.d.cts +15 -0
- package/dist/chart/layout-families.d.ts +15 -0
- package/dist/chart/layout-shared.d.cts +5 -0
- package/dist/chart/layout-shared.d.ts +5 -0
- package/dist/chart/maps.d.cts +43 -0
- package/dist/chart/maps.d.ts +43 -0
- package/dist/chart/race.d.cts +26 -0
- package/dist/chart/race.d.ts +26 -0
- package/dist/chart/schemas.d.cts +2 -0
- package/dist/chart/schemas.d.ts +2 -0
- package/dist/chart/spec.d.cts +1 -1
- package/dist/chart/spec.d.ts +1 -1
- package/dist/chart/types.d.cts +14 -2
- package/dist/chart/types.d.ts +14 -2
- package/dist/chart-maps/europe.d.cts +7 -0
- package/dist/chart-maps/europe.d.ts +7 -0
- package/dist/chart-maps/index.cjs +66 -0
- package/dist/chart-maps/index.d.cts +1 -0
- package/dist/chart-maps/index.d.ts +1 -0
- package/dist/chart-maps/index.js +66 -0
- package/dist/{chunk-IMRXC7OA.cjs → chunk-2F4GAZL5.cjs} +186 -96
- package/dist/{chunk-FEU2XVQH.js → chunk-34UCPHYO.js} +8 -2
- package/dist/{chunk-ODFCS7HF.cjs → chunk-E45AIIWY.cjs} +8 -8
- package/dist/{chunk-N7NBGT2Y.js → chunk-FKYGM35R.js} +2 -2
- package/dist/{chunk-64VWWR5U.cjs → chunk-GD34ICEX.cjs} +16 -16
- package/dist/{chunk-TVQW26AV.cjs → chunk-GTF4RYXJ.cjs} +29 -23
- package/dist/{chunk-V5GSY3NC.js → chunk-M52UCHVZ.js} +203 -3
- package/dist/{chunk-FRBRLKNQ.js → chunk-N3ACT6VJ.js} +92 -2
- package/dist/{chunk-CRFSQWKQ.js → chunk-OLCHKGGO.js} +7 -1
- package/dist/{chunk-2ZRN43IN.js → chunk-ORZF4JOO.js} +32 -4
- package/dist/{chunk-UVXTOZ2B.cjs → chunk-OUO3DSRT.cjs} +32 -4
- package/dist/{chunk-MKXK6E4O.cjs → chunk-SBAPXFPB.cjs} +51 -51
- package/dist/{chunk-2J5VLJHC.js → chunk-T62BRXMS.js} +3 -3
- package/dist/{chunk-Q25WAFTD.cjs → chunk-VKHJJUBK.cjs} +24 -18
- package/dist/{chunk-QO2T6KA5.cjs → chunk-WOIXNDHV.cjs} +247 -47
- package/dist/{chunk-X5W645RI.js → chunk-ZUPP4NKM.js} +3 -3
- package/dist/export/index.cjs +4 -4
- package/dist/export/index.js +3 -3
- package/dist/extensions/index.cjs +7 -7
- package/dist/extensions/index.js +6 -6
- package/dist/index.cjs +13 -13
- package/dist/index.js +7 -7
- package/package.json +11 -1
- package/skills/pixerate-chart/SKILL.md +36 -2
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
chartTreeTotal,
|
|
13
13
|
chartVariesColors,
|
|
14
14
|
shadeChartColor
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ORZF4JOO.js";
|
|
16
16
|
import {
|
|
17
17
|
NumberFormat
|
|
18
18
|
} from "./chunk-II2WIF5Z.js";
|
|
@@ -948,7 +948,7 @@ function drawCallouts(ctx, plot) {
|
|
|
948
948
|
}
|
|
949
949
|
if (unplaced) ctx.warn("annotations-skipped", `${unplaced} callout${unplaced === 1 ? " points" : "s point"} at a gap or a hidden value and ${unplaced === 1 ? "is" : "are"} not drawn.`);
|
|
950
950
|
}
|
|
951
|
-
var MUTED = /* @__PURE__ */ new Set(["node", "bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
|
|
951
|
+
var MUTED = /* @__PURE__ */ new Set(["node", "region", "bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
|
|
952
952
|
function applyHighlight(ctx) {
|
|
953
953
|
const { spec, marks } = ctx;
|
|
954
954
|
if (!spec.highlight.length) return;
|
|
@@ -1677,6 +1677,83 @@ function trendlineLegendItems(spec, color) {
|
|
|
1677
1677
|
);
|
|
1678
1678
|
}
|
|
1679
1679
|
|
|
1680
|
+
// src/chart/maps.ts
|
|
1681
|
+
var maps = /* @__PURE__ */ new Map();
|
|
1682
|
+
function registerChartMap(map) {
|
|
1683
|
+
maps.set(map.id, map);
|
|
1684
|
+
}
|
|
1685
|
+
function getChartMap(id) {
|
|
1686
|
+
return maps.get(id);
|
|
1687
|
+
}
|
|
1688
|
+
function listChartMaps() {
|
|
1689
|
+
return [...maps.values()];
|
|
1690
|
+
}
|
|
1691
|
+
var fold = (text) => text.normalize("NFKD").replace(/[̀-ͯ]/g, "").trim().toLowerCase();
|
|
1692
|
+
function matchChartMapRegions(map, categories) {
|
|
1693
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1694
|
+
for (const r of map.regions) {
|
|
1695
|
+
byKey.set(fold(r.id), r);
|
|
1696
|
+
byKey.set(fold(r.name), r);
|
|
1697
|
+
}
|
|
1698
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1699
|
+
return categories.map((c) => {
|
|
1700
|
+
const r = byKey.get(fold(c));
|
|
1701
|
+
if (!r || taken.has(r.id)) return null;
|
|
1702
|
+
taken.add(r.id);
|
|
1703
|
+
return r;
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
function chartMapBreaks(values, steps, thresholds) {
|
|
1707
|
+
if (thresholds.length) return [...thresholds];
|
|
1708
|
+
if (!values.length) return [];
|
|
1709
|
+
const lo = Math.min(...values);
|
|
1710
|
+
const hi = Math.max(...values);
|
|
1711
|
+
if (!(hi > lo)) return [];
|
|
1712
|
+
const exact = Array.from({ length: steps - 1 }, (_, k) => lo + (hi - lo) * (k + 1) / steps);
|
|
1713
|
+
for (let digits = 2; digits <= 6; digits++) {
|
|
1714
|
+
const rounded = exact.map((b) => Number(b.toPrecision(digits)));
|
|
1715
|
+
if (rounded.every((b, i) => i === 0 || b > rounded[i - 1])) return rounded;
|
|
1716
|
+
}
|
|
1717
|
+
return exact;
|
|
1718
|
+
}
|
|
1719
|
+
var chartMapClass = (value, breaks) => breaks.filter((b) => value >= b).length;
|
|
1720
|
+
function chartMapRamp(base, count) {
|
|
1721
|
+
if (count <= 1) return [base];
|
|
1722
|
+
return Array.from({ length: count }, (_, k) => shadeChartColor(base, 0.82 - 1.17 * k / (count - 1)));
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/chart/race.ts
|
|
1726
|
+
function chartRaceValueAt(series, index) {
|
|
1727
|
+
for (let i = Math.min(index, series.values.length - 1); i >= 0; i--) {
|
|
1728
|
+
const v = series.values[i];
|
|
1729
|
+
if (v !== null && v !== void 0 && Number.isFinite(v)) return v;
|
|
1730
|
+
}
|
|
1731
|
+
return 0;
|
|
1732
|
+
}
|
|
1733
|
+
function chartRaceMoment(count, frame) {
|
|
1734
|
+
const last = Math.max(0, count - 1);
|
|
1735
|
+
const at = frame === null || frame === void 0 || !Number.isFinite(frame) ? last : Math.max(0, Math.min(last, frame));
|
|
1736
|
+
const from = Math.floor(at);
|
|
1737
|
+
const to = Math.min(last, from + 1);
|
|
1738
|
+
return { from, to, t: at - from, at };
|
|
1739
|
+
}
|
|
1740
|
+
function chartRaceRanking(spec, frame) {
|
|
1741
|
+
const visible = spec.series.filter((s) => !s.hidden);
|
|
1742
|
+
const { from, to, t } = chartRaceMoment(spec.categories.length, frame);
|
|
1743
|
+
const rankAt = (i) => {
|
|
1744
|
+
const order2 = visible.map((s, k) => ({ id: s.id, v: chartRaceValueAt(s, i), k })).sort((a, b) => b.v - a.v || a.k - b.k);
|
|
1745
|
+
return new Map(order2.map((o, r) => [o.id, r]));
|
|
1746
|
+
};
|
|
1747
|
+
const r0 = rankAt(from);
|
|
1748
|
+
const r1 = rankAt(to);
|
|
1749
|
+
const near = t < 0.5 ? r0 : r1;
|
|
1750
|
+
return visible.map((s) => {
|
|
1751
|
+
const a = chartRaceValueAt(s, from);
|
|
1752
|
+
const b = chartRaceValueAt(s, to);
|
|
1753
|
+
return { seriesId: s.id, name: s.name, value: a + (b - a) * t, position: r0.get(s.id) + (r1.get(s.id) - r0.get(s.id)) * t, rank: near.get(s.id) };
|
|
1754
|
+
}).sort((a, b) => a.position - b.position || b.value - a.value);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1680
1757
|
// src/chart/layout-families.ts
|
|
1681
1758
|
var sliceColor = (ctx, series, i) => series.pointColors?.[i] || chartSliceColor(ctx.spec, i);
|
|
1682
1759
|
function layoutPie(ctx, area, series) {
|
|
@@ -2441,11 +2518,110 @@ function layoutSankey(ctx, area, series) {
|
|
|
2441
2518
|
if (hidden) ctx.warn("labels-truncated", `${hidden} node label${hidden === 1 ? " was" : "s were"} shortened to fit between the columns.`);
|
|
2442
2519
|
return { plot: area };
|
|
2443
2520
|
}
|
|
2521
|
+
function layoutBarRace(ctx, area, frame) {
|
|
2522
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2523
|
+
const top = spec.raceTopN;
|
|
2524
|
+
const moment = chartRaceMoment(spec.categories.length, frame);
|
|
2525
|
+
const ranking = chartRaceRanking(spec, frame);
|
|
2526
|
+
const shown = ranking.filter((e) => e.position < top);
|
|
2527
|
+
const labelW = Math.min(area.width * 0.3, Math.max(0, ...ranking.map((e) => measure(e.name, fs)))) + 8;
|
|
2528
|
+
const tickH = spec.yAxis.show ? fs * 1.6 : 4;
|
|
2529
|
+
const plot = { x: area.x + labelW, y: area.y + tickH, width: Math.max(1, area.width - labelW - fs * 4), height: Math.max(1, area.height - tickH) };
|
|
2530
|
+
const lead = Math.max(1e-9, ...shown.map((e) => e.value));
|
|
2531
|
+
const max = spec.yAxis.max !== null && spec.yAxis.max > 0 ? spec.yAxis.max : lead * 1.08;
|
|
2532
|
+
const xOf = (v) => plot.x + Math.max(0, Math.min(max, v)) / max * plot.width;
|
|
2533
|
+
const ticks = niceScale(0, max, 5).ticks.filter((t) => t <= max);
|
|
2534
|
+
const axisTarget = { kind: "axis", axis: "y" };
|
|
2535
|
+
const format = spec.yAxis.format;
|
|
2536
|
+
ticks.forEach((t, k) => {
|
|
2537
|
+
if (spec.yAxis.gridlines) marks.push({ kind: "line", key: `grid-y-${k}`, role: "grid", x1: xOf(t), y1: plot.y, x2: xOf(t), y2: plot.y + plot.height, stroke: theme.grid, strokeWidth: 1 });
|
|
2538
|
+
if (spec.yAxis.show) marks.push({ kind: "text", key: `tick-y-${k}`, role: "tick-label", x: xOf(t), y: area.y, text: formatChartNumber(t, format, format.decimals ?? stepDecimals(ticks[1] - ticks[0] || 1)), anchor: "middle", baseline: "top", size: fs - 1, fill: theme.mutedText, target: axisTarget });
|
|
2539
|
+
});
|
|
2540
|
+
const label = spec.categories[moment.t < 0.5 ? moment.from : moment.to] ?? "";
|
|
2541
|
+
const timeSize = Math.min(theme.titleSize * 3, plot.height * 0.2, plot.width * 0.5 / Math.max(1, label.length * 0.6));
|
|
2542
|
+
marks.push({ kind: "text", key: "race-time", role: "race-time", x: plot.x + plot.width, y: plot.y + plot.height - 4, text: label, anchor: "end", baseline: "bottom", size: timeSize, weight: 700, fill: theme.mutedText, opacity: 0.55, target: { kind: "category", index: moment.t < 0.5 ? moment.from : moment.to } });
|
|
2543
|
+
const band = plot.height / top;
|
|
2544
|
+
const barH = Math.max(2, band * (1 - Math.min(0.8, spec.gapWidth)));
|
|
2545
|
+
const index = Math.round(moment.at);
|
|
2546
|
+
for (const e of shown) {
|
|
2547
|
+
const si = spec.series.findIndex((s2) => s2.id === e.seriesId);
|
|
2548
|
+
const s = spec.series[si];
|
|
2549
|
+
const y = plot.y + e.position * band + (band - barH) / 2;
|
|
2550
|
+
const target = { kind: "point", seriesId: e.seriesId, index };
|
|
2551
|
+
const selected = ctx.isSelected(target) || ctx.seriesSelected(e.seriesId);
|
|
2552
|
+
const width = Math.max(0, xOf(e.value) - plot.x);
|
|
2553
|
+
marks.push({ kind: "rect", key: `bar-${e.seriesId}`, role: "bar", x: plot.x, y, width, height: barH, fill: chartSeriesColor(spec, si), radius: Math.min(3, barH / 4), target, selected, ...selected ? { stroke: theme.selection, strokeWidth: 1.5 } : {} });
|
|
2554
|
+
marks.push({ kind: "text", key: `label-name-${e.seriesId}`, role: "tick-label", x: plot.x - 6, y: y + barH / 2, text: truncate(e.name, labelW - 8, fs, measure), anchor: "end", baseline: "middle", size: fs, fill: selected ? theme.selection : theme.text, target: { kind: "series", seriesId: e.seriesId } });
|
|
2555
|
+
if (s.dataLabels ?? true) marks.push({ kind: "text", key: `label-${e.seriesId}`, role: "data-label", x: plot.x + width + 5, y: y + barH / 2, text: formatChartNumber(e.value, s.format ?? format, (s.format ?? format).decimals ?? (Math.abs(e.value) >= 100 ? 0 : void 0)), anchor: "start", baseline: "middle", size: fs - 1, fill: theme.text, target });
|
|
2556
|
+
}
|
|
2557
|
+
if (ranking.length > top) ctx.warn("values-skipped", `The race shows the top ${top} of ${ranking.length} names at each moment (raceTopN).`);
|
|
2558
|
+
return { plot, valueScale: { axis: "x", domain: [0, max], range: [plot.x, plot.x + plot.width], ticks } };
|
|
2559
|
+
}
|
|
2560
|
+
function placePath(d, s, ox, oy) {
|
|
2561
|
+
return d.replace(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g, (_, x, y) => `${r2(ox + Number(x) * s)},${r2(oy + Number(y) * s)}`);
|
|
2562
|
+
}
|
|
2563
|
+
function layoutChoropleth(ctx, area, series) {
|
|
2564
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2565
|
+
const map = getChartMap(spec.map);
|
|
2566
|
+
const matches = matchChartMapRegions(map, spec.categories);
|
|
2567
|
+
const unmatched = spec.categories.filter((_, i) => !matches[i]);
|
|
2568
|
+
if (unmatched.length)
|
|
2569
|
+
ctx.warn("values-skipped", `${unmatched.length} categor${unmatched.length === 1 ? "y names" : "ies name"} no region of ${map.name}: ${unmatched.slice(0, 8).map((c) => `"${c}"`).join(", ")}${unmatched.length > 8 ? ", \u2026" : ""}. Use codes (such as FRA) or names (France).`);
|
|
2570
|
+
const indexOf = /* @__PURE__ */ new Map();
|
|
2571
|
+
matches.forEach((r, i) => r && indexOf.set(r.id, i));
|
|
2572
|
+
const valueAt = (i) => {
|
|
2573
|
+
const v = series.values[i];
|
|
2574
|
+
return v !== null && v !== void 0 && Number.isFinite(v) ? v : null;
|
|
2575
|
+
};
|
|
2576
|
+
const present = [...indexOf.values()].map(valueAt).filter((v) => v !== null);
|
|
2577
|
+
const breaks = chartMapBreaks(present, spec.mapSteps, spec.mapThresholds);
|
|
2578
|
+
const ramp = chartMapRamp(series.color ?? chartSeriesColor(spec, spec.series.indexOf(series)), breaks.length + 1);
|
|
2579
|
+
const format = series.format ?? spec.yAxis.format;
|
|
2580
|
+
const legendH = fs * 2.2;
|
|
2581
|
+
const s = Math.max(1e-6, Math.min(area.width / map.width, (area.height - legendH) / map.height));
|
|
2582
|
+
const ox = area.x + (area.width - map.width * s) / 2;
|
|
2583
|
+
const oy = area.y;
|
|
2584
|
+
if (map.land) marks.push({ kind: "path", key: "map-land", role: "map-land", d: placePath(map.land, s, ox, oy), fill: theme.grid, opacity: 0.55 });
|
|
2585
|
+
const markers = [];
|
|
2586
|
+
const labels = [];
|
|
2587
|
+
for (const region of map.regions) {
|
|
2588
|
+
const i = indexOf.get(region.id);
|
|
2589
|
+
const value = i === void 0 ? null : valueAt(i);
|
|
2590
|
+
const fill = i !== void 0 && series.pointColors?.[i] ? series.pointColors[i] : value === null ? theme.grid : ramp[chartMapClass(value, breaks)];
|
|
2591
|
+
const target = i === void 0 ? void 0 : { kind: "category", index: i };
|
|
2592
|
+
const selected = target ? ctx.isSelected(target) || ctx.isSelected({ kind: "point", seriesId: series.id, index: i }) : false;
|
|
2593
|
+
const [cx, cy] = [ox + region.center[0] * s, oy + region.center[1] * s];
|
|
2594
|
+
if (region.d) marks.push({ kind: "path", key: `region-${region.id}`, role: "region", d: placePath(region.d, s, ox, oy), fill, stroke: selected ? theme.selection : theme.surface, strokeWidth: selected ? 2 : 0.6, ...target ? { target, selected } : {} });
|
|
2595
|
+
if (!region.d || region.area * s * s < 30)
|
|
2596
|
+
markers.push({ kind: "circle", key: `region-marker-${region.id}`, role: "region", cx, cy, r: 3.5, fill, stroke: selected ? theme.selection : theme.text, strokeWidth: selected ? 1.5 : 0.75, ...target ? { target, selected } : {} });
|
|
2597
|
+
if (value !== null && target && (series.dataLabels ?? spec.dataLabels) && region.area * s * s > (fs * 3) ** 2) {
|
|
2598
|
+
const text = formatChartNumber(value, format);
|
|
2599
|
+
labels.push({ kind: "text", key: `label-${region.id}`, role: "data-label", x: cx, y: cy, text, anchor: "middle", baseline: "middle", size: fs - 1, weight: 600, fill: contrastingTextColor(fill), target });
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
marks.push(...markers, ...labels);
|
|
2603
|
+
const fmt = (v) => formatChartNumber(v, format);
|
|
2604
|
+
const items = ramp.map((color, k) => ({
|
|
2605
|
+
color,
|
|
2606
|
+
text: breaks.length === 0 ? present.length ? fmt(present[0]) : "" : k === 0 ? `< ${fmt(breaks[0])}` : k === breaks.length ? `\u2265 ${fmt(breaks[k - 1])}` : `${fmt(breaks[k - 1])}\u2013${fmt(breaks[k])}`
|
|
2607
|
+
}));
|
|
2608
|
+
items.push({ color: theme.grid, text: "No data" });
|
|
2609
|
+
let x = area.x;
|
|
2610
|
+
const y = area.y + area.height - fs * 0.9;
|
|
2611
|
+
items.forEach((item, k) => {
|
|
2612
|
+
marks.push({ kind: "rect", key: `legend-swatch-${k}`, role: "legend-swatch", x, y: y - 5, width: 10, height: 10, radius: 2, fill: item.color });
|
|
2613
|
+
marks.push({ kind: "text", key: `legend-label-${k}`, role: "legend-label", x: x + 14, y, text: item.text, anchor: "start", baseline: "middle", size: fs - 1, fill: theme.text });
|
|
2614
|
+
x += 14 + measure(item.text, fs - 1) + 14;
|
|
2615
|
+
});
|
|
2616
|
+
if (x > area.x + area.width) ctx.warn("legend-truncated", "The map legend is wider than the chart; widen the chart or use fewer classes.");
|
|
2617
|
+
if (map.attribution) marks.push({ kind: "text", key: "map-attribution", role: "note", x: area.x + area.width, y, text: map.attribution, anchor: "end", baseline: "middle", size: Math.max(7, fs - 3), fill: theme.mutedText });
|
|
2618
|
+
return { plot: { x: ox, y: oy, width: map.width * s, height: map.height * s } };
|
|
2619
|
+
}
|
|
2444
2620
|
|
|
2445
2621
|
// src/chart/layout.ts
|
|
2446
2622
|
function legendItems(spec, ctx) {
|
|
2447
2623
|
const family = chartFamily(spec);
|
|
2448
|
-
if (spec.type === "funnel" || spec.type === "slope" || spec.type === "sankey") return [];
|
|
2624
|
+
if (spec.type === "funnel" || spec.type === "slope" || spec.type === "sankey" || spec.type === "barrace" || spec.type === "choropleth") return [];
|
|
2449
2625
|
if (chartSpecColorsByCategory(spec)) {
|
|
2450
2626
|
const first = spec.series.find((s) => !s.hidden);
|
|
2451
2627
|
const seats = (i) => Math.max(0, Math.round(first?.values[i] ?? 0));
|
|
@@ -2585,6 +2761,20 @@ function layoutChart(spec, options) {
|
|
|
2585
2761
|
return finish(layoutFunnel(ctx, area, visible[0]));
|
|
2586
2762
|
case "treemap":
|
|
2587
2763
|
return finish(spec.tree.length ? layoutTreemapTree(ctx, area, spec.series[0]) : layoutTreemap(ctx, area, visible[0]));
|
|
2764
|
+
case "choropleth": {
|
|
2765
|
+
const map = getChartMap(spec.map);
|
|
2766
|
+
if (!map) {
|
|
2767
|
+
ctx.warn("type-fallback", `The map "${spec.map}" is not registered, so the values are drawn as bars: register it with registerChartMap (the Europe outlines are EUROPE_CHART_MAP in @pixerate/editor/chart-maps).`);
|
|
2768
|
+
return finish(layoutCartesian(ctx, area, visible));
|
|
2769
|
+
}
|
|
2770
|
+
if (!matchChartMapRegions(map, spec.categories).some(Boolean)) {
|
|
2771
|
+
ctx.warn("type-fallback", `No category names a region of ${map.name}, so the values are drawn as bars: use codes (such as FRA) or names (France) as categories.`);
|
|
2772
|
+
return finish(layoutCartesian(ctx, area, visible));
|
|
2773
|
+
}
|
|
2774
|
+
return finish(layoutChoropleth(ctx, area, visible[0]));
|
|
2775
|
+
}
|
|
2776
|
+
case "barrace":
|
|
2777
|
+
return finish(layoutBarRace(ctx, area, options.raceFrame));
|
|
2588
2778
|
case "sankey":
|
|
2589
2779
|
if (!spec.links.length) {
|
|
2590
2780
|
ctx.warn("type-fallback", "A Sankey diagram needs flows (set_chart_links); it is drawn as a bar chart until then.");
|
|
@@ -2794,6 +2984,16 @@ export {
|
|
|
2794
2984
|
MAX_CHART_DATA_LABELS,
|
|
2795
2985
|
MAX_CHART_LINE_POINTS,
|
|
2796
2986
|
MAX_CHART_MARKERS,
|
|
2987
|
+
registerChartMap,
|
|
2988
|
+
getChartMap,
|
|
2989
|
+
listChartMaps,
|
|
2990
|
+
matchChartMapRegions,
|
|
2991
|
+
chartMapBreaks,
|
|
2992
|
+
chartMapClass,
|
|
2993
|
+
chartMapRamp,
|
|
2994
|
+
chartRaceValueAt,
|
|
2995
|
+
chartRaceMoment,
|
|
2996
|
+
chartRaceRanking,
|
|
2797
2997
|
layoutChart,
|
|
2798
2998
|
chartValueAtPoint,
|
|
2799
2999
|
chartXAtPoint,
|
|
@@ -4,14 +4,17 @@ import {
|
|
|
4
4
|
} from "./chunk-KGRZMHZB.js";
|
|
5
5
|
import {
|
|
6
6
|
chartMovingAverage,
|
|
7
|
+
chartRaceRanking,
|
|
7
8
|
chartSupportsTrendlines,
|
|
8
9
|
fitChartTrendline,
|
|
9
10
|
formatChartNumber,
|
|
11
|
+
getChartMap,
|
|
10
12
|
layoutChart,
|
|
13
|
+
matchChartMapRegions,
|
|
11
14
|
r2,
|
|
12
15
|
sameChartSelection,
|
|
13
16
|
summarizeChartSeries
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-M52UCHVZ.js";
|
|
15
18
|
import {
|
|
16
19
|
CHART_TYPE_LABELS,
|
|
17
20
|
DEFAULT_CHART_PALETTE,
|
|
@@ -44,7 +47,7 @@ import {
|
|
|
44
47
|
parseChartValue,
|
|
45
48
|
shadeChartColor,
|
|
46
49
|
uniqueChartSeriesId
|
|
47
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ORZF4JOO.js";
|
|
48
51
|
import {
|
|
49
52
|
parseCsv,
|
|
50
53
|
parseTsv
|
|
@@ -549,6 +552,22 @@ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((
|
|
|
549
552
|
}
|
|
550
553
|
return parts.join(" ");
|
|
551
554
|
}
|
|
555
|
+
if (family === "choropleth") {
|
|
556
|
+
const s = spec.series.find((x) => !x.hidden);
|
|
557
|
+
const map = getChartMap(spec.map);
|
|
558
|
+
const matches = map ? matchChartMapRegions(map, spec.categories) : spec.categories.map(() => null);
|
|
559
|
+
const rows = spec.categories.flatMap((c, i) => matches[i] && s.values[i] !== null ? [{ name: matches[i].name, v: s.values[i] }] : []).sort((a, b) => b.v - a.v);
|
|
560
|
+
const missing = spec.categories.filter((_, i) => !matches[i]).length;
|
|
561
|
+
if (!rows.length) return `${head} of ${map?.name ?? spec.map} with no regions matched.`;
|
|
562
|
+
return `${head} of ${map?.name ?? spec.map} shading ${rows.length} region${rows.length === 1 ? "" : "s"} by ${s.name}: highest ${rows[0].name} (${fmt(rows[0].v)}), lowest ${rows[rows.length - 1].name} (${fmt(rows[rows.length - 1].v)}).${missing ? ` ${missing} categor${missing === 1 ? "y names" : "ies name"} no region.` : ""}`;
|
|
563
|
+
}
|
|
564
|
+
if (family === "barrace") {
|
|
565
|
+
const n = spec.categories.length;
|
|
566
|
+
const first = chartRaceRanking(spec, 0)[0];
|
|
567
|
+
const last = chartRaceRanking(spec, n - 1)[0];
|
|
568
|
+
const lead = (e, at) => e ? `${e.name} leads at ${at} with ${fmt(e.value)}` : "";
|
|
569
|
+
return `${head} of ${series.length} names over ${n} time points (${spec.categories[0]} to ${spec.categories[n - 1]}), showing the top ${Math.min(spec.raceTopN, series.length)}. ${lead(first, spec.categories[0])}; ${lead(last, spec.categories[n - 1])}.`;
|
|
570
|
+
}
|
|
552
571
|
if (family === "sankey") {
|
|
553
572
|
if (!spec.links.length) return `${head} with no flows yet.`;
|
|
554
573
|
const total = spec.links.reduce((t, l) => t + l.value, 0);
|
|
@@ -776,6 +795,17 @@ function sameChartJson(a, b) {
|
|
|
776
795
|
if (keys.length !== Object.keys(bo).filter((k) => bo[k] !== void 0).length) return false;
|
|
777
796
|
return keys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && sameChartJson(ao[k], bo[k]));
|
|
778
797
|
}
|
|
798
|
+
var defaultRaceTicker = {
|
|
799
|
+
now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
800
|
+
next: (step) => {
|
|
801
|
+
if (typeof requestAnimationFrame === "function") {
|
|
802
|
+
const id2 = requestAnimationFrame(() => step());
|
|
803
|
+
return () => cancelAnimationFrame(id2);
|
|
804
|
+
}
|
|
805
|
+
const id = setTimeout(step, 16);
|
|
806
|
+
return () => clearTimeout(id);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
779
809
|
var fail = (message, issues) => ({ ok: false, message, ...issues ? { issues } : {} });
|
|
780
810
|
var ChartController = class {
|
|
781
811
|
history;
|
|
@@ -788,12 +818,16 @@ var ChartController = class {
|
|
|
788
818
|
revision = 0;
|
|
789
819
|
applying = false;
|
|
790
820
|
onChange;
|
|
821
|
+
raceTicker;
|
|
822
|
+
raceFrame = null;
|
|
823
|
+
raceStop = null;
|
|
791
824
|
listeners = /* @__PURE__ */ new Set();
|
|
792
825
|
unsubscribeHistory;
|
|
793
826
|
state;
|
|
794
827
|
constructor(options = {}) {
|
|
795
828
|
this.history = options.history ?? new HistoryManager();
|
|
796
829
|
this.onChange = options.onChange;
|
|
830
|
+
this.raceTicker = options.raceTicker ?? defaultRaceTicker;
|
|
797
831
|
const { spec, issues } = normalizeChartSpec(options.spec ?? {});
|
|
798
832
|
this.spec = spec;
|
|
799
833
|
this.issues = issues;
|
|
@@ -843,6 +877,8 @@ var ChartController = class {
|
|
|
843
877
|
canRedo: h.canRedo,
|
|
844
878
|
undoLabel: h.undoName,
|
|
845
879
|
redoLabel: h.redoName,
|
|
880
|
+
raceFrame: this.raceFrame === null ? null : Math.min(this.raceFrame, Math.max(0, this.spec.categories.length - 1)),
|
|
881
|
+
racePlaying: this.raceStop !== null,
|
|
846
882
|
revision: this.revision
|
|
847
883
|
};
|
|
848
884
|
}
|
|
@@ -1651,6 +1687,55 @@ var ChartController = class {
|
|
|
1651
1687
|
// -------------------------------------------------------------------------
|
|
1652
1688
|
// Selection and gestures
|
|
1653
1689
|
// -------------------------------------------------------------------------
|
|
1690
|
+
/**
|
|
1691
|
+
* Shows a bar race at a moment: a category index, fractional between time points (`null`:
|
|
1692
|
+
* the last one). View state like the selection, so not an undo step. Stops playback.
|
|
1693
|
+
*/
|
|
1694
|
+
setRaceFrame(frame) {
|
|
1695
|
+
const n = this.spec.categories.length;
|
|
1696
|
+
if (frame !== null && !(Number.isFinite(frame) && frame >= 0 && frame <= Math.max(0, n - 1)))
|
|
1697
|
+
return fail(n ? `The race has time points 0 to ${n - 1}${n > 1 ? ` ("${this.spec.categories[0]}" to "${this.spec.categories[n - 1]}")` : ""}; got ${frame}.` : "The chart has no time points (categories) yet.");
|
|
1698
|
+
this.stopRace();
|
|
1699
|
+
if (frame === this.raceFrame) return { ok: true };
|
|
1700
|
+
this.raceFrame = frame;
|
|
1701
|
+
this.notify(false);
|
|
1702
|
+
return { ok: true };
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Plays the bar race from the moment shown (from the start when it is at the end), moving
|
|
1706
|
+
* one time point every `msPerStep` (default 1000) until the last. Not an undo step.
|
|
1707
|
+
*/
|
|
1708
|
+
playRace(options = {}) {
|
|
1709
|
+
const last = this.spec.categories.length - 1;
|
|
1710
|
+
if (last < 1) return fail("A race needs at least two time points (categories) to play.");
|
|
1711
|
+
this.stopRace();
|
|
1712
|
+
const per = Math.max(50, options.msPerStep ?? 1e3);
|
|
1713
|
+
const from = this.raceFrame === null || this.raceFrame >= last ? 0 : this.raceFrame;
|
|
1714
|
+
const start = this.raceTicker.now();
|
|
1715
|
+
const step = () => {
|
|
1716
|
+
const at = Math.min(last, from + (this.raceTicker.now() - start) / per);
|
|
1717
|
+
this.raceFrame = at;
|
|
1718
|
+
if (at >= last) {
|
|
1719
|
+
this.raceStop = null;
|
|
1720
|
+
this.raceFrame = null;
|
|
1721
|
+
} else this.raceStop = this.raceTicker.next(step);
|
|
1722
|
+
this.notify(false);
|
|
1723
|
+
};
|
|
1724
|
+
this.raceFrame = from;
|
|
1725
|
+
this.raceStop = this.raceTicker.next(step);
|
|
1726
|
+
this.notify(false);
|
|
1727
|
+
return { ok: true };
|
|
1728
|
+
}
|
|
1729
|
+
/** Pauses bar race playback where it is. */
|
|
1730
|
+
pauseRace() {
|
|
1731
|
+
if (!this.raceStop) return;
|
|
1732
|
+
this.stopRace();
|
|
1733
|
+
this.notify(false);
|
|
1734
|
+
}
|
|
1735
|
+
stopRace() {
|
|
1736
|
+
this.raceStop?.();
|
|
1737
|
+
this.raceStop = null;
|
|
1738
|
+
}
|
|
1654
1739
|
/** Selects a chart element (view state; not an undo step). */
|
|
1655
1740
|
select(selection) {
|
|
1656
1741
|
const next = this.validSelection(selection, this.spec);
|
|
@@ -1773,6 +1858,7 @@ var ChartController = class {
|
|
|
1773
1858
|
}
|
|
1774
1859
|
/** Stops listening to the history (reconnected by the next `subscribe`). */
|
|
1775
1860
|
destroy() {
|
|
1861
|
+
this.stopRace();
|
|
1776
1862
|
this.unsubscribeHistory?.();
|
|
1777
1863
|
this.unsubscribeHistory = void 0;
|
|
1778
1864
|
this.listeners.clear();
|
|
@@ -1808,6 +1894,10 @@ function updateLabel(patch) {
|
|
|
1808
1894
|
parliamentArc: "Change hemicycle sweep",
|
|
1809
1895
|
parliamentMajority: "Change majority",
|
|
1810
1896
|
parliamentLabel: "Edit seat caption",
|
|
1897
|
+
raceTopN: "Change bars shown",
|
|
1898
|
+
map: "Change map",
|
|
1899
|
+
mapSteps: "Change colour classes",
|
|
1900
|
+
mapThresholds: "Change class breaks",
|
|
1811
1901
|
sunburstLabel: "Edit centre caption",
|
|
1812
1902
|
tree: "Replace grouped data",
|
|
1813
1903
|
links: "Replace flows",
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
isCartesianChart,
|
|
9
9
|
normalizeChartSpec,
|
|
10
10
|
parseChartColor
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ORZF4JOO.js";
|
|
12
12
|
|
|
13
13
|
// src/chart/ooxml.ts
|
|
14
14
|
var CHART_OOXML_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
@@ -475,6 +475,12 @@ function chartToOoxml(spec) {
|
|
|
475
475
|
} else if (family === "sunburst") {
|
|
476
476
|
issues.push(`Sunburst charts are written as doughnut charts of the ${spec.tree.length ? "top-level group totals" : "values"}: DrawingML has no sunburst type (Office 2016 stores it as chartex).`);
|
|
477
477
|
type = "donut";
|
|
478
|
+
} else if (family === "choropleth") {
|
|
479
|
+
issues.push("Maps are written as bar charts of the values by region: DrawingML has no map chart (Office 2016 stores region maps as chartex with Bing geodata).");
|
|
480
|
+
type = "bar";
|
|
481
|
+
} else if (family === "barrace") {
|
|
482
|
+
issues.push("Bar chart races are written as clustered bar charts of every name at every time point: DrawingML has no animation.");
|
|
483
|
+
type = "bar";
|
|
478
484
|
} else if (family === "sankey") {
|
|
479
485
|
issues.push("Sankey diagrams are written as bar charts of how much passes through each node: DrawingML has no flows.");
|
|
480
486
|
type = "bar";
|
|
@@ -426,7 +426,7 @@ function removeChartTreeRow(rows, row) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
// src/chart/spec.ts
|
|
429
|
-
var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "sankey", "slope", "likert", "parliament"];
|
|
429
|
+
var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "sankey", "slope", "likert", "parliament", "barrace", "choropleth"];
|
|
430
430
|
var CHART_MARKERS = ["circle", "square", "diamond", "triangle", "cross", "none"];
|
|
431
431
|
var CHART_TRENDLINE_TYPES = ["linear", "exponential", "logarithmic", "power", "polynomial", "moving-average"];
|
|
432
432
|
var CHART_LABEL_POSITIONS = ["auto", "inside", "outside", "center"];
|
|
@@ -461,7 +461,9 @@ var CHART_TYPE_LABELS = {
|
|
|
461
461
|
likert: "Likert",
|
|
462
462
|
parliament: "Parliament",
|
|
463
463
|
sunburst: "Sunburst",
|
|
464
|
-
sankey: "Sankey"
|
|
464
|
+
sankey: "Sankey",
|
|
465
|
+
barrace: "Bar race",
|
|
466
|
+
choropleth: "Map"
|
|
465
467
|
};
|
|
466
468
|
var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
|
|
467
469
|
var DEFAULT_CHART_THEME = {
|
|
@@ -513,6 +515,8 @@ function chartFamily(spec) {
|
|
|
513
515
|
case "parliament":
|
|
514
516
|
case "sunburst":
|
|
515
517
|
case "sankey":
|
|
518
|
+
case "barrace":
|
|
519
|
+
case "choropleth":
|
|
516
520
|
return spec.type;
|
|
517
521
|
case "bubble":
|
|
518
522
|
return "xy";
|
|
@@ -984,6 +988,25 @@ function normalizeChartSpec(input) {
|
|
|
984
988
|
if (Number.isInteger(m) && m >= 1) parliamentMajority = m;
|
|
985
989
|
else issues.push({ path: "parliamentMajority", message: "parliamentMajority must be a whole number of seats, 1 or more, or null for half plus one." });
|
|
986
990
|
}
|
|
991
|
+
let mapSteps = 5;
|
|
992
|
+
if (value.mapSteps !== void 0) {
|
|
993
|
+
const n = Number(value.mapSteps);
|
|
994
|
+
if (value.mapSteps !== null && Number.isInteger(n) && n >= 2 && n <= 9) mapSteps = n;
|
|
995
|
+
else issues.push({ path: "mapSteps", message: "mapSteps must be a whole number of colour classes from 2 to 9." });
|
|
996
|
+
}
|
|
997
|
+
let mapThresholds = [];
|
|
998
|
+
if (value.mapThresholds !== void 0 && value.mapThresholds !== null) {
|
|
999
|
+
const list = Array.isArray(value.mapThresholds) ? value.mapThresholds.map((t) => typeof t === "number" ? t : Number(t)) : null;
|
|
1000
|
+
if (!list || list.some((t) => !Number.isFinite(t)) || list.length > 8 || list.some((t, i) => i > 0 && t <= list[i - 1]))
|
|
1001
|
+
issues.push({ path: "mapThresholds", message: "mapThresholds must be up to 8 numbers in ascending order (the class breaks), or [] for equal classes." });
|
|
1002
|
+
else mapThresholds = list;
|
|
1003
|
+
}
|
|
1004
|
+
let raceTopN = 10;
|
|
1005
|
+
if (value.raceTopN !== void 0) {
|
|
1006
|
+
const n = Number(value.raceTopN);
|
|
1007
|
+
if (value.raceTopN !== null && Number.isInteger(n) && n >= 1 && n <= 30) raceTopN = n;
|
|
1008
|
+
else issues.push({ path: "raceTopN", message: "raceTopN must be a whole number of bars from 1 to 30." });
|
|
1009
|
+
}
|
|
987
1010
|
let radarTicks = null;
|
|
988
1011
|
if (value.radarTicks !== void 0 && value.radarTicks !== null) {
|
|
989
1012
|
const t = Number(value.radarTicks);
|
|
@@ -1029,7 +1052,11 @@ function normalizeChartSpec(input) {
|
|
|
1029
1052
|
sunburstLabel: text(value.sunburstLabel, "", "sunburstLabel", issues),
|
|
1030
1053
|
links,
|
|
1031
1054
|
sankeyNodes: links.length ? sankeyNodes : [],
|
|
1032
|
-
sankeyLinkColor: pick(value.sankeyLinkColor, CHART_SANKEY_LINK_COLORS, "source", "sankeyLinkColor", issues)
|
|
1055
|
+
sankeyLinkColor: pick(value.sankeyLinkColor, CHART_SANKEY_LINK_COLORS, "source", "sankeyLinkColor", issues),
|
|
1056
|
+
raceTopN,
|
|
1057
|
+
map: text(value.map, "europe", "map", issues).trim() || "europe",
|
|
1058
|
+
mapSteps,
|
|
1059
|
+
mapThresholds
|
|
1033
1060
|
};
|
|
1034
1061
|
if (spec.stacking === "percent")
|
|
1035
1062
|
for (const name of ["yAxis", "yAxis2"]) {
|
|
@@ -1055,7 +1082,8 @@ function cloneChartSpec(spec) {
|
|
|
1055
1082
|
referenceBands: spec.referenceBands.map((b) => ({ ...b })),
|
|
1056
1083
|
tree: cloneChartTree(spec.tree),
|
|
1057
1084
|
links: spec.links.map((l) => ({ ...l })),
|
|
1058
|
-
sankeyNodes: [...spec.sankeyNodes]
|
|
1085
|
+
sankeyNodes: [...spec.sankeyNodes],
|
|
1086
|
+
mapThresholds: [...spec.mapThresholds]
|
|
1059
1087
|
};
|
|
1060
1088
|
}
|
|
1061
1089
|
function cloneChartSeries(series) {
|
|
@@ -426,7 +426,7 @@ function removeChartTreeRow(rows, row) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
// src/chart/spec.ts
|
|
429
|
-
var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "sankey", "slope", "likert", "parliament"];
|
|
429
|
+
var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "sankey", "slope", "likert", "parliament", "barrace", "choropleth"];
|
|
430
430
|
var CHART_MARKERS = ["circle", "square", "diamond", "triangle", "cross", "none"];
|
|
431
431
|
var CHART_TRENDLINE_TYPES = ["linear", "exponential", "logarithmic", "power", "polynomial", "moving-average"];
|
|
432
432
|
var CHART_LABEL_POSITIONS = ["auto", "inside", "outside", "center"];
|
|
@@ -461,7 +461,9 @@ var CHART_TYPE_LABELS = {
|
|
|
461
461
|
likert: "Likert",
|
|
462
462
|
parliament: "Parliament",
|
|
463
463
|
sunburst: "Sunburst",
|
|
464
|
-
sankey: "Sankey"
|
|
464
|
+
sankey: "Sankey",
|
|
465
|
+
barrace: "Bar race",
|
|
466
|
+
choropleth: "Map"
|
|
465
467
|
};
|
|
466
468
|
var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
|
|
467
469
|
var DEFAULT_CHART_THEME = {
|
|
@@ -513,6 +515,8 @@ function chartFamily(spec) {
|
|
|
513
515
|
case "parliament":
|
|
514
516
|
case "sunburst":
|
|
515
517
|
case "sankey":
|
|
518
|
+
case "barrace":
|
|
519
|
+
case "choropleth":
|
|
516
520
|
return spec.type;
|
|
517
521
|
case "bubble":
|
|
518
522
|
return "xy";
|
|
@@ -984,6 +988,25 @@ function normalizeChartSpec(input) {
|
|
|
984
988
|
if (Number.isInteger(m) && m >= 1) parliamentMajority = m;
|
|
985
989
|
else issues.push({ path: "parliamentMajority", message: "parliamentMajority must be a whole number of seats, 1 or more, or null for half plus one." });
|
|
986
990
|
}
|
|
991
|
+
let mapSteps = 5;
|
|
992
|
+
if (value.mapSteps !== void 0) {
|
|
993
|
+
const n = Number(value.mapSteps);
|
|
994
|
+
if (value.mapSteps !== null && Number.isInteger(n) && n >= 2 && n <= 9) mapSteps = n;
|
|
995
|
+
else issues.push({ path: "mapSteps", message: "mapSteps must be a whole number of colour classes from 2 to 9." });
|
|
996
|
+
}
|
|
997
|
+
let mapThresholds = [];
|
|
998
|
+
if (value.mapThresholds !== void 0 && value.mapThresholds !== null) {
|
|
999
|
+
const list = Array.isArray(value.mapThresholds) ? value.mapThresholds.map((t) => typeof t === "number" ? t : Number(t)) : null;
|
|
1000
|
+
if (!list || list.some((t) => !Number.isFinite(t)) || list.length > 8 || list.some((t, i) => i > 0 && t <= list[i - 1]))
|
|
1001
|
+
issues.push({ path: "mapThresholds", message: "mapThresholds must be up to 8 numbers in ascending order (the class breaks), or [] for equal classes." });
|
|
1002
|
+
else mapThresholds = list;
|
|
1003
|
+
}
|
|
1004
|
+
let raceTopN = 10;
|
|
1005
|
+
if (value.raceTopN !== void 0) {
|
|
1006
|
+
const n = Number(value.raceTopN);
|
|
1007
|
+
if (value.raceTopN !== null && Number.isInteger(n) && n >= 1 && n <= 30) raceTopN = n;
|
|
1008
|
+
else issues.push({ path: "raceTopN", message: "raceTopN must be a whole number of bars from 1 to 30." });
|
|
1009
|
+
}
|
|
987
1010
|
let radarTicks = null;
|
|
988
1011
|
if (value.radarTicks !== void 0 && value.radarTicks !== null) {
|
|
989
1012
|
const t = Number(value.radarTicks);
|
|
@@ -1029,7 +1052,11 @@ function normalizeChartSpec(input) {
|
|
|
1029
1052
|
sunburstLabel: text(value.sunburstLabel, "", "sunburstLabel", issues),
|
|
1030
1053
|
links,
|
|
1031
1054
|
sankeyNodes: links.length ? sankeyNodes : [],
|
|
1032
|
-
sankeyLinkColor: pick(value.sankeyLinkColor, CHART_SANKEY_LINK_COLORS, "source", "sankeyLinkColor", issues)
|
|
1055
|
+
sankeyLinkColor: pick(value.sankeyLinkColor, CHART_SANKEY_LINK_COLORS, "source", "sankeyLinkColor", issues),
|
|
1056
|
+
raceTopN,
|
|
1057
|
+
map: text(value.map, "europe", "map", issues).trim() || "europe",
|
|
1058
|
+
mapSteps,
|
|
1059
|
+
mapThresholds
|
|
1033
1060
|
};
|
|
1034
1061
|
if (spec.stacking === "percent")
|
|
1035
1062
|
for (const name of ["yAxis", "yAxis2"]) {
|
|
@@ -1055,7 +1082,8 @@ function cloneChartSpec(spec) {
|
|
|
1055
1082
|
referenceBands: spec.referenceBands.map((b) => ({ ...b })),
|
|
1056
1083
|
tree: cloneChartTree(spec.tree),
|
|
1057
1084
|
links: spec.links.map((l) => ({ ...l })),
|
|
1058
|
-
sankeyNodes: [...spec.sankeyNodes]
|
|
1085
|
+
sankeyNodes: [...spec.sankeyNodes],
|
|
1086
|
+
mapThresholds: [...spec.mapThresholds]
|
|
1059
1087
|
};
|
|
1060
1088
|
}
|
|
1061
1089
|
function cloneChartSeries(series) {
|