@skdx/angular-charts 0.38.0 → 0.40.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/fesm2022/skdx-angular-charts-candlestick.mjs +9 -2
- package/fesm2022/skdx-angular-charts-candlestick.mjs.map +1 -1
- package/fesm2022/skdx-angular-charts-internal.mjs +476 -171
- package/fesm2022/skdx-angular-charts-internal.mjs.map +1 -1
- package/fesm2022/skdx-angular-charts.mjs.map +1 -1
- package/package.json +2 -2
- package/types/skdx-angular-charts-candlestick.d.ts +5 -2
- package/types/skdx-angular-charts-internal.d.ts +23 -9
- package/types/skdx-angular-charts.d.ts +1 -1
|
@@ -457,13 +457,29 @@ const size = (input, w = 600, h = 300) => ({
|
|
|
457
457
|
width: (input.width ?? w) - legendSide(input),
|
|
458
458
|
height: (input.height ?? h) - (legendOptions(input)?.position === 'bottom' ? LEGEND_HEIGHT : 0),
|
|
459
459
|
});
|
|
460
|
-
/**
|
|
461
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Vertical room the legend rows take above the plot, to shift the plot down by. Pass the legend
|
|
462
|
+
* items and the layout width to reserve every wrapped row; without them one row is assumed.
|
|
463
|
+
*/
|
|
464
|
+
const legendTop = (input, items, width) => {
|
|
462
465
|
const l = legendOptions(input);
|
|
463
|
-
|
|
466
|
+
if (!l || !(l.position === undefined || l.position === 'top'))
|
|
467
|
+
return 0;
|
|
468
|
+
const rows = items ? Math.max(1, legendRows(items, width ?? size(input).width, l)) : 1;
|
|
469
|
+
return rows * LEGEND_HEIGHT;
|
|
464
470
|
};
|
|
465
471
|
/** Average glyph width at the chart font size, for text-width estimates. */
|
|
466
472
|
const CHAR = 6.5;
|
|
473
|
+
/** Fit visible chart text while callers keep the complete string as a title or item label. */
|
|
474
|
+
function fitText(text, width) {
|
|
475
|
+
const chars = Array.from(text);
|
|
476
|
+
const capacity = Math.max(0, Math.floor(width / CHAR));
|
|
477
|
+
return chars.length <= capacity
|
|
478
|
+
? text
|
|
479
|
+
: capacity === 0
|
|
480
|
+
? ''
|
|
481
|
+
: `${chars.slice(0, capacity - 1).join('')}…`;
|
|
482
|
+
}
|
|
467
483
|
/** The series color for index `i`: an explicit override, else the chart palette, else the tokens. */
|
|
468
484
|
const colorOf = (input, i, override) => override ??
|
|
469
485
|
(input.palette && input.palette.length > 0
|
|
@@ -619,9 +635,29 @@ function placeLegend(layout) {
|
|
|
619
635
|
}
|
|
620
636
|
/** A tooltip box next to the pointer, kept inside the canvas; one line per entry. */
|
|
621
637
|
function tooltipShapes(x, y, text, width, height, placement = 'auto') {
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
const
|
|
638
|
+
const original = Array.isArray(text) ? text : [text];
|
|
639
|
+
const capacity = Math.max(1, Math.floor((width - 12) / CHAR));
|
|
640
|
+
const wrapped = original.flatMap((line) => {
|
|
641
|
+
const result = [];
|
|
642
|
+
let rest = Array.from(line);
|
|
643
|
+
while (rest.length > capacity) {
|
|
644
|
+
let end = rest.slice(0, capacity + 1).lastIndexOf(' ');
|
|
645
|
+
if (end < 1)
|
|
646
|
+
end = capacity;
|
|
647
|
+
result.push(rest.slice(0, end).join(''));
|
|
648
|
+
rest = rest.slice(end);
|
|
649
|
+
if (rest[0] === ' ')
|
|
650
|
+
rest.shift();
|
|
651
|
+
}
|
|
652
|
+
result.push(rest.join(''));
|
|
653
|
+
return result;
|
|
654
|
+
});
|
|
655
|
+
const maxLines = Math.max(1, Math.floor((height - 6) / 16));
|
|
656
|
+
const lines = wrapped.slice(0, maxLines);
|
|
657
|
+
if (wrapped.length > maxLines)
|
|
658
|
+
lines[maxLines - 1] = fitText(`${lines[maxLines - 1]}…`, width - 12);
|
|
659
|
+
const w = Math.min(width, Math.max(0, ...lines.map((l) => Array.from(l).length)) * CHAR + 12);
|
|
660
|
+
const h = Math.min(height, 6 + lines.length * 16);
|
|
625
661
|
// `auto` sits above-right of the anchor and slides inside the canvas; a fixed placement
|
|
626
662
|
// centres the box on the named side and is only clamped.
|
|
627
663
|
const wanted = placement === 'top'
|
|
@@ -639,13 +675,14 @@ function tooltipShapes(x, y, text, width, height, placement = 'auto') {
|
|
|
639
675
|
{
|
|
640
676
|
kind: 'rect',
|
|
641
677
|
part: 'tooltip',
|
|
678
|
+
title: original.join('\n'),
|
|
642
679
|
x: px(bx),
|
|
643
680
|
y: px(by),
|
|
644
681
|
width: px(w),
|
|
645
682
|
height: h,
|
|
646
683
|
fill: 'var(--skdx-color-surface-raised)',
|
|
647
684
|
stroke: CHART_COLORS.axis,
|
|
648
|
-
rx:
|
|
685
|
+
rx: 8,
|
|
649
686
|
},
|
|
650
687
|
...lines.map((line, i) => ({
|
|
651
688
|
kind: 'text',
|
|
@@ -735,6 +772,12 @@ function axis([min, max], range, options = false, chartFormat) {
|
|
|
735
772
|
zero: px(scale(Math.min(Math.max(0, domain[0]), domain[1]))),
|
|
736
773
|
};
|
|
737
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Left (or right) margin wide enough for the widest tick label a value axis over `[lo, hi]` will
|
|
777
|
+
* draw, never below the default. Charts measure it before laying out so a long label such as
|
|
778
|
+
* "$180.00" is not clipped by the svg edge.
|
|
779
|
+
*/
|
|
780
|
+
const valueAxisRoom = ([lo, hi], format) => Math.max(MARGIN$1.left, niceTicks(lo, hi).reduce((w, v) => Math.max(w, format(v).length), 0) * CHAR + 14);
|
|
738
781
|
/** Whether an axis draws its grid lines. */
|
|
739
782
|
const gridOn = (opts) => opts?.grid !== false;
|
|
740
783
|
/** Keeps every n-th tick so labels of the given widths do not overlap along `width`. */
|
|
@@ -771,7 +814,7 @@ function stateCanvas(input, empty, w, h) {
|
|
|
771
814
|
width,
|
|
772
815
|
height,
|
|
773
816
|
fill: 'var(--skdx-color-surface-sunken)',
|
|
774
|
-
rx:
|
|
817
|
+
rx: 8,
|
|
775
818
|
});
|
|
776
819
|
shapes.push({
|
|
777
820
|
kind: 'text',
|
|
@@ -791,6 +834,19 @@ function stateCanvas(input, empty, w, h) {
|
|
|
791
834
|
* This file is part of the SkandaDX organization.
|
|
792
835
|
* Licensed under the MIT License. See LICENSE in the repository root.
|
|
793
836
|
*/
|
|
837
|
+
/**
|
|
838
|
+
* Anchor for a tick label centred at `pos`: `end` when half of it would run past `width`, `start`
|
|
839
|
+
* when it would run past 0, else `middle`. The outermost tick sits on the plot edge, where a long
|
|
840
|
+
* date or currency label would otherwise be clipped by the svg.
|
|
841
|
+
*/
|
|
842
|
+
const tickAnchor = (pos, text, width) => {
|
|
843
|
+
const half = (text.length * CHAR) / 2;
|
|
844
|
+
if (pos + half > width)
|
|
845
|
+
return 'end';
|
|
846
|
+
if (pos - half < 0)
|
|
847
|
+
return 'start';
|
|
848
|
+
return 'middle';
|
|
849
|
+
};
|
|
794
850
|
/** Grid, axis line and tick labels of an axis frame, as shapes. */
|
|
795
851
|
function frameShapes(frame, options = {}) {
|
|
796
852
|
const { plot, xTicks, yTicks, baseline } = frame;
|
|
@@ -825,7 +881,7 @@ function frameShapes(frame, options = {}) {
|
|
|
825
881
|
baseline: 'middle',
|
|
826
882
|
rotate: rot,
|
|
827
883
|
})
|
|
828
|
-
:
|
|
884
|
+
: boundedLabel(x, y, text, frame.width, { part, anchor: tickAnchor(x, text, frame.width) });
|
|
829
885
|
// Short marks across an axis line at the ticks.
|
|
830
886
|
const marks = (ticks, along, at, dir, size) => {
|
|
831
887
|
for (const t of ticks)
|
|
@@ -890,7 +946,10 @@ function frameShapes(frame, options = {}) {
|
|
|
890
946
|
const ly = px(top ? y - 8 : y + 20);
|
|
891
947
|
for (const t of a.ticks)
|
|
892
948
|
shapes.push(top
|
|
893
|
-
? label(t.pos, ly, t.label, {
|
|
949
|
+
? label(t.pos, ly, t.label, {
|
|
950
|
+
part: 'x-label-top',
|
|
951
|
+
anchor: tickAnchor(t.pos, t.label, frame.width),
|
|
952
|
+
})
|
|
894
953
|
: rotated(t.pos, ly, t.label, 'x-label'));
|
|
895
954
|
// The first axis keeps its title at the top left; stacked axes title their own label row.
|
|
896
955
|
if (a.label)
|
|
@@ -1006,6 +1065,54 @@ function frameShapes(frame, options = {}) {
|
|
|
1006
1065
|
return shapes;
|
|
1007
1066
|
}
|
|
1008
1067
|
const label = (x, y, text, extra = {}) => ({ kind: 'text', part: 'label', x, y, text, fill: CHART_COLORS.label, ...extra });
|
|
1068
|
+
/** A label that stays inside the SVG, with the full wording available as its native title. */
|
|
1069
|
+
function boundedLabel(x, y, text, width, extra = {}) {
|
|
1070
|
+
const at = Math.max(4, Math.min(width - 4, x));
|
|
1071
|
+
const anchor = extra.anchor ?? 'start';
|
|
1072
|
+
const available = anchor === 'middle'
|
|
1073
|
+
? 2 * Math.min(at - 4, width - at - 4)
|
|
1074
|
+
: anchor === 'end'
|
|
1075
|
+
? at - 4
|
|
1076
|
+
: width - at - 4;
|
|
1077
|
+
const visible = fitText(text, available);
|
|
1078
|
+
return {
|
|
1079
|
+
kind: 'text',
|
|
1080
|
+
part: 'label',
|
|
1081
|
+
x: at,
|
|
1082
|
+
y,
|
|
1083
|
+
fill: CHART_COLORS.label,
|
|
1084
|
+
...extra,
|
|
1085
|
+
text: visible,
|
|
1086
|
+
title: visible === text ? extra.title : text,
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
/** Keep inside and reference labels readable over arbitrary series colours. */
|
|
1090
|
+
function withLabelSurfaces(shapes) {
|
|
1091
|
+
return shapes.flatMap((shape) => {
|
|
1092
|
+
if (shape.kind !== 'text' ||
|
|
1093
|
+
(shape.fill !== 'var(--skdx-color-text-on-accent)' && shape.part !== 'reference-label'))
|
|
1094
|
+
return [shape];
|
|
1095
|
+
const width = shape.text.length * CHAR + 8;
|
|
1096
|
+
const x = shape.x - (shape.anchor === 'middle' ? width / 2 : shape.anchor === 'end' ? width - 4 : 4);
|
|
1097
|
+
const y = shape.y - (shape.baseline === 'hanging' ? 2 : shape.baseline === 'middle' ? 9 : 14);
|
|
1098
|
+
return [
|
|
1099
|
+
{
|
|
1100
|
+
kind: 'rect',
|
|
1101
|
+
part: 'label-background',
|
|
1102
|
+
x,
|
|
1103
|
+
y,
|
|
1104
|
+
width,
|
|
1105
|
+
height: 18,
|
|
1106
|
+
rx: 8,
|
|
1107
|
+
fill: 'var(--skdx-color-surface-raised)',
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
...shape,
|
|
1111
|
+
fill: shape.part === 'reference-label' ? shape.fill : 'var(--skdx-color-text-default)',
|
|
1112
|
+
},
|
|
1113
|
+
];
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1009
1116
|
/** A dot with its native tooltip. */
|
|
1010
1117
|
const point$1 = (cx, cy, r, fill, title, item) => ({ kind: 'circle', part: 'point', cx, cy, r, fill, title, item });
|
|
1011
1118
|
|
|
@@ -1725,25 +1832,39 @@ function layoutPoints(input, options) {
|
|
|
1725
1832
|
const legendRoom = input.colorScale
|
|
1726
1833
|
? colorLegendRoom(input.colorLegend)
|
|
1727
1834
|
: { bottom: 0, right: 0, options: {} };
|
|
1835
|
+
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
1728
1836
|
const plot = plotBox(width, height, {
|
|
1729
1837
|
...MARGIN$1,
|
|
1730
|
-
top: MARGIN$1.top + legendTop(input) + (input.yAxis?.label ? 12 : 0),
|
|
1838
|
+
top: MARGIN$1.top + legendTop(input, legend, width) + (input.yAxis?.label ? 12 : 0),
|
|
1731
1839
|
right: MARGIN$1.right + legendRoom.right,
|
|
1732
1840
|
bottom: MARGIN$1.bottom + (input.xAxis?.label ? 14 : 0) + legendRoom.bottom,
|
|
1733
1841
|
});
|
|
1734
1842
|
const markerSize = input.markerSize ?? 3;
|
|
1735
1843
|
const hitRadius = Math.max(0, input.hitRadius ?? 0);
|
|
1736
|
-
const
|
|
1844
|
+
const requestedJitter = Math.max(0, input.jitter ?? 0);
|
|
1845
|
+
const sizes = minMax(dots.map((p) => p.size));
|
|
1846
|
+
const extentFactor = (symbol) => symbol === 'diamond' ? 1.3 : symbol === 'triangle' ? 1.2 : 1;
|
|
1847
|
+
const requestedRadius = (dot, sized) => Math.max(0, sized ? options.radius(dot.size, sizes) : markerSize);
|
|
1848
|
+
let largestExtent = 0;
|
|
1849
|
+
for (const series of shown) {
|
|
1850
|
+
const sized = series.data.some((dot) => dot.size !== undefined);
|
|
1851
|
+
const factor = extentFactor(series.symbol);
|
|
1852
|
+
for (const dot of series.data)
|
|
1853
|
+
largestExtent = Math.max(largestExtent, requestedRadius(dot, sized) * factor);
|
|
1854
|
+
}
|
|
1855
|
+
// Scale visible extents together only when a tiny plot cannot contain the requested size.
|
|
1856
|
+
// Keep data domains intact and use the same inset scales for marks/references/trends.
|
|
1857
|
+
const extentScale = Math.min(1, (Math.min(plot.width, plot.height) * 0.45) / Math.max(1, largestExtent + requestedJitter));
|
|
1858
|
+
const jitter = requestedJitter * extentScale;
|
|
1859
|
+
const inset = largestExtent * extentScale + jitter;
|
|
1737
1860
|
// Duplicates at one pixel are spread deterministically on a ring around it.
|
|
1738
1861
|
const seen = new Map();
|
|
1739
1862
|
// A color scale maps each dot's `value`; dots without one keep their series color.
|
|
1740
1863
|
const colors = input.colorScale
|
|
1741
1864
|
? makeColorScale(input.colorScale, dots.map((d) => d.value), colorOf(input, 0), undefined, makeFormatter(input.format))
|
|
1742
1865
|
: undefined;
|
|
1743
|
-
const x = axis(extent(dots.map((p) => p.x), false), [plot.x, plot.x + plot.width], input.xAxis ?? {}, input.format);
|
|
1744
|
-
const y = axis(extent(dots.map((p) => p.y), false), [plot.y + plot.height, plot.y], input.yAxis ?? {}, input.format);
|
|
1745
|
-
const sizes = minMax(dots.map((p) => p.size));
|
|
1746
|
-
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
1866
|
+
const x = axis(extent(dots.map((p) => p.x), false), [plot.x + inset, plot.x + plot.width - inset], input.xAxis ?? {}, input.format);
|
|
1867
|
+
const y = axis(extent(dots.map((p) => p.y), false), [plot.y + plot.height - inset, plot.y + inset], input.yAxis ?? {}, input.format);
|
|
1747
1868
|
const c = canvas(input, width, height, legend);
|
|
1748
1869
|
const marks = [];
|
|
1749
1870
|
const texts = [];
|
|
@@ -1755,7 +1876,7 @@ function layoutPoints(input, options) {
|
|
|
1755
1876
|
for (const dot of s.data) {
|
|
1756
1877
|
const di = dot.index;
|
|
1757
1878
|
const title = options.describe(s.name, dot, x, y);
|
|
1758
|
-
const r =
|
|
1879
|
+
const r = requestedRadius(dot, sized) * extentScale;
|
|
1759
1880
|
let cx = px(x.scale(dot.x));
|
|
1760
1881
|
let cy = px(y.scale(dot.y));
|
|
1761
1882
|
if (jitter > 0) {
|
|
@@ -1847,7 +1968,29 @@ function layoutPoints(input, options) {
|
|
|
1847
1968
|
}
|
|
1848
1969
|
}
|
|
1849
1970
|
}
|
|
1850
|
-
|
|
1971
|
+
// Check the final edge anchors as well as average tick spacing.
|
|
1972
|
+
let tickRight = -Infinity;
|
|
1973
|
+
const candidates = thinTicks(x.ticks, Math.max(1, plot.width - 2 * inset));
|
|
1974
|
+
const kept = new Set();
|
|
1975
|
+
for (const tick of [...candidates].sort((a, b) => a.pos - b.pos)) {
|
|
1976
|
+
const textWidth = tick.label.length * CHAR;
|
|
1977
|
+
const rotation = input.xAxis?.tickRotation ?? 0;
|
|
1978
|
+
const anchor = rotation
|
|
1979
|
+
? rotation < 0
|
|
1980
|
+
? 'end'
|
|
1981
|
+
: 'start'
|
|
1982
|
+
: tickAnchor(tick.pos, tick.label, width);
|
|
1983
|
+
const left = -(anchor === 'end' ? textWidth : anchor === 'middle' ? textWidth / 2 : 0);
|
|
1984
|
+
const angle = (rotation * Math.PI) / 180;
|
|
1985
|
+
const corners = [left, left + textWidth].flatMap((x) => [-7, 7].map((y) => tick.pos + x * Math.cos(angle) - y * Math.sin(angle)));
|
|
1986
|
+
const start = Math.min(...corners), end = Math.max(...corners);
|
|
1987
|
+
if (start < tickRight + 4)
|
|
1988
|
+
continue;
|
|
1989
|
+
tickRight = end;
|
|
1990
|
+
kept.add(tick);
|
|
1991
|
+
}
|
|
1992
|
+
const xTicks = candidates.filter((tick) => kept.has(tick));
|
|
1993
|
+
const frame = { ...c, plot, xTicks, yTicks: y.ticks, baseline: y.zero };
|
|
1851
1994
|
const scales = {
|
|
1852
1995
|
plot,
|
|
1853
1996
|
x: (v) => x.scale(categoryValue(v)),
|
|
@@ -1873,7 +2016,20 @@ function layoutPoints(input, options) {
|
|
|
1873
2016
|
return {
|
|
1874
2017
|
...c,
|
|
1875
2018
|
shapes: [
|
|
1876
|
-
...frameShapes(frame,
|
|
2019
|
+
...frameShapes({ ...frame, height: frame.height - legendRoom.bottom }, {
|
|
2020
|
+
xLabel: input.xAxis?.label,
|
|
2021
|
+
x: input.xAxis,
|
|
2022
|
+
// Frame axes spread decoration fields; never forward a second title through y.
|
|
2023
|
+
y: {
|
|
2024
|
+
showLine: input.yAxis?.showLine,
|
|
2025
|
+
showTicks: input.yAxis?.showTicks,
|
|
2026
|
+
tickSize: input.yAxis?.tickSize,
|
|
2027
|
+
tickRotation: input.yAxis?.tickRotation,
|
|
2028
|
+
},
|
|
2029
|
+
}),
|
|
2030
|
+
...(input.yAxis?.label
|
|
2031
|
+
? [label(4, plot.y - 14, input.yAxis.label, { part: 'axis-title', anchor: 'start' })]
|
|
2032
|
+
: []),
|
|
1877
2033
|
...references,
|
|
1878
2034
|
...(opacity !== undefined && marks.some((m) => m.part === options.part)
|
|
1879
2035
|
? marks.map((m) => (m.kind === 'circle' || m.kind === 'path') && m.part !== 'hit' ? { ...m, opacity } : m)
|
|
@@ -2343,8 +2499,8 @@ function layoutCartesian(input, defaultType) {
|
|
|
2343
2499
|
return MARGIN$1.left;
|
|
2344
2500
|
const [lo, hi] = extentOf(opts.id);
|
|
2345
2501
|
const f = makeFormatter(opts.format ?? percentFormat ?? input.format);
|
|
2346
|
-
|
|
2347
|
-
|
|
2502
|
+
return (valueAxisRoom([opts.min ?? lo, opts.max ?? hi], f) +
|
|
2503
|
+
(opts.showTicks ? (opts.tickSize ?? 4) : 0));
|
|
2348
2504
|
};
|
|
2349
2505
|
// Horizontal charts run the value axes along the bottom (near) and top (far) edges. Under RTL
|
|
2350
2506
|
// the vertical sides swap so the first axis sits at the right, next to the reader.
|
|
@@ -2364,8 +2520,12 @@ function layoutCartesian(input, defaultType) {
|
|
|
2364
2520
|
}
|
|
2365
2521
|
// The default bottom margin already holds the first bottom axis.
|
|
2366
2522
|
const firstBottom = shownAxes.find((a) => sideOf(a) === 'bottom');
|
|
2523
|
+
const legend = all.map((s) => ({ name: s.name, color: s.color }));
|
|
2367
2524
|
const margin = {
|
|
2368
|
-
top: MARGIN$1.top +
|
|
2525
|
+
top: MARGIN$1.top +
|
|
2526
|
+
legendTop(input, legend, width) +
|
|
2527
|
+
(defs.some((a) => a.label) ? 12 : 0) +
|
|
2528
|
+
room.top,
|
|
2369
2529
|
right: horizontal
|
|
2370
2530
|
? rtl
|
|
2371
2531
|
? Math.max(MARGIN$1.left, widestCategory + 16)
|
|
@@ -2433,7 +2593,7 @@ function layoutCartesian(input, defaultType) {
|
|
|
2433
2593
|
: plot.width /
|
|
2434
2594
|
Math.max(0.2, Math.abs(Math.cos(((input.xAxis?.tickRotation ?? 0) * Math.PI) / 180))));
|
|
2435
2595
|
const frame = {
|
|
2436
|
-
...canvas(input, width, height,
|
|
2596
|
+
...canvas(input, width, height, legend),
|
|
2437
2597
|
plot,
|
|
2438
2598
|
xTicks: categoryTicks,
|
|
2439
2599
|
yTicks: horizontal ? thinTicks(left.ticks, plot.width) : left.ticks,
|
|
@@ -2488,7 +2648,10 @@ function layoutCartesian(input, defaultType) {
|
|
|
2488
2648
|
},
|
|
2489
2649
|
y: (v, a) => value(v, a),
|
|
2490
2650
|
};
|
|
2491
|
-
|
|
2651
|
+
const references = referenceShapes(input.references, scales);
|
|
2652
|
+
shapes.push(...references.filter((shape) => shape.part !== 'reference-label'));
|
|
2653
|
+
// The reference geometry stays behind data, but its label must survive overlapping marks.
|
|
2654
|
+
const referenceLabels = withLabelSurfaces(references.filter((shape) => shape.part === 'reference-label'));
|
|
2492
2655
|
// One slot per plain bar series and one per bar stack group, side by side inside the band.
|
|
2493
2656
|
const slots = [];
|
|
2494
2657
|
const slotOf = new Map();
|
|
@@ -2881,7 +3044,7 @@ function layoutCartesian(input, defaultType) {
|
|
|
2881
3044
|
width: 6,
|
|
2882
3045
|
height: box.height - 16,
|
|
2883
3046
|
fill: CHART_COLORS.axis,
|
|
2884
|
-
rx:
|
|
3047
|
+
rx: 8,
|
|
2885
3048
|
}, {
|
|
2886
3049
|
kind: 'rect',
|
|
2887
3050
|
part: 'brush-handle',
|
|
@@ -2890,7 +3053,7 @@ function layoutCartesian(input, defaultType) {
|
|
|
2890
3053
|
width: 6,
|
|
2891
3054
|
height: box.height - 16,
|
|
2892
3055
|
fill: CHART_COLORS.axis,
|
|
2893
|
-
rx:
|
|
3056
|
+
rx: 8,
|
|
2894
3057
|
});
|
|
2895
3058
|
hit.brush = { box, window };
|
|
2896
3059
|
}
|
|
@@ -2904,6 +3067,7 @@ function layoutCartesian(input, defaultType) {
|
|
|
2904
3067
|
...errorMarks,
|
|
2905
3068
|
...points,
|
|
2906
3069
|
...texts,
|
|
3070
|
+
...referenceLabels,
|
|
2907
3071
|
...annotationShapes(input.annotations, scales),
|
|
2908
3072
|
...brush,
|
|
2909
3073
|
...frame.shapes,
|
|
@@ -3384,7 +3548,8 @@ function layoutPieChart(input) {
|
|
|
3384
3548
|
: (p) => `${Math.round(p * 100)}%`;
|
|
3385
3549
|
const cornerRadius = Math.max(0, input.cornerRadius ?? 0);
|
|
3386
3550
|
const { width, height } = size(input, 300, 300);
|
|
3387
|
-
const
|
|
3551
|
+
const legend = input.data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
3552
|
+
const top = legendTop(input, legend, width);
|
|
3388
3553
|
const start = ((input.startAngle ?? 0) * Math.PI) / 180;
|
|
3389
3554
|
const end = ((input.endAngle ?? (input.startAngle ?? 0) + 360) * Math.PI) / 180;
|
|
3390
3555
|
const sweep = Math.max(0, Math.min(TAU$1, end - start));
|
|
@@ -3474,7 +3639,7 @@ function layoutPieChart(input) {
|
|
|
3474
3639
|
y2: px(cy - (r + 12) * Math.cos(mid)),
|
|
3475
3640
|
stroke: CHART_COLORS.axis,
|
|
3476
3641
|
});
|
|
3477
|
-
labels.push(
|
|
3642
|
+
labels.push(boundedLabel(px(cx + (r + 16) * sin), ly, text, width, {
|
|
3478
3643
|
part: 'slice-label',
|
|
3479
3644
|
anchor: sin > 0.1 ? 'start' : sin < -0.1 ? 'end' : 'middle',
|
|
3480
3645
|
baseline: 'middle',
|
|
@@ -3484,7 +3649,7 @@ function layoutPieChart(input) {
|
|
|
3484
3649
|
}
|
|
3485
3650
|
else if (percent > 0.03) {
|
|
3486
3651
|
const lr = (r + r0) / 2;
|
|
3487
|
-
labels.push(
|
|
3652
|
+
labels.push(boundedLabel(px(cx + lr * Math.sin(mid)), px(cy - lr * Math.cos(mid)), text, width, {
|
|
3488
3653
|
part: 'slice-label',
|
|
3489
3654
|
anchor: 'middle',
|
|
3490
3655
|
baseline: 'middle',
|
|
@@ -3513,8 +3678,12 @@ function layoutPieChart(input) {
|
|
|
3513
3678
|
fill: 'var(--skdx-color-text-default)',
|
|
3514
3679
|
weight: 'bold',
|
|
3515
3680
|
}));
|
|
3516
|
-
const c = canvas(input, width, height,
|
|
3517
|
-
return {
|
|
3681
|
+
const c = canvas(input, width, height, legend);
|
|
3682
|
+
return {
|
|
3683
|
+
...c,
|
|
3684
|
+
shapes: withLabelSurfaces([...slices, ...connectors, ...c.shapes, ...labels]),
|
|
3685
|
+
issues,
|
|
3686
|
+
};
|
|
3518
3687
|
}
|
|
3519
3688
|
|
|
3520
3689
|
/*
|
|
@@ -3674,7 +3843,8 @@ function layoutRadarChart(input) {
|
|
|
3674
3843
|
const issues = [];
|
|
3675
3844
|
const format = makeFormatter(input.format);
|
|
3676
3845
|
const { width, height } = size(input, 300, 300);
|
|
3677
|
-
const
|
|
3846
|
+
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
3847
|
+
const top = legendTop(input, legend, width);
|
|
3678
3848
|
const cx = width / 2;
|
|
3679
3849
|
const cy = top + (height - top) / 2;
|
|
3680
3850
|
const radius = Math.max(0, Math.min(width, height - top) / 2 - 36);
|
|
@@ -3836,7 +4006,7 @@ function layoutRadarChart(input) {
|
|
|
3836
4006
|
item: { label: s.name, series: s.name, seriesId: s.id },
|
|
3837
4007
|
});
|
|
3838
4008
|
});
|
|
3839
|
-
const c = canvas(input, width, height,
|
|
4009
|
+
const c = canvas(input, width, height, legend);
|
|
3840
4010
|
return { ...c, shapes: [...grid, ...polygons, ...points, ...texts, ...c.shapes], issues };
|
|
3841
4011
|
}
|
|
3842
4012
|
|
|
@@ -3988,7 +4158,12 @@ function layoutHeatmap(input) {
|
|
|
3988
4158
|
part: 'cell-label',
|
|
3989
4159
|
anchor: 'middle',
|
|
3990
4160
|
baseline: 'middle',
|
|
3991
|
-
fill:
|
|
4161
|
+
// The cell composites its fill over the surface at the ramp's opacity: a faint cell
|
|
4162
|
+
// is mostly surface and reads with the default text token, a strong one is the fill
|
|
4163
|
+
// and needs the token meant to sit on it. A fixed token fails one end or the other.
|
|
4164
|
+
fill: fill.opacity >= 0.5
|
|
4165
|
+
? 'var(--skdx-color-text-on-accent)'
|
|
4166
|
+
: 'var(--skdx-color-text-default)',
|
|
3992
4167
|
}));
|
|
3993
4168
|
});
|
|
3994
4169
|
});
|
|
@@ -4068,7 +4243,8 @@ function layoutFunnelChart(input) {
|
|
|
4068
4243
|
const max = Math.max(0, ...data.map((d) => d.value));
|
|
4069
4244
|
const total = data.reduce((s, d) => s + Math.max(0, d.value), 0);
|
|
4070
4245
|
const first = data[0]?.value ?? 0;
|
|
4071
|
-
const
|
|
4246
|
+
const legend = data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
4247
|
+
const offset = legendTop(input, legend, width);
|
|
4072
4248
|
// Along: the axis stages are stacked on; across: the axis a stage's width is measured on.
|
|
4073
4249
|
const along = horizontal ? width : height - offset;
|
|
4074
4250
|
const across = horizontal ? height - offset : width;
|
|
@@ -4082,7 +4258,6 @@ function layoutFunnelChart(input) {
|
|
|
4082
4258
|
const percentFormat = input.percentFormat
|
|
4083
4259
|
? makeFormatter(input.percentFormat)
|
|
4084
4260
|
: (p) => `${Math.round(p * 100)}%`;
|
|
4085
|
-
const legend = data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
4086
4261
|
const mid = (horizontal ? offset + (height - offset) / 2 : width / 2) - (outside ? across / 6 : 0);
|
|
4087
4262
|
const stages = data.flatMap((d, i) => {
|
|
4088
4263
|
const near = widthOf(d.value);
|
|
@@ -4091,8 +4266,9 @@ function layoutFunnelChart(input) {
|
|
|
4091
4266
|
const p1 = p0 + stageLen - gap;
|
|
4092
4267
|
const percent = pyramid ? (total > 0 ? d.value / total : 0) : first > 0 ? d.value / first : 0;
|
|
4093
4268
|
const previous = data[i - 1]?.value;
|
|
4269
|
+
const change = previous !== undefined && previous > 0 ? d.value / previous - 1 : 0;
|
|
4094
4270
|
const drop = !pyramid && previous !== undefined && previous > 0
|
|
4095
|
-
? ` ·
|
|
4271
|
+
? ` · ${change < 0 ? '−' : change > 0 ? '+' : ''}${percentFormat(Math.abs(change))} from ${data[i - 1]?.name}`
|
|
4096
4272
|
: '';
|
|
4097
4273
|
const title = `${d.name}: ${format(d.value)} (${percentFormat(percent)})${drop}`;
|
|
4098
4274
|
const d0 = horizontal
|
|
@@ -4115,7 +4291,7 @@ function layoutFunnelChart(input) {
|
|
|
4115
4291
|
const at = mid + full / 2 + PAD$2;
|
|
4116
4292
|
return [
|
|
4117
4293
|
stage,
|
|
4118
|
-
|
|
4294
|
+
boundedLabel(px(horizontal ? center : at), px(horizontal ? at : center), text, width, {
|
|
4119
4295
|
part: 'stage-label',
|
|
4120
4296
|
anchor: horizontal ? 'middle' : 'start',
|
|
4121
4297
|
baseline: horizontal ? 'hanging' : 'middle',
|
|
@@ -4123,14 +4299,27 @@ function layoutFunnelChart(input) {
|
|
|
4123
4299
|
}),
|
|
4124
4300
|
];
|
|
4125
4301
|
}
|
|
4302
|
+
const caption = boundedLabel(px(horizontal ? center : mid), px(horizontal ? mid : center), text, width, {
|
|
4303
|
+
part: 'stage-label',
|
|
4304
|
+
anchor: 'middle',
|
|
4305
|
+
baseline: 'middle',
|
|
4306
|
+
fill: 'var(--skdx-color-text-default)',
|
|
4307
|
+
});
|
|
4126
4308
|
return [
|
|
4127
4309
|
stage,
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4310
|
+
// A stage can be narrower than its label, and arbitrary series colors
|
|
4311
|
+
// cannot guarantee contrast. Keep the bounded label on a token surface.
|
|
4312
|
+
{
|
|
4313
|
+
kind: 'rect',
|
|
4314
|
+
part: 'stage-label-background',
|
|
4315
|
+
x: px(caption.x - (caption.text.length * CHAR + 8) / 2),
|
|
4316
|
+
y: px((horizontal ? mid : center) - 9),
|
|
4317
|
+
width: caption.text.length * CHAR + 8,
|
|
4318
|
+
height: 18,
|
|
4319
|
+
rx: 8,
|
|
4320
|
+
fill: 'var(--skdx-color-surface-default)',
|
|
4321
|
+
},
|
|
4322
|
+
caption,
|
|
4134
4323
|
];
|
|
4135
4324
|
});
|
|
4136
4325
|
const c = canvas(input, width, height, legend);
|
|
@@ -4154,7 +4343,8 @@ function layoutRadialBar(input) {
|
|
|
4154
4343
|
return state;
|
|
4155
4344
|
const format = makeFormatter(input.format);
|
|
4156
4345
|
const { width, height } = size(input, 300, 300);
|
|
4157
|
-
const
|
|
4346
|
+
const legend = data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
4347
|
+
const top = legendTop(input, legend, width);
|
|
4158
4348
|
const cx = width / 2;
|
|
4159
4349
|
const cy = top + (height - top) / 2;
|
|
4160
4350
|
const outer = Math.max(0, Math.min(width, height - top) / 2 - (input.bands ? 20 : 8));
|
|
@@ -4169,7 +4359,6 @@ function layoutRadialBar(input) {
|
|
|
4169
4359
|
const rounded = input.rounded ?? true;
|
|
4170
4360
|
const ratio = (v) => (max > 0 ? Math.max(0, Math.min(1, v / max)) : 0);
|
|
4171
4361
|
const shapes = [];
|
|
4172
|
-
const legend = data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
4173
4362
|
// Threshold sectors outside the arcs, e.g. poor / fair / good.
|
|
4174
4363
|
for (const b of input.bands ?? []) {
|
|
4175
4364
|
const a0 = start + ratio(b.from) * sweep;
|
|
@@ -4765,6 +4954,12 @@ function layoutRangeChart(input) {
|
|
|
4765
4954
|
* This file is part of the SkandaDX organization.
|
|
4766
4955
|
* Licensed under the MIT License. See LICENSE in the repository root.
|
|
4767
4956
|
*/
|
|
4957
|
+
/**
|
|
4958
|
+
* Trigonometry rounded to 1e-6: `Math.cos`/`Math.sin` are implementation-defined, so their
|
|
4959
|
+
* last-bit differences between a Node render and a browser render would otherwise reach the
|
|
4960
|
+
* markup and break hydration.
|
|
4961
|
+
*/
|
|
4962
|
+
const q = (v) => Math.round(v * 1e6) / 1e6;
|
|
4768
4963
|
/** Node labels are drawn by default up to this many nodes. */
|
|
4769
4964
|
const NETWORK_LABEL_LIMIT = 60;
|
|
4770
4965
|
/** Per-edge titles and keyboard stops are emitted up to this many edges. */
|
|
@@ -4808,13 +5003,14 @@ function layoutNetworkChart(input) {
|
|
|
4808
5003
|
const { width, height } = size(input);
|
|
4809
5004
|
const [rMin, rMax] = input.nodeRange ?? [5, 16];
|
|
4810
5005
|
const labels = input.labels ?? nodes.length <= NETWORK_LABEL_LIMIT;
|
|
5006
|
+
const groups = [...new Set(input.nodes.map(groupOf).filter((g) => g !== undefined))];
|
|
5007
|
+
const legend = groups.map((g, i) => ({ name: g, color: colorOf(input, i) }));
|
|
4811
5008
|
const plot = plotBox(width, height, {
|
|
4812
|
-
top: legendTop(input) + rMax + 8,
|
|
5009
|
+
top: legendTop(input, legend, width) + rMax + 8,
|
|
4813
5010
|
right: rMax + (labels ? 48 : 8),
|
|
4814
5011
|
bottom: rMax + 8,
|
|
4815
5012
|
left: rMax + 8,
|
|
4816
5013
|
});
|
|
4817
|
-
const groups = [...new Set(input.nodes.map(groupOf).filter((g) => g !== undefined))];
|
|
4818
5014
|
const colorFor = (n, i) => n.color ?? colorOf(input, n.group !== undefined ? groups.indexOf(n.group) : i);
|
|
4819
5015
|
const [, vMax] = minMax(nodes.map((n) => n.value));
|
|
4820
5016
|
const radius = (v) => isFinite(v) && vMax > 0 ? rMin + Math.sqrt(Math.max(0, v) / vMax) * (rMax - rMin) : rMin;
|
|
@@ -4834,8 +5030,8 @@ function layoutNetworkChart(input) {
|
|
|
4834
5030
|
ys = nodes.map((node) => node.y);
|
|
4835
5031
|
}
|
|
4836
5032
|
else if (mode === 'circular' || mode === 'manual' || n < 3) {
|
|
4837
|
-
xs = nodes.map((_, i) => Math.cos((2 * Math.PI * i) / n - Math.PI / 2));
|
|
4838
|
-
ys = nodes.map((_, i) => Math.sin((2 * Math.PI * i) / n - Math.PI / 2));
|
|
5033
|
+
xs = nodes.map((_, i) => q(Math.cos((2 * Math.PI * i) / n - Math.PI / 2)));
|
|
5034
|
+
ys = nodes.map((_, i) => q(Math.sin((2 * Math.PI * i) / n - Math.PI / 2)));
|
|
4839
5035
|
}
|
|
4840
5036
|
else {
|
|
4841
5037
|
({ xs, ys } = force(n, edges.map((e) => [at.get(e.source), at.get(e.target)]), input.iterations ?? Math.max(50, Math.min(300, Math.floor(30_000 / n)))));
|
|
@@ -4908,7 +5104,7 @@ function layoutNetworkChart(input) {
|
|
|
4908
5104
|
item,
|
|
4909
5105
|
};
|
|
4910
5106
|
}
|
|
4911
|
-
const len = Math.
|
|
5107
|
+
const len = Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y)) || 1;
|
|
4912
5108
|
const ux = (b.x - a.x) / len;
|
|
4913
5109
|
const uy = (b.y - a.y) / len;
|
|
4914
5110
|
if (input.directed) {
|
|
@@ -4989,7 +5185,6 @@ function layoutNetworkChart(input) {
|
|
|
4989
5185
|
fill: dim(p.node.id) === undefined ? 'var(--skdx-color-text-default)' : CHART_COLORS.grid,
|
|
4990
5186
|
}));
|
|
4991
5187
|
}
|
|
4992
|
-
const legend = groups.map((g, i) => ({ name: g, color: colorOf(input, i) }));
|
|
4993
5188
|
const c = canvas(input, width, height, legend);
|
|
4994
5189
|
return {
|
|
4995
5190
|
...c,
|
|
@@ -4999,11 +5194,15 @@ function layoutNetworkChart(input) {
|
|
|
4999
5194
|
}
|
|
5000
5195
|
/**
|
|
5001
5196
|
* Fruchterman–Reingold in the unit square with linear cooling. Deterministic: nodes start on a
|
|
5002
|
-
* circle in input order
|
|
5197
|
+
* circle in input order, the seed is quantised and every step afterwards uses only IEEE-exact
|
|
5198
|
+
* arithmetic (`+ - * /` and `sqrt`), so a server render and a browser render agree bit for bit —
|
|
5199
|
+
* `Math.cos`/`Math.sin`/`Math.hypot` are implementation-defined and their last-bit differences
|
|
5200
|
+
* would otherwise amplify over the iterations into a hydration mismatch.
|
|
5201
|
+
* ponytail: O(n²) per iteration; a Barnes–Hut tree if graphs exceed ~2000 nodes.
|
|
5003
5202
|
*/
|
|
5004
5203
|
function force(n, edges, iterations) {
|
|
5005
|
-
const xs = Array.from({ length: n }, (_, i) => Math.cos((2 * Math.PI * i) / n));
|
|
5006
|
-
const ys = Array.from({ length: n }, (_, i) => Math.sin((2 * Math.PI * i) / n));
|
|
5204
|
+
const xs = Array.from({ length: n }, (_, i) => q(Math.cos((2 * Math.PI * i) / n)));
|
|
5205
|
+
const ys = Array.from({ length: n }, (_, i) => q(Math.sin((2 * Math.PI * i) / n)));
|
|
5007
5206
|
const k = Math.sqrt(4 / n);
|
|
5008
5207
|
const dx = new Array(n).fill(0);
|
|
5009
5208
|
const dy = new Array(n).fill(0);
|
|
@@ -5032,7 +5231,7 @@ function force(n, edges, iterations) {
|
|
|
5032
5231
|
continue;
|
|
5033
5232
|
const ex = xs[a] - xs[b];
|
|
5034
5233
|
const ey = ys[a] - ys[b];
|
|
5035
|
-
const d = Math.
|
|
5234
|
+
const d = Math.sqrt(ex * ex + ey * ey) || 1e-6;
|
|
5036
5235
|
const f = (d * d) / k / d;
|
|
5037
5236
|
dx[a] = dx[a] - ex * f;
|
|
5038
5237
|
dy[a] = dy[a] - ey * f;
|
|
@@ -5040,7 +5239,8 @@ function force(n, edges, iterations) {
|
|
|
5040
5239
|
dy[b] = dy[b] + ey * f;
|
|
5041
5240
|
}
|
|
5042
5241
|
for (let i = 0; i < n; i++) {
|
|
5043
|
-
const d = Math.
|
|
5242
|
+
const d = Math.sqrt(dx[i] * dx[i] + dy[i] * dy[i]) ||
|
|
5243
|
+
1e-6;
|
|
5044
5244
|
const step = Math.min(d, t);
|
|
5045
5245
|
xs[i] = xs[i] + (dx[i] / d) * step;
|
|
5046
5246
|
ys[i] = ys[i] + (dy[i] / d) * step;
|
|
@@ -5119,7 +5319,7 @@ function layoutTimelineChart(input) {
|
|
|
5119
5319
|
// A list: the date column, a spine, one marker and label per row.
|
|
5120
5320
|
const dateW = Math.max(...events.map((e) => dateOf(e.time).length)) * CHAR + 16;
|
|
5121
5321
|
const plot = plotBox(width, height, {
|
|
5122
|
-
top: 8 + legendTop(input),
|
|
5322
|
+
top: 8 + legendTop(input, legend, width),
|
|
5123
5323
|
right: 8,
|
|
5124
5324
|
bottom: 8,
|
|
5125
5325
|
left: dateW + 12,
|
|
@@ -5173,7 +5373,7 @@ function layoutTimelineChart(input) {
|
|
|
5173
5373
|
// Half a date label at each end, so the first and last ticks stay inside the canvas.
|
|
5174
5374
|
const dateW = (Math.max(dateOf(t0).length, dateOf(t1).length) * CHAR) / 2 + 4;
|
|
5175
5375
|
const plot = plotBox(width, height, {
|
|
5176
|
-
top: 8 + legendTop(input),
|
|
5376
|
+
top: 8 + legendTop(input, legend, width),
|
|
5177
5377
|
right: Math.max(16, dateW),
|
|
5178
5378
|
bottom: 32 + (input.xAxis?.label ? 14 : 0),
|
|
5179
5379
|
left: Math.max(16, laneW, dateW),
|
|
@@ -5267,6 +5467,26 @@ function layoutTimelineChart(input) {
|
|
|
5267
5467
|
return { ...c, shapes: [...shapes, ...texts, ...marks, ...c.shapes], issues };
|
|
5268
5468
|
}
|
|
5269
5469
|
|
|
5470
|
+
/*
|
|
5471
|
+
* Copyright (c) 2026 SkandaDX
|
|
5472
|
+
* This file is part of the SkandaDX organization.
|
|
5473
|
+
* Licensed under the MIT License. See LICENSE in the repository root.
|
|
5474
|
+
*/
|
|
5475
|
+
/** Shared input/surface rounding; consumer tokens can customize it without changing data geometry. */
|
|
5476
|
+
const CHART_SURFACE_RADIUS = 'var(--skdx-radius-default, var(--skdx-radius-md, 0.5rem))';
|
|
5477
|
+
/** Only rectangular UI surfaces follow control rounding. Data marks retain their geometry/options. */
|
|
5478
|
+
function chartSurfaceRadius(part) {
|
|
5479
|
+
return [
|
|
5480
|
+
'tooltip',
|
|
5481
|
+
'skeleton',
|
|
5482
|
+
'label-background',
|
|
5483
|
+
'stage-label-background',
|
|
5484
|
+
'brush-handle',
|
|
5485
|
+
].includes(part)
|
|
5486
|
+
? CHART_SURFACE_RADIUS
|
|
5487
|
+
: undefined;
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5270
5490
|
/*
|
|
5271
5491
|
* Copyright (c) 2026 SkandaDX
|
|
5272
5492
|
* This file is part of the SkandaDX organization.
|
|
@@ -5829,7 +6049,7 @@ function shapeMarkup(s) {
|
|
|
5829
6049
|
case 'circle':
|
|
5830
6050
|
return `<circle${attrs([part, ['cx', s.cx], ['cy', s.cy], ['r', s.r], ['fill', s.fill], ['stroke', s.stroke]])}>${title}</circle>`;
|
|
5831
6051
|
case 'text':
|
|
5832
|
-
return `<text${attrs([part, ['x', s.x], ['y', s.y], ['text-anchor', s.anchor], ['dominant-baseline', s.baseline], ['fill', s.fill], ['font-weight', s.weight], ['transform', s.rotate ? `rotate(${s.rotate} ${s.x} ${s.y})` : undefined]])}>${esc(s.text)}</text>`;
|
|
6052
|
+
return `<text${attrs([part, ['aria-label', s.title], ['x', s.x], ['y', s.y], ['text-anchor', s.anchor], ['dominant-baseline', s.baseline], ['fill', s.fill], ['font-weight', s.weight], ['transform', s.rotate ? `rotate(${s.rotate} ${s.x} ${s.y})` : undefined]])}>${esc(s.text)}</text>`;
|
|
5833
6053
|
}
|
|
5834
6054
|
}
|
|
5835
6055
|
/** The `<defs>` block for a layout's gradients, or an empty string. */
|
|
@@ -5917,6 +6137,11 @@ function seriesCsv(series, categories = []) {
|
|
|
5917
6137
|
*/
|
|
5918
6138
|
/** A node's size: its own value, else the sum of its children's. */
|
|
5919
6139
|
const nodeValue = (n) => isFinite(n.value) ? n.value : (n.children ?? []).reduce((s, c) => s + nodeValue(c), 0);
|
|
6140
|
+
/**
|
|
6141
|
+
* Whether this node or anything under it carries a value. A branch in a tree where nobody sets
|
|
6142
|
+
* `value` sums to 0, which is not data and must not be printed as one.
|
|
6143
|
+
*/
|
|
6144
|
+
const hasValue = (n) => isFinite(n.value) || (n.children ?? []).some(hasValue);
|
|
5920
6145
|
/** Levels below the given nodes (0 for none). */
|
|
5921
6146
|
const depthOf = (nodes) => nodes.length === 0 ? 0 : 1 + Math.max(...nodes.map((n) => depthOf(n.children ?? [])));
|
|
5922
6147
|
/** A node's identity: its id, else its name. */
|
|
@@ -6376,13 +6601,13 @@ function layoutTreemap(input) {
|
|
|
6376
6601
|
const formatValue = makeFormatter(input.format);
|
|
6377
6602
|
const { width, height } = size(input);
|
|
6378
6603
|
const crumbs = input.breadcrumbs && path.length > 0;
|
|
6379
|
-
const
|
|
6604
|
+
const legend = nodes.map((n, i) => ({ name: n.name, color: colorOf(input, i, n.color) }));
|
|
6605
|
+
const top = legendTop(input, legend, width) + (crumbs ? 20 : 0);
|
|
6380
6606
|
const pad = Math.max(0, input.padding ?? 2);
|
|
6381
6607
|
const sort = input.sort ?? 'desc';
|
|
6382
6608
|
const tiling = input.tiling ?? 'squarify';
|
|
6383
6609
|
const showLabels = input.labels ?? true;
|
|
6384
6610
|
const shapes = [];
|
|
6385
|
-
const legend = nodes.map((n, i) => ({ name: n.name, color: colorOf(input, i, n.color) }));
|
|
6386
6611
|
// Leaves are colored by value when a scale is given; branches keep the branch color.
|
|
6387
6612
|
const leaves = [];
|
|
6388
6613
|
const collect = (list) => {
|
|
@@ -6430,11 +6655,14 @@ function layoutTreemap(input) {
|
|
|
6430
6655
|
});
|
|
6431
6656
|
if (branch)
|
|
6432
6657
|
tile(t.node.children, tx + pad, ty + pad, Math.max(0, tw - 2 * pad), Math.max(0, th - 2 * pad), t.color, depth + 1);
|
|
6433
|
-
else if (showLabels && tw > 40 && th > 20)
|
|
6434
|
-
|
|
6658
|
+
else if (showLabels && tw > 40 && th > 20) {
|
|
6659
|
+
const capacity = Math.max(1, Math.floor((tw - 12) / CHAR));
|
|
6660
|
+
const text = t.node.name.length > capacity ? `${t.node.name.slice(0, capacity - 1)}…` : t.node.name;
|
|
6661
|
+
shapes.push(label(px(tx + 6), px(ty + 6), text, {
|
|
6435
6662
|
baseline: 'hanging',
|
|
6436
6663
|
fill: 'var(--skdx-color-text-on-accent)',
|
|
6437
6664
|
}));
|
|
6665
|
+
}
|
|
6438
6666
|
};
|
|
6439
6667
|
if (tiling === 'squarify')
|
|
6440
6668
|
squarify(tiles, x, y, w, h, place);
|
|
@@ -6443,9 +6671,9 @@ function layoutTreemap(input) {
|
|
|
6443
6671
|
};
|
|
6444
6672
|
tile(nodes, 0, top, width, height - top, undefined, 0);
|
|
6445
6673
|
if (crumbs)
|
|
6446
|
-
shapes.push(...breadcrumbShapes(path, 8, legendTop(input) + 10));
|
|
6674
|
+
shapes.push(...breadcrumbShapes(path, 8, legendTop(input, legend, width) + 10));
|
|
6447
6675
|
const c = canvas(input, width, height, legend);
|
|
6448
|
-
return { ...c, shapes: [...shapes, ...c.shapes] };
|
|
6676
|
+
return { ...c, shapes: withLabelSurfaces([...shapes, ...c.shapes]) };
|
|
6449
6677
|
}
|
|
6450
6678
|
/** The drill path as clickable crumbs (each carries the node's id) separated by `›`, from `x`. */
|
|
6451
6679
|
function breadcrumbShapes(path, x, y) {
|
|
@@ -6498,6 +6726,7 @@ function layoutCandlestick(input) {
|
|
|
6498
6726
|
const UP = input.upColor ?? CHART_COLORS.positive;
|
|
6499
6727
|
const DOWN = input.downColor ?? CHART_COLORS.negative;
|
|
6500
6728
|
const priceFormat = makeFormatter(input.format);
|
|
6729
|
+
const volumeFormat = makeFormatter(input.volumeFormat ?? { style: 'integer' });
|
|
6501
6730
|
const count = all.length;
|
|
6502
6731
|
const start = Math.max(0, Math.min(count - 2, Math.floor(input.zoom?.start ?? 0)));
|
|
6503
6732
|
const end = input.zoom ? Math.max(start + 2, Math.min(count, Math.ceil(input.zoom.end))) : count;
|
|
@@ -6507,19 +6736,12 @@ function layoutCandlestick(input) {
|
|
|
6507
6736
|
const { width, height } = size(input);
|
|
6508
6737
|
const brushRoom = input.brush ? BRUSH_HEIGHT + BRUSH_GAP : 0;
|
|
6509
6738
|
const selector = input.rangeSelector ?? [];
|
|
6510
|
-
const outer = plotBox(width, height, {
|
|
6511
|
-
...MARGIN$1,
|
|
6512
|
-
top: MARGIN$1.top + legendTop(input) + (selector.length ? SELECTOR_HEIGHT : 0),
|
|
6513
|
-
bottom: MARGIN$1.bottom + brushRoom,
|
|
6514
|
-
});
|
|
6515
6739
|
// `compare` plots the change from the first visible close: y values (and the axis format)
|
|
6516
6740
|
// become fractions while tooltips keep the prices and add the percentage.
|
|
6517
6741
|
const base = input.compare ? data[0].close : undefined;
|
|
6518
6742
|
const cmp = (v) => (base !== undefined && base !== 0 ? v / base - 1 : v);
|
|
6519
6743
|
const pctFormat = base !== undefined ? { style: 'percent', digits: 1 } : undefined;
|
|
6520
6744
|
const pct = makeFormatter({ style: 'percent', digits: 1 });
|
|
6521
|
-
const volumeH = hasVolume ? outer.height * VOLUME_SHARE : 0;
|
|
6522
|
-
const plot = { ...outer, height: Math.max(0, outer.height - volumeH - (hasVolume ? 8 : 0)) };
|
|
6523
6745
|
// Moving averages run over every close, so zooming never shortens the window; the visible
|
|
6524
6746
|
// slice is drawn and read in the crosshair.
|
|
6525
6747
|
const closes = all.map((c) => c.close);
|
|
@@ -6535,12 +6757,30 @@ function layoutCandlestick(input) {
|
|
|
6535
6757
|
hidden: isHidden(input, name),
|
|
6536
6758
|
};
|
|
6537
6759
|
});
|
|
6538
|
-
|
|
6760
|
+
// The price extent is known before the plot box, so the left margin can hold the widest tick
|
|
6761
|
+
// label: a currency price such as "$180.00" overflows the default 48.
|
|
6762
|
+
const prices = extent([
|
|
6539
6763
|
...data.flatMap((c) => [cmp(c.low), cmp(c.high)]),
|
|
6540
6764
|
...overlays
|
|
6541
6765
|
.filter((o) => !o.hidden)
|
|
6542
6766
|
.flatMap((o) => o.values.map((v) => (isFinite(v) ? cmp(v) : v))),
|
|
6543
|
-
], false)
|
|
6767
|
+
], false);
|
|
6768
|
+
const legend = [
|
|
6769
|
+
{ name: 'Up', color: UP },
|
|
6770
|
+
{ name: 'Down', color: DOWN },
|
|
6771
|
+
...overlays.map((o) => ({ name: o.name, color: o.color })),
|
|
6772
|
+
];
|
|
6773
|
+
const outer = plotBox(width, height, {
|
|
6774
|
+
...MARGIN$1,
|
|
6775
|
+
top: MARGIN$1.top + legendTop(input, legend, width) + (selector.length ? SELECTOR_HEIGHT : 0),
|
|
6776
|
+
bottom: MARGIN$1.bottom + brushRoom,
|
|
6777
|
+
left: input.scale === 'log'
|
|
6778
|
+
? MARGIN$1.left
|
|
6779
|
+
: valueAxisRoom(prices, makeFormatter(pctFormat ?? input.yAxis?.format ?? input.format)),
|
|
6780
|
+
});
|
|
6781
|
+
const volumeH = hasVolume ? outer.height * VOLUME_SHARE : 0;
|
|
6782
|
+
const plot = { ...outer, height: Math.max(0, outer.height - volumeH - (hasVolume ? 8 : 0)) };
|
|
6783
|
+
const y = axis(prices, [plot.y + plot.height, plot.y], { scale: input.scale === 'log' ? 'log' : 'linear', format: pctFormat ?? input.yAxis?.format }, input.format);
|
|
6544
6784
|
// A time axis places candles by their label's time, so missing sessions leave gaps; the bar
|
|
6545
6785
|
// width is 80% of the smallest gap. Otherwise every candle takes an equal slot.
|
|
6546
6786
|
const times = data.map((c) => categoryValue(c.label));
|
|
@@ -6568,11 +6808,6 @@ function layoutCandlestick(input) {
|
|
|
6568
6808
|
.map((t) => ({ pos: px(tx(t)), label: categoryLabel(t, tickFormat) }));
|
|
6569
6809
|
}
|
|
6570
6810
|
const labels = data.map((c) => categoryLabel(c.label, input.xAxis?.format));
|
|
6571
|
-
const legend = [
|
|
6572
|
-
{ name: 'Up', color: UP },
|
|
6573
|
-
{ name: 'Down', color: DOWN },
|
|
6574
|
-
...overlays.map((o) => ({ name: o.name, color: o.color })),
|
|
6575
|
-
];
|
|
6576
6811
|
const c0 = canvas(input, width, height, legend);
|
|
6577
6812
|
const frame = {
|
|
6578
6813
|
...c0,
|
|
@@ -6616,7 +6851,7 @@ function layoutCandlestick(input) {
|
|
|
6616
6851
|
const color = up ? UP : DOWN;
|
|
6617
6852
|
const cx = px(x.center(i));
|
|
6618
6853
|
const change = base !== undefined ? ` (${pct(cmp(c.close))})` : '';
|
|
6619
|
-
const title = `${labels[i]}: O ${priceFormat(c.open)} H ${priceFormat(c.high)} L ${priceFormat(c.low)} C ${priceFormat(c.close)}${change}${isFinite(c.volume) ? ` V ${
|
|
6854
|
+
const title = `${labels[i]}: O ${priceFormat(c.open)} H ${priceFormat(c.high)} L ${priceFormat(c.low)} C ${priceFormat(c.close)}${change}${isFinite(c.volume) ? ` V ${volumeFormat(c.volume)}` : ''}`;
|
|
6620
6855
|
const item = {
|
|
6621
6856
|
label: title,
|
|
6622
6857
|
id: c.id,
|
|
@@ -6630,7 +6865,7 @@ function layoutCandlestick(input) {
|
|
|
6630
6865
|
`High: ${priceFormat(c.high)}`,
|
|
6631
6866
|
`Low: ${priceFormat(c.low)}`,
|
|
6632
6867
|
`Close: ${priceFormat(c.close)}${change}`,
|
|
6633
|
-
...(isFinite(c.volume) ? [`Volume: ${
|
|
6868
|
+
...(isFinite(c.volume) ? [`Volume: ${volumeFormat(c.volume)}`] : []),
|
|
6634
6869
|
...overlays
|
|
6635
6870
|
.filter((o) => !o.hidden)
|
|
6636
6871
|
.map((o) => `${o.name}: ${isFinite(o.values[i]) ? priceFormat(o.values[i]) : '—'}`),
|
|
@@ -6709,7 +6944,7 @@ function layoutCandlestick(input) {
|
|
|
6709
6944
|
height: px(Math.max(h, 0)),
|
|
6710
6945
|
fill: c.close >= c.open ? UP : DOWN,
|
|
6711
6946
|
opacity: 0.4,
|
|
6712
|
-
title: `${labels[i]}: volume ${
|
|
6947
|
+
title: `${labels[i]}: volume ${volumeFormat(c.volume)}`,
|
|
6713
6948
|
});
|
|
6714
6949
|
});
|
|
6715
6950
|
}
|
|
@@ -6808,7 +7043,7 @@ function layoutCandlestick(input) {
|
|
|
6808
7043
|
width: 6,
|
|
6809
7044
|
height: box.height - 16,
|
|
6810
7045
|
fill: CHART_COLORS.axis,
|
|
6811
|
-
rx:
|
|
7046
|
+
rx: 8,
|
|
6812
7047
|
}, {
|
|
6813
7048
|
kind: 'rect',
|
|
6814
7049
|
part: 'brush-handle',
|
|
@@ -6817,7 +7052,7 @@ function layoutCandlestick(input) {
|
|
|
6817
7052
|
width: 6,
|
|
6818
7053
|
height: box.height - 16,
|
|
6819
7054
|
fill: CHART_COLORS.axis,
|
|
6820
|
-
rx:
|
|
7055
|
+
rx: 8,
|
|
6821
7056
|
});
|
|
6822
7057
|
hit.brush = { box, window };
|
|
6823
7058
|
}
|
|
@@ -6909,19 +7144,20 @@ function layoutWaterfall(input) {
|
|
|
6909
7144
|
flat: true,
|
|
6910
7145
|
});
|
|
6911
7146
|
const widest = horizontal ? Math.max(0, ...steps.map((s) => s.name.length)) * CHAR + 16 : 0;
|
|
7147
|
+
const legend = [
|
|
7148
|
+
{ name: 'Increase', color: up },
|
|
7149
|
+
{ name: 'Decrease', color: down },
|
|
7150
|
+
];
|
|
6912
7151
|
const plot = plotBox(width, height, {
|
|
6913
7152
|
...MARGIN$1,
|
|
6914
|
-
top: MARGIN$1.top + legendTop(input),
|
|
7153
|
+
top: MARGIN$1.top + legendTop(input, legend, width),
|
|
6915
7154
|
left: Math.max(MARGIN$1.left, widest),
|
|
6916
7155
|
right: input.labels && horizontal ? 56 : MARGIN$1.right,
|
|
6917
7156
|
});
|
|
6918
7157
|
const totals = steps.flatMap((s) => [s.from, s.to]);
|
|
6919
7158
|
const value = axis([Math.min(0, ...totals), Math.max(0, ...totals)], horizontal ? [plot.x, plot.x + plot.width] : [plot.y + plot.height, plot.y], {}, input.format);
|
|
6920
7159
|
const band = bandScale(steps.length, horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width]);
|
|
6921
|
-
const c = canvas(input, width, height,
|
|
6922
|
-
{ name: 'Increase', color: up },
|
|
6923
|
-
{ name: 'Decrease', color: down },
|
|
6924
|
-
]);
|
|
7160
|
+
const c = canvas(input, width, height, legend);
|
|
6925
7161
|
const categoryTicks = steps.map((s, i) => ({ pos: px(band.center(i)), label: s.name }));
|
|
6926
7162
|
const shapes = frameShapes({
|
|
6927
7163
|
...c,
|
|
@@ -7133,8 +7369,9 @@ function layoutGantt(input) {
|
|
|
7133
7369
|
: '';
|
|
7134
7370
|
const widest = Math.max(0, ...rowNames.map((n) => n.length + rowSuffix(n).length)) * CHAR +
|
|
7135
7371
|
Math.max(0, ...rowNames.map((n) => (rowDepth.get(n) ?? 0) * 12));
|
|
7372
|
+
const legend = input.tasks.map((t, i) => ({ name: t.name, color: colorOf(input, i, t.color) }));
|
|
7136
7373
|
const plot = plotBox(width, height, {
|
|
7137
|
-
top: 8 + legendTop(input),
|
|
7374
|
+
top: 8 + legendTop(input, legend, width),
|
|
7138
7375
|
right: 16,
|
|
7139
7376
|
bottom: 32 + (input.xAxis?.label ? 14 : 0),
|
|
7140
7377
|
left: Math.min(width / 2, Math.max(64, widest + 16)),
|
|
@@ -7167,7 +7404,10 @@ function layoutGantt(input) {
|
|
|
7167
7404
|
y2: plot.y + plot.height,
|
|
7168
7405
|
stroke: CHART_COLORS.grid,
|
|
7169
7406
|
});
|
|
7170
|
-
shapes.push(label(t.pos, plot.y + plot.height + 20, t.label, {
|
|
7407
|
+
shapes.push(label(t.pos, plot.y + plot.height + 20, t.label, {
|
|
7408
|
+
part: 'x-label',
|
|
7409
|
+
anchor: tickAnchor(t.pos, t.label, width),
|
|
7410
|
+
}));
|
|
7171
7411
|
}
|
|
7172
7412
|
rowNames.forEach((name, i) => {
|
|
7173
7413
|
const owner = visible.find((t) => (t.group ?? t.name) === name);
|
|
@@ -7363,7 +7603,7 @@ function layoutGantt(input) {
|
|
|
7363
7603
|
});
|
|
7364
7604
|
}
|
|
7365
7605
|
shapes.push(...overlays, ...annotationShapes(input.annotations, scales));
|
|
7366
|
-
const c = canvas(input, width, height,
|
|
7606
|
+
const c = canvas(input, width, height, legend);
|
|
7367
7607
|
return {
|
|
7368
7608
|
...c,
|
|
7369
7609
|
shapes: [...shapes, ...c.shapes],
|
|
@@ -7528,9 +7768,10 @@ function layoutBoxplot(input) {
|
|
|
7528
7768
|
const horizontal = input.orientation === 'horizontal';
|
|
7529
7769
|
const groups = grouped ? categories : input.series.map((s) => s.name);
|
|
7530
7770
|
const widest = Math.max(0, ...groups.map((n) => n.length)) * CHAR;
|
|
7771
|
+
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
7531
7772
|
const plot = plotBox(width, height, {
|
|
7532
7773
|
...MARGIN$1,
|
|
7533
|
-
top: MARGIN$1.top + legendTop(input) + (input.yAxis?.label ? 12 : 0),
|
|
7774
|
+
top: MARGIN$1.top + legendTop(input, legend, width) + (input.yAxis?.label ? 12 : 0),
|
|
7534
7775
|
left: horizontal ? Math.max(MARGIN$1.left, widest + 16) : MARGIN$1.left,
|
|
7535
7776
|
bottom: MARGIN$1.bottom + (horizontal && input.yAxis?.label ? 14 : 0),
|
|
7536
7777
|
});
|
|
@@ -7548,7 +7789,7 @@ function layoutBoxplot(input) {
|
|
|
7548
7789
|
const slots = grouped ? [...new Set(shown.map((c) => c.si))] : [];
|
|
7549
7790
|
const slotW = grouped ? band.bandwidth / Math.max(1, slots.length) : band.bandwidth;
|
|
7550
7791
|
const center = (c) => grouped ? band.start(c.ci) + (slots.indexOf(c.si) + 0.5) * slotW : band.center(c.si);
|
|
7551
|
-
const c = canvas(input, width, height,
|
|
7792
|
+
const c = canvas(input, width, height, legend);
|
|
7552
7793
|
const categoryTicks = groups.map((g, i) => ({ pos: px(band.center(i)), label: g }));
|
|
7553
7794
|
const shapes = frameShapes({
|
|
7554
7795
|
...c,
|
|
@@ -7699,7 +7940,8 @@ function layoutSunburst(input) {
|
|
|
7699
7940
|
// Crumbs sit in the hole when there is one, else in a row above the rings.
|
|
7700
7941
|
const crumbs = input.breadcrumbs && path.length > 0;
|
|
7701
7942
|
const crumbsAbove = crumbs && inner === 0;
|
|
7702
|
-
const
|
|
7943
|
+
const legend = nodes.map((n, i) => ({ name: n.name, color: colorOf(input, i, n.color) }));
|
|
7944
|
+
const top = legendTop(input, legend, width) + (crumbsAbove ? 20 : 0);
|
|
7703
7945
|
const cx = width / 2;
|
|
7704
7946
|
const cy = top + (height - top) / 2;
|
|
7705
7947
|
const radius = Math.max(0, Math.min(width, height - top) / 2 - 8);
|
|
@@ -7779,11 +8021,11 @@ function layoutSunburst(input) {
|
|
|
7779
8021
|
if (crumbs) {
|
|
7780
8022
|
const w = path.reduce((s, n) => s + n.name.length * CHAR + 6, 0) + (path.length - 1) * (CHAR + 6);
|
|
7781
8023
|
texts.push(...(crumbsAbove
|
|
7782
|
-
? breadcrumbShapes(path, 8, legendTop(input) + 10)
|
|
8024
|
+
? breadcrumbShapes(path, 8, legendTop(input, legend, width) + 10)
|
|
7783
8025
|
: breadcrumbShapes(path, cx - w / 2, input.centerLabel ? cy + 8 : cy)));
|
|
7784
8026
|
}
|
|
7785
|
-
const c = canvas(input, width, height,
|
|
7786
|
-
return { ...c, shapes: [...shapes, ...texts, ...c.shapes] };
|
|
8027
|
+
const c = canvas(input, width, height, legend);
|
|
8028
|
+
return { ...c, shapes: withLabelSurfaces([...shapes, ...texts, ...c.shapes]) };
|
|
7787
8029
|
}
|
|
7788
8030
|
|
|
7789
8031
|
/*
|
|
@@ -7847,12 +8089,6 @@ function layoutChord(input) {
|
|
|
7847
8089
|
return { ...state, issues };
|
|
7848
8090
|
const formatValue = makeFormatter(input.format);
|
|
7849
8091
|
const { width, height } = size(input, 300, 300);
|
|
7850
|
-
const top = legendTop(input);
|
|
7851
|
-
const cx = width / 2;
|
|
7852
|
-
const cy = top + (height - top) / 2;
|
|
7853
|
-
const r = Math.max(0, Math.min(width, height - top) / 2 - 36);
|
|
7854
|
-
const r0 = Math.max(0, r - RING);
|
|
7855
|
-
const PAD = ((input.padding ?? 2.3) * Math.PI) / 180;
|
|
7856
8092
|
// Groups keep their input order (and color) but can be arranged by total flow.
|
|
7857
8093
|
const cellRaw = (i, j) => {
|
|
7858
8094
|
const v = given2[i]?.[j];
|
|
@@ -7864,6 +8100,17 @@ function layoutChord(input) {
|
|
|
7864
8100
|
order.sort((a, b) => input.sort === 'asc' ? totalOf(a) - totalOf(b) : totalOf(b) - totalOf(a));
|
|
7865
8101
|
const names = order.map((i) => given[i]);
|
|
7866
8102
|
const colorAt = (k) => colorOf(input, order[k]);
|
|
8103
|
+
const legend = names.map((name, i) => ({ name, color: colorAt(i) }));
|
|
8104
|
+
const top = legendTop(input, legend, width);
|
|
8105
|
+
const cx = width / 2;
|
|
8106
|
+
const cy = top + (height - top) / 2;
|
|
8107
|
+
// Group names sit outside the rim, so the circle keeps room for the widest one on either side;
|
|
8108
|
+
// without it the left-hand label runs off the svg.
|
|
8109
|
+
const nameGap = input.ticks ? 8 + TICK + 14 : 8;
|
|
8110
|
+
const nameRoom = nameGap + Math.max(0, ...given.map((s) => s.length)) * CHAR + 4;
|
|
8111
|
+
const r = Math.max(0, Math.min(width / 2 - nameRoom, (height - top) / 2 - 36));
|
|
8112
|
+
const r0 = Math.max(0, r - RING);
|
|
8113
|
+
const PAD = ((input.padding ?? 2.3) * Math.PI) / 180;
|
|
7867
8114
|
const n = names.length;
|
|
7868
8115
|
const selectedId = selectedIds(input.selected)[0];
|
|
7869
8116
|
const selected = selectedId === undefined ? -1 : names.indexOf(String(selectedId));
|
|
@@ -7943,7 +8190,7 @@ function layoutChord(input) {
|
|
|
7943
8190
|
const mid = (g.a0 + g.a1) / 2;
|
|
7944
8191
|
const sin = Math.sin(mid);
|
|
7945
8192
|
// Ticks sit on the rim; the group name moves out past their labels.
|
|
7946
|
-
const nameR = r +
|
|
8193
|
+
const nameR = r + nameGap;
|
|
7947
8194
|
shapes.push(label(px(cx + nameR * sin), px(cy - nameR * Math.cos(mid)), names[i], {
|
|
7948
8195
|
anchor: sin > 0.1 ? 'start' : sin < -0.1 ? 'end' : 'middle',
|
|
7949
8196
|
baseline: 'middle',
|
|
@@ -7981,7 +8228,7 @@ function layoutChord(input) {
|
|
|
7981
8228
|
}));
|
|
7982
8229
|
});
|
|
7983
8230
|
});
|
|
7984
|
-
const c = canvas(input, width, height,
|
|
8231
|
+
const c = canvas(input, width, height, legend);
|
|
7985
8232
|
return { ...c, shapes: [...shapes, ...c.shapes], issues };
|
|
7986
8233
|
}
|
|
7987
8234
|
|
|
@@ -8232,16 +8479,17 @@ function layoutMarimekkoChart(input) {
|
|
|
8232
8479
|
const format = makeFormatter(input.format);
|
|
8233
8480
|
const totalFormat = makeFormatter(input.xAxis?.format ?? input.format);
|
|
8234
8481
|
const { width, height } = size(input);
|
|
8482
|
+
const legend = segments.map((s) => ({ name: s.name, color: s.color }));
|
|
8235
8483
|
const plot = plotBox(width, height, {
|
|
8236
8484
|
...MARGIN$1,
|
|
8237
|
-
top: MARGIN$1.top + legendTop(input),
|
|
8485
|
+
top: MARGIN$1.top + legendTop(input, legend, width),
|
|
8238
8486
|
bottom: MARGIN$1.bottom + (input.xAxis?.label ? 14 : 0) + (input.xAxis?.format ? 14 : 0),
|
|
8239
8487
|
});
|
|
8240
8488
|
const gap = Math.max(0, Math.min(0.2, input.gap ?? 0.01)) * plot.width;
|
|
8241
8489
|
const gaps = Math.max(0, columns.length - 1);
|
|
8242
8490
|
const usable = Math.max(0, plot.width - gap * gaps);
|
|
8243
8491
|
const y = axis([0, 1], [plot.y + plot.height, plot.y], { format: { style: 'percent' } });
|
|
8244
|
-
const c = canvas(input, width, height,
|
|
8492
|
+
const c = canvas(input, width, height, legend);
|
|
8245
8493
|
// Column positions along x, each as wide as its share of the grand total.
|
|
8246
8494
|
let x = plot.x;
|
|
8247
8495
|
const placed = columns.map((col) => {
|
|
@@ -8303,7 +8551,7 @@ function layoutMarimekkoChart(input) {
|
|
|
8303
8551
|
}));
|
|
8304
8552
|
});
|
|
8305
8553
|
}
|
|
8306
|
-
return { ...c, shapes: [...shapes, ...cells, ...texts, ...c.shapes], issues };
|
|
8554
|
+
return { ...c, shapes: withLabelSurfaces([...shapes, ...cells, ...texts, ...c.shapes]), issues };
|
|
8307
8555
|
}
|
|
8308
8556
|
|
|
8309
8557
|
/*
|
|
@@ -8331,8 +8579,9 @@ function layoutParallelCoordinates(input) {
|
|
|
8331
8579
|
const first = dims[0];
|
|
8332
8580
|
const last = dims[dims.length - 1];
|
|
8333
8581
|
const pad = (d) => ((d.label ?? d.key).length * CHAR) / 2 + 8;
|
|
8582
|
+
const legend = groups.map((g, i) => ({ name: g, color: colorOf(input, i) }));
|
|
8334
8583
|
const plot = plotBox(width, height, {
|
|
8335
|
-
top: 28 + legendTop(input),
|
|
8584
|
+
top: 28 + legendTop(input, legend, width),
|
|
8336
8585
|
right: Math.max(16, pad(last)),
|
|
8337
8586
|
bottom: 16,
|
|
8338
8587
|
left: Math.max(48, pad(first)),
|
|
@@ -8435,7 +8684,7 @@ function layoutParallelCoordinates(input) {
|
|
|
8435
8684
|
: undefined,
|
|
8436
8685
|
};
|
|
8437
8686
|
});
|
|
8438
|
-
const c = canvas(input, width, height,
|
|
8687
|
+
const c = canvas(input, width, height, legend);
|
|
8439
8688
|
return { ...c, shapes: [...shapes, ...lines, ...c.shapes], issues };
|
|
8440
8689
|
}
|
|
8441
8690
|
|
|
@@ -8512,6 +8761,10 @@ function layoutDumbbellChart(input) {
|
|
|
8512
8761
|
const vertical = input.orientation === 'vertical';
|
|
8513
8762
|
const startLabel = input.startLabel ?? 'Start';
|
|
8514
8763
|
const endLabel = input.endLabel ?? 'End';
|
|
8764
|
+
// The legend toggles the two ends like any other series: a hidden end drops its markers, its
|
|
8765
|
+
// value labels and the connector that would dangle, and leaves the value axis to the rest.
|
|
8766
|
+
const hideStart = isHidden(input, startLabel);
|
|
8767
|
+
const hideEnd = isHidden(input, endLabel);
|
|
8515
8768
|
const sorted = [...data];
|
|
8516
8769
|
if (input.sort) {
|
|
8517
8770
|
const key = (d) => input.sort === 'change'
|
|
@@ -8522,21 +8775,22 @@ function layoutDumbbellChart(input) {
|
|
|
8522
8775
|
sorted.sort((a, b) => (input.sort === 'asc' ? key(a) - key(b) : key(b) - key(a)));
|
|
8523
8776
|
}
|
|
8524
8777
|
const widest = Math.max(0, ...sorted.map((d) => d.name.length)) * CHAR;
|
|
8778
|
+
const startColor = colorOf(input, 0);
|
|
8779
|
+
const endColor = colorOf(input, 1);
|
|
8780
|
+
const legend = [
|
|
8781
|
+
{ name: startLabel, color: startColor },
|
|
8782
|
+
{ name: endLabel, color: endColor },
|
|
8783
|
+
];
|
|
8525
8784
|
const plot = plotBox(width, height, {
|
|
8526
8785
|
...MARGIN$1,
|
|
8527
|
-
top: MARGIN$1.top + legendTop(input) + (vertical && input.xAxis?.label ? 12 : 0),
|
|
8786
|
+
top: MARGIN$1.top + legendTop(input, legend, width) + (vertical && input.xAxis?.label ? 12 : 0),
|
|
8528
8787
|
right: input.labels && !vertical ? 56 : MARGIN$1.right,
|
|
8529
8788
|
bottom: MARGIN$1.bottom + (!vertical && input.xAxis?.label ? 14 : 0),
|
|
8530
8789
|
left: vertical ? MARGIN$1.left : Math.max(MARGIN$1.left, widest + 16),
|
|
8531
8790
|
});
|
|
8532
|
-
const value = axis(extent(sorted.flatMap((d) => [d.start, d.end]), false), vertical ? [plot.y + plot.height, plot.y] : [plot.x, plot.x + plot.width], input.xAxis ?? {}, input.format);
|
|
8791
|
+
const value = axis(extent(sorted.flatMap((d) => [hideStart ? undefined : d.start, hideEnd ? undefined : d.end]), false), vertical ? [plot.y + plot.height, plot.y] : [plot.x, plot.x + plot.width], input.xAxis ?? {}, input.format);
|
|
8533
8792
|
const band = bandScale(sorted.length, vertical ? [plot.x, plot.x + plot.width] : [plot.y, plot.y + plot.height]);
|
|
8534
|
-
const
|
|
8535
|
-
const endColor = colorOf(input, 1);
|
|
8536
|
-
const c = canvas(input, width, height, [
|
|
8537
|
-
{ name: startLabel, color: startColor },
|
|
8538
|
-
{ name: endLabel, color: endColor },
|
|
8539
|
-
]);
|
|
8793
|
+
const c = canvas(input, width, height, legend);
|
|
8540
8794
|
const categoryTicks = sorted.map((d, i) => ({ pos: px(band.center(i)), label: d.name }));
|
|
8541
8795
|
const shapes = frameShapes({
|
|
8542
8796
|
...c,
|
|
@@ -8575,7 +8829,7 @@ function layoutDumbbellChart(input) {
|
|
|
8575
8829
|
index: i,
|
|
8576
8830
|
value: isFinite(d.end) ? d.end : undefined,
|
|
8577
8831
|
};
|
|
8578
|
-
if (isFinite(d.start) && isFinite(d.end)) {
|
|
8832
|
+
if (!hideStart && !hideEnd && isFinite(d.start) && isFinite(d.end)) {
|
|
8579
8833
|
const a = at(d.start, mid);
|
|
8580
8834
|
const b = at(d.end, mid);
|
|
8581
8835
|
connectors.push({
|
|
@@ -8591,11 +8845,11 @@ function layoutDumbbellChart(input) {
|
|
|
8591
8845
|
item,
|
|
8592
8846
|
});
|
|
8593
8847
|
}
|
|
8594
|
-
for (const [v, part, name, fill] of [
|
|
8595
|
-
[d.start, 'start', startLabel, startColor],
|
|
8596
|
-
[d.end, 'end', endLabel, endColor],
|
|
8848
|
+
for (const [v, part, name, fill, hidden] of [
|
|
8849
|
+
[d.start, 'start', startLabel, startColor, hideStart],
|
|
8850
|
+
[d.end, 'end', endLabel, endColor, hideEnd],
|
|
8597
8851
|
]) {
|
|
8598
|
-
if (!isFinite(v))
|
|
8852
|
+
if (hidden || !isFinite(v))
|
|
8599
8853
|
continue;
|
|
8600
8854
|
const p = at(v, mid);
|
|
8601
8855
|
const text = `${d.name} · ${name}: ${format(v)}`;
|
|
@@ -8801,7 +9055,7 @@ function layoutTreeChart(input) {
|
|
|
8801
9055
|
});
|
|
8802
9056
|
}
|
|
8803
9057
|
const value = nodeValue(p.node);
|
|
8804
|
-
const title = `${p.node.name}${p.node.subtitle ? ` · ${p.node.subtitle}` : ''}${
|
|
9058
|
+
const title = `${p.node.name}${p.node.subtitle ? ` · ${p.node.subtitle}` : ''}${hasValue(p.node) ? `: ${format(value)}` : ''}${p.collapsed ? ' (collapsed)' : ''}`;
|
|
8805
9059
|
const item = { label: title, id: nodeId(p.node), name: p.node.name, value };
|
|
8806
9060
|
if (box) {
|
|
8807
9061
|
marks.push({
|
|
@@ -8877,7 +9131,10 @@ function layoutTreeChart(input) {
|
|
|
8877
9131
|
]
|
|
8878
9132
|
: [];
|
|
8879
9133
|
const c = canvas(input, width, height);
|
|
8880
|
-
return {
|
|
9134
|
+
return {
|
|
9135
|
+
...c,
|
|
9136
|
+
shapes: withLabelSurfaces([...links, ...marks, ...texts, ...crumbs, ...c.shapes]),
|
|
9137
|
+
};
|
|
8881
9138
|
}
|
|
8882
9139
|
/** An organization chart: the tree with boxes and elbow links. */
|
|
8883
9140
|
const layoutOrganizationChart = (input) => layoutTreeChart({ ...input, nodeShape: 'box', linkStyle: 'elbow' });
|
|
@@ -8942,13 +9199,21 @@ function packCircles(radii, padding) {
|
|
|
8942
9199
|
function layoutCirclePacking(input) {
|
|
8943
9200
|
const issues = [];
|
|
8944
9201
|
const { nodes: roots } = drill(input.data, input.rootId);
|
|
8945
|
-
|
|
9202
|
+
// The legend lists every top-level branch, hidden ones included, so a hide can be undone; and
|
|
9203
|
+
// a branch keeps the colour of its place in that full list whatever is hidden.
|
|
9204
|
+
const branches = cutDepth(roots, input.maxDepth);
|
|
9205
|
+
const branchColor = new Map(branches.map((n, i) => [nodeId(n), colorOf(input, i, n.color)]));
|
|
9206
|
+
const nodes = branches.filter((n) => !isHidden(input, n.name));
|
|
8946
9207
|
const state = stateCanvas(input, !nodes.some((n) => nodeValue(n) > 0), 300, 300);
|
|
8947
9208
|
if (state)
|
|
8948
9209
|
return { ...state, issues };
|
|
8949
9210
|
const format = makeFormatter(input.format);
|
|
8950
9211
|
const { width, height } = size(input, 300, 300);
|
|
8951
|
-
const
|
|
9212
|
+
const legend = branches.map((n) => ({
|
|
9213
|
+
name: n.name,
|
|
9214
|
+
color: branchColor.get(nodeId(n)),
|
|
9215
|
+
}));
|
|
9216
|
+
const top = legendTop(input, legend, width);
|
|
8952
9217
|
const pad = input.padding ?? 2;
|
|
8953
9218
|
const labels = input.labels ?? true;
|
|
8954
9219
|
const leafValues = [];
|
|
@@ -8987,7 +9252,7 @@ function layoutCirclePacking(input) {
|
|
|
8987
9252
|
const r = Math.max(0, c.r * k - pad / 2);
|
|
8988
9253
|
const value = nodeValue(n);
|
|
8989
9254
|
const branch = (n.children?.length ?? 0) > 0;
|
|
8990
|
-
const color = n.color ?? inherited ?? colorOf(input, i);
|
|
9255
|
+
const color = n.color ?? inherited ?? branchColor.get(nodeId(n)) ?? colorOf(input, i);
|
|
8991
9256
|
const fill = branch ? color : input.colorScale ? (colors.color(value)?.fill ?? color) : color;
|
|
8992
9257
|
const title = `${n.name}: ${format(value)}`;
|
|
8993
9258
|
shapes.push({
|
|
@@ -9025,8 +9290,8 @@ function layoutCirclePacking(input) {
|
|
|
9025
9290
|
};
|
|
9026
9291
|
const radius = Math.max(0, Math.min(width, height - top) / 2 - 8);
|
|
9027
9292
|
pack(nodes, width / 2, top + (height - top) / 2, radius, undefined);
|
|
9028
|
-
const c = canvas(input, width, height,
|
|
9029
|
-
return { ...c, shapes: [...shapes, ...texts, ...c.shapes], issues };
|
|
9293
|
+
const c = canvas(input, width, height, legend);
|
|
9294
|
+
return { ...c, shapes: withLabelSurfaces([...shapes, ...texts, ...c.shapes]), issues };
|
|
9030
9295
|
}
|
|
9031
9296
|
|
|
9032
9297
|
/*
|
|
@@ -9049,7 +9314,8 @@ function layoutIcicle(input) {
|
|
|
9049
9314
|
const { width, height } = size(input);
|
|
9050
9315
|
const horizontal = input.orientation === 'horizontal';
|
|
9051
9316
|
const crumbs = input.breadcrumbs && path.length > 0;
|
|
9052
|
-
const
|
|
9317
|
+
const legend = nodes.map((n, i) => ({ name: n.name, color: colorOf(input, i, n.color) }));
|
|
9318
|
+
const top = legendTop(input, legend, width) + (crumbs ? 20 : 0);
|
|
9053
9319
|
const plot = plotBox(width, height, { top, right: 0, bottom: 0, left: 0 });
|
|
9054
9320
|
const gap = input.padding ?? 1;
|
|
9055
9321
|
const labels = input.labels ?? true;
|
|
@@ -9117,9 +9383,9 @@ function layoutIcicle(input) {
|
|
|
9117
9383
|
};
|
|
9118
9384
|
walk(nodes, 0, horizontal ? plot.y : plot.x, horizontal ? plot.height : plot.width, undefined);
|
|
9119
9385
|
if (crumbs)
|
|
9120
|
-
shapes.push(...breadcrumbShapes(path, 8, legendTop(input) + 10));
|
|
9121
|
-
const c = canvas(input, width, height,
|
|
9122
|
-
return { ...c, shapes: [...shapes, ...texts, ...c.shapes], issues };
|
|
9386
|
+
shapes.push(...breadcrumbShapes(path, 8, legendTop(input, legend, width) + 10));
|
|
9387
|
+
const c = canvas(input, width, height, legend);
|
|
9388
|
+
return { ...c, shapes: withLabelSurfaces([...shapes, ...texts, ...c.shapes]), issues };
|
|
9123
9389
|
}
|
|
9124
9390
|
|
|
9125
9391
|
/*
|
|
@@ -9177,16 +9443,17 @@ function layoutBumpChart(input) {
|
|
|
9177
9443
|
});
|
|
9178
9444
|
const showLabels = input.labels ?? true;
|
|
9179
9445
|
const widest = showLabels ? Math.max(0, ...drawn.map((s) => s.name.length)) * CHAR + 12 : 0;
|
|
9446
|
+
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
9180
9447
|
const plot = plotBox(width, height, {
|
|
9181
9448
|
...MARGIN$1,
|
|
9182
|
-
top: MARGIN$1.top + legendTop(input),
|
|
9449
|
+
top: MARGIN$1.top + legendTop(input, legend, width),
|
|
9183
9450
|
right: Math.max(MARGIN$1.right, widest),
|
|
9184
9451
|
bottom: MARGIN$1.bottom + (input.xAxis?.label ? 14 : 0),
|
|
9185
9452
|
});
|
|
9186
9453
|
// Rank 1 sits at the top: the axis runs down from half a rank above 1 to half a rank below `top`.
|
|
9187
9454
|
const y = linearScale([0.5, top + 0.5], [plot.y, plot.y + plot.height]);
|
|
9188
9455
|
const band = bandScale(count, [plot.x, plot.x + plot.width], 0);
|
|
9189
|
-
const c = canvas(input, width, height,
|
|
9456
|
+
const c = canvas(input, width, height, legend);
|
|
9190
9457
|
const rankTicks = Array.from({ length: top }, (_, k) => ({
|
|
9191
9458
|
pos: px(y(k + 1)),
|
|
9192
9459
|
label: `#${k + 1}`,
|
|
@@ -9294,7 +9561,8 @@ function layoutPolarAreaChart(input) {
|
|
|
9294
9561
|
return { ...state, issues };
|
|
9295
9562
|
const format = makeFormatter(input.format);
|
|
9296
9563
|
const { width, height } = size(input, 300, 300);
|
|
9297
|
-
const
|
|
9564
|
+
const legend = input.data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) }));
|
|
9565
|
+
const top = legendTop(input, legend, width);
|
|
9298
9566
|
const cx = width / 2;
|
|
9299
9567
|
const cy = top + (height - top) / 2;
|
|
9300
9568
|
const outer = Math.max(0, Math.min(width, height - top) / 2 - (input.labels ? 40 : 12));
|
|
@@ -9356,7 +9624,7 @@ function layoutPolarAreaChart(input) {
|
|
|
9356
9624
|
}));
|
|
9357
9625
|
}
|
|
9358
9626
|
});
|
|
9359
|
-
const c = canvas(input, width, height,
|
|
9627
|
+
const c = canvas(input, width, height, legend);
|
|
9360
9628
|
return { ...c, shapes: [...grid, ...sectors, ...texts, ...c.shapes], issues };
|
|
9361
9629
|
}
|
|
9362
9630
|
|
|
@@ -9419,15 +9687,16 @@ function layoutSwarmPlot(input) {
|
|
|
9419
9687
|
const { width, height } = size(input);
|
|
9420
9688
|
const horizontal = input.orientation === 'horizontal';
|
|
9421
9689
|
const widest = Math.max(0, ...input.series.map((s) => s.name.length)) * CHAR;
|
|
9690
|
+
const legend = input.series.map((s, i) => ({ name: s.name, color: colorOf(input, i, s.color) }));
|
|
9422
9691
|
const plot = plotBox(width, height, {
|
|
9423
9692
|
...MARGIN$1,
|
|
9424
|
-
top: MARGIN$1.top + legendTop(input) + (!horizontal && input.yAxis?.label ? 12 : 0),
|
|
9693
|
+
top: MARGIN$1.top + legendTop(input, legend, width) + (!horizontal && input.yAxis?.label ? 12 : 0),
|
|
9425
9694
|
left: horizontal ? Math.max(MARGIN$1.left, widest + 16) : MARGIN$1.left,
|
|
9426
9695
|
bottom: MARGIN$1.bottom + (horizontal && input.yAxis?.label ? 14 : 0),
|
|
9427
9696
|
});
|
|
9428
9697
|
const value = axis(extent(samples.flat(), false), horizontal ? [plot.x, plot.x + plot.width] : [plot.y + plot.height, plot.y], input.yAxis ?? {}, input.format);
|
|
9429
9698
|
const band = bandScale(input.series.length, horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width]);
|
|
9430
|
-
const c = canvas(input, width, height,
|
|
9699
|
+
const c = canvas(input, width, height, legend);
|
|
9431
9700
|
const categoryTicks = input.series.map((s, i) => ({ pos: px(band.center(i)), label: s.name }));
|
|
9432
9701
|
const shapes = frameShapes({
|
|
9433
9702
|
...c,
|
|
@@ -9540,7 +9809,23 @@ function layoutWaffleChart(input) {
|
|
|
9540
9809
|
const share = (v) => (total > 0 ? v / total : 0);
|
|
9541
9810
|
const counts = waffleCounts(data.map((d) => (isHidden(input, d.name) ? 0 : Math.min(1, share(d.value)) * cells)));
|
|
9542
9811
|
const { width, height } = size(input, 300, 300);
|
|
9543
|
-
|
|
9812
|
+
// `labels` appends the share to each legend entry; the entry still toggles by the datum's name.
|
|
9813
|
+
const shares = new Map(data.map((d) => [d.name, `${Math.round(share(d.value) * 100)}%`]));
|
|
9814
|
+
const legend = legendOptions(input);
|
|
9815
|
+
const legendItems = input.data.map((d, i) => ({
|
|
9816
|
+
name: d.name,
|
|
9817
|
+
color: colorOf(input, i, d.color),
|
|
9818
|
+
}));
|
|
9819
|
+
const legendInput = input.labels && legend
|
|
9820
|
+
? {
|
|
9821
|
+
...input,
|
|
9822
|
+
legend: {
|
|
9823
|
+
...legend,
|
|
9824
|
+
formatter: (n) => `${legend.formatter?.(n) ?? n} ${shares.get(n) ?? ''}`,
|
|
9825
|
+
},
|
|
9826
|
+
}
|
|
9827
|
+
: input;
|
|
9828
|
+
const top = legendTop(legendInput, legendItems, width);
|
|
9544
9829
|
const gap = Math.max(0, input.cellGap ?? 2);
|
|
9545
9830
|
const cell = Math.max(1, Math.min((width - 2 * PAD) / columns, (height - top - 2 * PAD) / rows));
|
|
9546
9831
|
const x0 = (width - cell * columns) / 2;
|
|
@@ -9550,7 +9835,7 @@ function layoutWaffleChart(input) {
|
|
|
9550
9835
|
const corner = (k) => horizontal
|
|
9551
9836
|
? { x: x0 + (k % columns) * cell, y: y0 + Math.floor(k / columns) * cell }
|
|
9552
9837
|
: { x: x0 + Math.floor(k / rows) * cell, y: y0 + (rows - 1 - (k % rows)) * cell };
|
|
9553
|
-
const rect = (k, fill, title, item, opacity) => {
|
|
9838
|
+
const rect = (k, fill, title, item, opacity, stroke) => {
|
|
9554
9839
|
const { x, y } = corner(k);
|
|
9555
9840
|
return {
|
|
9556
9841
|
kind: 'rect',
|
|
@@ -9560,6 +9845,7 @@ function layoutWaffleChart(input) {
|
|
|
9560
9845
|
width: px(Math.max(0.5, cell - gap)),
|
|
9561
9846
|
height: px(Math.max(0.5, cell - gap)),
|
|
9562
9847
|
fill,
|
|
9848
|
+
stroke,
|
|
9563
9849
|
opacity,
|
|
9564
9850
|
rx: input.cellRadius ?? 2,
|
|
9565
9851
|
title,
|
|
@@ -9577,20 +9863,10 @@ function layoutWaffleChart(input) {
|
|
|
9577
9863
|
});
|
|
9578
9864
|
const rest = total - sum;
|
|
9579
9865
|
const restTitle = rest > 0 ? `Other: ${format(rest)} (${Math.round(share(rest) * 100)}%)` : 'Empty';
|
|
9866
|
+
// A subtle fill alone is the same luminance as a dark stage; the outline carries the contrast.
|
|
9580
9867
|
for (; k < cells; k++)
|
|
9581
|
-
shapes.push(rect(k, CHART_COLORS.grid, restTitle, undefined, 0.6));
|
|
9582
|
-
|
|
9583
|
-
const shares = new Map(data.map((d) => [d.name, `${Math.round(share(d.value) * 100)}%`]));
|
|
9584
|
-
const legend = legendOptions(input);
|
|
9585
|
-
const c = canvas(input.labels && legend
|
|
9586
|
-
? {
|
|
9587
|
-
...input,
|
|
9588
|
-
legend: {
|
|
9589
|
-
...legend,
|
|
9590
|
-
formatter: (n) => `${legend.formatter?.(n) ?? n} ${shares.get(n) ?? ''}`,
|
|
9591
|
-
},
|
|
9592
|
-
}
|
|
9593
|
-
: input, width, height, input.data.map((d, i) => ({ name: d.name, color: colorOf(input, i, d.color) })));
|
|
9868
|
+
shapes.push(rect(k, CHART_COLORS.grid, restTitle, undefined, 0.6, CHART_COLORS.axis));
|
|
9869
|
+
const c = canvas(legendInput, width, height, legendItems);
|
|
9594
9870
|
return { ...c, shapes: [...shapes, ...c.shapes], issues };
|
|
9595
9871
|
}
|
|
9596
9872
|
|
|
@@ -9608,6 +9884,7 @@ function layoutWaffleChart(input) {
|
|
|
9608
9884
|
/** Draws a core shape list back to front; marks with an `item` carry their index for hit-testing. */
|
|
9609
9885
|
class SkdxChartShapesComponent {
|
|
9610
9886
|
constructor() {
|
|
9887
|
+
this.surfaceRadius = chartSurfaceRadius;
|
|
9611
9888
|
this.shapes = input.required(/* @ts-ignore */
|
|
9612
9889
|
...(ngDevMode ? [{ debugName: "shapes" }] : /* istanbul ignore next */ []));
|
|
9613
9890
|
}
|
|
@@ -9643,9 +9920,13 @@ class SkdxChartShapesComponent {
|
|
|
9643
9920
|
[attr.width]="s.width"
|
|
9644
9921
|
[attr.height]="s.height"
|
|
9645
9922
|
[attr.rx]="s.rx"
|
|
9923
|
+
[style.rx]="surfaceRadius(s.part)"
|
|
9646
9924
|
[attr.fill]="s.fill"
|
|
9647
9925
|
[attr.stroke]="s.stroke"
|
|
9648
9926
|
[attr.fill-opacity]="s.opacity"
|
|
9927
|
+
[attr.pointer-events]="
|
|
9928
|
+
s.part === 'stage-label-background' || s.part === 'label-background' ? 'none' : null
|
|
9929
|
+
"
|
|
9649
9930
|
>
|
|
9650
9931
|
@if (s.title) {
|
|
9651
9932
|
<svg:title>{{ s.title }}</svg:title>
|
|
@@ -9688,6 +9969,7 @@ class SkdxChartShapesComponent {
|
|
|
9688
9969
|
}
|
|
9689
9970
|
@case ('text') {
|
|
9690
9971
|
<svg:text
|
|
9972
|
+
[attr.aria-label]="s.title"
|
|
9691
9973
|
[attr.data-skdx-part]="s.part"
|
|
9692
9974
|
[attr.data-skdx-i]="s.item ? $index : null"
|
|
9693
9975
|
[attr.x]="s.x"
|
|
@@ -9742,9 +10024,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
9742
10024
|
[attr.width]="s.width"
|
|
9743
10025
|
[attr.height]="s.height"
|
|
9744
10026
|
[attr.rx]="s.rx"
|
|
10027
|
+
[style.rx]="surfaceRadius(s.part)"
|
|
9745
10028
|
[attr.fill]="s.fill"
|
|
9746
10029
|
[attr.stroke]="s.stroke"
|
|
9747
10030
|
[attr.fill-opacity]="s.opacity"
|
|
10031
|
+
[attr.pointer-events]="
|
|
10032
|
+
s.part === 'stage-label-background' || s.part === 'label-background' ? 'none' : null
|
|
10033
|
+
"
|
|
9748
10034
|
>
|
|
9749
10035
|
@if (s.title) {
|
|
9750
10036
|
<svg:title>{{ s.title }}</svg:title>
|
|
@@ -9787,6 +10073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
9787
10073
|
}
|
|
9788
10074
|
@case ('text') {
|
|
9789
10075
|
<svg:text
|
|
10076
|
+
[attr.aria-label]="s.title"
|
|
9790
10077
|
[attr.data-skdx-part]="s.part"
|
|
9791
10078
|
[attr.data-skdx-i]="s.item ? $index : null"
|
|
9792
10079
|
[attr.x]="s.x"
|
|
@@ -9882,12 +10169,16 @@ class SkdxChartRootComponent {
|
|
|
9882
10169
|
...(ngDevMode ? [{ debugName: "svg" }] : /* istanbul ignore next */ []));
|
|
9883
10170
|
this.hover = null;
|
|
9884
10171
|
this.token = {};
|
|
10172
|
+
this.dismissedCrosshair = signal(null, /* @ts-ignore */
|
|
10173
|
+
...(ngDevMode ? [{ debugName: "dismissedCrosshair" }] : /* istanbul ignore next */ []));
|
|
9885
10174
|
this.tip = signal([], /* @ts-ignore */
|
|
9886
10175
|
...(ngDevMode ? [{ debugName: "tip" }] : /* istanbul ignore next */ []));
|
|
9887
10176
|
this.tipShapes = computed(() => {
|
|
9888
10177
|
const own = this.tip();
|
|
9889
10178
|
const cross = this.crosshair();
|
|
9890
|
-
return own.length || !cross
|
|
10179
|
+
return own.length || !cross || cross === this.dismissedCrosshair()
|
|
10180
|
+
? own
|
|
10181
|
+
: crosshairAt(this.layout(), cross);
|
|
9891
10182
|
}, /* @ts-ignore */
|
|
9892
10183
|
...(ngDevMode ? [{ debugName: "tipShapes" }] : /* istanbul ignore next */ []));
|
|
9893
10184
|
this.lastCross = null;
|
|
@@ -9912,8 +10203,6 @@ class SkdxChartRootComponent {
|
|
|
9912
10203
|
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
9913
10204
|
this.focusable = computed(() => !this.layout().decorative && focusTargets(this.layout()).length > 0, /* @ts-ignore */
|
|
9914
10205
|
...(ngDevMode ? [{ debugName: "focusable" }] : /* istanbul ignore next */ []));
|
|
9915
|
-
this.descId = computed(() => this.layout().description ? `${this.kind()}-desc-${this.label().replace(/\W+/g, '-')}` : null, /* @ts-ignore */
|
|
9916
|
-
...(ngDevMode ? [{ debugName: "descId" }] : /* istanbul ignore next */ []));
|
|
9917
10206
|
this.focusShapes = computed(() => focusShapes(this.layout(), this.focused()), /* @ts-ignore */
|
|
9918
10207
|
...(ngDevMode ? [{ debugName: "focusShapes" }] : /* istanbul ignore next */ []));
|
|
9919
10208
|
this.drag = null;
|
|
@@ -10019,6 +10308,7 @@ class SkdxChartRootComponent {
|
|
|
10019
10308
|
this.shareCrosshair(null);
|
|
10020
10309
|
}
|
|
10021
10310
|
onPointerMove(e) {
|
|
10311
|
+
this.dismissedCrosshair.set(null);
|
|
10022
10312
|
const layout = this.layout();
|
|
10023
10313
|
if (this.pointers.has(e.pointerId))
|
|
10024
10314
|
this.pointers.set(e.pointerId, this.viewBox(e).x);
|
|
@@ -10233,6 +10523,14 @@ class SkdxChartRootComponent {
|
|
|
10233
10523
|
}
|
|
10234
10524
|
}
|
|
10235
10525
|
onKeyDown(e) {
|
|
10526
|
+
if (e.key === 'Escape') {
|
|
10527
|
+
e.preventDefault();
|
|
10528
|
+
this.focused.set(null);
|
|
10529
|
+
this.clearTip();
|
|
10530
|
+
this.dismissedCrosshair.set(this.crosshair());
|
|
10531
|
+
this.highlight(null);
|
|
10532
|
+
return;
|
|
10533
|
+
}
|
|
10236
10534
|
if (!this.focusable())
|
|
10237
10535
|
return;
|
|
10238
10536
|
const focused = this.focused();
|
|
@@ -10270,14 +10568,13 @@ class SkdxChartRootComponent {
|
|
|
10270
10568
|
[attr.role]="layout().decorative ? 'presentation' : 'img'"
|
|
10271
10569
|
[attr.aria-hidden]="layout().decorative ? 'true' : null"
|
|
10272
10570
|
[attr.aria-label]="layout().decorative ? null : label()"
|
|
10273
|
-
[attr.aria-
|
|
10571
|
+
[attr.aria-description]="layout().description"
|
|
10274
10572
|
[attr.tabindex]="focusable() ? 0 : null"
|
|
10275
10573
|
[attr.data-skdx-chart]="kind()"
|
|
10276
10574
|
[attr.font-family]="colors.fontFamily"
|
|
10277
10575
|
[attr.font-size]="colors.fontSize"
|
|
10278
10576
|
[style.max-width]="'100%'"
|
|
10279
10577
|
[style.height]="'auto'"
|
|
10280
|
-
[style.outline]="'none'"
|
|
10281
10578
|
[style.cursor]="canZoom() || canPan() ? 'grab' : brushing() ? 'crosshair' : null"
|
|
10282
10579
|
[style.touch-action]="active() ? 'none' : null"
|
|
10283
10580
|
[style.--skdx-chart-duration]="duration()"
|
|
@@ -10294,7 +10591,7 @@ class SkdxChartRootComponent {
|
|
|
10294
10591
|
>
|
|
10295
10592
|
<title>{{ label() }}</title>
|
|
10296
10593
|
@if (layout().description) {
|
|
10297
|
-
<svg:desc
|
|
10594
|
+
<svg:desc>{{ layout().description }}</svg:desc>
|
|
10298
10595
|
}
|
|
10299
10596
|
@if (layout().defs?.length) {
|
|
10300
10597
|
<svg:defs>
|
|
@@ -10340,7 +10637,7 @@ class SkdxChartRootComponent {
|
|
|
10340
10637
|
[attr.data-skdx-tooltip]="kind()"
|
|
10341
10638
|
[style.left.px]="p.x + 12"
|
|
10342
10639
|
[style.top.px]="p.y + 12"
|
|
10343
|
-
style="position:fixed;pointer-events:none;z-index:1000;background:var(--skdx-color-surface-raised);color:var(--skdx-color-text-default);border:1px solid var(--skdx-color-border-strong);border-radius:
|
|
10640
|
+
style="position:fixed;pointer-events:none;z-index:1000;background:var(--skdx-color-surface-raised);color:var(--skdx-color-text-default);border:1px solid var(--skdx-color-border-strong);border-radius:var(--skdx-radius-default, var(--skdx-radius-md, 0.5rem));padding:4px 8px;font-family:var(--skdx-font-family);font-size:var(--skdx-font-size-xs);white-space:nowrap"
|
|
10344
10641
|
>
|
|
10345
10642
|
@for (line of p.lines; track $index) {
|
|
10346
10643
|
<div [style.font-weight]="$index === 0 && p.lines.length > 1 ? 'bold' : null">
|
|
@@ -10401,14 +10698,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
10401
10698
|
[attr.role]="layout().decorative ? 'presentation' : 'img'"
|
|
10402
10699
|
[attr.aria-hidden]="layout().decorative ? 'true' : null"
|
|
10403
10700
|
[attr.aria-label]="layout().decorative ? null : label()"
|
|
10404
|
-
[attr.aria-
|
|
10701
|
+
[attr.aria-description]="layout().description"
|
|
10405
10702
|
[attr.tabindex]="focusable() ? 0 : null"
|
|
10406
10703
|
[attr.data-skdx-chart]="kind()"
|
|
10407
10704
|
[attr.font-family]="colors.fontFamily"
|
|
10408
10705
|
[attr.font-size]="colors.fontSize"
|
|
10409
10706
|
[style.max-width]="'100%'"
|
|
10410
10707
|
[style.height]="'auto'"
|
|
10411
|
-
[style.outline]="'none'"
|
|
10412
10708
|
[style.cursor]="canZoom() || canPan() ? 'grab' : brushing() ? 'crosshair' : null"
|
|
10413
10709
|
[style.touch-action]="active() ? 'none' : null"
|
|
10414
10710
|
[style.--skdx-chart-duration]="duration()"
|
|
@@ -10425,7 +10721,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
10425
10721
|
>
|
|
10426
10722
|
<title>{{ label() }}</title>
|
|
10427
10723
|
@if (layout().description) {
|
|
10428
|
-
<svg:desc
|
|
10724
|
+
<svg:desc>{{ layout().description }}</svg:desc>
|
|
10429
10725
|
}
|
|
10430
10726
|
@if (layout().defs?.length) {
|
|
10431
10727
|
<svg:defs>
|
|
@@ -10471,7 +10767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
10471
10767
|
[attr.data-skdx-tooltip]="kind()"
|
|
10472
10768
|
[style.left.px]="p.x + 12"
|
|
10473
10769
|
[style.top.px]="p.y + 12"
|
|
10474
|
-
style="position:fixed;pointer-events:none;z-index:1000;background:var(--skdx-color-surface-raised);color:var(--skdx-color-text-default);border:1px solid var(--skdx-color-border-strong);border-radius:
|
|
10770
|
+
style="position:fixed;pointer-events:none;z-index:1000;background:var(--skdx-color-surface-raised);color:var(--skdx-color-text-default);border:1px solid var(--skdx-color-border-strong);border-radius:var(--skdx-radius-default, var(--skdx-radius-md, 0.5rem));padding:4px 8px;font-family:var(--skdx-font-family);font-size:var(--skdx-font-size-xs);white-space:nowrap"
|
|
10475
10771
|
>
|
|
10476
10772
|
@for (line of p.lines; track $index) {
|
|
10477
10773
|
<div [style.font-weight]="$index === 0 && p.lines.length > 1 ? 'bold' : null">
|
|
@@ -10696,10 +10992,19 @@ class SkdxSizedBase {
|
|
|
10696
10992
|
};
|
|
10697
10993
|
}
|
|
10698
10994
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxSizedBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
10699
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.4", type: SkdxSizedBase, isStandalone: true, inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, hiddenSeries: { classPropertyName: "hiddenSeries", publicName: "hiddenSeries", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, highlighted: { classPropertyName: "highlighted", publicName: "highlighted", isSignal: true, isRequired: false, transformFunction: null }, highlightScope: { classPropertyName: "highlightScope", publicName: "highlightScope", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null }, accessibleTable: { classPropertyName: "accessibleTable", publicName: "accessibleTable", isSignal: true, isRequired: false, transformFunction: null }, animate: { classPropertyName: "animate", publicName: "animate", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, zoomable: { classPropertyName: "zoomable", publicName: "zoomable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, syncId: { classPropertyName: "syncId", publicName: "syncId", isSignal: true, isRequired: false, transformFunction: null }, crossFilter: { classPropertyName: "crossFilter", publicName: "crossFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hiddenSeries: "hiddenSeriesChange", selected: "selectedChange", highlighted: "highlightedChange", zoom: "zoomChange", view: "viewChange", itemClick: "itemClick", dataIssue: "dataIssue", taskChange: "taskChange" }, viewQueries: [{ propertyName: "chartRoot", first: true, predicate: SkdxChartRootComponent, descendants: true, isSignal: true }], ngImport: i0 }); }
|
|
10995
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.4", type: SkdxSizedBase, isStandalone: true, inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, hiddenSeries: { classPropertyName: "hiddenSeries", publicName: "hiddenSeries", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, highlighted: { classPropertyName: "highlighted", publicName: "highlighted", isSignal: true, isRequired: false, transformFunction: null }, highlightScope: { classPropertyName: "highlightScope", publicName: "highlightScope", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null }, accessibleTable: { classPropertyName: "accessibleTable", publicName: "accessibleTable", isSignal: true, isRequired: false, transformFunction: null }, animate: { classPropertyName: "animate", publicName: "animate", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, zoomable: { classPropertyName: "zoomable", publicName: "zoomable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, syncId: { classPropertyName: "syncId", publicName: "syncId", isSignal: true, isRequired: false, transformFunction: null }, crossFilter: { classPropertyName: "crossFilter", publicName: "crossFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hiddenSeries: "hiddenSeriesChange", selected: "selectedChange", highlighted: "highlightedChange", zoom: "zoomChange", view: "viewChange", itemClick: "itemClick", dataIssue: "dataIssue", taskChange: "taskChange" }, host: { properties: { "style.display": "responsive() ? 'block' : null", "style.width": "responsive() ? '100%' : null", "style.min-width": "responsive() ? '0' : null" } }, viewQueries: [{ propertyName: "chartRoot", first: true, predicate: SkdxChartRootComponent, descendants: true, isSignal: true }], ngImport: i0 }); }
|
|
10700
10996
|
}
|
|
10701
10997
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxSizedBase, decorators: [{
|
|
10702
|
-
type: Directive
|
|
10998
|
+
type: Directive,
|
|
10999
|
+
args: [{
|
|
11000
|
+
// A responsive chart must fill its container even when that container centers flex items.
|
|
11001
|
+
// Otherwise its last SVG width becomes the host's intrinsic width and it cannot grow again.
|
|
11002
|
+
host: {
|
|
11003
|
+
'[style.display]': "responsive() ? 'block' : null",
|
|
11004
|
+
'[style.width]': "responsive() ? '100%' : null",
|
|
11005
|
+
'[style.min-width]': "responsive() ? '0' : null",
|
|
11006
|
+
},
|
|
11007
|
+
}]
|
|
10703
11008
|
}], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], palette: [{ type: i0.Input, args: [{ isSignal: true, alias: "palette", required: false }] }], hiddenSeries: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenSeries", required: false }] }, { type: i0.Output, args: ["hiddenSeriesChange"] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], highlighted: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlighted", required: false }] }, { type: i0.Output, args: ["highlightedChange"] }], highlightScope: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightScope", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }], accessibleTable: [{ type: i0.Input, args: [{ isSignal: true, alias: "accessibleTable", required: false }] }], animate: [{ type: i0.Input, args: [{ isSignal: true, alias: "animate", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }, { type: i0.Output, args: ["zoomChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], zoomable: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomable", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], syncId: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncId", required: false }] }], crossFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "crossFilter", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], dataIssue: [{ type: i0.Output, args: ["dataIssue"] }], taskChange: [{ type: i0.Output, args: ["taskChange"] }], chartRoot: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SkdxChartRootComponent), { isSignal: true }] }] } });
|
|
10704
11009
|
/** Inputs the cartesian charts (bar, line, area) take. */
|
|
10705
11010
|
class SkdxChartBase extends SkdxSizedBase {
|
|
@@ -10824,5 +11129,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
10824
11129
|
* Generated bundle index. Do not edit.
|
|
10825
11130
|
*/
|
|
10826
11131
|
|
|
10827
|
-
export { ANIMATION_CSS, ANIMATION_LIMIT, CHART_COLORS, MARKER_LIMIT, NETWORK_LABEL_LIMIT, PARALLEL_TITLE_LIMIT, SAMPLING_THRESHOLD, SERIES_COLORS, SWARM_TITLE_LIMIT, SkdxChartBase, SkdxChartRootComponent, SkdxChartShapesComponent, SkdxSizedBase, TABLE_LIMIT, TITLE_LIMIT, aggregate, anchorOf, animationDuration, annotationShapes, applyTransforms, binCounts, binEdges, binSamples, boxFrom, brushBoxShape, brushDrag, brushGrab, categoryLabel, colorLegendShapes, crosshairAt, crosshairLine, crosshairRows, crosshairShapes, csv, cumulative, cutDepth, defsMarkup, depthOf, difference, drill, emphasize, exponentialMovingAverage, exportChart, filter, findPath, focusShapes, focusTargets, formatValue, ganttDrag, ganttGhost, ganttGrab, ganttKey, hoverTooltip, hoveredRef, insidePolygon, isSelected, isUnzoomed, itemAt, itemId, itemsInBox, itemsInPolygon, lassoShape, layoutAreaChart, layoutBarChart, layoutBoxplot, layoutBubbleChart, layoutBulletChart, layoutBumpChart, layoutCandlestick, layoutCartesian, layoutChord, layoutCirclePacking, layoutComboChart, layoutDumbbellChart, layoutFunnelChart, layoutGantt, layoutGaugeChart, layoutHeatmap, layoutHistogram, layoutIcicle, layoutLineChart, layoutLinearGauge, layoutMap, layoutMarimekkoChart, layoutNetworkChart, layoutOrganizationChart, layoutParallelCoordinates, layoutParetoChart, layoutPieChart, layoutPolarAreaChart, layoutRadarChart, layoutRadialBar, layoutRangeChart, layoutSankey, layoutScatterChart, layoutSparkline, layoutSunburst, layoutSwarmPlot, layoutTimelineChart, layoutTreeChart, layoutTreemap, layoutWaffleChart, layoutWaterfall, legendOptions, lttb, m4, makeColorScale, makeFormatter, mapPan, mapWheel, matchesItem, minMaxSample, mixStops, movingAverage, nextFocus, nodeId, nodeValue, normalize, packCircles, parseColor, percentageChange, placeLegend, pointerShapes, pointerTooltipText, rank, refOf, referenceShapes, sameRef, sampleIndexes, samplingOptions, selectedIds, seriesCsv, shapeMarkup, sortChildren, sortValues, stateCanvas, svgMarkup, svgToPng, swarmOffsets, syncGroup, tableRows, timeTicks, toRefs, toViewBox, toggleSelection, tooltipOptions, tooltipShapes, translatePath, translateShapes, variantColor, waffleCounts, zoomPan, zoomPinch, zoomWheel };
|
|
11132
|
+
export { ANIMATION_CSS, ANIMATION_LIMIT, CHART_COLORS, CHART_SURFACE_RADIUS, MARKER_LIMIT, NETWORK_LABEL_LIMIT, PARALLEL_TITLE_LIMIT, SAMPLING_THRESHOLD, SERIES_COLORS, SWARM_TITLE_LIMIT, SkdxChartBase, SkdxChartRootComponent, SkdxChartShapesComponent, SkdxSizedBase, TABLE_LIMIT, TITLE_LIMIT, aggregate, anchorOf, animationDuration, annotationShapes, applyTransforms, binCounts, binEdges, binSamples, boxFrom, brushBoxShape, brushDrag, brushGrab, categoryLabel, chartSurfaceRadius, colorLegendShapes, crosshairAt, crosshairLine, crosshairRows, crosshairShapes, csv, cumulative, cutDepth, defsMarkup, depthOf, difference, drill, emphasize, exponentialMovingAverage, exportChart, filter, findPath, focusShapes, focusTargets, formatValue, ganttDrag, ganttGhost, ganttGrab, ganttKey, hoverTooltip, hoveredRef, insidePolygon, isSelected, isUnzoomed, itemAt, itemId, itemsInBox, itemsInPolygon, lassoShape, layoutAreaChart, layoutBarChart, layoutBoxplot, layoutBubbleChart, layoutBulletChart, layoutBumpChart, layoutCandlestick, layoutCartesian, layoutChord, layoutCirclePacking, layoutComboChart, layoutDumbbellChart, layoutFunnelChart, layoutGantt, layoutGaugeChart, layoutHeatmap, layoutHistogram, layoutIcicle, layoutLineChart, layoutLinearGauge, layoutMap, layoutMarimekkoChart, layoutNetworkChart, layoutOrganizationChart, layoutParallelCoordinates, layoutParetoChart, layoutPieChart, layoutPolarAreaChart, layoutRadarChart, layoutRadialBar, layoutRangeChart, layoutSankey, layoutScatterChart, layoutSparkline, layoutSunburst, layoutSwarmPlot, layoutTimelineChart, layoutTreeChart, layoutTreemap, layoutWaffleChart, layoutWaterfall, legendOptions, lttb, m4, makeColorScale, makeFormatter, mapPan, mapWheel, matchesItem, minMaxSample, mixStops, movingAverage, nextFocus, nodeId, nodeValue, normalize, packCircles, parseColor, percentageChange, placeLegend, pointerShapes, pointerTooltipText, rank, refOf, referenceShapes, sameRef, sampleIndexes, samplingOptions, selectedIds, seriesCsv, shapeMarkup, sortChildren, sortValues, stateCanvas, svgMarkup, svgToPng, swarmOffsets, syncGroup, tableRows, timeTicks, toRefs, toViewBox, toggleSelection, tooltipOptions, tooltipShapes, translatePath, translateShapes, variantColor, waffleCounts, zoomPan, zoomPinch, zoomWheel };
|
|
10828
11133
|
//# sourceMappingURL=skdx-angular-charts-internal.mjs.map
|