@pixerate/editor 0.162.1 → 0.163.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/README.md +1 -1
- 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/commands.d.cts +2 -1
- package/dist/chart/commands.d.ts +2 -1
- package/dist/chart/controller.d.cts +109 -1
- package/dist/chart/controller.d.ts +109 -1
- package/dist/chart/index.cjs +605 -130
- package/dist/chart/index.d.cts +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chart/index.js +490 -15
- package/dist/chart/layout-annotations.d.cts +29 -0
- package/dist/chart/layout-annotations.d.ts +29 -0
- package/dist/chart/layout-families.d.cts +23 -0
- package/dist/chart/layout-families.d.ts +23 -0
- package/dist/chart/mermaid.d.cts +2 -1
- package/dist/chart/mermaid.d.ts +2 -1
- package/dist/chart/schemas.d.cts +13 -0
- package/dist/chart/schemas.d.ts +13 -0
- package/dist/chart/spec.d.cts +30 -5
- package/dist/chart/spec.d.ts +30 -5
- package/dist/chart/stats.d.cts +9 -1
- package/dist/chart/stats.d.ts +9 -1
- package/dist/chart/tools.d.cts +1 -1
- package/dist/chart/tools.d.ts +1 -1
- package/dist/chart/tree.d.cts +62 -0
- package/dist/chart/tree.d.ts +62 -0
- package/dist/chart/types.d.cts +77 -3
- package/dist/chart/types.d.ts +77 -3
- package/dist/{chunk-7YBDRJXT.cjs → chunk-22KLZZGH.cjs} +8 -8
- package/dist/{chunk-HLOKB7WW.js → chunk-3NEZJCBY.js} +366 -6
- package/dist/{chunk-EVPZ5IKN.js → chunk-5FNGLV2D.js} +65 -3
- package/dist/{chunk-EQ46F75O.cjs → chunk-D54JHQK2.cjs} +51 -51
- package/dist/{chunk-NO5QQDHZ.js → chunk-DAPQGA55.js} +2 -2
- package/dist/{chunk-3IHNXRUV.cjs → chunk-GKNEJ6EN.cjs} +399 -67
- package/dist/{chunk-4NGVHHJB.cjs → chunk-NZD2MZJC.cjs} +80 -18
- package/dist/{chunk-CX6JLDFC.js → chunk-OXERWBKE.js} +3 -3
- package/dist/{chunk-7CKPUUN4.js → chunk-POR54B55.js} +61 -10
- package/dist/{chunk-LL3H5XUH.cjs → chunk-R3P2M7HU.cjs} +367 -7
- package/dist/{chunk-HDGBM7EA.cjs → chunk-SBQVEBSU.cjs} +580 -53
- package/dist/{chunk-XDCFJEU7.js → chunk-SZA42R4N.js} +346 -14
- package/dist/{chunk-GLMP22FT.js → chunk-TALQONZ5.js} +3 -3
- package/dist/{chunk-NA4UVVJ4.cjs → chunk-YGG7WDWT.cjs} +76 -25
- package/dist/{chunk-EY22AY6R.cjs → chunk-YSAGBIRW.cjs} +16 -16
- package/dist/{chunk-DTSTNRPH.js → chunk-Z4P2RBMJ.js} +536 -9
- 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/dist/paint-editor/index.cjs +0 -20
- package/dist/paint-editor/index.js +0 -20
- package/package.json +1 -1
- package/skills/pixerate-canvas/SKILL.md +16 -0
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
var _chunkR3P2M7HUcjs = require('./chunk-R3P2M7HU.cjs');
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
var _chunk55AKPTLMcjs = require('./chunk-55AKPTLM.cjs');
|
|
@@ -507,6 +512,15 @@ function chartQuantile(sorted, q) {
|
|
|
507
512
|
const b = _nullishCoalesce(sorted[i + 1], () => ( a));
|
|
508
513
|
return a + (b - a) * (pos - i);
|
|
509
514
|
}
|
|
515
|
+
var roundish = (n) => Number(n.toPrecision(3));
|
|
516
|
+
function suggestChartReferences(spec) {
|
|
517
|
+
const values = spec.series.filter((s) => !s.hidden && s.axis !== "secondary").flatMap((s) => s.values).filter((v) => v !== null && Number.isFinite(v)).sort((a, b) => a - b);
|
|
518
|
+
if (!values.length) return { line: { value: 0, label: "Target" }, band: { from: 0, to: 1, label: "Target range" } };
|
|
519
|
+
const mean = values.reduce((t, v) => t + v, 0) / values.length;
|
|
520
|
+
const lo = roundish(chartQuantile(values, 0.25));
|
|
521
|
+
const hi = roundish(chartQuantile(values, 0.75));
|
|
522
|
+
return { line: { value: roundish(mean), label: "Average" }, band: { from: lo, to: hi > lo ? hi : lo + 1, label: "Middle half" } };
|
|
523
|
+
}
|
|
510
524
|
function chartBoxStats(values) {
|
|
511
525
|
const v = values.filter((x) => x !== null && Number.isFinite(x)).sort((a, b) => a - b);
|
|
512
526
|
if (!v.length) return null;
|
|
@@ -822,13 +836,164 @@ function chartSupportsTrendlines(type, stacking) {
|
|
|
822
836
|
return (type === "bar" || type === "line" || type === "area" || type === "scatter" || type === "bubble") && stacking === "none";
|
|
823
837
|
}
|
|
824
838
|
|
|
839
|
+
// src/chart/layout-annotations.ts
|
|
840
|
+
var axisOf = (item) => _nullishCoalesce(item.axis, () => ( "y"));
|
|
841
|
+
var chartAnnotationFamily = (family) => family === "cartesian" || family === "xy";
|
|
842
|
+
function referenceValues(spec, axis) {
|
|
843
|
+
const out = [];
|
|
844
|
+
for (const l of spec.referenceLines) if (axisOf(l) === axis) out.push(l.value);
|
|
845
|
+
for (const b of spec.referenceBands) if (axisOf(b) === axis) out.push(b.from, b.to);
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
function drawReferences(ctx, plot, axes, layer) {
|
|
849
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
850
|
+
const items = layer === "bands" ? spec.referenceBands : spec.referenceLines;
|
|
851
|
+
const missing = /* @__PURE__ */ new Set();
|
|
852
|
+
items.forEach((item, i) => {
|
|
853
|
+
const axis = axes[axisOf(item)];
|
|
854
|
+
if (!axis) {
|
|
855
|
+
missing.add(axisOf(item));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const { info, vertical } = axis;
|
|
859
|
+
const color = _nullishCoalesce(item.color, () => ( theme.text));
|
|
860
|
+
const lo = vertical ? plot.y : plot.x;
|
|
861
|
+
const hi = vertical ? plot.y + plot.height : plot.x + plot.width;
|
|
862
|
+
const inside = (p2) => Number.isFinite(p2) && p2 >= lo - 0.5 && p2 <= hi + 0.5;
|
|
863
|
+
const at = (v) => info.logBase && !(v > 0) ? NaN : info.raw(v);
|
|
864
|
+
if ("from" in item) {
|
|
865
|
+
const a = at(item.from);
|
|
866
|
+
const b = at(item.to);
|
|
867
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) return;
|
|
868
|
+
const p0 = Math.max(lo, Math.min(a, b));
|
|
869
|
+
const p1 = Math.min(hi, Math.max(a, b));
|
|
870
|
+
if (p1 <= p0) return;
|
|
871
|
+
const rect = vertical ? { x: plot.x, y: p0, width: plot.width, height: p1 - p0 } : { x: p0, y: plot.y, width: p1 - p0, height: plot.height };
|
|
872
|
+
marks.push({ kind: "rect", key: `reference-band-${i}`, role: "reference-band", ...rect, fill: color, opacity: item.color ? 0.16 : 0.07 });
|
|
873
|
+
if (item.label) {
|
|
874
|
+
const text = truncate(item.label, (vertical ? plot.width : rect.width) - 8, fs - 1, measure);
|
|
875
|
+
marks.push({ kind: "text", key: `reference-label-band-${i}`, role: "reference-label", x: rect.x + 4, y: rect.y + 3, text, anchor: "start", baseline: "top", size: fs - 1, fill: color, opacity: 0.85 });
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const p = at(item.value);
|
|
880
|
+
if (!inside(p)) {
|
|
881
|
+
ctx.warn("annotations-skipped", `The reference line at ${item.value} is outside the ${axisOf(item) === "x" ? "x" : "value"} axis range and is not drawn.`);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const line = vertical ? { x1: plot.x, y1: p, x2: plot.x + plot.width, y2: p } : { x1: p, y1: plot.y, x2: p, y2: plot.y + plot.height };
|
|
885
|
+
marks.push({ kind: "line", key: `reference-line-${i}`, role: "reference-line", ...line, stroke: color, strokeWidth: 1.25, dash: "5 3", opacity: 0.8 });
|
|
886
|
+
if (item.label) {
|
|
887
|
+
const text = truncate(item.label, (vertical ? plot.width : plot.width / 2) - 8, fs - 1, measure);
|
|
888
|
+
const label = vertical ? { x: plot.x + plot.width - 4, y: p - 3, anchor: "end", baseline: "bottom" } : { x: p + 4, y: plot.y + 2, anchor: "start", baseline: "top" };
|
|
889
|
+
marks.push({ kind: "text", key: `reference-label-line-${i}`, role: "reference-label", ...label, text, size: fs - 1, weight: 500, fill: color });
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
for (const axis of missing)
|
|
893
|
+
ctx.warn("annotations-skipped", axis === "x" ? "Reference lines and bands on the x axis need a scatter or bubble chart with numeric x values; they are not drawn." : "Reference lines and bands on the secondary axis need a series on that axis; they are not drawn.");
|
|
894
|
+
}
|
|
895
|
+
function anchorOf(mark, horizontal) {
|
|
896
|
+
if (mark.kind === "rect") return horizontal ? { x: mark.x + mark.width, y: mark.y + mark.height / 2 } : { x: mark.x + mark.width / 2, y: mark.y };
|
|
897
|
+
if (mark.kind === "circle") return { x: mark.cx, y: mark.cy - mark.r };
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
function drawCallouts(ctx, plot) {
|
|
901
|
+
const { spec, marks, theme, fs, measure } = ctx;
|
|
902
|
+
const horizontal = spec.orientation === "horizontal";
|
|
903
|
+
const placed = [];
|
|
904
|
+
const overlaps = (a) => placed.some((b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height);
|
|
905
|
+
let unplaced = 0;
|
|
906
|
+
for (const s of spec.series) {
|
|
907
|
+
if (s.hidden || !s.callouts) continue;
|
|
908
|
+
s.callouts.forEach((label, i) => {
|
|
909
|
+
if (!label) return;
|
|
910
|
+
const mark = marks.find((m) => (m.role === "bar" || m.role === "point") && _optionalChain([m, 'access', _2 => _2.target, 'optionalAccess', _3 => _3.kind]) === "point" && m.target.seriesId === s.id && m.target.index === i);
|
|
911
|
+
const anchor = mark ? anchorOf(mark, horizontal && mark.kind === "rect") : null;
|
|
912
|
+
if (!anchor) {
|
|
913
|
+
unplaced++;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const size = fs - 1;
|
|
917
|
+
const text = truncate(label, plot.width * 0.6, size, measure);
|
|
918
|
+
const w = measure(text, size, 600) + 10;
|
|
919
|
+
const h = size + 8;
|
|
920
|
+
const sideways = horizontal && mark.kind === "rect";
|
|
921
|
+
let bx = sideways ? anchor.x + 14 : anchor.x - w / 2;
|
|
922
|
+
let by = sideways ? anchor.y - h / 2 : anchor.y - h - 14;
|
|
923
|
+
const clamp = () => {
|
|
924
|
+
bx = Math.max(plot.x, Math.min(plot.x + plot.width - w, bx));
|
|
925
|
+
by = Math.max(plot.y - fs / 2, Math.min(plot.y + plot.height - h, by));
|
|
926
|
+
};
|
|
927
|
+
clamp();
|
|
928
|
+
const covers = anchor.x >= bx - 2 && anchor.x <= bx + w + 2 && anchor.y >= by - 2 && anchor.y <= by + h + 2;
|
|
929
|
+
if (covers) {
|
|
930
|
+
if (sideways) bx = anchor.x - w - 14;
|
|
931
|
+
else by = anchor.y + 14;
|
|
932
|
+
clamp();
|
|
933
|
+
}
|
|
934
|
+
for (let k = 0; k < 6 && overlaps({ x: bx, y: by, width: w, height: h }); k++) {
|
|
935
|
+
if (sideways) by += h + 2;
|
|
936
|
+
else by -= h + 2;
|
|
937
|
+
clamp();
|
|
938
|
+
}
|
|
939
|
+
const box = { x: bx, y: by, width: w, height: h };
|
|
940
|
+
placed.push(box);
|
|
941
|
+
const target = { kind: "point", seriesId: s.id, index: i };
|
|
942
|
+
const ex = Math.max(bx, Math.min(bx + w, anchor.x));
|
|
943
|
+
const ey = Math.max(by, Math.min(by + h, anchor.y));
|
|
944
|
+
if (Math.hypot(ex - anchor.x, ey - anchor.y) > 2) marks.push({ kind: "line", key: `callout-leader-${s.id}-${i}`, role: "callout", x1: anchor.x, y1: anchor.y, x2: ex, y2: ey, stroke: theme.axis, strokeWidth: 1 });
|
|
945
|
+
marks.push({ kind: "rect", key: `callout-box-${s.id}-${i}`, role: "callout", ...box, fill: theme.surface, stroke: theme.axis, strokeWidth: 1, radius: 3, target });
|
|
946
|
+
marks.push({ kind: "text", key: `callout-${s.id}-${i}`, role: "callout", x: bx + w / 2, y: by + h / 2, text, anchor: "middle", baseline: "middle", size, weight: 600, fill: theme.text, target });
|
|
947
|
+
});
|
|
948
|
+
}
|
|
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
|
+
}
|
|
951
|
+
var MUTED = /* @__PURE__ */ new Set(["bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
|
|
952
|
+
function applyHighlight(ctx) {
|
|
953
|
+
const { spec, marks } = ctx;
|
|
954
|
+
if (!spec.highlight.length) return;
|
|
955
|
+
const names = new Set(spec.highlight);
|
|
956
|
+
const categories = /* @__PURE__ */ new Set();
|
|
957
|
+
spec.categories.forEach((c, i) => names.has(c) && categories.add(i));
|
|
958
|
+
const series = new Set(spec.series.filter((s) => names.has(s.id) || names.has(s.name)).map((s) => s.id));
|
|
959
|
+
const unmatched = spec.highlight.filter((h) => !spec.categories.includes(h) && !spec.series.some((s) => s.id === h || s.name === h));
|
|
960
|
+
if (unmatched.length) ctx.warn("highlight-unmatched", `Highlight names ${unmatched.map((u) => `"${u}"`).join(", ")}, which ${unmatched.length === 1 ? "is" : "are"} neither a category nor a series.`);
|
|
961
|
+
if (!categories.size && !series.size) return;
|
|
962
|
+
const bright = (t) => {
|
|
963
|
+
switch (t.kind) {
|
|
964
|
+
case "point":
|
|
965
|
+
return series.has(t.seriesId) || categories.has(t.index);
|
|
966
|
+
case "series":
|
|
967
|
+
case "trendline":
|
|
968
|
+
return !series.size || series.has(t.seriesId);
|
|
969
|
+
case "category":
|
|
970
|
+
return !categories.size || categories.has(t.index);
|
|
971
|
+
default:
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
for (let k = 0; k < marks.length; k++) {
|
|
976
|
+
const m = marks[k];
|
|
977
|
+
if (!m.target || !MUTED.has(m.role) || bright(m.target)) continue;
|
|
978
|
+
marks[k] = { ...m, opacity: (_nullishCoalesce(m.opacity, () => ( 1))) * 0.25 };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
var noteHeight = (spec, fs) => spec.note ? fs * 1.5 : 0;
|
|
982
|
+
function drawNote(ctx, pad) {
|
|
983
|
+
const { spec, marks, theme, fs, measure, width, height } = ctx;
|
|
984
|
+
if (!spec.note) return;
|
|
985
|
+
const text = truncate(spec.note, width - pad * 2, fs - 1, measure);
|
|
986
|
+
if (text !== spec.note) ctx.warn("labels-truncated", "The note was shortened to fit the width.");
|
|
987
|
+
marks.push({ kind: "text", key: "note", role: "note", x: pad, y: height - pad + 2, text, anchor: "start", baseline: "bottom", size: fs - 1, fill: theme.mutedText });
|
|
988
|
+
}
|
|
989
|
+
|
|
825
990
|
// src/chart/layout-cartesian.ts
|
|
826
991
|
var MAX_CHART_DATA_LABELS = 200;
|
|
827
992
|
var MAX_CHART_LINE_POINTS = 2e3;
|
|
828
993
|
var MAX_CHART_MARKERS = 1e3;
|
|
829
994
|
function scaleInfo(scale, range, format, axis) {
|
|
830
|
-
const logBase = _optionalChain([axis, 'optionalAccess',
|
|
831
|
-
const r = _optionalChain([axis, 'optionalAccess',
|
|
995
|
+
const logBase = _optionalChain([axis, 'optionalAccess', _4 => _4.scale]) === "log" ? _nullishCoalesce(axis.logBase, () => ( 10)) : void 0;
|
|
996
|
+
const r = _optionalChain([axis, 'optionalAccess', _5 => _5.reverse]) ? [range[1], range[0]] : range;
|
|
832
997
|
const vs = { domain: [scale.min, scale.max], range: r, logBase };
|
|
833
998
|
const raw = (v) => chartScaleToPixel(v, vs);
|
|
834
999
|
const px = (v) => logBase && !(v > 0) ? r[0] : raw(Math.min(scale.max, Math.max(scale.min, v)));
|
|
@@ -849,7 +1014,7 @@ function categoryFrame(ctx, area, input) {
|
|
|
849
1014
|
};
|
|
850
1015
|
const valueLen0 = horizontal ? area.width : area.height;
|
|
851
1016
|
const valueLabelWidth = primary.axis.show ? estimate(primary, valueLen0) : 0;
|
|
852
|
-
const valueLabelWidth2 = _optionalChain([secondary, 'optionalAccess',
|
|
1017
|
+
const valueLabelWidth2 = _optionalChain([secondary, 'optionalAccess', _6 => _6.axis, 'access', _7 => _7.show]) ? estimate(secondary, valueLen0) : 0;
|
|
853
1018
|
const catLabelWidth = Math.max(0, ...labels.map((c) => measure(c, fs)));
|
|
854
1019
|
let left;
|
|
855
1020
|
let bottom;
|
|
@@ -973,7 +1138,7 @@ function categoryFrame(ctx, area, input) {
|
|
|
973
1138
|
marks.push({ kind: "text", key: `axis-title-${horizontal ? "y" : "x"}`, role: "axis-title", x: plot.x + plot.width / 2, y: area.y + area.height, text: bottomTitle, anchor: "middle", baseline: "bottom", size: fs + 1, weight: 500, fill: theme.text, target: horizontal ? yTarget : xTarget });
|
|
974
1139
|
if (leftTitle)
|
|
975
1140
|
marks.push({ kind: "text", key: `axis-title-${horizontal ? "x" : "y"}`, role: "axis-title", x: area.x, y: plot.y + plot.height / 2, text: leftTitle, anchor: "middle", baseline: "top", size: fs + 1, weight: 500, fill: theme.text, rotate: -90, target: horizontal ? xTarget : yTarget });
|
|
976
|
-
if (_optionalChain([secondary, 'optionalAccess',
|
|
1141
|
+
if (_optionalChain([secondary, 'optionalAccess', _8 => _8.axis, 'access', _9 => _9.title])) {
|
|
977
1142
|
const target = { kind: "axis", axis: "y2" };
|
|
978
1143
|
if (horizontal) marks.push({ kind: "text", key: "axis-title-y2", role: "axis-title", x: plot.x + plot.width / 2, y: area.y, text: secondary.axis.title, anchor: "middle", baseline: "top", size: fs + 1, weight: 500, fill: theme.text, target });
|
|
979
1144
|
else marks.push({ kind: "text", key: "axis-title-y2", role: "axis-title", x: area.x + area.width, y: plot.y + plot.height / 2, text: secondary.axis.title, anchor: "middle", baseline: "top", size: fs + 1, weight: 500, fill: theme.text, rotate: 90, target });
|
|
@@ -1074,7 +1239,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1074
1239
|
const { spec, theme, marks, measure, fs } = ctx;
|
|
1075
1240
|
const horizontal = spec.orientation === "horizontal";
|
|
1076
1241
|
const n = spec.categories.length;
|
|
1077
|
-
const types = new Map(visible.map((s) => [s.id,
|
|
1242
|
+
const types = new Map(visible.map((s) => [s.id, _chunkR3P2M7HUcjs.chartSeriesType.call(void 0, spec, s)]));
|
|
1078
1243
|
const stacked = spec.stacking !== "none";
|
|
1079
1244
|
const percent = spec.stacking === "percent";
|
|
1080
1245
|
const onSecondary = (s) => s.axis === "secondary";
|
|
@@ -1111,9 +1276,9 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1111
1276
|
const stack = stacks.get(s.id);
|
|
1112
1277
|
errors.set(
|
|
1113
1278
|
s.id,
|
|
1114
|
-
|
|
1279
|
+
_chunkR3P2M7HUcjs.chartErrorAmounts.call(void 0, s.errorBars, s.values).map((e, i) => {
|
|
1115
1280
|
const raw = s.values[i];
|
|
1116
|
-
const seg = _optionalChain([stack, 'optionalAccess',
|
|
1281
|
+
const seg = _optionalChain([stack, 'optionalAccess', _10 => _10.segments, 'access', _11 => _11[i]]);
|
|
1117
1282
|
if (!e || raw === null || stack && !seg) return null;
|
|
1118
1283
|
const k = seg && percent ? raw ? (seg[1] - seg[0]) / raw : 0 : 1;
|
|
1119
1284
|
const center = (seg ? seg[1] : raw) + (e.center - raw) * k;
|
|
@@ -1135,8 +1300,9 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1135
1300
|
const stack = stacks.get(s.id);
|
|
1136
1301
|
if (stack) stack.segments.forEach((seg) => seg && (see(seg[0]), see(seg[1])));
|
|
1137
1302
|
else s.values.forEach((v) => v !== null && see(v));
|
|
1138
|
-
_optionalChain([errors, 'access',
|
|
1303
|
+
_optionalChain([errors, 'access', _12 => _12.get, 'call', _13 => _13(s.id), 'optionalAccess', _14 => _14.forEach, 'call', _15 => _15((e) => e && (see(e.lo), see(e.hi)))]);
|
|
1139
1304
|
}
|
|
1305
|
+
for (const v of referenceValues(spec, secondary ? "y2" : "y")) see(v);
|
|
1140
1306
|
if (log) {
|
|
1141
1307
|
const skipped = group.reduce((t, s) => t + s.values.filter((v) => v !== null && Number.isFinite(v) && v <= 0).length, 0);
|
|
1142
1308
|
if (skipped) ctx.warn("values-skipped", `${skipped} value${skipped === 1 ? "" : "s"} of 0 or less cannot be drawn on the logarithmic ${secondary ? "secondary " : ""}value axis and ${skipped === 1 ? "was" : "were"} skipped.`);
|
|
@@ -1163,6 +1329,8 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1163
1329
|
});
|
|
1164
1330
|
const { plot, band, catCenter, map, catStart } = frame;
|
|
1165
1331
|
const infoOf = (s) => onSecondary(s) && frame.value2 ? frame.value2 : frame.value;
|
|
1332
|
+
const refAxes = { y: { info: frame.value, vertical: !horizontal }, ...frame.value2 ? { y2: { info: frame.value2, vertical: !horizontal } } : {} };
|
|
1333
|
+
drawReferences(ctx, plot, refAxes, "bands");
|
|
1166
1334
|
const bars = visible.filter((s) => types.get(s.id) === "bar");
|
|
1167
1335
|
const barAxes = [...new Set(bars.map((s) => onSecondary(s)))];
|
|
1168
1336
|
const slots = stacked ? barAxes.length : bars.length;
|
|
@@ -1178,7 +1346,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1178
1346
|
const drawOrder = [...visible].sort((a, b) => order(types.get(a.id)) - order(types.get(b.id)));
|
|
1179
1347
|
for (const s of drawOrder) {
|
|
1180
1348
|
const si = spec.series.indexOf(s);
|
|
1181
|
-
const color =
|
|
1349
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, si);
|
|
1182
1350
|
const type = types.get(s.id);
|
|
1183
1351
|
const stack = stacks.get(s.id);
|
|
1184
1352
|
const selSeries = ctx.seriesSelected(s.id);
|
|
@@ -1204,7 +1372,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1204
1372
|
const selected = ctx.isSelected(target);
|
|
1205
1373
|
const [x, y] = map({ c: c0, v: Math.min(a, b) });
|
|
1206
1374
|
const rect = horizontal ? { x: Math.min(a, b), y: c0, width: Math.abs(b - a), height: cw } : { x, y, width: cw, height: Math.abs(b - a) };
|
|
1207
|
-
const fill =
|
|
1375
|
+
const fill = _chunkR3P2M7HUcjs.chartPointColor.call(void 0, spec, si, i);
|
|
1208
1376
|
marks.push({
|
|
1209
1377
|
kind: "rect",
|
|
1210
1378
|
key: `bar-${s.id}-${i}`,
|
|
@@ -1232,7 +1400,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1232
1400
|
} else {
|
|
1233
1401
|
const curve = _nullishCoalesce(s.curve, () => ( spec.curve));
|
|
1234
1402
|
const pointValue = (i) => {
|
|
1235
|
-
const v = stack ? _nullishCoalesce(_optionalChain([stack, 'access',
|
|
1403
|
+
const v = stack ? _nullishCoalesce(_optionalChain([stack, 'access', _16 => _16.segments, 'access', _17 => _17[i], 'optionalAccess', _18 => _18[1]]), () => ( null)) : s.values[i];
|
|
1236
1404
|
return v !== null && Number.isFinite(v) && !(log && (v <= 0 || (_nullishCoalesce(s.values[i], () => ( 0))) <= 0)) ? v : null;
|
|
1237
1405
|
};
|
|
1238
1406
|
const runs = [];
|
|
@@ -1256,7 +1424,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1256
1424
|
const drawn = runs.map(thin);
|
|
1257
1425
|
if (type === "area") {
|
|
1258
1426
|
const parts = drawn.map((r) => {
|
|
1259
|
-
const baseline = stack ? r.map((pt) => ({ c: pt.c, v: valuePx(_nullishCoalesce(_optionalChain([stack, 'access',
|
|
1427
|
+
const baseline = stack ? r.map((pt) => ({ c: pt.c, v: valuePx(_nullishCoalesce(_optionalChain([stack, 'access', _19 => _19.segments, 'access', _20 => _20[pt.i], 'optionalAccess', _21 => _21[0]]), () => ( 0))) })).reverse() : [
|
|
1260
1428
|
{ c: r[r.length - 1].c, v: info.zero },
|
|
1261
1429
|
{ c: r[0].c, v: info.zero }
|
|
1262
1430
|
];
|
|
@@ -1283,7 +1451,7 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1283
1451
|
const target = { kind: "point", seriesId: s.id, index: i };
|
|
1284
1452
|
const selected = ctx.isSelected(target);
|
|
1285
1453
|
const r = type === "scatter" ? 4.5 : 3.5;
|
|
1286
|
-
const pointColor =
|
|
1454
|
+
const pointColor = _chunkR3P2M7HUcjs.chartPointColor.call(void 0, spec, si, i);
|
|
1287
1455
|
const fill = showDots ? type === "scatter" ? pointColor : theme.surface : "transparent";
|
|
1288
1456
|
marks.push(
|
|
1289
1457
|
pointMark({ key: `point-${s.id}-${i}`, target, selected: selected || selSeries, opacity: type === "scatter" ? 0.85 : void 0 }, showDots ? marker : "hit", cx, cy, showDots ? r : 6, fill, showDots ? pointColor : void 0, 2)
|
|
@@ -1320,13 +1488,14 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
1320
1488
|
if (d) errorMarks.push({ kind: "path", key: `error-bar-${s.id}-${i}`, role: "error-bar", d, fill: "none", stroke: type === "bar" ? theme.text : color, strokeWidth: selSeries ? 2 : 1.25, target: seriesTarget, selected: selSeries });
|
|
1321
1489
|
});
|
|
1322
1490
|
}
|
|
1323
|
-
if (trendsAllowed && _optionalChain([s, 'access',
|
|
1491
|
+
if (trendsAllowed && _optionalChain([s, 'access', _22 => _22.trendlines, 'optionalAccess', _23 => _23.length])) {
|
|
1324
1492
|
const points = [];
|
|
1325
1493
|
s.values.forEach((v, i) => v !== null && Number.isFinite(v) && points.push([i + 1, v]));
|
|
1326
1494
|
const toPx = (x, y) => map({ c: catCenter(x - 1), v: info.raw(y) });
|
|
1327
1495
|
drawTrendlines(ctx, plot, s, color, points, toPx, trendLabels, !!info.logBase);
|
|
1328
1496
|
}
|
|
1329
1497
|
}
|
|
1498
|
+
drawReferences(ctx, plot, refAxes, "lines");
|
|
1330
1499
|
marks.push(...errorMarks, ...labelMarks);
|
|
1331
1500
|
drawTrendLabels(ctx, plot, trendLabels);
|
|
1332
1501
|
if (hiddenLabels) ctx.warn("data-labels-hidden", `${hiddenLabels} data label${hiddenLabels === 1 ? "" : "s"} did not fit and ${hiddenLabels === 1 ? "is" : "are"} hidden.`);
|
|
@@ -1345,7 +1514,7 @@ function layoutXy(ctx, area, visible) {
|
|
|
1345
1514
|
const xLog = xAxis.scale === "log";
|
|
1346
1515
|
const yLog = yAxis.scale === "log";
|
|
1347
1516
|
let skipped = 0;
|
|
1348
|
-
const pointsOf = (s) => s.values.map((y, i) => ({ i, x: _nullishCoalesce(_optionalChain([s, 'access',
|
|
1517
|
+
const pointsOf = (s) => s.values.map((y, i) => ({ i, x: _nullishCoalesce(_optionalChain([s, 'access', _24 => _24.x, 'optionalAccess', _25 => _25[i]]), () => ( (bubble || !s.x ? i + 1 : null))), y, size: _nullishCoalesce(_optionalChain([s, 'access', _26 => _26.sizes, 'optionalAccess', _27 => _27[i]]), () => ( null)) })).filter((p) => {
|
|
1349
1518
|
if (p.x === null || p.y === null || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return false;
|
|
1350
1519
|
if (xLog && p.x <= 0 || yLog && p.y <= 0) return skipped++, false;
|
|
1351
1520
|
return true;
|
|
@@ -1355,8 +1524,8 @@ function layoutXy(ctx, area, visible) {
|
|
|
1355
1524
|
const yErrors = /* @__PURE__ */ new Map();
|
|
1356
1525
|
const xErrors = /* @__PURE__ */ new Map();
|
|
1357
1526
|
for (const { s } of all) {
|
|
1358
|
-
if (s.errorBars) yErrors.set(s.id,
|
|
1359
|
-
if (s.xErrorBars) xErrors.set(s.id,
|
|
1527
|
+
if (s.errorBars) yErrors.set(s.id, _chunkR3P2M7HUcjs.chartErrorAmounts.call(void 0, s.errorBars, s.values));
|
|
1528
|
+
if (s.xErrorBars) xErrors.set(s.id, _chunkR3P2M7HUcjs.chartErrorAmounts.call(void 0, s.xErrorBars, s.values.map((_, i) => _nullishCoalesce(_optionalChain([s, 'access', _28 => _28.x, 'optionalAccess', _29 => _29[i]]), () => ( (bubble || !s.x ? i + 1 : null))))));
|
|
1360
1529
|
}
|
|
1361
1530
|
let xlo = Infinity;
|
|
1362
1531
|
let xhi = -Infinity;
|
|
@@ -1378,9 +1547,9 @@ function layoutXy(ctx, area, visible) {
|
|
|
1378
1547
|
seeX(p.x);
|
|
1379
1548
|
seeY(p.y);
|
|
1380
1549
|
if (p.size !== null && p.size > maxSize) maxSize = p.size;
|
|
1381
|
-
const ey = _optionalChain([yErrors, 'access',
|
|
1550
|
+
const ey = _optionalChain([yErrors, 'access', _30 => _30.get, 'call', _31 => _31(s.id), 'optionalAccess', _32 => _32[p.i]]);
|
|
1382
1551
|
if (ey) seeY(ey.center - ey.minus), seeY(ey.center + ey.plus);
|
|
1383
|
-
const ex = _optionalChain([xErrors, 'access',
|
|
1552
|
+
const ex = _optionalChain([xErrors, 'access', _33 => _33.get, 'call', _34 => _34(s.id), 'optionalAccess', _35 => _35[p.i]]);
|
|
1384
1553
|
if (ex) seeX(ex.center - ex.minus), seeX(ex.center + ex.plus);
|
|
1385
1554
|
}
|
|
1386
1555
|
for (const t of _nullishCoalesce(s.trendlines, () => ( []))) {
|
|
@@ -1389,6 +1558,8 @@ function layoutXy(ctx, area, visible) {
|
|
|
1389
1558
|
seeX(Math.max(...pts.map((p) => p.x)) + (_nullishCoalesce(t.forward, () => ( 0))));
|
|
1390
1559
|
}
|
|
1391
1560
|
}
|
|
1561
|
+
for (const v of referenceValues(spec, "y")) seeY(v);
|
|
1562
|
+
for (const v of referenceValues(spec, "x")) seeX(v);
|
|
1392
1563
|
if (!Number.isFinite(xlo)) [xlo, xhi] = xLog ? [1, 10] : [0, 1];
|
|
1393
1564
|
if (!Number.isFinite(ylo)) [ylo, yhi] = yLog ? [1, 10] : [0, 1];
|
|
1394
1565
|
const titleBand = fs * 1.3 + 6;
|
|
@@ -1430,13 +1601,15 @@ function layoutXy(ctx, area, visible) {
|
|
|
1430
1601
|
if (!ctx.isSelected({ kind: "axis", axis })) continue;
|
|
1431
1602
|
ctx.outline(`axis-${axis}-selection`, axis === "y" ? { x: area.x, y: plot.y - fs / 2, width: left - 2, height: plot.height + fs } : { x: plot.x - 4, y: plot.y + plot.height + 2, width: plot.width + 8, height: area.y + area.height - plot.y - plot.height - 2 });
|
|
1432
1603
|
}
|
|
1604
|
+
const refAxes = { y: { info: yInfo, vertical: true }, x: { info: xInfo, vertical: false } };
|
|
1605
|
+
drawReferences(ctx, plot, refAxes, "bands");
|
|
1433
1606
|
const labelMarks = [];
|
|
1434
1607
|
const errorMarks = [];
|
|
1435
1608
|
const trendLabels = [];
|
|
1436
1609
|
for (const { s, pts } of all) {
|
|
1437
1610
|
const si = spec.series.indexOf(s);
|
|
1438
|
-
const color =
|
|
1439
|
-
const type =
|
|
1611
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, si);
|
|
1612
|
+
const type = _chunkR3P2M7HUcjs.chartSeriesType.call(void 0, spec, s);
|
|
1440
1613
|
const selSeries = ctx.seriesSelected(s.id);
|
|
1441
1614
|
const seriesTarget = { kind: "series", seriesId: s.id };
|
|
1442
1615
|
if (type === "line" || type === "area") {
|
|
@@ -1461,7 +1634,7 @@ function layoutXy(ctx, area, visible) {
|
|
|
1461
1634
|
}
|
|
1462
1635
|
const target = { kind: "point", seriesId: s.id, index: p.i };
|
|
1463
1636
|
const selected = ctx.isSelected(target);
|
|
1464
|
-
const pointColor =
|
|
1637
|
+
const pointColor = _chunkR3P2M7HUcjs.chartPointColor.call(void 0, spec, si, p.i);
|
|
1465
1638
|
const r = bubble ? maxSize > 0 && p.size !== null && p.size > 0 ? Math.max(2, Math.sqrt(p.size / maxSize) * maxR) : 2 : 4.5;
|
|
1466
1639
|
if (bubble) marks.push({ kind: "circle", key: `point-${s.id}-${p.i}`, role: "point", cx, cy, r, fill: pointColor, stroke: pointColor, strokeWidth: 1, opacity: 0.6, target, selected: selected || selSeries });
|
|
1467
1640
|
else marks.push(pointMark({ key: `point-${s.id}-${p.i}`, target, selected: selected || selSeries, opacity: 0.85 }, marker === "none" ? "hit" : marker, cx, cy, marker === "none" ? 6 : r, marker === "none" ? "transparent" : pointColor, marker === "none" ? void 0 : pointColor, 2));
|
|
@@ -1471,14 +1644,14 @@ function layoutXy(ctx, area, visible) {
|
|
|
1471
1644
|
const stroke = color;
|
|
1472
1645
|
const errorBar = (key, d) => d && errorMarks.push({ kind: "path", key, role: "error-bar", d, fill: "none", stroke, strokeWidth: selSeries ? 2 : 1.25, target: seriesTarget, selected: selSeries });
|
|
1473
1646
|
for (const p of pts) {
|
|
1474
|
-
const ey = _optionalChain([yErrors, 'access',
|
|
1647
|
+
const ey = _optionalChain([yErrors, 'access', _36 => _36.get, 'call', _37 => _37(s.id), 'optionalAccess', _38 => _38[p.i]]);
|
|
1475
1648
|
if (ey) {
|
|
1476
1649
|
const lo = ey.center - ey.minus;
|
|
1477
1650
|
const hi = ey.center + ey.plus;
|
|
1478
1651
|
const cap = s.errorBars.cap !== false;
|
|
1479
1652
|
errorBar(`error-bar-${s.id}-${p.i}`, errorBarPath((q) => [q.c, q.v], xPx(p.x), yPx(lo), yPx(hi), 8, cap && lo < ey.center, cap && hi > ey.center));
|
|
1480
1653
|
}
|
|
1481
|
-
const ex = _optionalChain([xErrors, 'access',
|
|
1654
|
+
const ex = _optionalChain([xErrors, 'access', _39 => _39.get, 'call', _40 => _40(s.id), 'optionalAccess', _41 => _41[p.i]]);
|
|
1482
1655
|
if (ex) {
|
|
1483
1656
|
const lo = ex.center - ex.minus;
|
|
1484
1657
|
const hi = ex.center + ex.plus;
|
|
@@ -1486,8 +1659,9 @@ function layoutXy(ctx, area, visible) {
|
|
|
1486
1659
|
errorBar(`error-bar-x-${s.id}-${p.i}`, errorBarPath((q) => [q.v, q.c], yPx(p.y), xPx(lo), xPx(hi), 8, cap && lo < ex.center, cap && hi > ex.center));
|
|
1487
1660
|
}
|
|
1488
1661
|
}
|
|
1489
|
-
if (_optionalChain([s, 'access',
|
|
1662
|
+
if (_optionalChain([s, 'access', _42 => _42.trendlines, 'optionalAccess', _43 => _43.length])) drawTrendlines(ctx, plot, s, color, pts.map((p) => [p.x, p.y]), (x, y) => [xRaw(x), yRaw(y)], trendLabels, xLog || yLog);
|
|
1490
1663
|
}
|
|
1664
|
+
drawReferences(ctx, plot, refAxes, "lines");
|
|
1491
1665
|
marks.push(...errorMarks, ...labelMarks);
|
|
1492
1666
|
drawTrendLabels(ctx, plot, trendLabels);
|
|
1493
1667
|
return {
|
|
@@ -1504,7 +1678,7 @@ function trendlineLegendItems(spec, color) {
|
|
|
1504
1678
|
}
|
|
1505
1679
|
|
|
1506
1680
|
// src/chart/layout-families.ts
|
|
1507
|
-
var sliceColor = (ctx, series, i) => _optionalChain([series, 'access',
|
|
1681
|
+
var sliceColor = (ctx, series, i) => _optionalChain([series, 'access', _44 => _44.pointColors, 'optionalAccess', _45 => _45[i]]) || _chunkR3P2M7HUcjs.chartSliceColor.call(void 0, ctx.spec, i);
|
|
1508
1682
|
function layoutPie(ctx, area, series) {
|
|
1509
1683
|
const { spec, theme, marks, fs, selection } = ctx;
|
|
1510
1684
|
const values = spec.categories.map((_, i) => {
|
|
@@ -1534,7 +1708,7 @@ function layoutPie(ctx, area, series) {
|
|
|
1534
1708
|
angle = end;
|
|
1535
1709
|
const target = { kind: "point", seriesId: series.id, index: i };
|
|
1536
1710
|
const selected = ctx.isSelected(target) || ctx.isSelected({ kind: "category", index: i });
|
|
1537
|
-
const seriesSel = _optionalChain([selection, 'optionalAccess',
|
|
1711
|
+
const seriesSel = _optionalChain([selection, 'optionalAccess', _46 => _46.kind]) === "series" && selection.seriesId === series.id;
|
|
1538
1712
|
const mid = (start + end) / 2;
|
|
1539
1713
|
const off = selected ? 6 : 0;
|
|
1540
1714
|
const ox = cx + off * Math.sin(mid);
|
|
@@ -1620,7 +1794,7 @@ function layoutRadar(ctx, area, visible) {
|
|
|
1620
1794
|
});
|
|
1621
1795
|
for (const s of visible) {
|
|
1622
1796
|
const si = spec.series.indexOf(s);
|
|
1623
|
-
const color =
|
|
1797
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, si);
|
|
1624
1798
|
const selSeries = ctx.seriesSelected(s.id);
|
|
1625
1799
|
const pts = s.values.map((v, i) => v !== null && Number.isFinite(v) ? at(i, rOf(v)) : null);
|
|
1626
1800
|
const present = pts.filter((p) => p !== null);
|
|
@@ -1670,7 +1844,7 @@ function layoutWaterfall(ctx, area, series) {
|
|
|
1670
1844
|
});
|
|
1671
1845
|
const { band, catCenter, map, value } = frame;
|
|
1672
1846
|
const w = band * (1 - spec.gapWidth);
|
|
1673
|
-
const color =
|
|
1847
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(series));
|
|
1674
1848
|
const format = _nullishCoalesce(series.format, () => ( spec.yAxis.format));
|
|
1675
1849
|
const labelsOn = _nullishCoalesce(series.dataLabels, () => ( spec.dataLabels));
|
|
1676
1850
|
let prevEnd = null;
|
|
@@ -1729,7 +1903,7 @@ function layoutHistogram(ctx, area, visible) {
|
|
|
1729
1903
|
const gap = k > 1 ? band * spec.gapWidth * 0.5 : 0;
|
|
1730
1904
|
const w = (band - gap) / k;
|
|
1731
1905
|
visible.forEach((s, si) => {
|
|
1732
|
-
const color =
|
|
1906
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(s));
|
|
1733
1907
|
const target = { kind: "series", seriesId: s.id };
|
|
1734
1908
|
const selected = ctx.seriesSelected(s.id);
|
|
1735
1909
|
counts[si].forEach((c, b) => {
|
|
@@ -1760,7 +1934,7 @@ function layoutBox(ctx, area, visible) {
|
|
|
1760
1934
|
visible.forEach((s, i) => {
|
|
1761
1935
|
const st = stats[i];
|
|
1762
1936
|
if (!st) return;
|
|
1763
|
-
const color =
|
|
1937
|
+
const color = _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(s));
|
|
1764
1938
|
const target = { kind: "series", seriesId: s.id };
|
|
1765
1939
|
const selected = ctx.seriesSelected(s.id);
|
|
1766
1940
|
const c = catCenter(i);
|
|
@@ -1843,16 +2017,349 @@ function layoutTreemap(ctx, area, series) {
|
|
|
1843
2017
|
if (unlabelled) ctx.warn("data-labels-hidden", `${unlabelled} treemap cell${unlabelled === 1 ? " is" : "s are"} too small for a label.`);
|
|
1844
2018
|
return { plot: area };
|
|
1845
2019
|
}
|
|
2020
|
+
function dodge(ys, gap, top, bottom) {
|
|
2021
|
+
const out = [...ys];
|
|
2022
|
+
for (let k = 1; k < out.length; k++) out[k] = Math.max(out[k], out[k - 1] + gap);
|
|
2023
|
+
const over = out.length ? out[out.length - 1] - bottom : 0;
|
|
2024
|
+
if (over > 0) for (let k = out.length - 1; k >= 0; k--) out[k] = Math.max(top, Math.min(out[k] - over, k < out.length - 1 ? out[k + 1] - gap : Infinity));
|
|
2025
|
+
return out;
|
|
2026
|
+
}
|
|
2027
|
+
function layoutSlope(ctx, area, visible) {
|
|
2028
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2029
|
+
const [a, b] = visible;
|
|
2030
|
+
if (visible.length > 2) ctx.warn("values-skipped", `A slope chart joins two series; ${visible.length - 2} more ${visible.length === 3 ? "is" : "are"} not drawn.`);
|
|
2031
|
+
const at = (s, i) => {
|
|
2032
|
+
const v = s.values[i];
|
|
2033
|
+
return v !== null && Number.isFinite(v) ? v : null;
|
|
2034
|
+
};
|
|
2035
|
+
const all = spec.categories.flatMap((_, i) => [at(a, i), at(b, i)]).filter((v) => v !== null);
|
|
2036
|
+
const lo = Math.min(...all);
|
|
2037
|
+
const hi = Math.max(...all);
|
|
2038
|
+
const labelsOn = _nullishCoalesce(_nullishCoalesce(a.dataLabels, () => ( b.dataLabels)), () => ( spec.dataLabels));
|
|
2039
|
+
const fmtA = (v) => formatChartNumber(v, _nullishCoalesce(a.format, () => ( spec.yAxis.format)));
|
|
2040
|
+
const fmtB = (v) => formatChartNumber(v, _nullishCoalesce(b.format, () => ( spec.yAxis.format)));
|
|
2041
|
+
const leftText = (i) => {
|
|
2042
|
+
const v = at(a, i);
|
|
2043
|
+
return labelsOn && v !== null ? `${spec.categories[i]} ${fmtA(v)}` : spec.categories[i];
|
|
2044
|
+
};
|
|
2045
|
+
const rightText = (i) => {
|
|
2046
|
+
const v = at(b, i);
|
|
2047
|
+
return labelsOn && v !== null ? `${fmtB(v)} ${spec.categories[i]}` : spec.categories[i];
|
|
2048
|
+
};
|
|
2049
|
+
const maxW = area.width * 0.3;
|
|
2050
|
+
const leftW = Math.min(maxW, Math.max(0, ...spec.categories.map((_, i) => measure(leftText(i), fs))));
|
|
2051
|
+
const rightW = Math.min(maxW, Math.max(0, ...spec.categories.map((_, i) => measure(rightText(i), fs))));
|
|
2052
|
+
const headerH = fs * 1.8;
|
|
2053
|
+
const plot = { x: area.x + leftW + 10, y: area.y + headerH + fs / 2, width: Math.max(1, area.width - leftW - rightW - 20), height: Math.max(1, area.height - headerH - fs) };
|
|
2054
|
+
const scale = axisScale(lo, hi, plot.height, spec.yAxis, ctx.options.valueDomain, true);
|
|
2055
|
+
const yOf = (v) => linearMap(Math.min(scale.max, Math.max(scale.min, v)), [scale.min, scale.max], [plot.y + plot.height, plot.y]);
|
|
2056
|
+
const xL = plot.x;
|
|
2057
|
+
const xR = plot.x + plot.width;
|
|
2058
|
+
for (const [s, x, anchor] of [[a, xL, "end"], [b, xR, "start"]]) {
|
|
2059
|
+
const target = { kind: "series", seriesId: s.id };
|
|
2060
|
+
const selected = ctx.seriesSelected(s.id);
|
|
2061
|
+
marks.push({ kind: "line", key: `axis-slope-${s.id}`, role: "axis", x1: x, y1: plot.y, x2: x, y2: plot.y + plot.height, stroke: theme.axis, strokeWidth: 1, target });
|
|
2062
|
+
marks.push({ kind: "text", key: `axis-title-${s.id}`, role: "axis-title", x, y: area.y, text: truncate(s.name, maxW + plot.width / 2, fs + 1, measure), anchor: anchor === "end" ? "end" : "start", baseline: "top", size: fs + 1, weight: 600, fill: selected ? theme.selection : theme.text, target, selected });
|
|
2063
|
+
}
|
|
2064
|
+
const leftLabels = [];
|
|
2065
|
+
const rightLabels = [];
|
|
2066
|
+
spec.categories.forEach((_, i) => {
|
|
2067
|
+
const va = at(a, i);
|
|
2068
|
+
const vb = at(b, i);
|
|
2069
|
+
const color = sliceColor(ctx, a, i);
|
|
2070
|
+
const catTarget = { kind: "category", index: i };
|
|
2071
|
+
const catSelected = ctx.isSelected(catTarget);
|
|
2072
|
+
if (va !== null && vb !== null)
|
|
2073
|
+
marks.push({ kind: "line", key: `line-slope-${i}`, role: "line", x1: xL, y1: yOf(va), x2: xR, y2: yOf(vb), stroke: color, strokeWidth: catSelected ? 3.5 : 2, target: catTarget, selected: catSelected });
|
|
2074
|
+
for (const [s, v, x, labels] of [[a, va, xL, leftLabels], [b, vb, xR, rightLabels]]) {
|
|
2075
|
+
if (v === null) continue;
|
|
2076
|
+
const target = { kind: "point", seriesId: s.id, index: i };
|
|
2077
|
+
const selected = ctx.isSelected(target);
|
|
2078
|
+
marks.push({ kind: "circle", key: `point-${s.id}-${i}`, role: "point", cx: x, cy: yOf(v), r: 4, fill: color, stroke: theme.surface, strokeWidth: 1.5, target, selected: selected || catSelected || ctx.seriesSelected(s.id) });
|
|
2079
|
+
if (selected) marks.push({ kind: "circle", key: `point-${s.id}-${i}-selection`, role: "selection", cx: x, cy: yOf(v), r: 7.5, fill: "none", stroke: theme.selection, strokeWidth: 1.5 });
|
|
2080
|
+
labels.push({ i, y: yOf(v) });
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
let moved = 0;
|
|
2084
|
+
for (const [labels, x, anchor, text, width] of [[leftLabels, xL - 8, "end", leftText, leftW], [rightLabels, xR + 8, "start", rightText, rightW]]) {
|
|
2085
|
+
labels.sort((p, q) => p.y - q.y);
|
|
2086
|
+
const ys = dodge(labels.map((l) => l.y), fs * 1.15, plot.y - fs / 2, plot.y + plot.height + fs / 2);
|
|
2087
|
+
labels.forEach((l, k) => {
|
|
2088
|
+
if (Math.abs(ys[k] - l.y) > 0.5) moved++;
|
|
2089
|
+
const target = { kind: "category", index: l.i };
|
|
2090
|
+
const selected = ctx.isSelected(target);
|
|
2091
|
+
marks.push({ kind: "text", key: `label-slope-${anchor === "end" ? "left" : "right"}-${l.i}`, role: "data-label", x, y: ys[k], text: truncate(text(l.i), width, fs, measure), anchor, baseline: "middle", size: fs, fill: selected ? theme.selection : theme.text, weight: selected ? 600 : void 0, target, selected });
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
if (moved) ctx.warn("labels-truncated", `${moved} slope label${moved === 1 ? " was" : "s were"} moved off its point so the labels do not overlap.`);
|
|
2095
|
+
return { plot, valueScale: { axis: "y", domain: [scale.min, scale.max], range: [plot.y + plot.height, plot.y], ticks: scale.ticks } };
|
|
2096
|
+
}
|
|
2097
|
+
function layoutLikert(ctx, area, visible) {
|
|
2098
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2099
|
+
const n = visible.length;
|
|
2100
|
+
const side = Math.floor(n / 2);
|
|
2101
|
+
const neutral = n % 2 === 1 ? side : -1;
|
|
2102
|
+
const separate = neutral >= 0 && spec.likertNeutral === "separate";
|
|
2103
|
+
if (spec.likertNeutral === "separate" && neutral < 0) ctx.warn("type-fallback", "A separate neutral column needs an odd number of answers; the middle answer is not set apart.");
|
|
2104
|
+
const shares = spec.categories.map((_, i) => {
|
|
2105
|
+
const raw = visible.map((s) => {
|
|
2106
|
+
const v = s.values[i];
|
|
2107
|
+
return v !== null && Number.isFinite(v) ? Math.max(0, v) : 0;
|
|
2108
|
+
});
|
|
2109
|
+
const total = raw.reduce((t, v) => t + v, 0);
|
|
2110
|
+
return raw.map((v) => total ? v / total : 0);
|
|
2111
|
+
});
|
|
2112
|
+
const sum = (p, from, to) => p.slice(from, to).reduce((t, v) => t + v, 0);
|
|
2113
|
+
const negOf = (p) => sum(p, 0, side) + (neutral >= 0 && !separate ? p[neutral] / 2 : 0);
|
|
2114
|
+
const posOf = (p) => sum(p, n - side, n) + (neutral >= 0 && !separate ? p[neutral] / 2 : 0);
|
|
2115
|
+
const rows = spec.categories.map((_, i) => i);
|
|
2116
|
+
if (spec.likertSort === "positive") rows.sort((p, q) => sum(shares[q], n - side, n) - sum(shares[p], n - side, n));
|
|
2117
|
+
else if (spec.likertSort === "negative") rows.sort((p, q) => sum(shares[q], 0, side) - sum(shares[p], 0, side));
|
|
2118
|
+
const extent = Math.max(0.01, ...shares.map((p) => Math.max(negOf(p), posOf(p))));
|
|
2119
|
+
const labelW = spec.xAxis.show ? Math.min(area.width * 0.35, Math.max(0, ...spec.categories.map((c) => measure(c, fs)))) + 10 : 4;
|
|
2120
|
+
const neutralW = separate ? Math.max(40, area.width * 0.18) : 0;
|
|
2121
|
+
const axisH = spec.yAxis.show ? fs * 1.6 : 2;
|
|
2122
|
+
const headH = separate ? fs * 1.5 : 0;
|
|
2123
|
+
const plot = { x: area.x + labelW, y: area.y + headH, width: Math.max(1, area.width - labelW - (separate ? neutralW + 14 : 0)), height: Math.max(1, area.height - axisH - headH) };
|
|
2124
|
+
const half = axisScale(0, extent, plot.width / 2, { ...spec.yAxis, min: 0, max: null, scale: void 0, reverse: void 0 });
|
|
2125
|
+
const m = half.max;
|
|
2126
|
+
const xOf = (v) => plot.x + (v + m) / (2 * m) * plot.width;
|
|
2127
|
+
const percent = { style: "percent", decimals: spec.yAxis.format.decimals };
|
|
2128
|
+
const ticks = [...half.ticks.slice(1).map((t) => -t).reverse(), ...half.ticks];
|
|
2129
|
+
const axisTarget = { kind: "axis", axis: "y" };
|
|
2130
|
+
ticks.forEach((t, k) => {
|
|
2131
|
+
const x = xOf(t);
|
|
2132
|
+
if (spec.yAxis.gridlines && t !== 0) marks.push({ kind: "line", key: `grid-y-${k}`, role: "grid", x1: x, y1: plot.y, x2: x, y2: plot.y + plot.height, stroke: theme.grid, strokeWidth: 1 });
|
|
2133
|
+
if (spec.yAxis.show) marks.push({ kind: "text", key: `tick-y-${k}`, role: "tick-label", x, y: plot.y + plot.height + 6, text: formatChartNumber(Math.abs(t), percent, _nullishCoalesce(percent.decimals, () => ( stepDecimals(half.step * 100)))), anchor: "middle", baseline: "top", size: fs, fill: theme.mutedText, target: axisTarget });
|
|
2134
|
+
});
|
|
2135
|
+
const band = plot.height / Math.max(1, rows.length);
|
|
2136
|
+
const barH = Math.max(2, band * (1 - Math.min(0.9, spec.gapWidth)));
|
|
2137
|
+
const neutralMax = separate ? Math.max(0.01, ...shares.map((p) => p[neutral])) : 1;
|
|
2138
|
+
const neutralX = plot.x + plot.width + 14;
|
|
2139
|
+
if (separate) {
|
|
2140
|
+
const s = visible[neutral];
|
|
2141
|
+
marks.push({ kind: "text", key: "likert-neutral-title", role: "axis-title", x: neutralX, y: area.y, text: truncate(s.name, neutralW, fs, measure), anchor: "start", baseline: "top", size: fs, weight: 600, fill: theme.mutedText, target: { kind: "series", seriesId: s.id } });
|
|
2142
|
+
}
|
|
2143
|
+
let hidden = 0;
|
|
2144
|
+
rows.forEach((i, r) => {
|
|
2145
|
+
const p = shares[i];
|
|
2146
|
+
const y = plot.y + r * band + (band - barH) / 2;
|
|
2147
|
+
const catTarget = { kind: "category", index: i };
|
|
2148
|
+
const catSelected = ctx.isSelected(catTarget);
|
|
2149
|
+
if (spec.xAxis.show)
|
|
2150
|
+
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: plot.x - 8, y: y + barH / 2, text: truncate(spec.categories[i], labelW - 10, fs, measure), anchor: "end", baseline: "middle", size: fs, fill: catSelected ? theme.selection : theme.mutedText, target: catTarget, selected: catSelected });
|
|
2151
|
+
const segment = (j, x0, x1, key) => {
|
|
2152
|
+
const s = visible[j];
|
|
2153
|
+
if (x1 - x0 <= 0) return;
|
|
2154
|
+
const target = { kind: "point", seriesId: s.id, index: i };
|
|
2155
|
+
const selected = ctx.isSelected(target);
|
|
2156
|
+
const fill = _optionalChain([s, 'access', _47 => _47.pointColors, 'optionalAccess', _48 => _48[i]]) || _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(s));
|
|
2157
|
+
const rect = { x: x0, y, width: x1 - x0, height: barH };
|
|
2158
|
+
marks.push({ kind: "rect", key, role: "bar", ...rect, fill, target, selected: selected || ctx.seriesSelected(s.id) || catSelected });
|
|
2159
|
+
if (selected) ctx.outline(`${key}-selection`, { x: rect.x - 2, y: rect.y - 2, width: rect.width + 4, height: rect.height + 4 });
|
|
2160
|
+
if (!(_nullishCoalesce(s.dataLabels, () => ( spec.dataLabels)))) return;
|
|
2161
|
+
const text = formatChartNumber(p[j], percent, _nullishCoalesce(percent.decimals, () => ( 0)));
|
|
2162
|
+
if (measure(text, fs - 1) + 6 > rect.width || barH < fs) {
|
|
2163
|
+
hidden++;
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
marks.push({ kind: "text", key: `label-${s.id}-${i}`, role: "data-label", x: rect.x + rect.width / 2, y: y + barH / 2, text, anchor: "middle", baseline: "middle", size: fs - 1, fill: contrastingTextColor(fill), target });
|
|
2167
|
+
};
|
|
2168
|
+
let cursor = -negOf(p);
|
|
2169
|
+
visible.forEach((s, j) => {
|
|
2170
|
+
if (separate && j === neutral) return;
|
|
2171
|
+
segment(j, xOf(cursor), xOf(cursor + p[j]), `bar-${s.id}-${i}`);
|
|
2172
|
+
cursor += p[j];
|
|
2173
|
+
});
|
|
2174
|
+
if (separate) segment(neutral, neutralX, neutralX + p[neutral] / neutralMax * neutralW, `bar-${visible[neutral].id}-${i}`);
|
|
2175
|
+
});
|
|
2176
|
+
marks.push({ kind: "line", key: "axis-likert-centre", role: "axis", x1: xOf(0), y1: plot.y, x2: xOf(0), y2: plot.y + plot.height, stroke: theme.axis, strokeWidth: 1.25, target: axisTarget });
|
|
2177
|
+
if (hidden) ctx.warn("data-labels-hidden", `${hidden} data label${hidden === 1 ? "" : "s"} did not fit and ${hidden === 1 ? "is" : "are"} hidden.`);
|
|
2178
|
+
return { plot, percent: true };
|
|
2179
|
+
}
|
|
2180
|
+
function layoutParliament(ctx, area, series) {
|
|
2181
|
+
const { spec, theme, marks, fs } = ctx;
|
|
2182
|
+
const seats = spec.categories.map((_, i) => {
|
|
2183
|
+
const v = series.values[i];
|
|
2184
|
+
return v !== null && Number.isFinite(v) ? Math.max(0, Math.round(v)) : 0;
|
|
2185
|
+
});
|
|
2186
|
+
if (series.values.some((v) => v !== null && Number.isFinite(v) && (v < 0 || !Number.isInteger(v)))) ctx.warn("values-skipped", "Seats are whole numbers of 0 or more; other values were rounded.");
|
|
2187
|
+
const total = seats.reduce((t, v) => t + v, 0);
|
|
2188
|
+
const empty = (text) => {
|
|
2189
|
+
marks.push({ kind: "text", key: "empty", role: "empty", x: area.x + area.width / 2, y: area.y + area.height / 2, text, anchor: "middle", baseline: "middle", size: fs + 1, fill: theme.mutedText });
|
|
2190
|
+
return { plot: area };
|
|
2191
|
+
};
|
|
2192
|
+
if (!total) return empty("No seats");
|
|
2193
|
+
if (total > 2e3) return empty("Too many seats to draw (more than 2,000)");
|
|
2194
|
+
const arc = spec.parliamentArc * Math.PI / 180;
|
|
2195
|
+
const start = Math.PI / 2 + arc / 2;
|
|
2196
|
+
const angles = [start, start - arc, ...[0, 0.5, 1, 1.5, 2, 2.5].map((k) => k * Math.PI).filter((t) => t < start && t > start - arc)];
|
|
2197
|
+
const xs = [0, ...angles.map(Math.cos)];
|
|
2198
|
+
const ys = [0, ...angles.map(Math.sin)];
|
|
2199
|
+
const pad = fs * 1.2;
|
|
2200
|
+
const radius = Math.max(8, Math.min((area.width - pad * 2) / (Math.max(...xs) - Math.min(...xs)), (area.height - pad * 2) / (Math.max(...ys) - Math.min(...ys))));
|
|
2201
|
+
const cx = area.x + area.width / 2 - (Math.max(...xs) + Math.min(...xs)) / 2 * radius;
|
|
2202
|
+
const cy = area.y + area.height / 2 + (Math.max(...ys) + Math.min(...ys)) / 2 * radius;
|
|
2203
|
+
const inner = radius * 0.4;
|
|
2204
|
+
let rows = 1;
|
|
2205
|
+
for (; rows < 60; rows++) {
|
|
2206
|
+
const gap2 = (radius - inner) / rows;
|
|
2207
|
+
const length = Array.from({ length: rows }, (_, k) => (inner + gap2 * (k + 0.5)) * arc).reduce((t, l) => t + l, 0);
|
|
2208
|
+
if (length / total >= gap2 * 0.9) break;
|
|
2209
|
+
}
|
|
2210
|
+
const gap = (radius - inner) / rows;
|
|
2211
|
+
const radii = Array.from({ length: rows }, (_, k) => inner + gap * (k + 0.5));
|
|
2212
|
+
const lengths = radii.map((r) => r * arc);
|
|
2213
|
+
const all = lengths.reduce((t, l) => t + l, 0);
|
|
2214
|
+
const exact = lengths.map((l) => l / all * total);
|
|
2215
|
+
const counts = exact.map(Math.floor);
|
|
2216
|
+
exact.map((e, k) => ({ k, rest: e - counts[k] })).sort((p, q) => q.rest - p.rest).slice(0, total - counts.reduce((t, c) => t + c, 0)).forEach(({ k }) => counts[k]++);
|
|
2217
|
+
const places = [];
|
|
2218
|
+
radii.forEach((r, k) => {
|
|
2219
|
+
for (let j = 0; j < counts[k]; j++) places.push({ angle: counts[k] === 1 ? Math.PI / 2 : start - arc * (j + 0.5) / counts[k], r });
|
|
2220
|
+
});
|
|
2221
|
+
places.sort((p, q) => q.angle - p.angle || p.r - q.r);
|
|
2222
|
+
const dot = Math.max(1, Math.min(gap, all / total) * 0.42);
|
|
2223
|
+
let next = 0;
|
|
2224
|
+
seats.forEach((count, i) => {
|
|
2225
|
+
const target = { kind: "point", seriesId: series.id, index: i };
|
|
2226
|
+
const selected = ctx.isSelected(target) || ctx.isSelected({ kind: "category", index: i }) || ctx.seriesSelected(series.id);
|
|
2227
|
+
const fill = sliceColor(ctx, series, i);
|
|
2228
|
+
for (let k = 0; k < count; k++, next++) {
|
|
2229
|
+
const { angle, r } = places[next];
|
|
2230
|
+
marks.push({ kind: "circle", key: `seat-${i}-${k}`, role: "point", cx: cx + r * Math.cos(angle), cy: cy - r * Math.sin(angle), r: dot, fill, ...selected ? { stroke: theme.selection, strokeWidth: 1.5 } : {}, target, selected });
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
const majority = _nullishCoalesce(spec.parliamentMajority, () => ( Math.floor(total / 2) + 1));
|
|
2234
|
+
if (majority <= total) {
|
|
2235
|
+
const angle = start - arc * (majority / total);
|
|
2236
|
+
const r0 = inner - gap * 0.3;
|
|
2237
|
+
const r1 = radius + gap * 0.3;
|
|
2238
|
+
marks.push({ kind: "line", key: "majority-line", role: "reference-line", x1: cx + r0 * Math.cos(angle), y1: cy - r0 * Math.sin(angle), x2: cx + r1 * Math.cos(angle), y2: cy - r1 * Math.sin(angle), stroke: theme.text, strokeWidth: 1.25, dash: "4 3", opacity: 0.8 });
|
|
2239
|
+
const lr = r1 + fs * 0.4;
|
|
2240
|
+
const lx = cx + lr * Math.cos(angle);
|
|
2241
|
+
const ly = cy - lr * Math.sin(angle);
|
|
2242
|
+
marks.push({ kind: "text", key: "majority-label", role: "reference-label", x: lx, y: ly, text: `Majority ${majority}`, anchor: Math.abs(Math.cos(angle)) < 0.25 ? "middle" : Math.cos(angle) > 0 ? "start" : "end", baseline: Math.sin(angle) >= 0 ? "bottom" : "top", size: fs - 1, weight: 500, fill: theme.text });
|
|
2243
|
+
} else ctx.warn("values-skipped", `The majority of ${majority} is more than the ${total} seats; the majority line is not drawn.`);
|
|
2244
|
+
if (inner > fs * 2.2) {
|
|
2245
|
+
const size = Math.min(theme.titleSize + 8, inner * 0.6);
|
|
2246
|
+
const ty = spec.parliamentArc <= 200 ? cy - size * 0.2 : cy;
|
|
2247
|
+
const totalTarget = { kind: "series", seriesId: series.id };
|
|
2248
|
+
marks.push({ kind: "text", key: "parliament-total", role: "data-label", x: cx, y: ty, text: formatChartNumber(total, {}), anchor: "middle", baseline: spec.parliamentArc <= 200 ? "bottom" : "middle", size, weight: 700, fill: theme.text, target: totalTarget });
|
|
2249
|
+
marks.push({ kind: "text", key: "parliament-caption", role: "data-label", x: cx, y: spec.parliamentArc <= 200 ? ty + 2 : ty + size * 0.6, text: spec.parliamentLabel || "seats", anchor: "middle", baseline: "top", size: fs, fill: theme.mutedText, target: totalTarget });
|
|
2250
|
+
}
|
|
2251
|
+
return { plot: { x: cx - radius, y: cy - radius, width: radius * 2, height: radius * 2 } };
|
|
2252
|
+
}
|
|
2253
|
+
function treeColor(ctx, series, node, group, base, shade) {
|
|
2254
|
+
if (node.color) return node.color;
|
|
2255
|
+
if (base === null) return _optionalChain([series, 'optionalAccess', _49 => _49.pointColors, 'optionalAccess', _50 => _50[group]]) || _chunkR3P2M7HUcjs.chartSliceColor.call(void 0, ctx.spec, group);
|
|
2256
|
+
return _chunkR3P2M7HUcjs.shadeChartColor.call(void 0, base, shade);
|
|
2257
|
+
}
|
|
2258
|
+
function layoutTreemapTree(ctx, area, series) {
|
|
2259
|
+
const { spec, marks, theme, fs, measure } = ctx;
|
|
2260
|
+
const format = _nullishCoalesce(_optionalChain([series, 'optionalAccess', _51 => _51.format]), () => ( spec.yAxis.format));
|
|
2261
|
+
const labelsOn = _nullishCoalesce(_optionalChain([series, 'optionalAccess', _52 => _52.dataLabels]), () => ( true));
|
|
2262
|
+
let unlabelled = 0;
|
|
2263
|
+
const draw = (nodes, rect, key, group, base) => {
|
|
2264
|
+
const rects = chartSquarify(nodes.map(_chunkR3P2M7HUcjs.chartTreeTotal), rect);
|
|
2265
|
+
nodes.forEach((node, k) => {
|
|
2266
|
+
const r = rects[k];
|
|
2267
|
+
if (r.width <= 0.5 || r.height <= 0.5) return;
|
|
2268
|
+
const g = _nullishCoalesce(group, () => ( k));
|
|
2269
|
+
const target = { kind: "category", index: g };
|
|
2270
|
+
const selected = ctx.isSelected(target) || ctx.isSelected({ kind: "point", seriesId: _nullishCoalesce(_optionalChain([series, 'optionalAccess', _53 => _53.id]), () => ( "")), index: g });
|
|
2271
|
+
const id = `${key}-${k}`;
|
|
2272
|
+
const fill = treeColor(ctx, series, node, g, base, base === null ? 0 : (k % 4 - 1.5) * 0.14);
|
|
2273
|
+
if (node.children) {
|
|
2274
|
+
const header = r.height > fs * 3.2 && r.width > fs * 3 ? fs * 1.5 : 0;
|
|
2275
|
+
marks.push({ kind: "rect", key: `group-${id}`, role: "cell", ...r, fill, stroke: selected ? theme.selection : theme.surface, strokeWidth: selected ? 2.5 : 2, target, selected });
|
|
2276
|
+
if (header) marks.push({ kind: "text", key: `label-group-${id}`, role: "data-label", x: r.x + 5, y: r.y + 4, text: truncate(node.name, r.width - 10, fs, measure), anchor: "start", baseline: "top", size: fs, weight: 700, fill: contrastingTextColor(fill), target });
|
|
2277
|
+
const inner = { x: r.x + 2, y: r.y + (header || 2), width: Math.max(0, r.width - 4), height: Math.max(0, r.height - (header || 2) - 2) };
|
|
2278
|
+
draw(node.children, inner, id, g, _nullishCoalesce(base, () => ( fill)));
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
marks.push({ kind: "rect", key: `cell-${id}`, role: "cell", ...r, fill, stroke: selected ? theme.selection : theme.surface, strokeWidth: selected ? 2.5 : 1, target, selected });
|
|
2282
|
+
if (!labelsOn) return;
|
|
2283
|
+
const room = r.width - 8;
|
|
2284
|
+
if (room < fs * 2 || r.height < fs * 1.5) {
|
|
2285
|
+
unlabelled++;
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const color = contrastingTextColor(fill);
|
|
2289
|
+
marks.push({ kind: "text", key: `label-cell-${id}`, role: "data-label", x: r.x + 4, y: r.y + 4, text: truncate(node.name, room, fs, measure), anchor: "start", baseline: "top", size: fs, weight: 600, fill: color, target });
|
|
2290
|
+
if (r.height >= fs * 3) marks.push({ kind: "text", key: `label-value-${id}`, role: "data-label", x: r.x + 4, y: r.y + 6 + fs * 1.2, text: truncate(formatChartNumber(_nullishCoalesce(node.value, () => ( 0)), format), room, fs - 1, measure), anchor: "start", baseline: "top", size: fs - 1, fill: color, target });
|
|
2291
|
+
});
|
|
2292
|
+
};
|
|
2293
|
+
draw(spec.tree, area, "n", null, null);
|
|
2294
|
+
if (unlabelled) ctx.warn("data-labels-hidden", `${unlabelled} treemap cell${unlabelled === 1 ? " is" : "s are"} too small for a label.`);
|
|
2295
|
+
return { plot: area };
|
|
2296
|
+
}
|
|
2297
|
+
function layoutSunburst(ctx, area, series) {
|
|
2298
|
+
const { spec, marks, theme, fs, measure } = ctx;
|
|
2299
|
+
const tree = spec.tree.length ? spec.tree : _chunkR3P2M7HUcjs.chartTreeFromSeries.call(void 0, spec.categories, _nullishCoalesce(_optionalChain([series, 'optionalAccess', _54 => _54.values]), () => ( [])));
|
|
2300
|
+
const indexOf = new Map(spec.categories.map((c, i) => [c.trim() || `${i + 1}`, i]));
|
|
2301
|
+
const depth = _chunkR3P2M7HUcjs.chartTreeDepth.call(void 0, tree);
|
|
2302
|
+
const total = tree.reduce((t, n) => t + _chunkR3P2M7HUcjs.chartTreeTotal.call(void 0, n), 0);
|
|
2303
|
+
const cx = area.x + area.width / 2;
|
|
2304
|
+
const cy = area.y + area.height / 2;
|
|
2305
|
+
const outer = Math.max(8, Math.min(area.width, area.height) / 2 - 4);
|
|
2306
|
+
const plot = { x: cx - outer, y: cy - outer, width: outer * 2, height: outer * 2 };
|
|
2307
|
+
if (!(total > 0)) {
|
|
2308
|
+
marks.push({ kind: "text", key: "empty", role: "empty", x: cx, y: cy, text: "No positive values", anchor: "middle", baseline: "middle", size: fs + 1, fill: theme.mutedText });
|
|
2309
|
+
return { plot };
|
|
2310
|
+
}
|
|
2311
|
+
const hole = outer * 0.28;
|
|
2312
|
+
const ring = (outer - hole) / Math.max(1, depth);
|
|
2313
|
+
const format = _nullishCoalesce(_optionalChain([series, 'optionalAccess', _55 => _55.format]), () => ( spec.yAxis.format));
|
|
2314
|
+
let unlabelled = 0;
|
|
2315
|
+
const walk = (nodes, start, sweep, level, key, group, base) => {
|
|
2316
|
+
const sum = nodes.reduce((t, n) => t + _chunkR3P2M7HUcjs.chartTreeTotal.call(void 0, n), 0);
|
|
2317
|
+
let at = start;
|
|
2318
|
+
nodes.forEach((node, k) => {
|
|
2319
|
+
const value = _chunkR3P2M7HUcjs.chartTreeTotal.call(void 0, node);
|
|
2320
|
+
if (!(value > 0) || !sum) return;
|
|
2321
|
+
const a0 = at;
|
|
2322
|
+
const a1 = at + sweep * value / sum;
|
|
2323
|
+
at = a1;
|
|
2324
|
+
const g = _nullishCoalesce(_nullishCoalesce(group, () => ( indexOf.get(node.name))), () => ( k));
|
|
2325
|
+
const target = { kind: "category", index: g };
|
|
2326
|
+
const selected = ctx.isSelected(target) || ctx.isSelected({ kind: "point", seriesId: _nullishCoalesce(_optionalChain([series, 'optionalAccess', _56 => _56.id]), () => ( "")), index: g });
|
|
2327
|
+
const fill = treeColor(ctx, series, node, g, base, 0.22 * level);
|
|
2328
|
+
const id = `${key}-${k}`;
|
|
2329
|
+
const r0 = hole + ring * level;
|
|
2330
|
+
const r1 = r0 + ring;
|
|
2331
|
+
marks.push({ kind: "path", key: `slice-${id}`, role: "slice", d: arcPath(cx, cy, r1, r0, a0, a1), fill, stroke: selected ? theme.selection : theme.surface, strokeWidth: selected ? 2 : 1, target, selected });
|
|
2332
|
+
const mid = (a0 + a1) / 2;
|
|
2333
|
+
const rm = (r0 + r1) / 2;
|
|
2334
|
+
const text = node.name;
|
|
2335
|
+
const width = measure(text, fs - 1);
|
|
2336
|
+
if (ring >= fs * 1.3 && 2 * rm * Math.sin(Math.min(Math.PI / 2, (a1 - a0) / 2)) >= width + 6) marks.push({ kind: "text", key: `label-slice-${id}`, role: "data-label", x: cx + rm * Math.sin(mid), y: cy - rm * Math.cos(mid), text, anchor: "middle", baseline: "middle", size: fs - 1, weight: level === 0 ? 600 : void 0, fill: contrastingTextColor(fill), target });
|
|
2337
|
+
else unlabelled++;
|
|
2338
|
+
if (node.children) walk(node.children, a0, a1 - a0, level + 1, id, g, _nullishCoalesce(base, () => ( fill)));
|
|
2339
|
+
});
|
|
2340
|
+
};
|
|
2341
|
+
walk(tree, 0, Math.PI * 2, 0, "n", null, null);
|
|
2342
|
+
if (unlabelled) ctx.warn("data-labels-hidden", `${unlabelled} sunburst arc${unlabelled === 1 ? " is" : "s are"} too narrow for a label.`);
|
|
2343
|
+
if (hole > fs * 2) {
|
|
2344
|
+
const size = Math.min(theme.titleSize + 4, hole * 0.45);
|
|
2345
|
+
const centre = series ? { kind: "series", seriesId: series.id } : void 0;
|
|
2346
|
+
const caption = spec.sunburstLabel;
|
|
2347
|
+
marks.push({ kind: "text", key: "sunburst-total", role: "data-label", x: cx, y: caption ? cy - 2 : cy, text: formatChartNumber(total, format), anchor: "middle", baseline: caption ? "bottom" : "middle", size, weight: 700, fill: theme.text, ...centre ? { target: centre } : {} });
|
|
2348
|
+
if (caption) marks.push({ kind: "text", key: "sunburst-caption", role: "data-label", x: cx, y: cy + 2, text: truncate(caption, hole * 1.8, fs - 1, measure), anchor: "middle", baseline: "top", size: fs - 1, fill: theme.mutedText, ...centre ? { target: centre } : {} });
|
|
2349
|
+
}
|
|
2350
|
+
return { plot };
|
|
2351
|
+
}
|
|
1846
2352
|
|
|
1847
2353
|
// src/chart/layout.ts
|
|
1848
2354
|
function legendItems(spec, ctx) {
|
|
1849
|
-
const family =
|
|
1850
|
-
if (spec.type === "funnel") return [];
|
|
1851
|
-
if (
|
|
2355
|
+
const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, spec);
|
|
2356
|
+
if (spec.type === "funnel" || spec.type === "slope") return [];
|
|
2357
|
+
if (_chunkR3P2M7HUcjs.chartSpecColorsByCategory.call(void 0, spec)) {
|
|
1852
2358
|
const first = spec.series.find((s) => !s.hidden);
|
|
2359
|
+
const seats = (i) => Math.max(0, Math.round(_nullishCoalesce(_optionalChain([first, 'optionalAccess', _57 => _57.values, 'access', _58 => _58[i]]), () => ( 0))));
|
|
1853
2360
|
return spec.categories.map((c, i) => ({
|
|
1854
|
-
label: c,
|
|
1855
|
-
color: _optionalChain([first, 'optionalAccess',
|
|
2361
|
+
label: spec.type === "parliament" ? `${c} ${seats(i)}` : c,
|
|
2362
|
+
color: _optionalChain([first, 'optionalAccess', _59 => _59.pointColors, 'optionalAccess', _60 => _60[i]]) || _chunkR3P2M7HUcjs.chartSliceColor.call(void 0, spec, i),
|
|
1856
2363
|
target: { kind: "category", index: i },
|
|
1857
2364
|
faded: false,
|
|
1858
2365
|
selected: ctx.isSelected({ kind: "category", index: i }),
|
|
@@ -1866,17 +2373,17 @@ function legendItems(spec, ctx) {
|
|
|
1866
2373
|
return [
|
|
1867
2374
|
{ label: "Increase", color: ctx.theme.positive, target, faded: false, selected: false, shape: "square" },
|
|
1868
2375
|
{ label: "Decrease", color: ctx.theme.negative, target, faded: false, selected: false, shape: "square" },
|
|
1869
|
-
{ label: "Total", color:
|
|
2376
|
+
{ label: "Total", color: _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(first)), target, faded: false, selected: false, shape: "square" }
|
|
1870
2377
|
];
|
|
1871
2378
|
}
|
|
1872
2379
|
if (family === "box") return [];
|
|
1873
|
-
const trends = trendlineLegendItems(spec, (i) =>
|
|
2380
|
+
const trends = trendlineLegendItems(spec, (i) => _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, i));
|
|
1874
2381
|
const items = [];
|
|
1875
2382
|
spec.series.forEach((s, i) => {
|
|
1876
|
-
const type =
|
|
2383
|
+
const type = _chunkR3P2M7HUcjs.chartSeriesType.call(void 0, spec, s);
|
|
1877
2384
|
items.push({
|
|
1878
2385
|
label: s.name,
|
|
1879
|
-
color:
|
|
2386
|
+
color: _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, i),
|
|
1880
2387
|
target: { kind: "series", seriesId: s.id },
|
|
1881
2388
|
faded: !!s.hidden,
|
|
1882
2389
|
selected: ctx.seriesSelected(s.id),
|
|
@@ -1889,7 +2396,7 @@ function legendItems(spec, ctx) {
|
|
|
1889
2396
|
function layoutChart(spec, options) {
|
|
1890
2397
|
const width = Math.max(1, Number.isFinite(options.width) ? options.width : 1);
|
|
1891
2398
|
const height = Math.max(1, Number.isFinite(options.height) ? options.height : 1);
|
|
1892
|
-
const theme = { ...
|
|
2399
|
+
const theme = { ..._chunkR3P2M7HUcjs.DEFAULT_CHART_THEME, ...options.theme };
|
|
1893
2400
|
const selection = _nullishCoalesce(options.selection, () => ( null));
|
|
1894
2401
|
const marks = [];
|
|
1895
2402
|
const warnings = [];
|
|
@@ -1905,7 +2412,7 @@ function layoutChart(spec, options) {
|
|
|
1905
2412
|
warnings,
|
|
1906
2413
|
selection,
|
|
1907
2414
|
isSelected: (target) => sameChartSelection(selection, target),
|
|
1908
|
-
seriesSelected: (id) => _optionalChain([selection, 'optionalAccess',
|
|
2415
|
+
seriesSelected: (id) => _optionalChain([selection, 'optionalAccess', _61 => _61.kind]) === "series" && selection.seriesId === id,
|
|
1909
2416
|
outline: (key, box) => marks.push({ kind: "rect", key, role: "selection", ...box, fill: "none", stroke: theme.selection, strokeWidth: 1, radius: 3 }),
|
|
1910
2417
|
warn: (code, message) => {
|
|
1911
2418
|
if (!warnings.some((w) => w.code === code && w.message === message)) warnings.push({ code, message });
|
|
@@ -1928,15 +2435,22 @@ function layoutChart(spec, options) {
|
|
|
1928
2435
|
if (spec.title) titleMark("title", spec.title, theme.titleSize, 600, theme.text);
|
|
1929
2436
|
if (spec.subtitle) titleMark("subtitle", spec.subtitle, fs + 1, 400, theme.mutedText);
|
|
1930
2437
|
if (spec.title || spec.subtitle) top += 6;
|
|
1931
|
-
const family =
|
|
2438
|
+
const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, spec);
|
|
1932
2439
|
const visible = spec.series.filter((s) => !s.hidden);
|
|
1933
2440
|
const items = legendItems(spec, ctx);
|
|
1934
|
-
let area = { x: pad, y: top, width: width - pad * 2, height: height - top - pad };
|
|
2441
|
+
let area = { x: pad, y: top, width: width - pad * 2, height: height - top - pad - noteHeight(spec, fs) };
|
|
1935
2442
|
const seriesLegend = family === "cartesian" || family === "xy" || family === "radar" || family === "histogram";
|
|
1936
2443
|
const hasTrend = items.some((i) => i.shape === "dash");
|
|
1937
|
-
const showLegend = spec.legend.show && items.length > 0 && (!seriesLegend || spec.series.length > 1 || spec.series.some((s) => s.hidden) || hasTrend);
|
|
2444
|
+
const showLegend = spec.legend.show && items.length > 0 && (!seriesLegend || _chunkR3P2M7HUcjs.chartVariesColors.call(void 0, spec) || spec.series.length > 1 || spec.series.some((s) => s.hidden) || hasTrend);
|
|
1938
2445
|
if (showLegend) area = layoutLegend(items, spec.legend.position, area, ctx, ctx.isSelected({ kind: "legend" }));
|
|
1939
2446
|
const finish = (result) => {
|
|
2447
|
+
if (result.plot.width > 0) {
|
|
2448
|
+
if (chartAnnotationFamily(_chunkR3P2M7HUcjs.chartFamily.call(void 0, spec))) drawCallouts(ctx, result.plot);
|
|
2449
|
+
else if (spec.referenceLines.length || spec.referenceBands.length || spec.series.some((s) => s.callouts))
|
|
2450
|
+
ctx.warn("annotations-skipped", `Reference lines, bands and callouts are drawn on bar, line, area, scatter and bubble charts; this ${spec.type} chart leaves them out.`);
|
|
2451
|
+
}
|
|
2452
|
+
applyHighlight(ctx);
|
|
2453
|
+
drawNote(ctx, pad);
|
|
1940
2454
|
if (options.readout) addReadout(ctx, options.readout, result.plot);
|
|
1941
2455
|
const kept = finalizeMarks(marks, width, height);
|
|
1942
2456
|
if (kept.length < marks.length) ctx.warn("values-skipped", `${marks.length - kept.length} mark${marks.length - kept.length === 1 ? " was" : "s were"} left out: their values are not finite numbers.`);
|
|
@@ -1978,7 +2492,19 @@ function layoutChart(spec, options) {
|
|
|
1978
2492
|
case "funnel":
|
|
1979
2493
|
return finish(layoutFunnel(ctx, area, visible[0]));
|
|
1980
2494
|
case "treemap":
|
|
1981
|
-
return finish(layoutTreemap(ctx, area, visible[0]));
|
|
2495
|
+
return finish(spec.tree.length ? layoutTreemapTree(ctx, area, spec.series[0]) : layoutTreemap(ctx, area, visible[0]));
|
|
2496
|
+
case "sunburst":
|
|
2497
|
+
return finish(layoutSunburst(ctx, area, spec.tree.length ? spec.series[0] : visible[0]));
|
|
2498
|
+
case "slope":
|
|
2499
|
+
if (visible.length < 2) {
|
|
2500
|
+
ctx.warn("type-fallback", "A slope chart needs two series (before and after); it is drawn as a line chart until then.");
|
|
2501
|
+
return finish(layoutCartesian(ctx, area, visible));
|
|
2502
|
+
}
|
|
2503
|
+
return finish(layoutSlope(ctx, area, visible));
|
|
2504
|
+
case "likert":
|
|
2505
|
+
return finish(layoutLikert(ctx, area, visible));
|
|
2506
|
+
case "parliament":
|
|
2507
|
+
return finish(layoutParliament(ctx, area, visible[0]));
|
|
1982
2508
|
case "xy":
|
|
1983
2509
|
return finish(layoutXy(ctx, area, visible));
|
|
1984
2510
|
default:
|
|
@@ -2062,9 +2588,9 @@ function layoutLegend(items, position, area, ctx, legendSelected) {
|
|
|
2062
2588
|
function addReadout(ctx, readout, plot) {
|
|
2063
2589
|
const { spec, marks, theme, fs, measure } = ctx;
|
|
2064
2590
|
const series = spec.series.find((s) => s.id === readout.seriesId);
|
|
2065
|
-
const value = _optionalChain([series, 'optionalAccess',
|
|
2591
|
+
const value = _optionalChain([series, 'optionalAccess', _62 => _62.values, 'access', _63 => _63[readout.index]]);
|
|
2066
2592
|
if (!series || value === null || value === void 0) return;
|
|
2067
|
-
const mark = marks.find((m) => (m.role === "point" || m.role === "bar") && _optionalChain([m, 'access',
|
|
2593
|
+
const mark = marks.find((m) => (m.role === "point" || m.role === "bar") && _optionalChain([m, 'access', _64 => _64.target, 'optionalAccess', _65 => _65.kind]) === "point" && m.target.seriesId === readout.seriesId && m.target.index === readout.index);
|
|
2068
2594
|
if (!mark) return;
|
|
2069
2595
|
let ax;
|
|
2070
2596
|
let ay;
|
|
@@ -2076,8 +2602,8 @@ function addReadout(ctx, readout, plot) {
|
|
|
2076
2602
|
ay = mark.cy - mark.r;
|
|
2077
2603
|
} else return;
|
|
2078
2604
|
const axis = series.axis === "secondary" ? spec.yAxis2 : spec.yAxis;
|
|
2079
|
-
const x = _optionalChain([series, 'access',
|
|
2080
|
-
const text = x !== null && x !== void 0 &&
|
|
2605
|
+
const x = _optionalChain([series, 'access', _66 => _66.x, 'optionalAccess', _67 => _67[readout.index]]);
|
|
2606
|
+
const text = x !== null && x !== void 0 && _chunkR3P2M7HUcjs.chartFamily.call(void 0, spec) === "xy" ? `${formatChartNumber(x, spec.xAxis.format)}, ${formatChartNumber(value, axis.format)}` : `${_nullishCoalesce(spec.categories[readout.index], () => ( ""))}: ${formatChartNumber(value, _nullishCoalesce(series.format, () => ( axis.format)))}`;
|
|
2081
2607
|
const w = measure(text, fs, 600) + 10;
|
|
2082
2608
|
const h = fs + 8;
|
|
2083
2609
|
const bx = Math.max(plot.x, Math.min(plot.x + plot.width - w, ax - w / 2));
|
|
@@ -2126,7 +2652,7 @@ function chartXAtPoint(scene, point) {
|
|
|
2126
2652
|
if (!scale) return void 0;
|
|
2127
2653
|
return chartScaleFromPixel(point.x, scale);
|
|
2128
2654
|
}
|
|
2129
|
-
function snapChartValue(scene, value, step, ticks = _nullishCoalesce(_optionalChain([scene, 'access',
|
|
2655
|
+
function snapChartValue(scene, value, step, ticks = _nullishCoalesce(_optionalChain([scene, 'access', _68 => _68.valueScale, 'optionalAccess', _69 => _69.ticks]), () => ( [])), logBase) {
|
|
2130
2656
|
if (logBase && step === void 0) return value > 0 && Number.isFinite(value) ? Number(value.toPrecision(2)) : value;
|
|
2131
2657
|
const unit = _nullishCoalesce(step, () => ( (ticks.length > 1 ? (ticks[1] - ticks[0]) / 10 : 1)));
|
|
2132
2658
|
if (!(unit > 0)) return value;
|
|
@@ -2173,4 +2699,5 @@ function snapChartValue(scene, value, step, ticks = _nullishCoalesce(_optionalCh
|
|
|
2173
2699
|
|
|
2174
2700
|
|
|
2175
2701
|
|
|
2176
|
-
|
|
2702
|
+
|
|
2703
|
+
exports.parseChartDate = parseChartDate; exports.CHART_DATE_STEPS = CHART_DATE_STEPS; exports.formatChartDate = formatChartDate; exports.chartDateLabels = chartDateLabels; exports.compileChartFormatCode = compileChartFormatCode; exports.formatChartValueWithCode = formatChartValueWithCode; exports.chartSerialToDate = chartSerialToDate; exports.chartDateToSerial = chartDateToSerial; exports.niceStep = niceStep; exports.niceScale = niceScale; exports.stepDecimals = stepDecimals; exports.formatChartNumber = formatChartNumber; exports.linearMap = linearMap; exports.niceLogScale = niceLogScale; exports.chartScaleToPixel = chartScaleToPixel; exports.chartScaleFromPixel = chartScaleFromPixel; exports.estimateChartTextWidth = estimateChartTextWidth; exports.sameChartSelection = sameChartSelection; exports.contrastingTextColor = contrastingTextColor; exports.r2 = r2; exports.chartHistogram = chartHistogram; exports.chartQuantile = chartQuantile; exports.suggestChartReferences = suggestChartReferences; exports.chartBoxStats = chartBoxStats; exports.chartSquarify = chartSquarify; exports.decimateChartPoints = decimateChartPoints; exports.summarizeChartSeries = summarizeChartSeries; exports.fitChartTrendline = fitChartTrendline; exports.trendNumberText = trendNumberText; exports.chartMovingAverage = chartMovingAverage; exports.chartTrendlineName = chartTrendlineName; exports.chartSupportsTrendlines = chartSupportsTrendlines; exports.MAX_CHART_DATA_LABELS = MAX_CHART_DATA_LABELS; exports.MAX_CHART_LINE_POINTS = MAX_CHART_LINE_POINTS; exports.MAX_CHART_MARKERS = MAX_CHART_MARKERS; exports.layoutChart = layoutChart; exports.chartValueAtPoint = chartValueAtPoint; exports.chartXAtPoint = chartXAtPoint; exports.snapChartValue = snapChartValue;
|