@fundar/data-chart-telling 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -17
- package/dist/charts/{Chart.svelte.d.ts → BaseChart.svelte.d.ts} +3 -3
- package/dist/charts/bar/Chart.svelte +5 -5
- package/dist/charts/bar/Chart.svelte.d.ts +1 -2
- package/dist/charts/heatmap/Chart.svelte +8 -7
- package/dist/charts/heatmap/Chart.svelte.d.ts +2 -3
- package/dist/charts/line/Chart.svelte +5 -5
- package/dist/charts/line/Chart.svelte.d.ts +1 -2
- package/dist/charts/pyramid/Chart.svelte +7 -6
- package/dist/charts/pyramid/Chart.svelte.d.ts +2 -3
- package/dist/configuration/config.svelte.js +1 -1
- package/dist/configuration/themes/index.d.ts +56 -40
- package/dist/index.d.ts +26 -12
- package/dist/index.js +12 -7
- package/dist/layout/facet/FacetLayout.svelte +1 -1
- package/dist/layout/facet/FacetLayout.svelte.d.ts +1 -1
- package/dist/layout/geo/GeoLayout.svelte +47 -0
- package/dist/layout/geo/GeoLayout.svelte.d.ts +22 -0
- package/dist/layout/geo/resolveProjection.d.ts +10 -0
- package/dist/layout/geo/resolveProjection.js +48 -0
- package/dist/layout/legend/DiscreteSection.svelte +1 -1
- package/dist/layout/plot/AxisLayout.svelte +95 -0
- package/dist/layout/plot/AxisLayout.svelte.d.ts +39 -0
- package/dist/layout/plot/BasePlotLayout.svelte +302 -0
- package/dist/layout/plot/BasePlotLayout.svelte.d.ts +95 -0
- package/dist/layout/plot/GridLayout.svelte +30 -0
- package/dist/layout/plot/GridLayout.svelte.d.ts +27 -0
- package/dist/layout/plot/RuleLayout.svelte +111 -0
- package/dist/layout/plot/RuleLayout.svelte.d.ts +40 -0
- package/dist/layout/plot/margins.d.ts +34 -0
- package/dist/layout/plot/margins.js +26 -0
- package/dist/layout/timeline/TimelineLayout.svelte +1 -1
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
- package/dist/layout/tooltip/Tooltip.svelte +15 -0
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +7 -0
- package/dist/layout/tooltip/TooltipLayout.svelte +45 -0
- package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +41 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +4 -3
- package/dist/layout/tooltip/controller.svelte.js +5 -6
- package/dist/layout/tooltip/hover.svelte.d.ts +1 -1
- package/dist/layout/tooltip/hover.svelte.js +1 -1
- package/dist/layout/tooltip/utils.d.ts +2 -2
- package/dist/markers/ContourClipMarker.svelte +77 -0
- package/dist/markers/ContourClipMarker.svelte.d.ts +10 -0
- package/dist/{plots/markers → markers}/DeltaMarker.svelte +4 -4
- package/dist/{plots/markers → markers}/DeltaMarker.svelte.d.ts +3 -3
- package/dist/{plots/markers → markers}/DotMarker.svelte +3 -3
- package/dist/{plots/markers → markers}/DotMarker.svelte.d.ts +3 -3
- package/dist/markers/HoverMarker.svelte +136 -0
- package/dist/{plots/markers → markers}/HoverMarker.svelte.d.ts +4 -4
- package/dist/{plots/markers → markers}/PeakMarker.svelte +2 -2
- package/dist/{plots/markers → markers}/PeakMarker.svelte.d.ts +2 -2
- package/dist/{plots/markers → markers}/TextMarker.svelte +11 -4
- package/dist/{plots/markers → markers}/TextMarker.svelte.d.ts +3 -3
- package/dist/markers/inset/InsetMarker.svelte +170 -0
- package/dist/markers/inset/InsetMarker.svelte.d.ts +51 -0
- package/dist/markers/inset/insetLayout.d.ts +25 -0
- package/dist/markers/inset/insetLayout.js +39 -0
- package/dist/markers/inset/insetProjection.d.ts +8 -0
- package/dist/markers/inset/insetProjection.js +39 -0
- package/dist/plots/bar/BarSegments.svelte +66 -0
- package/dist/plots/bar/BarSegments.svelte.d.ts +36 -0
- package/dist/plots/bar/Plot.svelte +132 -76
- package/dist/plots/bar/Plot.svelte.d.ts +7 -3
- package/dist/plots/bar/ValueLabels.svelte +89 -0
- package/dist/plots/bar/ValueLabels.svelte.d.ts +39 -0
- package/dist/plots/bar/hoverPoints.d.ts +15 -0
- package/dist/plots/bar/hoverPoints.js +35 -0
- package/dist/plots/bar/layout.svelte.d.ts +37 -0
- package/dist/plots/bar/layout.svelte.js +132 -0
- package/dist/plots/geo/Plot.svelte +548 -0
- package/dist/plots/geo/Plot.svelte.d.ts +29 -0
- package/dist/plots/geo/TileLayer.svelte +77 -0
- package/dist/plots/geo/TileLayer.svelte.d.ts +11 -0
- package/dist/plots/geo/projections/argentina.d.ts +8 -0
- package/dist/plots/geo/projections/argentina.js +35 -0
- package/dist/plots/geo/projections/fit.d.ts +8 -0
- package/dist/plots/geo/projections/fit.js +14 -0
- package/dist/plots/geo/rotate.svelte.d.ts +26 -0
- package/dist/plots/geo/rotate.svelte.js +79 -0
- package/dist/plots/geo/zoom.svelte.d.ts +32 -0
- package/dist/plots/geo/zoom.svelte.js +50 -0
- package/dist/plots/heatmap/Plot.svelte +129 -47
- package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
- package/dist/plots/line/Plot.svelte +44 -16
- package/dist/plots/line/Plot.svelte.d.ts +7 -3
- package/dist/plots/pyramid/Plot.svelte +70 -25
- package/dist/plots/pyramid/Plot.svelte.d.ts +7 -3
- package/dist/plots/utils/delta.d.ts +1 -1
- package/dist/plots/utils/geoAccessors.d.ts +64 -0
- package/dist/plots/utils/geoAccessors.js +188 -0
- package/dist/plots/utils/geoSegments.d.ts +14 -0
- package/dist/plots/utils/geoSegments.js +17 -0
- package/dist/plots/utils/segments.d.ts +14 -5
- package/dist/plots/utils/segments.js +25 -13
- package/dist/plots/utils/tiles.d.ts +89 -0
- package/dist/plots/utils/tiles.js +159 -0
- package/dist/plots/utils/topojson.d.ts +9 -0
- package/dist/plots/utils/topojson.js +30 -0
- package/dist/types/charts/common.d.ts +2 -1
- package/dist/types/charts/legend.d.ts +1 -1
- package/dist/types/charts/props.d.ts +9 -6
- package/dist/types/configuration/styling.d.ts +11 -5
- package/dist/types/layout/tooltip.d.ts +14 -21
- package/dist/types/{plots/markers.d.ts → markers/common.d.ts} +2 -18
- package/dist/types/markers/geo.d.ts +237 -0
- package/dist/types/markers/props.d.ts +11 -0
- package/dist/types/plots/axis.d.ts +55 -0
- package/dist/types/plots/axis.js +1 -0
- package/dist/types/plots/constants.d.ts +30 -0
- package/dist/types/plots/constants.js +1 -0
- package/dist/types/plots/data/common.d.ts +40 -0
- package/dist/types/plots/data/common.js +1 -0
- package/dist/types/plots/data/geo.d.ts +74 -0
- package/dist/types/plots/data/geo.js +1 -0
- package/dist/types/plots/delta.d.ts +2 -2
- package/dist/types/plots/props.d.ts +105 -16
- package/dist/types/plots/scales/geo.d.ts +44 -0
- package/dist/types/plots/scales/geo.js +1 -0
- package/dist/types/plots/segments/common.d.ts +25 -0
- package/dist/types/plots/segments/common.js +1 -0
- package/dist/types/plots/segments/config.d.ts +51 -0
- package/dist/types/plots/segments/config.js +1 -0
- package/dist/types/plots/styles/common.d.ts +37 -0
- package/dist/types/plots/styles/common.js +1 -0
- package/dist/types/plots/styles/geo.d.ts +42 -0
- package/dist/types/plots/styles/geo.js +1 -0
- package/dist/types/plots/styling.d.ts +45 -0
- package/dist/types/plots/styling.js +1 -0
- package/dist/utils/grouping.d.ts +2 -1
- package/dist/utils/interpolate.d.ts +2 -1
- package/package.json +27 -13
- package/dist/layout/plot/PlotLayout.svelte +0 -294
- package/dist/layout/plot/PlotLayout.svelte.d.ts +0 -70
- package/dist/plots/markers/HoverMarker.svelte +0 -98
- package/dist/types/plots/common.d.ts +0 -97
- package/dist/types/plots/styles.d.ts +0 -187
- /package/dist/charts/{Chart.svelte → BaseChart.svelte} +0 -0
- /package/dist/types/{plots → markers}/common.js +0 -0
- /package/dist/types/{plots/markers.js → markers/geo.js} +0 -0
- /package/dist/types/{plots/styles.js → markers/props.js} +0 -0
|
@@ -5,22 +5,43 @@
|
|
|
5
5
|
import { SvelteMap } from 'svelte/reactivity';
|
|
6
6
|
import { paletteColor } from '../../utils/color';
|
|
7
7
|
import { buildSeries } from '../../utils/grouping';
|
|
8
|
-
import {
|
|
8
|
+
import { BarX, BarY } from 'svelteplot';
|
|
9
9
|
import { resolveAccessor } from '../utils/accessors';
|
|
10
10
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
11
11
|
import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX } from '../utils/segments';
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
12
|
+
import { createBarLayout } from './layout.svelte';
|
|
13
|
+
import { buildHoverPoints } from './hoverPoints';
|
|
14
|
+
import BarSegments from './BarSegments.svelte';
|
|
15
|
+
import ValueLabels from './ValueLabels.svelte';
|
|
16
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
17
|
+
import AxisLayout from '../../layout/plot/AxisLayout.svelte';
|
|
18
|
+
import GridLayout from '../../layout/plot/GridLayout.svelte';
|
|
19
|
+
import RuleLayout from '../../layout/plot/RuleLayout.svelte';
|
|
20
|
+
import HoverMarker from '../../markers/HoverMarker.svelte';
|
|
21
|
+
import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
|
|
22
|
+
import type { Marker } from '../../types/markers/common';
|
|
23
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
24
|
+
import type { AxisScale } from '../../types/plots/axis';
|
|
25
|
+
import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
26
|
+
import type { AxisBasedScalesConfig } from '../../types/plots/props';
|
|
27
|
+
import type { BasePlotStyles } from '../../types/plots/styles/common';
|
|
28
|
+
import type { ValueAnchor } from '../../types/plots/constants';
|
|
29
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
18
30
|
|
|
19
31
|
/**
|
|
20
|
-
* A vertical
|
|
21
|
-
* `data/x/y/z` props — mirrors the line/heatmap plot contract
|
|
22
|
-
* chart-level container/timeline/facet/legend wrap it unchanged.
|
|
32
|
+
* A bar chart, vertical by default. Accepts either a pre-built `series`
|
|
33
|
+
* array or flat `data/x/y/z` props — mirrors the line/heatmap plot contract
|
|
34
|
+
* so the chart-level container/timeline/facet/legend wrap it unchanged.
|
|
23
35
|
*/
|
|
36
|
+
type Props = BasePlotProps<
|
|
37
|
+
SeriesProps<TData> | DataProps<TData>,
|
|
38
|
+
TData,
|
|
39
|
+
BasePlotStyles,
|
|
40
|
+
BarSegmentStyle,
|
|
41
|
+
Marker,
|
|
42
|
+
AxisBasedScalesConfig<TData>
|
|
43
|
+
>;
|
|
44
|
+
|
|
24
45
|
let {
|
|
25
46
|
width,
|
|
26
47
|
height,
|
|
@@ -35,14 +56,16 @@
|
|
|
35
56
|
scales = {},
|
|
36
57
|
margins,
|
|
37
58
|
tooltip = undefined,
|
|
38
|
-
}:
|
|
59
|
+
}: Props = $props();
|
|
39
60
|
|
|
40
61
|
$effect(() => { validateSegments(segments); });
|
|
41
62
|
|
|
42
63
|
const cfg = $derived(getConfiguration());
|
|
43
64
|
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
44
65
|
const groupedSeries = $derived(
|
|
45
|
-
|
|
66
|
+
// connect: false — bars are discrete; there's no visual gap between
|
|
67
|
+
// adjacent categories to bridge (see buildGroupedSeries's own doc).
|
|
68
|
+
buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments, false),
|
|
46
69
|
);
|
|
47
70
|
const showVals = $derived(styles.values?.show ?? false);
|
|
48
71
|
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
@@ -59,85 +82,117 @@
|
|
|
59
82
|
return map;
|
|
60
83
|
});
|
|
61
84
|
|
|
85
|
+
// `xAcc`/`yAcc` are always data-semantic (category/value) — independent of
|
|
86
|
+
// which *visual* axis ends up drawing them, decided by `categoricalAxis`.
|
|
62
87
|
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
63
88
|
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
64
89
|
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Which visual axis draws the category, and therefore whether this renders
|
|
93
|
+
* as vertical bars (`BarY`, category on X) or horizontal bars (`BarX`,
|
|
94
|
+
* category on Y). Mirrors how pyramid/heatmap default an `AxisScale.type`
|
|
95
|
+
* themselves: `scales.x`/`scales.y` always describe *that visual axis*
|
|
96
|
+
* regardless of which data field lands there, so marking `scales.y.type:
|
|
97
|
+
* 'categorical'` simply says "the category is drawn on Y this time" — x
|
|
98
|
+
* defaults to categorical when neither is marked.
|
|
99
|
+
*/
|
|
100
|
+
const categoricalAxis = $derived.by((): 'x' | 'y' => {
|
|
101
|
+
const xCategorical = scales.x?.type === 'categorical';
|
|
102
|
+
const yCategorical = scales.y?.type === 'categorical';
|
|
103
|
+
if (xCategorical && yCategorical) {
|
|
104
|
+
console.warn(
|
|
105
|
+
"BarPlot: scales.x.type and scales.y.type can't both be 'categorical' — " +
|
|
106
|
+
'mark only one axis as the category axis. Defaulting to x.',
|
|
107
|
+
);
|
|
108
|
+
return 'x';
|
|
109
|
+
}
|
|
110
|
+
return yCategorical ? 'y' : 'x';
|
|
111
|
+
});
|
|
112
|
+
const isHorizontal = $derived(categoricalAxis === 'y');
|
|
113
|
+
const BarMark = $derived(isHorizontal ? BarX : BarY);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* `'categorical'` is our own sentinel (BarPlot-only), not a real svelteplot
|
|
117
|
+
* scale type — svelteplot's x/y scales don't recognise it (it's valid only
|
|
118
|
+
* for `color`/`symbol`). Strip it before the scale reaches svelteplot so it
|
|
119
|
+
* auto-infers the real type (typically `point`/`band`) the same way it
|
|
120
|
+
* already does when `type` is left unset entirely.
|
|
121
|
+
*/
|
|
122
|
+
function stripCategoricalType(scale?: AxisScale<TData>): AxisScale<TData> | undefined {
|
|
123
|
+
if (scale?.type !== 'categorical') return scale;
|
|
124
|
+
const rest = { ...scale };
|
|
125
|
+
delete rest.type;
|
|
126
|
+
return rest;
|
|
127
|
+
}
|
|
128
|
+
// The single, fully-resolved scales object BasePlotLayout reads for
|
|
129
|
+
// everything (ticks, grid, sort, and the real svelteplot scale) — avoids
|
|
130
|
+
// keeping a second, separately-patched copy of `scales.x`/`scales.y` in
|
|
131
|
+
// sync with the one actually forwarded.
|
|
132
|
+
const resolvedScales = $derived({
|
|
133
|
+
...scales,
|
|
134
|
+
x: stripCategoricalType(scales.x),
|
|
135
|
+
y: stripCategoricalType(scales.y),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// The accessor feeding each *visual* channel — swapped from the
|
|
139
|
+
// data-semantic xAcc/yAcc when the category is drawn on Y.
|
|
140
|
+
const plotGetX = $derived(isHorizontal ? (d: TData) => Number(yAcc(d)) : xAcc);
|
|
141
|
+
const plotGetY = $derived(isHorizontal ? xAcc : (d: TData) => Number(yAcc(d)));
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Series-layout geometry (grouped/stacked offsets, band-width estimate,
|
|
145
|
+
* value baseline) — the single source of truth shared by bar-segment
|
|
146
|
+
* rendering, value labels, and hover markers below. See `layout.svelte.ts`.
|
|
147
|
+
*/
|
|
148
|
+
const barLayout = createBarLayout<TData>({
|
|
149
|
+
scales: () => scales,
|
|
150
|
+
isHorizontal: () => isHorizontal,
|
|
151
|
+
width: () => width,
|
|
152
|
+
height: () => height,
|
|
153
|
+
margins: () => margins,
|
|
154
|
+
flat: () => flat,
|
|
155
|
+
xAcc: () => xAcc,
|
|
156
|
+
groupedSeries: () => groupedSeries,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// `x`/`y` here are visual (matched against BasePlotLayout's own getX/getY,
|
|
160
|
+
// which drive tick/domain computation and the Pointer's hit-testing) —
|
|
161
|
+
// `row` (see BasePlotLayout/HoverPoint) is what lets the template recover the
|
|
162
|
+
// semantic category regardless of which axis it landed on.
|
|
163
|
+
const hoverPoints = $derived(buildHoverPoints(resolvedSeries, isHorizontal, barLayout, cfg.palette));
|
|
164
|
+
|
|
165
|
+
const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
|
|
166
|
+
const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, categoricalAxis));
|
|
167
|
+
const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
|
|
79
168
|
</script>
|
|
80
169
|
|
|
81
|
-
<
|
|
170
|
+
<BasePlotLayout
|
|
82
171
|
{width}
|
|
83
172
|
{height}
|
|
84
|
-
x={scales.x}
|
|
85
|
-
y={scales.y}
|
|
86
173
|
data={flat}
|
|
87
|
-
getX={
|
|
88
|
-
getY={
|
|
174
|
+
getX={plotGetX}
|
|
175
|
+
getY={plotGetY}
|
|
89
176
|
{hoverPoints}
|
|
90
177
|
label={(r) => String(yAcc(r))}
|
|
91
178
|
{tooltip}
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
{
|
|
179
|
+
{hoverActive}
|
|
180
|
+
{hoverStrategy}
|
|
181
|
+
{hoverSync}
|
|
182
|
+
scales={resolvedScales}
|
|
95
183
|
{margins}
|
|
96
|
-
|
|
184
|
+
xTickRotate={resolvedScales?.x?.tickRotate}
|
|
97
185
|
>
|
|
98
|
-
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
|
|
186
|
+
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
|
|
187
|
+
<AxisLayout data={flat} getX={plotGetX} getY={plotGetY} {width} {height} {numericMargins} scales={resolvedScales} />
|
|
188
|
+
<GridLayout scales={resolvedScales} />
|
|
189
|
+
<RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY}>
|
|
99
190
|
{#each groupedSeries as group, seriesIndex (group.series.name)}
|
|
100
191
|
{@const defaultColor = paletteColor(seriesIndex, cfg.palette)}
|
|
101
|
-
{
|
|
102
|
-
{@const yFn = resolveAccessor(group.series.y)}
|
|
103
|
-
{#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
|
|
104
|
-
<BarY
|
|
105
|
-
data={seg.data}
|
|
106
|
-
x={xFn}
|
|
107
|
-
y={yFn}
|
|
108
|
-
fill={seg.style.fill ?? defaultColor}
|
|
109
|
-
fillOpacity={seg.style.fillOpacity ?? 1}
|
|
110
|
-
/>
|
|
111
|
-
{/each}
|
|
192
|
+
<BarSegments {group} {seriesIndex} {barLayout} {isHorizontal} {BarMark} {defaultColor} />
|
|
112
193
|
|
|
113
194
|
{#if showVals}
|
|
114
|
-
{
|
|
115
|
-
? (d: TData) => Number(yFn(d)) / 2
|
|
116
|
-
: valAnchor === 'end'
|
|
117
|
-
? () => 0
|
|
118
|
-
: (d: TData) => Number(yFn(d))}
|
|
119
|
-
{@const defaultDy = valAnchor === 'outside' ? -6 : valAnchor === 'start' ? 4 : valAnchor === 'end' ? -4 : 0}
|
|
120
|
-
{@const defaultLA = valAnchor === 'outside' || valAnchor === 'end'
|
|
121
|
-
? ('bottom' as const)
|
|
122
|
-
: valAnchor === 'start'
|
|
123
|
-
? ('top' as const)
|
|
124
|
-
: ('middle' as const)}
|
|
125
|
-
<Text
|
|
126
|
-
data={group.series.data}
|
|
127
|
-
x={xFn}
|
|
128
|
-
y={yTextFn}
|
|
129
|
-
text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
|
|
130
|
-
dx={styles.values?.dx ?? 0}
|
|
131
|
-
dy={styles.values?.dy ?? defaultDy}
|
|
132
|
-
textAnchor={styles.values?.textAnchor ?? 'middle'}
|
|
133
|
-
lineAnchor={styles.values?.lineAnchor ?? defaultLA}
|
|
134
|
-
lineHeight={styles.values?.lineHeight}
|
|
135
|
-
rotate={styles.values?.rotate}
|
|
136
|
-
class={styles.values?.class}
|
|
137
|
-
textClass={styles.values?.textClass}
|
|
138
|
-
fill={cfg.axis.color}
|
|
139
|
-
fontSize={12}
|
|
140
|
-
/>
|
|
195
|
+
<ValueLabels {group} {seriesIndex} {barLayout} {isHorizontal} {valAnchor} {fmtVal} values={styles.values} axisColor={cfg.axis.color} />
|
|
141
196
|
{/if}
|
|
142
197
|
|
|
143
198
|
{#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-hover-${i}`)}
|
|
@@ -146,7 +201,7 @@
|
|
|
146
201
|
{#if marker.scope === 'segment'}
|
|
147
202
|
{@const segs = resolveSegmentsForSeries(segments, group.series.name)}
|
|
148
203
|
{#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
|
|
149
|
-
{@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.
|
|
204
|
+
{@const segPoints = seriesPoints.filter((p) => matchesSegmentX(xAcc(p.row), seg))}
|
|
150
205
|
{#if segPoints.length > 0}
|
|
151
206
|
<HoverMarker
|
|
152
207
|
data={segPoints}
|
|
@@ -176,5 +231,6 @@
|
|
|
176
231
|
{/if}
|
|
177
232
|
{/each}
|
|
178
233
|
{/each}
|
|
234
|
+
</RuleLayout>
|
|
179
235
|
{/snippet}
|
|
180
|
-
</
|
|
236
|
+
</BasePlotLayout>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Marker } from '../../types/markers/common';
|
|
2
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
3
|
+
import type { SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
4
|
+
import type { AxisBasedScalesConfig } from '../../types/plots/props';
|
|
5
|
+
import type { BasePlotStyles } from '../../types/plots/styles/common';
|
|
6
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
3
7
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
-
props:
|
|
8
|
+
props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, BasePlotStyles, BarSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
|
|
5
9
|
exports: {};
|
|
6
10
|
bindings: "";
|
|
7
11
|
slots: {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { Text } from 'svelteplot';
|
|
3
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
4
|
+
import type { Series } from '../../types/plots/data/common';
|
|
5
|
+
import type { ValuesStyle } from '../../types/plots/styles/common';
|
|
6
|
+
import type { ValueAnchor } from '../../types/plots/constants';
|
|
7
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
8
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
9
|
+
import type { BarLayout } from './layout.svelte';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders one series-group's value label — anchor-driven default
|
|
13
|
+
* position/offset, overridable per-field via `styles.values`. Positions
|
|
14
|
+
* account for the same grouped/stacked geometry as the bars themselves
|
|
15
|
+
* (via {@link BarLayout}), so a label lands on its own bar/segment instead
|
|
16
|
+
* of a shared default position.
|
|
17
|
+
*/
|
|
18
|
+
let {
|
|
19
|
+
group,
|
|
20
|
+
seriesIndex,
|
|
21
|
+
barLayout,
|
|
22
|
+
isHorizontal,
|
|
23
|
+
valAnchor,
|
|
24
|
+
fmtVal,
|
|
25
|
+
values,
|
|
26
|
+
axisColor,
|
|
27
|
+
}: {
|
|
28
|
+
group: VisualGroup<Series<TData>, BarSegmentStyle>;
|
|
29
|
+
seriesIndex: number;
|
|
30
|
+
barLayout: BarLayout<TData>;
|
|
31
|
+
isHorizontal: boolean;
|
|
32
|
+
valAnchor: ValueAnchor;
|
|
33
|
+
fmtVal: (value: number, seriesName: string) => string;
|
|
34
|
+
values: ValuesStyle | undefined;
|
|
35
|
+
axisColor: string;
|
|
36
|
+
} = $props();
|
|
37
|
+
|
|
38
|
+
const xFn = $derived(resolveAccessor(group.series.x));
|
|
39
|
+
const yFn = $derived(resolveAccessor(group.series.y));
|
|
40
|
+
|
|
41
|
+
const valueTextFn = $derived(
|
|
42
|
+
barLayout.layout === 'stacked'
|
|
43
|
+
? (d: TData) => barLayout.stackedBaseline(xFn(d), seriesIndex) + Number(yFn(d)) / 2
|
|
44
|
+
: valAnchor === 'middle' && !isHorizontal
|
|
45
|
+
? (d: TData) => Number(yFn(d)) / 2
|
|
46
|
+
: valAnchor === 'end'
|
|
47
|
+
? () => 0
|
|
48
|
+
: (d: TData) => Number(yFn(d)),
|
|
49
|
+
);
|
|
50
|
+
const defaultOffset = $derived(
|
|
51
|
+
valAnchor === 'outside' ? (isHorizontal ? 6 : -6)
|
|
52
|
+
: valAnchor === 'start' ? (isHorizontal ? -4 : 4)
|
|
53
|
+
: valAnchor === 'end' ? (isHorizontal ? 4 : -4)
|
|
54
|
+
: 0,
|
|
55
|
+
);
|
|
56
|
+
const defaultLA = $derived(
|
|
57
|
+
valAnchor === 'outside' || valAnchor === 'end' ? ('bottom' as const)
|
|
58
|
+
: valAnchor === 'start' ? ('top' as const)
|
|
59
|
+
: ('middle' as const),
|
|
60
|
+
);
|
|
61
|
+
const groupOffset = $derived(barLayout.groupOffset(seriesIndex));
|
|
62
|
+
|
|
63
|
+
// `textAnchor: 'outside'` is a per-point escape hatch resolved by
|
|
64
|
+
// HoverMarker (which knows each point's signed x) — value labels already
|
|
65
|
+
// have their own `anchor: 'outside'` concept (computed above), so if it
|
|
66
|
+
// leaks through here just fall back to the anchor-derived default.
|
|
67
|
+
const resolvedTextAnchor = $derived(values?.textAnchor === 'outside' ? undefined : values?.textAnchor);
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<Text
|
|
71
|
+
data={group.series.data}
|
|
72
|
+
{...isHorizontal ? { y: xFn, x: valueTextFn } : { x: xFn, y: valueTextFn }}
|
|
73
|
+
text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
|
|
74
|
+
dx={values?.dx ?? (isHorizontal ? defaultOffset : groupOffset.dx)}
|
|
75
|
+
dy={values?.dy ?? (isHorizontal ? groupOffset.dy : defaultOffset)}
|
|
76
|
+
textAnchor={resolvedTextAnchor ?? (isHorizontal ? (valAnchor === 'outside' || valAnchor === 'end' ? 'start' : valAnchor === 'start' ? 'end' : 'middle') : 'middle')}
|
|
77
|
+
lineAnchor={values?.lineAnchor ?? (isHorizontal ? 'middle' : defaultLA)}
|
|
78
|
+
lineHeight={values?.lineHeight}
|
|
79
|
+
rotate={values?.rotate}
|
|
80
|
+
class={values?.class}
|
|
81
|
+
textClass={values?.textClass}
|
|
82
|
+
fill={values?.fill ?? axisColor}
|
|
83
|
+
fontSize={values?.fontSize ?? 12}
|
|
84
|
+
fontWeight={values?.fontWeight}
|
|
85
|
+
fontStyle={values?.fontStyle}
|
|
86
|
+
stroke={values?.stroke}
|
|
87
|
+
strokeWidth={values?.strokeWidth}
|
|
88
|
+
paintOrder={values?.paintOrder}
|
|
89
|
+
/>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Series } from '../../types/plots/data/common';
|
|
2
|
+
import type { ValuesStyle } from '../../types/plots/styles/common';
|
|
3
|
+
import type { ValueAnchor } from '../../types/plots/constants';
|
|
4
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
5
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
6
|
+
import type { BarLayout } from './layout.svelte';
|
|
7
|
+
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
8
|
+
props: {
|
|
9
|
+
group: VisualGroup<Series<TData>, BarSegmentStyle>;
|
|
10
|
+
seriesIndex: number;
|
|
11
|
+
barLayout: BarLayout<TData>;
|
|
12
|
+
isHorizontal: boolean;
|
|
13
|
+
valAnchor: ValueAnchor;
|
|
14
|
+
fmtVal: (value: number, seriesName: string) => string;
|
|
15
|
+
values: ValuesStyle | undefined;
|
|
16
|
+
axisColor: string;
|
|
17
|
+
};
|
|
18
|
+
exports: {};
|
|
19
|
+
bindings: "";
|
|
20
|
+
slots: {};
|
|
21
|
+
events: {};
|
|
22
|
+
};
|
|
23
|
+
declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
24
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
25
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
26
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
27
|
+
bindings(): "";
|
|
28
|
+
exports(): {};
|
|
29
|
+
}
|
|
30
|
+
interface $$IsomorphicComponent {
|
|
31
|
+
new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
32
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
33
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
34
|
+
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
35
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
36
|
+
}
|
|
37
|
+
declare const ValueLabels: $$IsomorphicComponent;
|
|
38
|
+
type ValueLabels<TData extends Record<string, unknown>> = InstanceType<typeof ValueLabels<TData>>;
|
|
39
|
+
export default ValueLabels;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Series } from '../../types/plots/data/common';
|
|
2
|
+
import type { HoverPoint } from '../../types/layout/tooltip';
|
|
3
|
+
import type { BarLayout } from './layout.svelte';
|
|
4
|
+
/**
|
|
5
|
+
* Builds hover-marker candidate points for every row across every series.
|
|
6
|
+
*
|
|
7
|
+
* `x`/`y` (the values matching is keyed off) stay the raw category/value so
|
|
8
|
+
* every series at the same category still matches together under the 'x'
|
|
9
|
+
* strategy; `dx`/`dy` only nudge where the matched point is *drawn* — see
|
|
10
|
+
* {@link import('../../types/layout/tooltip').HoverDisplayPoint}. For
|
|
11
|
+
* 'stacked' layout, `value` itself is adjusted to the series' cumulative
|
|
12
|
+
* height instead of `dx`/`dy`, since that offset is representable as a
|
|
13
|
+
* regular data value (unlike 'grouped''s pixel-only slice offset).
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildHoverPoints<TData extends Record<string, unknown>>(resolvedSeries: Series<TData>[], isHorizontal: boolean, geometry: Pick<BarLayout<TData>, 'layout' | 'stackedBaseline' | 'groupOffset'>, palette: string[]): HoverPoint<TData>[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
2
|
+
import { paletteColor } from '../../utils/color';
|
|
3
|
+
/**
|
|
4
|
+
* Builds hover-marker candidate points for every row across every series.
|
|
5
|
+
*
|
|
6
|
+
* `x`/`y` (the values matching is keyed off) stay the raw category/value so
|
|
7
|
+
* every series at the same category still matches together under the 'x'
|
|
8
|
+
* strategy; `dx`/`dy` only nudge where the matched point is *drawn* — see
|
|
9
|
+
* {@link import('../../types/layout/tooltip').HoverDisplayPoint}. For
|
|
10
|
+
* 'stacked' layout, `value` itself is adjusted to the series' cumulative
|
|
11
|
+
* height instead of `dx`/`dy`, since that offset is representable as a
|
|
12
|
+
* regular data value (unlike 'grouped''s pixel-only slice offset).
|
|
13
|
+
*/
|
|
14
|
+
export function buildHoverPoints(resolvedSeries, isHorizontal, geometry, palette) {
|
|
15
|
+
return resolvedSeries.flatMap((s, idx) => {
|
|
16
|
+
const sx = resolveAccessor(s.x);
|
|
17
|
+
const sy = resolveAccessor(s.y);
|
|
18
|
+
return s.data.map((d) => {
|
|
19
|
+
const category = sx(d);
|
|
20
|
+
const rawValue = Number(sy(d));
|
|
21
|
+
const value = geometry.layout === 'stacked' ? geometry.stackedBaseline(category, idx) + rawValue : rawValue;
|
|
22
|
+
const { dx, dy } = geometry.groupOffset(idx);
|
|
23
|
+
return {
|
|
24
|
+
x: isHorizontal ? value : category,
|
|
25
|
+
y: isHorizontal ? category : value,
|
|
26
|
+
dx,
|
|
27
|
+
dy,
|
|
28
|
+
row: d,
|
|
29
|
+
label: String(sy(d)),
|
|
30
|
+
series: s.name,
|
|
31
|
+
color: paletteColor(idx, palette),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AxisValue } from '../../types/plots/axis';
|
|
2
|
+
import type { Series } from '../../types/plots/data/common';
|
|
3
|
+
import type { AxisBasedScalesConfig } from '../../types/plots/props';
|
|
4
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
5
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
6
|
+
import type { MarginConfig } from '../../types/plots/props';
|
|
7
|
+
export type BarSeriesLayout = 'overlap' | 'grouped' | 'stacked';
|
|
8
|
+
/**
|
|
9
|
+
* Reactive geometry for BarPlot's series-layout modes — the single source of
|
|
10
|
+
* truth for "where does series N's bar actually sit," shared by bar-segment
|
|
11
|
+
* rendering, value labels, and hover markers. Centralized here after a bug
|
|
12
|
+
* where hover markers reproduced (and initially missed) the same grouped/
|
|
13
|
+
* stacked math value labels already accounted for.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createBarLayout<TData extends Record<string, unknown>>(args: {
|
|
16
|
+
scales: () => AxisBasedScalesConfig<TData>;
|
|
17
|
+
isHorizontal: () => boolean;
|
|
18
|
+
width: () => number;
|
|
19
|
+
height: () => number;
|
|
20
|
+
margins: () => MarginConfig | undefined;
|
|
21
|
+
flat: () => TData[];
|
|
22
|
+
xAcc: () => (d: TData) => AxisValue;
|
|
23
|
+
groupedSeries: () => VisualGroup<Series<TData>, BarSegmentStyle>[];
|
|
24
|
+
}): {
|
|
25
|
+
readonly layout: BarSeriesLayout;
|
|
26
|
+
readonly valueBaseline: number;
|
|
27
|
+
readonly estimatedBandwidth: number;
|
|
28
|
+
readonly seriesCount: number;
|
|
29
|
+
readonly seriesStep: number;
|
|
30
|
+
readonly seriesGap: number;
|
|
31
|
+
stackedBaseline: (category: AxisValue, seriesIndex: number) => number;
|
|
32
|
+
groupOffset: (seriesIndex: number) => {
|
|
33
|
+
dx: number;
|
|
34
|
+
dy: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type BarLayout<TData extends Record<string, unknown>> = ReturnType<typeof createBarLayout<TData>>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
3
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
4
|
+
import { numericMargin } from '../../layout/plot/margins';
|
|
5
|
+
/**
|
|
6
|
+
* Reactive geometry for BarPlot's series-layout modes — the single source of
|
|
7
|
+
* truth for "where does series N's bar actually sit," shared by bar-segment
|
|
8
|
+
* rendering, value labels, and hover markers. Centralized here after a bug
|
|
9
|
+
* where hover markers reproduced (and initially missed) the same grouped/
|
|
10
|
+
* stacked math value labels already accounted for.
|
|
11
|
+
*/
|
|
12
|
+
export function createBarLayout(args) {
|
|
13
|
+
const cfg = $derived(getConfiguration());
|
|
14
|
+
/**
|
|
15
|
+
* How series sharing the same category are arranged — see
|
|
16
|
+
* {@link import('../../types/plots/axis').AxisScale.seriesLayout}. Read
|
|
17
|
+
* off `scales.z` since it's the grouping axis, not the category axis itself.
|
|
18
|
+
*/
|
|
19
|
+
const layout = $derived(args.scales().z?.seriesLayout ?? 'overlap');
|
|
20
|
+
const categoricalScale = $derived(args.isHorizontal() ? args.scales().y : args.scales().x);
|
|
21
|
+
const valueScale = $derived(args.isHorizontal() ? args.scales().x : args.scales().y);
|
|
22
|
+
/**
|
|
23
|
+
* Bars default to a 0 baseline. When the value axis's domain is set and
|
|
24
|
+
* excludes 0 (e.g. `[75, 85]`, to zoom in on a tight range), a literal 0
|
|
25
|
+
* baseline sits way outside the visible domain — the scale still maps it
|
|
26
|
+
* linearly, so the bar's "start" lands far past the plot's own edge and
|
|
27
|
+
* the bar overflows into the margin instead of appearing to end at the
|
|
28
|
+
* frame. Clamping the baseline to whichever domain bound is closest to 0
|
|
29
|
+
* makes bars cut off exactly at the visible edge instead, which is the
|
|
30
|
+
* point of setting a truncated domain in the first place. Left as 0 (no
|
|
31
|
+
* clamp) when the domain isn't set or already straddles 0 naturally.
|
|
32
|
+
*/
|
|
33
|
+
const valueBaseline = $derived.by(() => {
|
|
34
|
+
const domain = valueScale?.domain;
|
|
35
|
+
if (!domain || domain.length < 2)
|
|
36
|
+
return 0;
|
|
37
|
+
const lo = Number(domain[0]);
|
|
38
|
+
const hi = Number(domain[domain.length - 1]);
|
|
39
|
+
if (Number.isNaN(lo) || Number.isNaN(hi))
|
|
40
|
+
return 0;
|
|
41
|
+
const min = Math.min(lo, hi);
|
|
42
|
+
const max = Math.max(lo, hi);
|
|
43
|
+
if (min > 0)
|
|
44
|
+
return min;
|
|
45
|
+
if (max < 0)
|
|
46
|
+
return max;
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Approximate per-category band size in pixels (width for vertical bars,
|
|
51
|
+
* height for horizontal ones), used only to size each series' slice in
|
|
52
|
+
* 'grouped' layout via inset props. Derived from this plot's own
|
|
53
|
+
* `width`/`height`/`margins` rather than svelteplot's actual measured
|
|
54
|
+
* scale (not part of its public API) — close enough for a grouped look,
|
|
55
|
+
* though it can be slightly off when an 'auto' margin ends up wider than
|
|
56
|
+
* the estimate (e.g. unusually long tick labels). Follows the same
|
|
57
|
+
* band-scale formula (and 0.15 default padding) svelteplot itself uses,
|
|
58
|
+
* so the category axis's own `paddingInner`/`paddingOuter` — the space
|
|
59
|
+
* *between* categories — is reflected here too, not just svelteplot's
|
|
60
|
+
* real render.
|
|
61
|
+
*/
|
|
62
|
+
const estimatedBandwidth = $derived.by(() => {
|
|
63
|
+
const categoryCount = new SvelteSet(args.flat().map((d) => args.xAcc()(d))).size;
|
|
64
|
+
if (categoryCount === 0)
|
|
65
|
+
return 0;
|
|
66
|
+
const baseMargins = { ...cfg.margins, ...args.margins() };
|
|
67
|
+
const innerSize = args.isHorizontal()
|
|
68
|
+
? Math.max(0, args.height() - numericMargin('top', baseMargins.top) - numericMargin('bottom', baseMargins.bottom))
|
|
69
|
+
: Math.max(0, args.width() - numericMargin('left', baseMargins.left) - numericMargin('right', baseMargins.right));
|
|
70
|
+
const paddingInner = categoricalScale?.paddingInner ?? categoricalScale?.padding ?? 0.15;
|
|
71
|
+
const paddingOuter = categoricalScale?.paddingOuter ?? categoricalScale?.padding ?? 0.15;
|
|
72
|
+
const step = innerSize / (categoryCount - paddingInner + 2 * paddingOuter);
|
|
73
|
+
return step * (1 - paddingInner);
|
|
74
|
+
});
|
|
75
|
+
const seriesCount = $derived(args.groupedSeries().length);
|
|
76
|
+
// Each series gets an equal slice of the band; `seriesPadding` (0–1, space
|
|
77
|
+
// *within* a group, between its series) reserves a fraction of that slice
|
|
78
|
+
// as a gap split evenly on either side of the bar.
|
|
79
|
+
const seriesStep = $derived(seriesCount > 0 ? estimatedBandwidth / seriesCount : 0);
|
|
80
|
+
const seriesGap = $derived(Math.max(0, Math.min(1, args.scales().z?.seriesPadding ?? 0)) * seriesStep);
|
|
81
|
+
/**
|
|
82
|
+
* Per-category cumulative sums for 'stacked' layout — `stackedPrefixSums.get(category)[i]`
|
|
83
|
+
* is the sum of series `0..i-1`'s value at that category, i.e. series `i`'s
|
|
84
|
+
* own segment spans `[prefix, prefix + value]`. Computed by hand (rather
|
|
85
|
+
* than svelteplot's `stackY`/`stackX` transform, which needs every series'
|
|
86
|
+
* rows in one call to line up cumulative offsets) so the same numbers can
|
|
87
|
+
* position the bar segment, its middle-anchored label, and its hover marker.
|
|
88
|
+
*/
|
|
89
|
+
const stackedPrefixSums = $derived.by(() => {
|
|
90
|
+
const sums = new SvelteMap();
|
|
91
|
+
if (layout !== 'stacked')
|
|
92
|
+
return sums;
|
|
93
|
+
for (const category of new SvelteSet(args.flat().map((d) => args.xAcc()(d)))) {
|
|
94
|
+
let running = 0;
|
|
95
|
+
const prefix = [];
|
|
96
|
+
for (const group of args.groupedSeries()) {
|
|
97
|
+
prefix.push(running);
|
|
98
|
+
const seriesXFn = resolveAccessor(group.series.x);
|
|
99
|
+
const seriesYFn = resolveAccessor(group.series.y);
|
|
100
|
+
const row = group.series.data.find((d) => seriesXFn(d) === category);
|
|
101
|
+
running += row ? Number(seriesYFn(row)) : 0;
|
|
102
|
+
}
|
|
103
|
+
sums.set(category, prefix);
|
|
104
|
+
}
|
|
105
|
+
return sums;
|
|
106
|
+
});
|
|
107
|
+
function stackedBaseline(category, seriesIndex) {
|
|
108
|
+
return stackedPrefixSums.get(category)?.[seriesIndex] ?? 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Pixel offset for a 'grouped' series' own slice of the category band —
|
|
112
|
+
* shared by bar segments (`insetLeft`/`insetTop`), value labels
|
|
113
|
+
* (`dx`/`dy`), and hover markers, so all three stay in sync with wherever
|
|
114
|
+
* this series' bar actually renders.
|
|
115
|
+
*/
|
|
116
|
+
function groupOffset(seriesIndex) {
|
|
117
|
+
if (layout !== 'grouped')
|
|
118
|
+
return { dx: 0, dy: 0 };
|
|
119
|
+
const offset = -(estimatedBandwidth / 2) + seriesIndex * seriesStep + seriesStep / 2;
|
|
120
|
+
return args.isHorizontal() ? { dx: 0, dy: offset } : { dx: offset, dy: 0 };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
get layout() { return layout; },
|
|
124
|
+
get valueBaseline() { return valueBaseline; },
|
|
125
|
+
get estimatedBandwidth() { return estimatedBandwidth; },
|
|
126
|
+
get seriesCount() { return seriesCount; },
|
|
127
|
+
get seriesStep() { return seriesStep; },
|
|
128
|
+
get seriesGap() { return seriesGap; },
|
|
129
|
+
stackedBaseline,
|
|
130
|
+
groupOffset,
|
|
131
|
+
};
|
|
132
|
+
}
|