@fundar/data-chart-telling 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/bar/Chart.svelte +2 -0
- package/dist/charts/heatmap/Chart.svelte +3 -1
- package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
- package/dist/charts/line/Chart.svelte +2 -0
- package/dist/charts/pyramid/Chart.svelte +3 -1
- package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
- package/dist/charts/scatter/Chart.svelte +95 -0
- package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
- package/dist/configuration/config.svelte.js +13 -1
- package/dist/configuration/themes/index.d.ts +76 -0
- package/dist/index.d.ts +20 -14
- package/dist/index.js +9 -3
- package/dist/layout/coordinates/CoordinatesLayout.svelte +95 -0
- package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
- package/dist/layout/coordinates/margins.d.ts +16 -0
- package/dist/layout/coordinates/margins.js +11 -0
- package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
- package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
- package/dist/layout/coordinates/resolveProjection.js +24 -0
- package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
- package/dist/layout/geometry/GeometryLayout.svelte +231 -0
- package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
- package/dist/layout/plot/BasePlotLayout.svelte +142 -155
- package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
- package/dist/layout/plot/margins.d.ts +0 -16
- package/dist/layout/plot/margins.js +0 -11
- package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
- package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
- package/dist/layout/scales/buildScale.d.ts +8 -0
- package/dist/layout/scales/buildScale.js +43 -0
- package/dist/layout/styles/StylesLayout.svelte +75 -0
- package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
- package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
- package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
- package/dist/layout/tooltip/Tooltip.svelte +16 -20
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
- package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
- package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
- package/dist/layout/tooltip/controller.svelte.js +28 -16
- package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
- package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
- package/dist/layout/tooltip/utils.d.ts +27 -19
- package/dist/layout/tooltip/utils.js +16 -15
- package/dist/markers/BarMarker.svelte +92 -0
- package/dist/markers/BarMarker.svelte.d.ts +7 -0
- package/dist/markers/CellMarker.svelte +21 -0
- package/dist/markers/CellMarker.svelte.d.ts +7 -0
- package/dist/markers/ContourMarker.svelte +28 -0
- package/dist/markers/ContourMarker.svelte.d.ts +7 -0
- package/dist/markers/DeltaMarker.svelte +32 -39
- package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
- package/dist/markers/DotMarker.svelte +35 -6
- package/dist/markers/DotMarker.svelte.d.ts +11 -2
- package/dist/markers/GeoFeaturesMarker.svelte +19 -0
- package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
- package/dist/markers/GeoHoverMarker.svelte +25 -0
- package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
- package/dist/markers/GraticuleMarker.svelte +18 -0
- package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
- package/dist/markers/HoverMarker.svelte +32 -33
- package/dist/markers/HoverMarker.svelte.d.ts +15 -9
- package/dist/markers/LineMarker.svelte +19 -0
- package/dist/markers/LineMarker.svelte.d.ts +7 -0
- package/dist/markers/LinkMarker.svelte +42 -0
- package/dist/markers/LinkMarker.svelte.d.ts +7 -0
- package/dist/markers/RuleMarker.svelte +24 -0
- package/dist/markers/RuleMarker.svelte.d.ts +10 -0
- package/dist/markers/SphereMarker.svelte +15 -0
- package/dist/markers/SphereMarker.svelte.d.ts +7 -0
- package/dist/markers/TextMarker.svelte +1 -1
- package/dist/markers/TextMarker.svelte.d.ts +1 -1
- package/dist/markers/VectorMarker.svelte +20 -0
- package/dist/markers/VectorMarker.svelte.d.ts +7 -0
- package/dist/markers/inset/InsetMarker.svelte +38 -45
- package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
- package/dist/plots/bar/Plot.svelte +54 -84
- package/dist/plots/bar/Plot.svelte.d.ts +4 -5
- package/dist/plots/bar/ValueLabels.svelte +1 -1
- package/dist/plots/bar/ValueLabels.svelte.d.ts +1 -1
- package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
- package/dist/plots/bar/buildBarMarkers.js +100 -0
- package/dist/plots/bar/layout.svelte.d.ts +1 -2
- package/dist/plots/bar/layout.svelte.js +1 -1
- package/dist/plots/geo/Plot.svelte +113 -387
- package/dist/plots/geo/Plot.svelte.d.ts +5 -4
- package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
- package/dist/plots/geo/buildGeoMarkers.js +47 -0
- package/dist/plots/heatmap/Plot.svelte +24 -49
- package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
- package/dist/plots/heatmap/ValueLabels.svelte +2 -2
- package/dist/plots/heatmap/ValueLabels.svelte.d.ts +2 -2
- package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
- package/dist/plots/heatmap/buildCellMarkers.js +25 -0
- package/dist/plots/line/Plot.svelte +68 -141
- package/dist/plots/line/Plot.svelte.d.ts +4 -4
- package/dist/plots/line/ValueLabels.svelte +94 -49
- package/dist/plots/line/ValueLabels.svelte.d.ts +6 -1
- package/dist/plots/line/buildLineMarkers.d.ts +15 -0
- package/dist/plots/line/buildLineMarkers.js +57 -0
- package/dist/plots/pyramid/Plot.svelte +41 -117
- package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
- package/dist/plots/pyramid/ValueLabels.svelte +1 -1
- package/dist/plots/pyramid/ValueLabels.svelte.d.ts +1 -1
- package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
- package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
- package/dist/plots/scatter/Plot.svelte +206 -0
- package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/Plot.svelte.d.ts} +9 -20
- package/dist/plots/scatter/ValueLabels.svelte +106 -0
- package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/ValueLabels.svelte.d.ts} +11 -5
- package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
- package/dist/plots/scatter/buildScatterMarkers.js +37 -0
- package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
- package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
- package/dist/plots/utils/declutter.d.ts +82 -33
- package/dist/plots/utils/declutter.js +182 -40
- package/dist/plots/utils/geoAccessors.d.ts +1 -1
- package/dist/plots/utils/geoAccessors.js +1 -1
- package/dist/plots/utils/segments.d.ts +17 -10
- package/dist/plots/utils/segments.js +26 -16
- package/dist/types/charts/common.d.ts +1 -1
- package/dist/types/charts/props.d.ts +5 -6
- package/dist/types/configuration/styling.d.ts +24 -2
- package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
- package/dist/types/layout/geometry.d.ts +17 -0
- package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
- package/dist/types/layout/styles.d.ts +126 -0
- package/dist/types/layout/tooltip.d.ts +20 -1
- package/dist/types/markers/all.d.ts +11 -0
- package/dist/types/markers/base.d.ts +45 -0
- package/dist/types/markers/base.js +1 -0
- package/dist/types/markers/common.d.ts +64 -16
- package/dist/types/markers/geo.d.ts +75 -100
- package/dist/types/markers/line.d.ts +34 -0
- package/dist/types/markers/line.js +1 -0
- package/dist/types/markers/props.d.ts +1 -1
- package/dist/types/plots/data/common.d.ts +1 -1
- package/dist/types/plots/data/scatter.d.ts +13 -0
- package/dist/types/plots/data/scatter.js +1 -0
- package/dist/types/plots/props.d.ts +46 -74
- package/dist/types/plots/segments/common.d.ts +6 -12
- package/dist/types/plots/segments/config.d.ts +1 -1
- package/dist/types/plots/styling.d.ts +10 -0
- package/dist/{plots/utils → utils}/delta.d.ts +2 -2
- package/dist/{plots/utils → utils}/delta.js +1 -1
- package/dist/utils/edgeAwareAnchor.d.ts +37 -0
- package/dist/utils/edgeAwareAnchor.js +65 -0
- package/dist/utils/grouping.d.ts +1 -1
- package/dist/utils/interpolate.d.ts +1 -1
- package/package.json +3 -1
- package/dist/layout/geo/GeoLayout.svelte +0 -51
- package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
- package/dist/layout/geo/resolveProjection.js +0 -48
- package/dist/layout/plot/GridLayout.svelte +0 -30
- package/dist/layout/plot/RuleLayout.svelte +0 -111
- package/dist/plots/bar/BarSegments.svelte +0 -69
- package/dist/types/plots/delta.d.ts +0 -19
- package/dist/types/plots/styles/common.d.ts +0 -52
- package/dist/types/plots/styles/geo.d.ts +0 -42
- /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
- /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
- /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
- /package/dist/types/{plots/axis.js → layout/coordinates.js} +0 -0
- /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
- /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
- /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
- /package/dist/types/{plots/styles/geo.js → markers/all.js} +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TData extends Record<string, unknown>"
|
|
4
|
+
>
|
|
5
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
6
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
7
|
+
import { paletteColor } from '../../utils/color';
|
|
8
|
+
import { buildSeries } from '../../utils/grouping';
|
|
9
|
+
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
10
|
+
import { disabledDotsOverride } from '../utils/legendDisabled';
|
|
11
|
+
import { buildScatterMarkers } from './buildScatterMarkers';
|
|
12
|
+
import { resolveRadiusPaddedDomain } from './resolveRadiusPaddedDomain';
|
|
13
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
14
|
+
import ValueLabels from './ValueLabels.svelte';
|
|
15
|
+
import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
|
|
16
|
+
import { buildGroupedSeries, validateSegments, buildScopedMarkerGroups } from '../utils/segments';
|
|
17
|
+
import { numericMargin } from '../../layout/plot/margins';
|
|
18
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
19
|
+
import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
20
|
+
import type { Series } from '../../types/plots/data/common';
|
|
21
|
+
import type { ScatterDataProps } from '../../types/plots/data/scatter';
|
|
22
|
+
import type { BasePlotStylesConfig } from '../../types/layout/styles';
|
|
23
|
+
import type { ScatterSegmentStyle } from '../../types/plots/segments/common';
|
|
24
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A point cloud on continuous x/y axes. Accepts either a pre-built `series`
|
|
28
|
+
* array or flat `data/x/y/z` props — mirrors the line/bar plot contract so
|
|
29
|
+
* the chart-level container/timeline/facet/legend wrap it unchanged. `r`
|
|
30
|
+
* gives every dot a per-row radius (bubble sizing), independent of `z`
|
|
31
|
+
* grouping/coloring — see {@link ScatterDataProps} for its auto-scaling
|
|
32
|
+
* behavior.
|
|
33
|
+
*/
|
|
34
|
+
type Props = BasePlotProps<
|
|
35
|
+
ScatterDataProps<TData>,
|
|
36
|
+
TData,
|
|
37
|
+
BasePlotStylesConfig,
|
|
38
|
+
ScatterSegmentStyle,
|
|
39
|
+
AxisBasedMarkersConfig,
|
|
40
|
+
AxisBasedScalesConfig<TData>
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
width,
|
|
45
|
+
height,
|
|
46
|
+
series,
|
|
47
|
+
data,
|
|
48
|
+
x,
|
|
49
|
+
y,
|
|
50
|
+
z,
|
|
51
|
+
r,
|
|
52
|
+
styles = {},
|
|
53
|
+
segments = {},
|
|
54
|
+
markers = [],
|
|
55
|
+
scales = {},
|
|
56
|
+
margins,
|
|
57
|
+
tooltip = undefined,
|
|
58
|
+
}: Props = $props();
|
|
59
|
+
|
|
60
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
61
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
62
|
+
|
|
63
|
+
$effect(() => { validateSegments(segments); });
|
|
64
|
+
|
|
65
|
+
// A scatter point cloud is discrete (no connecting line), so adjacent
|
|
66
|
+
// segments never need a bridge point at their boundary — see
|
|
67
|
+
// buildGroupedSeries's own doc.
|
|
68
|
+
const groupedSeries = $derived(
|
|
69
|
+
buildGroupedSeries<Series<TData>, ScatterSegmentStyle>(resolvedSeries, segments, false),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const cfg = $derived(getConfiguration());
|
|
73
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
74
|
+
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
75
|
+
|
|
76
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
77
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
78
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
79
|
+
|
|
80
|
+
// A legend-disabled series is dimmed on the mark itself, but never surfaces
|
|
81
|
+
// in hover/tooltip — its points are simply absent from the candidate list.
|
|
82
|
+
const hoverPoints = $derived(
|
|
83
|
+
resolvedSeries.flatMap((s, idx) => {
|
|
84
|
+
if (legendInteraction?.disabledSeries.has(s.name)) return [];
|
|
85
|
+
const sx = resolveAccessor(s.x);
|
|
86
|
+
const sy = resolveAccessor(s.y);
|
|
87
|
+
return s.data.map((d) => ({
|
|
88
|
+
x: sx(d) as AxisValue,
|
|
89
|
+
y: Number(sy(d)),
|
|
90
|
+
row: d,
|
|
91
|
+
label: `${sx(d)}, ${sy(d)}`,
|
|
92
|
+
series: s.name,
|
|
93
|
+
color: paletteColor(idx, cfg.palette),
|
|
94
|
+
}));
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Points scatter freely across x/y rather than sharing discrete x values —
|
|
99
|
+
// 'punctual' matches the single nearest point, not a whole column.
|
|
100
|
+
const hover = $derived({
|
|
101
|
+
active: tooltip != null || hasHoverMarker(markers),
|
|
102
|
+
points: hoverPoints,
|
|
103
|
+
label: (row: TData) => `${xAcc(row)}, ${yAcc(row)}`,
|
|
104
|
+
strategy: resolveHoverStrategy(tooltip?.strategy, markers, 'punctual'),
|
|
105
|
+
sync: resolveHoverSync(tooltip?.sync, markers),
|
|
106
|
+
tooltip,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const scatterMarkers = $derived(
|
|
110
|
+
buildScatterMarkers({
|
|
111
|
+
groupedSeries,
|
|
112
|
+
r,
|
|
113
|
+
disabledDotsFor: (name) => disabledDotsOverride(legendInteraction?.disabledSeries.get(name)),
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const scopedGroups = $derived(
|
|
118
|
+
buildScopedMarkerGroups<TData, Series<TData>, ScatterSegmentStyle>({
|
|
119
|
+
groupedSeries,
|
|
120
|
+
segments,
|
|
121
|
+
colorFor: (_series, seriesIndex) => paletteColor(seriesIndex, cfg.palette),
|
|
122
|
+
segmentColorFor: (style, defaultColor) => style?.dotFill ?? defaultColor,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// svelteplot treats a per-row `r` (a field key or accessor function) as a
|
|
127
|
+
// proper channel bound to its own auto sqrt scale — the raw values it
|
|
128
|
+
// returns (e.g. a population in the millions) are data, not pixels, and
|
|
129
|
+
// get compressed into a modest, roughly data-magnitude-independent pixel
|
|
130
|
+
// range regardless of how large the raw numbers are. A literal constant
|
|
131
|
+
// `r` (or the theme/segment `dotRadius` fallback), by contrast, *is*
|
|
132
|
+
// already a literal pixel value. `AUTO_SCALED_RADIUS_ESTIMATE` stands in
|
|
133
|
+
// for whatever svelteplot's own r-scale ends up rendering at, since the
|
|
134
|
+
// real value isn't knowable until that scale is built.
|
|
135
|
+
const AUTO_SCALED_RADIUS_ESTIMATE = 20;
|
|
136
|
+
|
|
137
|
+
// The largest radius any dot actually renders at — pads the scale domain
|
|
138
|
+
// below so a point at the data extreme doesn't get its own circle clipped
|
|
139
|
+
// by the frame. svelteplot's own 'auto' margin only measures axis chrome,
|
|
140
|
+
// never mark geometry.
|
|
141
|
+
const maxDotRadius = $derived.by(() => {
|
|
142
|
+
let max = 0;
|
|
143
|
+
for (const marker of scatterMarkers) {
|
|
144
|
+
const rConst = typeof marker.r === 'number' ? marker.r : undefined;
|
|
145
|
+
const isChannel = typeof marker.r === 'function';
|
|
146
|
+
const fallbackR = marker.style?.dotRadius ?? cfg.scatter.dotRadius;
|
|
147
|
+
if (rConst !== undefined) {
|
|
148
|
+
max = Math.max(max, rConst);
|
|
149
|
+
} else if (isChannel) {
|
|
150
|
+
max = Math.max(max, AUTO_SCALED_RADIUS_ESTIMATE);
|
|
151
|
+
} else {
|
|
152
|
+
max = Math.max(max, fallbackR);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return max;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Best-effort pre-render estimate of the plot's own inner content size —
|
|
159
|
+
// the real size isn't known until svelteplot measures its own axis
|
|
160
|
+
// chrome, so this is only used to convert a pixel radius into a domain
|
|
161
|
+
// padding amount, not as the actual rendered margin.
|
|
162
|
+
const estimatedMargins = $derived({ ...cfg.margins, ...margins });
|
|
163
|
+
const estimatedInnerWidth = $derived(
|
|
164
|
+
width - numericMargin('left', estimatedMargins.left) - numericMargin('right', estimatedMargins.right),
|
|
165
|
+
);
|
|
166
|
+
const estimatedInnerHeight = $derived(
|
|
167
|
+
height - numericMargin('top', estimatedMargins.top) - numericMargin('bottom', estimatedMargins.bottom),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const resolvedScales = $derived.by((): AxisBasedScalesConfig<TData> => {
|
|
171
|
+
if (maxDotRadius <= 0) return scales;
|
|
172
|
+
const xValues = flat.map((d) => Number(xAcc(d)));
|
|
173
|
+
const yValues = flat.map((d) => Number(yAcc(d)));
|
|
174
|
+
const paddedX = scales.x?.domain
|
|
175
|
+
? undefined
|
|
176
|
+
: resolveRadiusPaddedDomain(xValues, maxDotRadius, estimatedInnerWidth);
|
|
177
|
+
const paddedY = scales.y?.domain
|
|
178
|
+
? undefined
|
|
179
|
+
: resolveRadiusPaddedDomain(yValues, maxDotRadius, estimatedInnerHeight);
|
|
180
|
+
if (!paddedX && !paddedY) return scales;
|
|
181
|
+
return {
|
|
182
|
+
...scales,
|
|
183
|
+
x: paddedX ? { ...scales.x, domain: paddedX } : scales.x,
|
|
184
|
+
y: paddedY ? { ...scales.y, domain: paddedY } : scales.y,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<BasePlotLayout
|
|
190
|
+
{width}
|
|
191
|
+
{height}
|
|
192
|
+
{styles}
|
|
193
|
+
data={flat}
|
|
194
|
+
getX={xAcc}
|
|
195
|
+
getY={(d) => Number(yAcc(d))}
|
|
196
|
+
{hover}
|
|
197
|
+
scales={resolvedScales}
|
|
198
|
+
{margins}
|
|
199
|
+
markers={[...scatterMarkers, ...markers]}
|
|
200
|
+
seriesData={resolvedSeries}
|
|
201
|
+
{scopedGroups}
|
|
202
|
+
>
|
|
203
|
+
{#if showVals}
|
|
204
|
+
<ValueLabels data={flat} getX={xAcc} getY={(d) => Number(yAcc(d))} {fmtVal} values={styles.values} defaultColor={cfg.axis.color} />
|
|
205
|
+
{/if}
|
|
206
|
+
</BasePlotLayout>
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
1
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
3
|
+
import type { ScatterDataProps } from '../../types/plots/data/scatter';
|
|
4
|
+
import type { BasePlotStylesConfig } from '../../types/layout/styles';
|
|
5
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
6
6
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
7
|
-
props:
|
|
8
|
-
group: VisualGroup<Series<TData>, BarSegmentStyle>;
|
|
9
|
-
seriesIndex: number;
|
|
10
|
-
barLayout: BarLayout<TData>;
|
|
11
|
-
isHorizontal: boolean;
|
|
12
|
-
BarMark: typeof BarX | typeof BarY;
|
|
13
|
-
defaultColor: string;
|
|
14
|
-
disabledFill?: {
|
|
15
|
-
fill?: string;
|
|
16
|
-
fillOpacity?: number;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
7
|
+
props: BasePlotProps<ScatterDataProps<TData>, TData, BasePlotStylesConfig, import("../..").DotStyle, AxisBasedMarkersConfig, AxisBasedScalesConfig<TData>>;
|
|
19
8
|
exports: {};
|
|
20
9
|
bindings: "";
|
|
21
10
|
slots: {};
|
|
@@ -35,6 +24,6 @@ interface $$IsomorphicComponent {
|
|
|
35
24
|
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
36
25
|
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
26
|
}
|
|
38
|
-
declare const
|
|
39
|
-
type
|
|
40
|
-
export default
|
|
27
|
+
declare const Plot: $$IsomorphicComponent;
|
|
28
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
29
|
+
export default Plot;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { Text, usePlot } from 'svelteplot';
|
|
3
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
+
import { resolveEdgeAwareAnchor, estimateTextWidth } from '../../utils/edgeAwareAnchor';
|
|
5
|
+
import type { AxisValue } from '../../types/layout/scales';
|
|
6
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders every point's value label in one mark — scatter has no
|
|
10
|
+
* per-series endpoint to single out (unlike line), so this covers the
|
|
11
|
+
* whole flat dataset at once, mirroring heatmap's own `ValueLabels`.
|
|
12
|
+
* Labels the Y value, matching the "Y is the value axis" convention every
|
|
13
|
+
* other plot kind uses. Placement is edge-aware: a label whose default
|
|
14
|
+
* anchor/offset would push it past the plot's own content box flips to
|
|
15
|
+
* the opposite side instead of clipping. Deliberately read-only against
|
|
16
|
+
* `usePlot()`'s live scale — doesn't register into svelteplot's shared
|
|
17
|
+
* margin-auto-sizing context, which can spike CPU/memory into
|
|
18
|
+
* unresponsiveness when many `<Plot>` instances mount/unmount together
|
|
19
|
+
* (e.g. switching from a multi-story docs page to a single story).
|
|
20
|
+
*/
|
|
21
|
+
let {
|
|
22
|
+
data,
|
|
23
|
+
getX,
|
|
24
|
+
getY,
|
|
25
|
+
fmtVal,
|
|
26
|
+
values,
|
|
27
|
+
defaultColor,
|
|
28
|
+
}: {
|
|
29
|
+
data: TData[];
|
|
30
|
+
getX: (d: TData) => AxisValue;
|
|
31
|
+
getY: (d: TData) => AxisValue;
|
|
32
|
+
fmtVal: (value: number, seriesName: string) => string;
|
|
33
|
+
values: ValuesStyle | undefined;
|
|
34
|
+
defaultColor: string;
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
const font = $derived(values?.fontStyle);
|
|
38
|
+
const preferredTextAnchor = $derived(
|
|
39
|
+
font?.textAnchor === 'outside' || font?.textAnchor === undefined ? 'start' : font.textAnchor,
|
|
40
|
+
);
|
|
41
|
+
const preferredLineAnchor = $derived(font?.lineAnchor ?? 'middle');
|
|
42
|
+
const gapX = $derived(Math.abs(font?.dx ?? 8));
|
|
43
|
+
const gapY = $derived(Math.abs(font?.dy ?? 0));
|
|
44
|
+
const fontSize = $derived(typeof font?.fontSize === 'number' ? font.fontSize : 11);
|
|
45
|
+
|
|
46
|
+
const plot = usePlot();
|
|
47
|
+
|
|
48
|
+
function textOf(d: TData): string {
|
|
49
|
+
return fmtVal(Number(getY(d)), '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One placement per row, keyed by row reference — avoids recomputing the same scale/edge lookup for each of the four accessor channels below. */
|
|
53
|
+
const placements = $derived.by(() => {
|
|
54
|
+
const map = new SvelteMap<TData, ReturnType<typeof resolveEdgeAwareAnchor>>();
|
|
55
|
+
const xFn = plot.scales.x?.fn;
|
|
56
|
+
const yFn = plot.scales.y?.fn;
|
|
57
|
+
if (!xFn || !yFn) return map;
|
|
58
|
+
for (const d of data) {
|
|
59
|
+
const px = Number(xFn(getX(d) as never));
|
|
60
|
+
const py = Number(yFn(getY(d) as never));
|
|
61
|
+
const text = textOf(d);
|
|
62
|
+
map.set(
|
|
63
|
+
d,
|
|
64
|
+
resolveEdgeAwareAnchor({
|
|
65
|
+
px,
|
|
66
|
+
py,
|
|
67
|
+
width: plot.plotWidth,
|
|
68
|
+
height: plot.plotHeight,
|
|
69
|
+
textAnchor: preferredTextAnchor,
|
|
70
|
+
lineAnchor: preferredLineAnchor,
|
|
71
|
+
gapX,
|
|
72
|
+
gapY,
|
|
73
|
+
// Rough average glyph width — these labels are short numeric/formatted strings, not measured DOM text.
|
|
74
|
+
extent: { width: estimateTextWidth(text, fontSize), height: fontSize * 1.2 },
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return map;
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<Text
|
|
83
|
+
{data}
|
|
84
|
+
x={getX}
|
|
85
|
+
y={getY}
|
|
86
|
+
text={(d: TData) => textOf(d)}
|
|
87
|
+
fill={font?.fill ?? defaultColor}
|
|
88
|
+
{fontSize}
|
|
89
|
+
fontWeight={font?.fontWeight ?? 500}
|
|
90
|
+
fontStyle={font?.fontStyle}
|
|
91
|
+
textAnchor={(d: TData) => placements.get(d)?.textAnchor ?? preferredTextAnchor}
|
|
92
|
+
lineAnchor={
|
|
93
|
+
// svelteplot's own `lineAnchor` accessor type isn't parameterized over the mark's row type like its siblings are.
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
(d: any) => placements.get(d)?.lineAnchor ?? preferredLineAnchor
|
|
96
|
+
}
|
|
97
|
+
dx={(d: TData) => placements.get(d)?.dx ?? gapX}
|
|
98
|
+
dy={(d: TData) => placements.get(d)?.dy ?? gapY}
|
|
99
|
+
lineHeight={font?.lineHeight}
|
|
100
|
+
rotate={font?.rotate}
|
|
101
|
+
stroke={font?.stroke}
|
|
102
|
+
strokeWidth={font?.strokeWidth}
|
|
103
|
+
paintOrder={font?.paintOrder}
|
|
104
|
+
class={font?.class}
|
|
105
|
+
textClass={font?.textClass}
|
|
106
|
+
/>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AxisValue } from '../../types/layout/scales';
|
|
2
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
2
3
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
3
4
|
props: {
|
|
4
|
-
|
|
5
|
+
data: TData[];
|
|
6
|
+
getX: (d: TData) => AxisValue;
|
|
7
|
+
getY: (d: TData) => AxisValue;
|
|
8
|
+
fmtVal: (value: number, seriesName: string) => string;
|
|
9
|
+
values: ValuesStyle | undefined;
|
|
10
|
+
defaultColor: string;
|
|
5
11
|
};
|
|
6
12
|
exports: {};
|
|
7
13
|
bindings: "";
|
|
@@ -22,6 +28,6 @@ interface $$IsomorphicComponent {
|
|
|
22
28
|
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
23
29
|
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
24
30
|
}
|
|
25
|
-
declare const
|
|
26
|
-
type
|
|
27
|
-
export default
|
|
31
|
+
declare const ValueLabels: $$IsomorphicComponent;
|
|
32
|
+
type ValueLabels<TData extends Record<string, unknown>> = InstanceType<typeof ValueLabels<TData>>;
|
|
33
|
+
export default ValueLabels;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Series, Accessor } from '../../types/plots/data/common';
|
|
2
|
+
import type { ScatterSegmentStyle } from '../../types/plots/segments/common';
|
|
3
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
4
|
+
import type { DotStyle } from '../../types/plots/styling';
|
|
5
|
+
import type { DotMarkerConfig } from '../../types/markers/common';
|
|
6
|
+
/** Every `'dot'` marker for one ScatterPlot render: one per visual segment. */
|
|
7
|
+
export declare function buildScatterMarkers<TData extends Record<string, unknown>>(args: {
|
|
8
|
+
groupedSeries: VisualGroup<Series<TData>, ScatterSegmentStyle>[];
|
|
9
|
+
r?: Accessor<TData, number> | number;
|
|
10
|
+
disabledDotsFor: (seriesName: string) => DotStyle | undefined;
|
|
11
|
+
}): DotMarkerConfig[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
2
|
+
import { paletteColor } from '../../utils/color';
|
|
3
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
4
|
+
/** Every `'dot'` marker for one ScatterPlot render: one per visual segment. */
|
|
5
|
+
export function buildScatterMarkers(args) {
|
|
6
|
+
const { groupedSeries, r, disabledDotsFor } = args;
|
|
7
|
+
const cfg = getConfiguration();
|
|
8
|
+
const rFn = r === undefined || typeof r === 'number' ? r : resolveAccessor(r);
|
|
9
|
+
const markers = [];
|
|
10
|
+
groupedSeries.forEach((group, seriesIndex) => {
|
|
11
|
+
const defaultColor = paletteColor(seriesIndex, cfg.palette);
|
|
12
|
+
const disabledDots = disabledDotsFor(group.series.name);
|
|
13
|
+
const xFn = resolveAccessor(group.series.x);
|
|
14
|
+
const yFn = resolveAccessor(group.series.y);
|
|
15
|
+
for (const seg of group.visualSegments) {
|
|
16
|
+
const dotStyle = seg.style;
|
|
17
|
+
markers.push({
|
|
18
|
+
type: 'dot',
|
|
19
|
+
data: seg.data,
|
|
20
|
+
x: xFn,
|
|
21
|
+
y: yFn,
|
|
22
|
+
r: rFn,
|
|
23
|
+
style: {
|
|
24
|
+
dotRadius: disabledDots?.dotRadius ?? dotStyle.dotRadius ?? cfg.scatter.dotRadius,
|
|
25
|
+
dotFill: disabledDots?.dotFill ?? dotStyle.dotFill ?? defaultColor,
|
|
26
|
+
dotSymbol: disabledDots?.dotSymbol ?? dotStyle.dotSymbol,
|
|
27
|
+
dotStroke: disabledDots?.dotStroke ?? dotStyle.dotStroke,
|
|
28
|
+
dotStrokeWidth: disabledDots?.dotStrokeWidth ?? dotStyle.dotStrokeWidth,
|
|
29
|
+
dotFillOpacity: disabledDots?.dotFillOpacity ?? dotStyle.dotFillOpacity,
|
|
30
|
+
dotStrokeOpacity: disabledDots?.dotStrokeOpacity ?? dotStyle.dotStrokeOpacity,
|
|
31
|
+
dotStrokeDasharray: disabledDots?.dotStrokeDasharray ?? dotStyle.dotStrokeDasharray,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return markers;
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extends a `[min, max]` data domain so the largest rendered dot radius
|
|
3
|
+
* doesn't get clipped by the plot's own frame — svelteplot's own `'auto'`
|
|
4
|
+
* margin only measures axis chrome (tick labels), never mark geometry, so a
|
|
5
|
+
* point sitting at the domain edge with a non-trivial radius otherwise
|
|
6
|
+
* overflows past the frame. `innerSizePx` is an estimate (the real pixel
|
|
7
|
+
* range isn't known until the scale itself is built from this domain), so
|
|
8
|
+
* this errs generous rather than exact.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveRadiusPaddedDomain(values: number[], maxRadiusPx: number, innerSizePx: number): [number, number] | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extends a `[min, max]` data domain so the largest rendered dot radius
|
|
3
|
+
* doesn't get clipped by the plot's own frame — svelteplot's own `'auto'`
|
|
4
|
+
* margin only measures axis chrome (tick labels), never mark geometry, so a
|
|
5
|
+
* point sitting at the domain edge with a non-trivial radius otherwise
|
|
6
|
+
* overflows past the frame. `innerSizePx` is an estimate (the real pixel
|
|
7
|
+
* range isn't known until the scale itself is built from this domain), so
|
|
8
|
+
* this errs generous rather than exact.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveRadiusPaddedDomain(values, maxRadiusPx, innerSizePx) {
|
|
11
|
+
const finite = values.filter((v) => Number.isFinite(v));
|
|
12
|
+
if (finite.length === 0 || innerSizePx <= 0 || maxRadiusPx <= 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const min = Math.min(...finite);
|
|
15
|
+
const max = Math.max(...finite);
|
|
16
|
+
if (min === max) {
|
|
17
|
+
// A degenerate single-value domain has no data-per-pixel ratio to pad by — fall back to a fixed unit either side.
|
|
18
|
+
return [min - 1, max + 1];
|
|
19
|
+
}
|
|
20
|
+
const dataPerPixel = (max - min) / innerSizePx;
|
|
21
|
+
const pad = maxRadiusPx * dataPerPixel;
|
|
22
|
+
return [min - pad, max + pad];
|
|
23
|
+
}
|
|
@@ -26,46 +26,95 @@ export type Declutter1DResult<T> = {
|
|
|
26
26
|
* already >= minGap and the average of two such gaps is too.
|
|
27
27
|
*
|
|
28
28
|
* Also records, per adjacent pair, whether either pass actually had to push
|
|
29
|
-
* — the ground truth for "did these two really interact," which
|
|
30
|
-
* needs and can't reliably reconstruct from positions alone
|
|
31
|
-
* (see its own doc comment).
|
|
29
|
+
* — the ground truth for "did these two really interact," which
|
|
30
|
+
* `laneAssignment` needs and can't reliably reconstruct from positions alone
|
|
31
|
+
* after the fact (see its own doc comment).
|
|
32
|
+
*
|
|
33
|
+
* `bounds`, if given, keeps every *run* (a maximal stretch of connected
|
|
34
|
+
* items — lone, unconnected items are left at their true position
|
|
35
|
+
* regardless) inside `[lo, hi]`: nothing pushed apart by this function is
|
|
36
|
+
* ever allowed to end up off the edge of whatever pixel range the caller
|
|
37
|
+
* considers in-bounds (typically the plot's own content box). A run that
|
|
38
|
+
* already fits is only shifted, never resized; a run too big to fit even
|
|
39
|
+
* at its natural spacing is shrunk just enough to fit, uniformly, so it's
|
|
40
|
+
* still evenly spaced — just tighter than `minGap` calls for. See
|
|
41
|
+
* `fitRunsWithinBounds` for the actual shift/shrink math.
|
|
42
|
+
*
|
|
43
|
+
* `bounds` also biases *which* pass a run's average leans on, via
|
|
44
|
+
* {@link runBiasWeights}: a run sitting close to one edge has little slack on
|
|
45
|
+
* that side, so a plain 50/50 blend would still ask the up-pass and
|
|
46
|
+
* down-pass to pull the block symmetrically toward *both* edges before
|
|
47
|
+
* `fitRunsWithinBounds` shifts/shrinks it back — the up-pass reaching for
|
|
48
|
+
* room that was never really there, needlessly widening how far members end
|
|
49
|
+
* up travelling from their original positions (`laneAssignment`'s own lane
|
|
50
|
+
* count grows with exactly that travel distance — see its doc comment) — so
|
|
51
|
+
* a run near an edge leans toward whichever pass already grows away from it
|
|
52
|
+
* instead — the block still expands to clear `minGap` everywhere, just
|
|
53
|
+
* mostly in the one direction that was actually available, the way a person
|
|
54
|
+
* sliding books apart on a shelf pinned against one wall would.
|
|
32
55
|
*/
|
|
33
56
|
export declare function declutter1D<T extends {
|
|
34
57
|
pos: number;
|
|
35
|
-
}>(items: T[], minGap: number): Declutter1DResult<T>;
|
|
58
|
+
}>(items: T[], minGap: number, bounds?: readonly [number, number]): Declutter1DResult<T>;
|
|
36
59
|
/**
|
|
37
60
|
* Groups `declutter1D`'s own `connected` flags into runs (maximal stretches
|
|
38
|
-
* of consecutive connected pairs), then
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
61
|
+
* of consecutive connected pairs), then gives every run member its own,
|
|
62
|
+
* unique lane — no two members ever share one, deliberately (see below) —
|
|
63
|
+
* ordered so that a leader line drawn through lane `rank` never crosses, or
|
|
64
|
+
* even draws visually flush against, one drawn through any other lane of
|
|
65
|
+
* the same run.
|
|
66
|
+
*
|
|
67
|
+
* Every member's leader line is the same three-segment elbow: a horizontal
|
|
68
|
+
* stub from the data point out to its lane, a vertical run up/down that
|
|
69
|
+
* lane to the label's height, then a final horizontal run in to the shared
|
|
70
|
+
* label column. Two such elbows, `i` (in the tighter of the two lanes) and
|
|
71
|
+
* `j` (further out), are safe from crossing *specifically* when neither of
|
|
72
|
+
* these can happen:
|
|
73
|
+
* - `j`'s own stub (at `j`'s original height) reaches out past `i`'s lane
|
|
74
|
+
* while `i`'s vertical run passes through that height — `j`'s stub would
|
|
75
|
+
* cut across `i`'s vertical segment.
|
|
76
|
+
* - `i`'s final run (at `i`'s label height) reaches past `i`'s own lane out
|
|
77
|
+
* to the shared column, and in doing so crosses `j`'s lane while `j`'s
|
|
78
|
+
* vertical run passes through *that* height — `i`'s own final segment
|
|
79
|
+
* would cut across `j`'s vertical segment.
|
|
80
|
+
*
|
|
81
|
+
* Whether `i` can safely take the tighter lane depends on both members'
|
|
82
|
+
* *actual* pixel geometry — not simply on which one moved further — so each
|
|
83
|
+
* pair is checked directly per {@link saferNearer} rather than inferred from
|
|
84
|
+
* a single sort key. A pair with a clean, provable answer either way (the
|
|
85
|
+
* overwhelmingly common case for `declutter1D`'s own output) is ordered by
|
|
86
|
+
* that answer; a pair with no clean answer (both directions equally
|
|
87
|
+
* "unsafe," or, symmetrically, both fine) falls back to the same *travel
|
|
88
|
+
* distance* heuristic used before this per-pair check existed — furthest
|
|
89
|
+
* `|final - original|` first — which is what decides ties and keeps the
|
|
90
|
+
* "furthest traveller gets the tightest lane, so it starts bending
|
|
91
|
+
* immediately rather than reaching across everyone else's leader lines
|
|
92
|
+
* first" intent for every pair the geometry itself doesn't already settle.
|
|
45
93
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
94
|
+
* No sharing, ever, is the other half of this: two members can only have
|
|
95
|
+
* non-overlapping travel spans (and so pass the old, now-removed,
|
|
96
|
+
* share-a-lane check) by sitting far apart along the axis — but "far apart"
|
|
97
|
+
* is exactly the case with the most room to spare, so it never needed the
|
|
98
|
+
* lane it would save by sharing, while any pair close enough to actually be
|
|
99
|
+
* short on room has spans close enough together that sharing draws their
|
|
100
|
+
* two leader lines only a couple pixels apart — visually indistinguishable
|
|
101
|
+
* from one continuous line touching the next label down. A run of `n`
|
|
102
|
+
* members uses exactly `n` lanes; letting the lane count grow is the price
|
|
103
|
+
* of a fan that never has two leader lines touching *or* crossing, and
|
|
104
|
+
* `ValueLabels`' own auto-margin measurement (see its doc comment) already
|
|
105
|
+
* grows `margins.right` to fit however many lanes that turns out to be, so
|
|
106
|
+
* nothing has to be guessed up front.
|
|
57
107
|
*
|
|
58
|
-
*
|
|
59
|
-
* point (a short first stub); higher
|
|
60
|
-
* (right up to the shared label
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* This also roughly halves the lane depth (and so the horizontal room) a
|
|
66
|
-
* run needs, versus giving every member its own lane.
|
|
108
|
+
* `rank` is the lane index (0-indexed) to drive a leader line's routing:
|
|
109
|
+
* `rank` 0 bends right off the data point (a short first stub); higher
|
|
110
|
+
* ranks travel further before bending (right up to the shared label
|
|
111
|
+
* column). `lanesUsed` is the run's own total lane count — always equal to
|
|
112
|
+
* `runSize` — shared by every member; `runSize` is the run's member count,
|
|
113
|
+
* for callers that just need to know whether a given member belongs to a
|
|
114
|
+
* real run at all.
|
|
67
115
|
*/
|
|
68
|
-
export declare function
|
|
116
|
+
export declare function laneAssignment(originals: number[], finals: number[], connected: boolean[]): {
|
|
69
117
|
rank: number;
|
|
70
|
-
|
|
118
|
+
lanesUsed: number;
|
|
119
|
+
runSize: number;
|
|
71
120
|
}[];
|