@fundar/data-chart-telling 0.0.38 → 0.0.40
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/plots/bar/Plot.svelte +275 -249
- package/dist/plots/line/Plot.svelte +207 -182
- package/dist/plots/pyramid/Plot.svelte +288 -260
- package/dist/plots/scatter/Plot.svelte +266 -242
- package/dist/plots/utils/labelOverflow.svelte.d.ts +17 -1
- package/dist/plots/utils/labelOverflow.svelte.js +57 -11
- package/package.json +1 -1
|
@@ -1,187 +1,212 @@
|
|
|
1
|
-
<script
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
3
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
4
|
+
import { paletteColor } from '../../utils/color';
|
|
5
|
+
import { buildSeries } from '../../utils/grouping';
|
|
6
|
+
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
7
|
+
import { disabledStrokeOverride, disabledDotsOverride } from '../utils/legendDisabled';
|
|
8
|
+
import { buildLineMarkers } from './buildLineMarkers';
|
|
9
|
+
import { buildGapMarkers } from './buildGapMarkers';
|
|
10
|
+
import { isGapValue } from '../utils/gaps';
|
|
11
|
+
import { createLabelMarginTracker } from '../utils/labelOverflow.svelte';
|
|
12
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
13
|
+
import ValueLabels from './ValueLabels.svelte';
|
|
14
|
+
import {
|
|
15
|
+
hasHoverMarker,
|
|
16
|
+
resolveHoverStrategy,
|
|
17
|
+
resolveHoverSync
|
|
18
|
+
} from '../../layout/tooltip/utils';
|
|
19
|
+
import {
|
|
20
|
+
buildGroupedSeries,
|
|
21
|
+
validateSegments,
|
|
22
|
+
buildScopedMarkerGroups
|
|
23
|
+
} from '../utils/segments';
|
|
24
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
25
|
+
import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
26
|
+
import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
27
|
+
import type { GapsAwarePlotStylesConfig } from '../../types/layout/styles';
|
|
28
|
+
import type { LineSegmentStyle } from '../../types/plots/segments/common';
|
|
29
|
+
import type { LineMarkersConfig } from '../../types/markers/line';
|
|
30
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
31
|
+
|
|
32
|
+
type Props = BasePlotProps<
|
|
33
|
+
SeriesProps<TData> | DataProps<TData>,
|
|
34
|
+
TData,
|
|
35
|
+
GapsAwarePlotStylesConfig<LineSegmentStyle>,
|
|
36
|
+
LineSegmentStyle,
|
|
37
|
+
LineMarkersConfig,
|
|
38
|
+
AxisBasedScalesConfig<TData>
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
series,
|
|
45
|
+
data,
|
|
46
|
+
x,
|
|
47
|
+
y,
|
|
48
|
+
z,
|
|
49
|
+
styles = {},
|
|
50
|
+
segments = {},
|
|
51
|
+
markers = [],
|
|
52
|
+
scales = {},
|
|
53
|
+
margins,
|
|
54
|
+
tooltip = undefined
|
|
55
|
+
}: Props = $props();
|
|
56
|
+
|
|
57
|
+
// Fills missing-data gaps with an interpolated, distinctly-styled bridge,
|
|
58
|
+
// resolved per series.
|
|
59
|
+
const gaps = $derived(styles.gaps ?? {});
|
|
60
|
+
|
|
61
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
62
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
validateSegments(segments);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const groupedSeries = $derived(
|
|
69
|
+
buildGroupedSeries<Series<TData>, LineSegmentStyle>(resolvedSeries, segments)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const cfg = $derived(getConfiguration());
|
|
73
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
74
|
+
const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
|
|
75
|
+
|
|
76
|
+
// Dims every other series like a legend toggle when a value label is
|
|
77
|
+
// hovered, local to this `<Plot>` instance.
|
|
78
|
+
let hoverIsolatedSeries = $state<string | null>(null);
|
|
79
|
+
|
|
80
|
+
function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
|
|
81
|
+
return (
|
|
82
|
+
legendInteraction?.disabledSeries.get(name) ??
|
|
83
|
+
(hoverIsolate && hoverIsolatedSeries && hoverIsolatedSeries !== name
|
|
84
|
+
? { opacity: cfg.legend.disabledOpacity }
|
|
85
|
+
: undefined)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
90
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
91
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
92
|
+
// Legend-disabled series and gap rows are excluded from hover candidates.
|
|
93
|
+
const hoverPoints = $derived(
|
|
94
|
+
resolvedSeries.flatMap((s, idx) => {
|
|
95
|
+
if (legendInteraction?.disabledSeries.has(s.name)) return [];
|
|
96
|
+
const sx = resolveAccessor(s.x);
|
|
97
|
+
const sy = resolveAccessor(s.y);
|
|
98
|
+
return s.data
|
|
99
|
+
.filter((d) => !isGapValue(sy(d)))
|
|
100
|
+
.map((d) => ({
|
|
101
|
+
x: sx(d) as AxisValue,
|
|
102
|
+
y: Number(sy(d)),
|
|
103
|
+
row: d,
|
|
104
|
+
label: String(sy(d)),
|
|
105
|
+
series: s.name,
|
|
106
|
+
color: paletteColor(idx, cfg.palette)
|
|
107
|
+
}));
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const hover = $derived({
|
|
112
|
+
active: tooltip != null || hasHoverMarker(markers),
|
|
113
|
+
points: hoverPoints,
|
|
114
|
+
label: (r: TData) => String(yAcc(r)),
|
|
115
|
+
strategy: resolveHoverStrategy(tooltip?.strategy, markers, 'x'),
|
|
116
|
+
sync: resolveHoverSync(tooltip?.sync, markers),
|
|
117
|
+
tooltip
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const lineMarkers = $derived(
|
|
121
|
+
buildLineMarkers({
|
|
122
|
+
groupedSeries,
|
|
123
|
+
disabledStrokeFor: (name) => disabledStrokeOverride(effectiveDisabledFor(name)),
|
|
124
|
+
disabledDotsFor: (name) => disabledDotsOverride(effectiveDisabledFor(name))
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const gapMarkers = $derived(
|
|
129
|
+
buildGapMarkers<TData>({
|
|
130
|
+
seriesList: resolvedSeries,
|
|
131
|
+
gaps,
|
|
132
|
+
colorFor: (seriesIndex) => paletteColor(seriesIndex, cfg.palette)
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const scopedGroups = $derived(
|
|
137
|
+
buildScopedMarkerGroups<TData, Series<TData>, LineSegmentStyle>({
|
|
138
|
+
groupedSeries,
|
|
139
|
+
segments,
|
|
140
|
+
colorFor: (_series, seriesIndex) => paletteColor(seriesIndex, cfg.palette),
|
|
141
|
+
segmentColorFor: (style, defaultColor) => style?.stroke?.stroke ?? defaultColor
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const lastPoints = $derived(
|
|
146
|
+
resolvedSeries
|
|
147
|
+
.map((s, idx) => {
|
|
148
|
+
const sy = resolveAccessor(s.y);
|
|
149
|
+
const lastRow = [...s.data].reverse().find((d) => !isGapValue(sy(d)));
|
|
150
|
+
if (lastRow === undefined) return undefined;
|
|
151
|
+
const group = scopedGroups.find((g) => g.name === s.name);
|
|
152
|
+
const matchedSeg = group?.segments.find((seg) => seg.match(lastRow));
|
|
153
|
+
const color =
|
|
154
|
+
matchedSeg?.color ??
|
|
155
|
+
group?.segments[0]?.color ??
|
|
156
|
+
group?.color ??
|
|
157
|
+
paletteColor(idx, cfg.palette);
|
|
158
|
+
return { series: s, lastRow, color };
|
|
159
|
+
})
|
|
160
|
+
.filter((p): p is { series: Series<TData>; lastRow: TData; color: string } => p !== undefined)
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Grows the plot's margin to fit value labels that overflow the content
|
|
164
|
+
// box. Resets on `resolvedSeries` (e.g. a timeline scrubber revealing a
|
|
165
|
+
// different slice of rows) so overflow measured against an earlier frame's
|
|
166
|
+
// labels doesn't linger and inflate the margin once that frame is gone —
|
|
167
|
+
// see `createLabelMarginTracker`'s own doc comment.
|
|
168
|
+
const labelMargin = createLabelMarginTracker(
|
|
169
|
+
() => margins,
|
|
170
|
+
() => resolvedSeries
|
|
171
|
+
);
|
|
155
172
|
</script>
|
|
156
173
|
|
|
157
174
|
<BasePlotLayout
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
{width}
|
|
176
|
+
{height}
|
|
177
|
+
{styles}
|
|
178
|
+
data={flat}
|
|
179
|
+
getX={xAcc}
|
|
180
|
+
getY={(d) => Number(yAcc(d))}
|
|
181
|
+
{hover}
|
|
182
|
+
{scales}
|
|
183
|
+
{...labelMargin.plotProps}
|
|
184
|
+
xTickRotate={scales?.x?.tickRotate}
|
|
185
|
+
markers={[...gapMarkers, ...lineMarkers, ...markers]}
|
|
186
|
+
seriesData={resolvedSeries}
|
|
187
|
+
{scopedGroups}
|
|
171
188
|
>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
{#snippet children({ numericMargins })}
|
|
190
|
+
{#if showVals}
|
|
191
|
+
<ValueLabels
|
|
192
|
+
{lastPoints}
|
|
193
|
+
values={styles.values}
|
|
194
|
+
{width}
|
|
195
|
+
{height}
|
|
196
|
+
{numericMargins}
|
|
197
|
+
onOverflow={labelMargin.report}
|
|
198
|
+
disabledFor={effectiveDisabledFor}
|
|
199
|
+
onHoverSeries={hoverIsolate
|
|
200
|
+
? (name) => {
|
|
201
|
+
hoverIsolatedSeries = name;
|
|
202
|
+
}
|
|
203
|
+
: undefined}
|
|
204
|
+
onHoverEnd={hoverIsolate
|
|
205
|
+
? () => {
|
|
206
|
+
hoverIsolatedSeries = null;
|
|
207
|
+
}
|
|
208
|
+
: undefined}
|
|
209
|
+
/>
|
|
210
|
+
{/if}
|
|
211
|
+
{/snippet}
|
|
187
212
|
</BasePlotLayout>
|