@fundar/data-chart-telling 0.0.39 → 0.0.41
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 +227 -182
- package/dist/plots/line/Plot.svelte.d.ts +2 -2
- 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 +44 -8
- package/package.json +1 -1
|
@@ -1,187 +1,232 @@
|
|
|
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, MarginConfig } 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 = $bindable(),
|
|
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
|
+
);
|
|
172
|
+
|
|
173
|
+
// Writes the resolved margin back into the bindable `margins` prop once it
|
|
174
|
+
// settles, so a caller binding it (`bind:margins`) can capture and reuse a
|
|
175
|
+
// fixed margin later. `lastWritten` is a plain variable (not `$state`) so
|
|
176
|
+
// the effect only depends on `resolvedMargins`, never on `margins` itself.
|
|
177
|
+
let lastWritten: MarginConfig | undefined;
|
|
178
|
+
$effect(() => {
|
|
179
|
+
const next = labelMargin.resolvedMargins;
|
|
180
|
+
if (!next) return;
|
|
181
|
+
const changed =
|
|
182
|
+
!lastWritten ||
|
|
183
|
+
next.top !== lastWritten.top ||
|
|
184
|
+
next.right !== lastWritten.right ||
|
|
185
|
+
next.bottom !== lastWritten.bottom ||
|
|
186
|
+
next.left !== lastWritten.left;
|
|
187
|
+
if (changed) {
|
|
188
|
+
lastWritten = { top: next.top, right: next.right, bottom: next.bottom, left: next.left };
|
|
189
|
+
margins = next;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
155
192
|
</script>
|
|
156
193
|
|
|
157
194
|
<BasePlotLayout
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
195
|
+
{width}
|
|
196
|
+
{height}
|
|
197
|
+
{styles}
|
|
198
|
+
data={flat}
|
|
199
|
+
getX={xAcc}
|
|
200
|
+
getY={(d) => Number(yAcc(d))}
|
|
201
|
+
{hover}
|
|
202
|
+
{scales}
|
|
203
|
+
{...labelMargin.plotProps}
|
|
204
|
+
xTickRotate={scales?.x?.tickRotate}
|
|
205
|
+
markers={[...gapMarkers, ...lineMarkers, ...markers]}
|
|
206
|
+
seriesData={resolvedSeries}
|
|
207
|
+
{scopedGroups}
|
|
171
208
|
>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
209
|
+
{#snippet children({ numericMargins })}
|
|
210
|
+
{#if showVals}
|
|
211
|
+
<ValueLabels
|
|
212
|
+
{lastPoints}
|
|
213
|
+
values={styles.values}
|
|
214
|
+
{width}
|
|
215
|
+
{height}
|
|
216
|
+
{numericMargins}
|
|
217
|
+
onOverflow={labelMargin.report}
|
|
218
|
+
disabledFor={effectiveDisabledFor}
|
|
219
|
+
onHoverSeries={hoverIsolate
|
|
220
|
+
? (name) => {
|
|
221
|
+
hoverIsolatedSeries = name;
|
|
222
|
+
}
|
|
223
|
+
: undefined}
|
|
224
|
+
onHoverEnd={hoverIsolate
|
|
225
|
+
? () => {
|
|
226
|
+
hoverIsolatedSeries = null;
|
|
227
|
+
}
|
|
228
|
+
: undefined}
|
|
229
|
+
/>
|
|
230
|
+
{/if}
|
|
231
|
+
{/snippet}
|
|
187
232
|
</BasePlotLayout>
|
|
@@ -7,7 +7,7 @@ import type { LineMarkersConfig } from '../../types/markers/line';
|
|
|
7
7
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
8
8
|
props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, GapsAwarePlotStylesConfig<LineSegmentStyle>, LineSegmentStyle, LineMarkersConfig, AxisBasedScalesConfig<TData>>;
|
|
9
9
|
exports: {};
|
|
10
|
-
bindings: "";
|
|
10
|
+
bindings: "margins";
|
|
11
11
|
slots: {};
|
|
12
12
|
events: {};
|
|
13
13
|
};
|
|
@@ -15,7 +15,7 @@ declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
|
15
15
|
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
16
16
|
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
17
17
|
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
18
|
-
bindings(): "";
|
|
18
|
+
bindings(): "margins";
|
|
19
19
|
exports(): {};
|
|
20
20
|
}
|
|
21
21
|
interface $$IsomorphicComponent {
|