@fundar/data-chart-telling 0.0.1

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.
Files changed (106) hide show
  1. package/README.md +250 -0
  2. package/dist/charts/Chart.svelte +125 -0
  3. package/dist/charts/Chart.svelte.d.ts +42 -0
  4. package/dist/charts/bar/Chart.svelte +104 -0
  5. package/dist/charts/bar/Chart.svelte.d.ts +27 -0
  6. package/dist/charts/container/ChartContainer.svelte +127 -0
  7. package/dist/charts/container/ChartContainer.svelte.d.ts +14 -0
  8. package/dist/charts/heatmap/Chart.svelte +107 -0
  9. package/dist/charts/heatmap/Chart.svelte.d.ts +30 -0
  10. package/dist/charts/line/Chart.svelte +106 -0
  11. package/dist/charts/line/Chart.svelte.d.ts +27 -0
  12. package/dist/charts/pyramid/Chart.svelte +127 -0
  13. package/dist/charts/pyramid/Chart.svelte.d.ts +31 -0
  14. package/dist/configuration/config.svelte.d.ts +12 -0
  15. package/dist/configuration/config.svelte.js +159 -0
  16. package/dist/configuration/themes/compact.d.ts +2 -0
  17. package/dist/configuration/themes/compact.js +9 -0
  18. package/dist/configuration/themes/dark.d.ts +2 -0
  19. package/dist/configuration/themes/dark.js +17 -0
  20. package/dist/configuration/themes/default.d.ts +3 -0
  21. package/dist/configuration/themes/default.js +2 -0
  22. package/dist/configuration/themes/editorial.d.ts +2 -0
  23. package/dist/configuration/themes/editorial.js +14 -0
  24. package/dist/configuration/themes/index.d.ts +481 -0
  25. package/dist/configuration/themes/index.js +16 -0
  26. package/dist/index.d.ts +32 -0
  27. package/dist/index.js +27 -0
  28. package/dist/layout/facet/FacetLayout.svelte +97 -0
  29. package/dist/layout/facet/FacetLayout.svelte.d.ts +35 -0
  30. package/dist/layout/legend/ContinuousSection.svelte +95 -0
  31. package/dist/layout/legend/ContinuousSection.svelte.d.ts +7 -0
  32. package/dist/layout/legend/DiscreteSection.svelte +87 -0
  33. package/dist/layout/legend/DiscreteSection.svelte.d.ts +7 -0
  34. package/dist/layout/legend/LegendLayout.svelte +47 -0
  35. package/dist/layout/legend/LegendLayout.svelte.d.ts +7 -0
  36. package/dist/layout/plot/PlotLayout.svelte +236 -0
  37. package/dist/layout/plot/PlotLayout.svelte.d.ts +70 -0
  38. package/dist/layout/timeline/Timeline.svelte +86 -0
  39. package/dist/layout/timeline/Timeline.svelte.d.ts +11 -0
  40. package/dist/layout/timeline/TimelineLayout.svelte +181 -0
  41. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +38 -0
  42. package/dist/layout/tooltip/Tooltip.svelte +55 -0
  43. package/dist/layout/tooltip/Tooltip.svelte.d.ts +12 -0
  44. package/dist/layout/tooltip/controller.svelte.d.ts +52 -0
  45. package/dist/layout/tooltip/controller.svelte.js +128 -0
  46. package/dist/layout/tooltip/hover.svelte.d.ts +7 -0
  47. package/dist/layout/tooltip/hover.svelte.js +28 -0
  48. package/dist/layout/tooltip/utils.d.ts +41 -0
  49. package/dist/layout/tooltip/utils.js +78 -0
  50. package/dist/plots/bar/Plot.svelte +176 -0
  51. package/dist/plots/bar/Plot.svelte.d.ts +26 -0
  52. package/dist/plots/heatmap/Plot.svelte +175 -0
  53. package/dist/plots/heatmap/Plot.svelte.d.ts +26 -0
  54. package/dist/plots/line/Plot.svelte +232 -0
  55. package/dist/plots/line/Plot.svelte.d.ts +26 -0
  56. package/dist/plots/markers/DeltaMarker.svelte +104 -0
  57. package/dist/plots/markers/DeltaMarker.svelte.d.ts +30 -0
  58. package/dist/plots/markers/DotMarker.svelte +41 -0
  59. package/dist/plots/markers/DotMarker.svelte.d.ts +31 -0
  60. package/dist/plots/markers/HoverMarker.svelte +78 -0
  61. package/dist/plots/markers/HoverMarker.svelte.d.ts +22 -0
  62. package/dist/plots/markers/PeakMarker.svelte +80 -0
  63. package/dist/plots/markers/PeakMarker.svelte.d.ts +13 -0
  64. package/dist/plots/markers/TextMarker.svelte +55 -0
  65. package/dist/plots/markers/TextMarker.svelte.d.ts +32 -0
  66. package/dist/plots/pyramid/Plot.svelte +282 -0
  67. package/dist/plots/pyramid/Plot.svelte.d.ts +26 -0
  68. package/dist/plots/utils/accessors.d.ts +6 -0
  69. package/dist/plots/utils/accessors.js +10 -0
  70. package/dist/plots/utils/delta.d.ts +12 -0
  71. package/dist/plots/utils/delta.js +69 -0
  72. package/dist/plots/utils/segments.d.ts +33 -0
  73. package/dist/plots/utils/segments.js +153 -0
  74. package/dist/stores/device.d.ts +7 -0
  75. package/dist/stores/device.js +39 -0
  76. package/dist/types/charts/common.d.ts +64 -0
  77. package/dist/types/charts/common.js +1 -0
  78. package/dist/types/charts/interpolate.d.ts +2 -0
  79. package/dist/types/charts/interpolate.js +1 -0
  80. package/dist/types/charts/legend.d.ts +29 -0
  81. package/dist/types/charts/legend.js +1 -0
  82. package/dist/types/charts/props.d.ts +35 -0
  83. package/dist/types/charts/props.js +1 -0
  84. package/dist/types/configuration/styling.d.ts +97 -0
  85. package/dist/types/configuration/styling.js +1 -0
  86. package/dist/types/layout/tooltip.d.ts +88 -0
  87. package/dist/types/layout/tooltip.js +1 -0
  88. package/dist/types/plots/common.d.ts +100 -0
  89. package/dist/types/plots/common.js +1 -0
  90. package/dist/types/plots/delta.d.ts +19 -0
  91. package/dist/types/plots/delta.js +1 -0
  92. package/dist/types/plots/markers.d.ts +56 -0
  93. package/dist/types/plots/markers.js +1 -0
  94. package/dist/types/plots/props.d.ts +23 -0
  95. package/dist/types/plots/props.js +1 -0
  96. package/dist/types/plots/styles.d.ts +176 -0
  97. package/dist/types/plots/styles.js +1 -0
  98. package/dist/types/viewport.d.ts +4 -0
  99. package/dist/types/viewport.js +1 -0
  100. package/dist/utils/color.d.ts +5 -0
  101. package/dist/utils/color.js +42 -0
  102. package/dist/utils/grouping.d.ts +14 -0
  103. package/dist/utils/grouping.js +36 -0
  104. package/dist/utils/interpolate.d.ts +30 -0
  105. package/dist/utils/interpolate.js +71 -0
  106. package/package.json +76 -0
@@ -0,0 +1,282 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ import { SvelteMap } from 'svelte/reactivity';
3
+ import { BarX, Text } from 'svelteplot';
4
+ import { resolveAccessor } from '../utils/accessors';
5
+ import { getConfiguration } from '../../configuration/config.svelte';
6
+ import { paletteColor } from '../../utils/color';
7
+ import { buildSeries } from '../../utils/grouping';
8
+ import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX, getDataForSegments } from '../utils/segments';
9
+ import PlotLayout from '../../layout/plot/PlotLayout.svelte';
10
+ import HoverMarker from '../markers/HoverMarker.svelte';
11
+ import type { PlotProps } from '../../types/plots/props';
12
+ import type { Series, AxisValue } from '../../types/plots/common';
13
+ import type { BarSegmentStyle, ValueAnchor } from '../../types/plots/styles';
14
+ import type { Marker } from '../../types/plots/markers';
15
+
16
+ /**
17
+ * A population-pyramid-style diverging bar chart: each series is mirrored
18
+ * left or right of a shared zero baseline along a categorical band axis.
19
+ * Mirrors the bar/line plot contract — series in, marks out — so the
20
+ * chart-level container/timeline/facet/legend wrap it unchanged. Like
21
+ * `LinePlot`/`BarPlot`, accepts either pre-built `series` or flat `data` +
22
+ * `x`/`y`/`z` accessors.
23
+ *
24
+ * Side assignment is automatic: series names are sorted alphabetically and
25
+ * the first goes left, the second right. Pass `scales.z.reverse: true` to
26
+ * flip the order.
27
+ */
28
+
29
+ type PyramidSide = 'left' | 'right';
30
+ type InternalSeries = Series<TData> & { side: PyramidSide };
31
+
32
+ let {
33
+ width,
34
+ height,
35
+ series,
36
+ data,
37
+ x: magnitude,
38
+ y: category,
39
+ z: sides,
40
+ styles = {},
41
+ scales = {},
42
+ margins,
43
+ segments = {},
44
+ markers = [],
45
+ tooltip = undefined,
46
+ }: PlotProps<TData, BarSegmentStyle> = $props();
47
+
48
+ $effect(() => { validateSegments(segments); });
49
+
50
+ const cfg = $derived(getConfiguration());
51
+ const leftColor = $derived(styles.colors?.left ?? paletteColor(0, cfg.palette));
52
+ const rightColor = $derived(styles.colors?.right ?? paletteColor(1, cfg.palette));
53
+ const showVals = $derived(styles.values?.show ?? false);
54
+ const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
55
+ const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'outside');
56
+
57
+ // Sort series names alphabetically; even index → left, odd → right.
58
+ // scales.z.reverse flips the assignment so the second name goes left.
59
+ const zReversed = $derived(scales.z?.reverse ?? false);
60
+ const resolvedSeries = $derived.by((): InternalSeries[] => {
61
+ const base: Series<TData>[] = series ?? buildSeries(data!, category!, magnitude!, sides);
62
+ const sortedNames = [...base.map((s) => s.name)].sort((a, b) => a.localeCompare(b));
63
+ return base.map((s) => {
64
+ const idx = sortedNames.indexOf(s.name);
65
+ const side: PyramidSide = (idx % 2 === 0) !== zReversed ? 'left' : 'right';
66
+ return { ...s, side };
67
+ });
68
+ });
69
+
70
+ const groupedSeries = $derived(
71
+ buildGroupedSeries<InternalSeries, BarSegmentStyle>(resolvedSeries, segments),
72
+ );
73
+
74
+ function signedValue(s: InternalSeries, d: TData): number {
75
+ const v = Math.abs(Number(resolveAccessor(s.y)(d)));
76
+ return s.side === 'left' ? -v : v;
77
+ }
78
+
79
+ // svelteplot's `Pointer` rebuilds its quadtree from shallow-cloned rows
80
+ // (`{ ...d }`), never the original references, so a row's owning series
81
+ // can't be recovered by identity once hover is active. Pre-compute
82
+ // everything `getX`/`getY`/`labelFor` need as plain enumerable properties —
83
+ // they survive the clone automatically.
84
+ type TaggedRow = TData & {
85
+ __side: string;
86
+ __x: number;
87
+ __y: AxisValue;
88
+ __label: string;
89
+ __series: string;
90
+ };
91
+
92
+ const flat = $derived.by((): TaggedRow[] =>
93
+ resolvedSeries.flatMap((s) => {
94
+ const getCategory = resolveAccessor(s.x);
95
+ const getMagnitude = resolveAccessor(s.y);
96
+ return s.data.map((d) => {
97
+ const v = Math.abs(Number(getMagnitude(d)));
98
+ return {
99
+ ...d,
100
+ __side: s.side,
101
+ __x: s.side === 'left' ? -v : v,
102
+ __y: getCategory(d) as AxisValue,
103
+ __label: fmtVal(v, s.name),
104
+ __series: s.name,
105
+ } as TaggedRow;
106
+ });
107
+ }),
108
+ );
109
+
110
+ const getX = (d: TaggedRow) => d.__x;
111
+ const getY = (d: TaggedRow) => d.__y;
112
+ const labelFor = (d: TaggedRow) => d.__label;
113
+
114
+ const hoverPoints = $derived(
115
+ flat.map((d) => ({
116
+ x: d.__x,
117
+ y: d.__y,
118
+ row: d,
119
+ label: d.__label,
120
+ series: d.__series,
121
+ })),
122
+ );
123
+
124
+ const yDomain = $derived([
125
+ ...new Set(
126
+ resolvedSeries.flatMap((s) => {
127
+ const getCategory = resolveAccessor(s.x);
128
+ return s.data.map((d) => getCategory(d) as AxisValue);
129
+ }),
130
+ ),
131
+ ]);
132
+
133
+ const maxMagnitude = $derived.by(() => {
134
+ const vals = resolvedSeries.flatMap((s) => {
135
+ const getMagnitude = resolveAccessor(s.y);
136
+ return s.data.map((d) => Math.abs(Number(getMagnitude(d))));
137
+ });
138
+ return vals.length ? Math.max(...vals) : 1;
139
+ });
140
+
141
+ const seriesMarkers = $derived.by(() => {
142
+ const map = new SvelteMap<string, Marker[]>();
143
+ for (const marker of markers) {
144
+ if ((marker.type === 'component' || marker.type === 'hover') && marker.series) {
145
+ if (!map.has(marker.series)) map.set(marker.series, []);
146
+ map.get(marker.series)!.push(marker);
147
+ }
148
+ }
149
+ return map;
150
+ });
151
+ </script>
152
+
153
+ <PlotLayout
154
+ {width}
155
+ {height}
156
+ x={{
157
+ domain: [-maxMagnitude, maxMagnitude],
158
+ tickFormat: (v: unknown) => fmtVal(Math.abs(Number(v)), ''),
159
+ ...(scales.x ?? {}),
160
+ }}
161
+ y={{ type: 'band', padding: 0.2, domain: yDomain, ...(scales.y ?? {}) }}
162
+ data={flat}
163
+ {getX}
164
+ {getY}
165
+ matchY={getY}
166
+ {hoverPoints}
167
+ label={labelFor}
168
+ {tooltip}
169
+ {markers}
170
+ seriesData={resolvedSeries}
171
+ {scales}
172
+ {margins}
173
+ defaultStrategy="y"
174
+ >
175
+ {#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
176
+ {#each groupedSeries as group (group.series.name)}
177
+ {@const defaultColor = group.series.side === 'left' ? leftColor : rightColor}
178
+ {@const getCategory = resolveAccessor(group.series.x)}
179
+ {#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
180
+ <BarX
181
+ data={seg.data}
182
+ x1={0}
183
+ x2={(d: TData) => signedValue(group.series, d)}
184
+ y={getCategory}
185
+ fill={seg.style.fill ?? defaultColor}
186
+ fillOpacity={seg.style.fillOpacity ?? 1}
187
+ />
188
+ {/each}
189
+
190
+ {#if showVals}
191
+ {@const isLeft = group.series.side === 'left'}
192
+ {@const xFn = valAnchor === 'middle'
193
+ ? (d: TData) => signedValue(group.series, d) / 2
194
+ : valAnchor === 'end'
195
+ ? () => 0
196
+ : (d: TData) => signedValue(group.series, d)}
197
+ {@const defaultDx = valAnchor === 'middle' ? 0
198
+ : valAnchor === 'end' ? (isLeft ? -4 : 4)
199
+ : (isLeft ? -6 : 6)}
200
+ {@const defaultTextAnchor = valAnchor === 'middle'
201
+ ? ('middle' as const)
202
+ : isLeft
203
+ ? ('end' as const)
204
+ : ('start' as const)}
205
+ <Text
206
+ data={group.series.data}
207
+ x={xFn}
208
+ y={getCategory}
209
+ text={(d: TData) => fmtVal(Math.abs(Number(resolveAccessor(group.series.y)(d))), group.series.name)}
210
+ dx={styles.values?.dx ?? defaultDx}
211
+ dy={styles.values?.dy ?? 0}
212
+ textAnchor={styles.values?.textAnchor ?? defaultTextAnchor}
213
+ lineAnchor={styles.values?.lineAnchor ?? 'middle'}
214
+ lineHeight={styles.values?.lineHeight}
215
+ rotate={styles.values?.rotate}
216
+ class={styles.values?.class}
217
+ textClass={styles.values?.textClass}
218
+ fill={cfg.axis.color}
219
+ fontSize={12}
220
+ />
221
+ {/if}
222
+
223
+ {#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-${marker.type}-${i}`)}
224
+ {#if marker.type === 'component'}
225
+ {#if marker.scope === 'segment'}
226
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
227
+ {@const segmentDataArrays = getDataForSegments(group.series.data, getCategory, segs)}
228
+ {#each segmentDataArrays as segmentData, segIdx (`${group.series.name}-marker-segment-${segIdx}`)}
229
+ {#if segmentData.length > 0}
230
+ {@const segmentColor = segs[segIdx]?.style?.fill ?? defaultColor}
231
+ <marker.component
232
+ {...marker.props}
233
+ series={group.series.name}
234
+ data={segmentData}
235
+ seriesColor={segmentColor}
236
+ />
237
+ {/if}
238
+ {/each}
239
+ {:else}
240
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
241
+ {@const seriesColor = segs[0]?.style?.fill ?? defaultColor}
242
+ <marker.component
243
+ {...marker.props}
244
+ series={group.series.name}
245
+ data={group.series.data}
246
+ seriesColor={seriesColor}
247
+ />
248
+ {/if}
249
+ {:else if marker.type === 'hover'}
250
+ {@const seriesPoints = matchedPoints.filter((p) => p.series === group.series.name)}
251
+ {#if marker.scope === 'segment'}
252
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
253
+ {#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
254
+ {@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.x, seg))}
255
+ {#if segPoints.length > 0}
256
+ <HoverMarker
257
+ data={segPoints}
258
+ ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
259
+ ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
260
+ showLabels={marker.showLabels}
261
+ ruleStyle={marker.ruleStyle}
262
+ dotStyle={marker.dotStyle}
263
+ fontStyle={marker.fontStyle}
264
+ />
265
+ {/if}
266
+ {/each}
267
+ {:else}
268
+ <HoverMarker
269
+ data={seriesPoints}
270
+ ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
271
+ ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
272
+ showLabels={marker.showLabels}
273
+ ruleStyle={marker.ruleStyle}
274
+ dotStyle={marker.dotStyle}
275
+ fontStyle={marker.fontStyle}
276
+ />
277
+ {/if}
278
+ {/if}
279
+ {/each}
280
+ {/each}
281
+ {/snippet}
282
+ </PlotLayout>
@@ -0,0 +1,26 @@
1
+ import type { PlotProps } from '../../types/plots/props';
2
+ import type { BarSegmentStyle } from '../../types/plots/styles';
3
+ declare function $$render<TData extends Record<string, unknown>>(): {
4
+ props: PlotProps<TData, BarSegmentStyle>;
5
+ exports: {};
6
+ bindings: "";
7
+ slots: {};
8
+ events: {};
9
+ };
10
+ declare class __sveltets_Render<TData extends Record<string, unknown>> {
11
+ props(): ReturnType<typeof $$render<TData>>['props'];
12
+ events(): ReturnType<typeof $$render<TData>>['events'];
13
+ slots(): ReturnType<typeof $$render<TData>>['slots'];
14
+ bindings(): "";
15
+ exports(): {};
16
+ }
17
+ interface $$IsomorphicComponent {
18
+ 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']>> & {
19
+ $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
20
+ } & ReturnType<__sveltets_Render<TData>['exports']>;
21
+ <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
22
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
23
+ }
24
+ declare const Plot: $$IsomorphicComponent;
25
+ type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
26
+ export default Plot;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Converts a field key or a function into a data accessor function.
3
+ * Svelteplot marks expect `(d: T) => value` — this bridges the
4
+ * shorthand key notation used in series definitions.
5
+ */
6
+ export declare function resolveAccessor<T extends Record<string, unknown>>(field: keyof T | ((d: T) => any)): (d: T) => any;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Converts a field key or a function into a data accessor function.
3
+ * Svelteplot marks expect `(d: T) => value` — this bridges the
4
+ * shorthand key notation used in series definitions.
5
+ */
6
+ export function resolveAccessor(
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ field) {
9
+ return typeof field === 'function' ? field : (d) => d[field];
10
+ }
@@ -0,0 +1,12 @@
1
+ import type { Series } from '../../types/plots/common';
2
+ import type { DeltaConfig } from '../../types/plots/delta';
3
+ export type ResolvedDelta = {
4
+ cfg: DeltaConfig;
5
+ at: number;
6
+ from: number;
7
+ to: number;
8
+ diff: number;
9
+ mid: number;
10
+ };
11
+ export declare function resolveXDeltas<T extends Record<string, unknown>>(allSeries: Series<T>[], configs: DeltaConfig[]): ResolvedDelta[];
12
+ export declare function resolveYDeltas<T extends Record<string, unknown>>(allSeries: Series<T>[], configs: DeltaConfig[]): ResolvedDelta[];
@@ -0,0 +1,69 @@
1
+ import { resolveAccessor } from './accessors';
2
+ /**
3
+ * Sorts `data` by `sortBy`, then finds the first adjacent pair where `scanBy`
4
+ * brackets `target` and linearly interpolates `measureBy` at that crossing.
5
+ *
6
+ * X delta usage: sortBy=getX, scanBy=getX, measureBy=getY → Y at given X
7
+ * Y delta usage: sortBy=getX, scanBy=getY, measureBy=getX → X at given Y (first crossing)
8
+ */
9
+ function interpolate(data, sortBy, scanBy, measureBy, target) {
10
+ const sorted = [...data].sort((a, b) => sortBy(a) - sortBy(b));
11
+ for (let i = 0; i < sorted.length; i++) {
12
+ if (scanBy(sorted[i]) === target)
13
+ return measureBy(sorted[i]);
14
+ }
15
+ for (let i = 0; i < sorted.length - 1; i++) {
16
+ const s0 = scanBy(sorted[i]);
17
+ const s1 = scanBy(sorted[i + 1]);
18
+ if ((s0 <= target && target <= s1) || (s1 <= target && target <= s0)) {
19
+ const t = (target - s0) / (s1 - s0);
20
+ return measureBy(sorted[i]) + t * (measureBy(sorted[i + 1]) - measureBy(sorted[i]));
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ function resolveXDeltaEndpoint(allSeries, target, atX) {
26
+ if (target.type === 'axis')
27
+ return target.value ?? 0;
28
+ const s = allSeries.find(s => s.name === target.series);
29
+ if (!s)
30
+ return null;
31
+ const getX = (d) => Number(resolveAccessor(s.x)(d));
32
+ const getY = (d) => Number(resolveAccessor(s.y)(d));
33
+ return interpolate(s.data, getX, getX, getY, atX);
34
+ }
35
+ function resolveYDeltaEndpoint(allSeries, target, atY) {
36
+ if (target.type === 'axis')
37
+ return target.value ?? 0;
38
+ const s = allSeries.find(s => s.name === target.series);
39
+ if (!s)
40
+ return null;
41
+ const getX = (d) => Number(resolveAccessor(s.x)(d));
42
+ const getY = (d) => Number(resolveAccessor(s.y)(d));
43
+ return interpolate(s.data, getX, getY, getX, atY);
44
+ }
45
+ function buildDelta(cfg, from, to) {
46
+ const diff = Math.abs(from - to);
47
+ const mid = (from + to) / 2;
48
+ return { cfg, at: Number(cfg.at), from, to, diff, mid };
49
+ }
50
+ export function resolveXDeltas(allSeries, configs) {
51
+ return configs.flatMap(cfg => {
52
+ const atX = Number(cfg.at);
53
+ const from = resolveXDeltaEndpoint(allSeries, cfg.from, atX);
54
+ const to = cfg.to ? resolveXDeltaEndpoint(allSeries, cfg.to, atX) : 0;
55
+ if (from === null || to === null)
56
+ return [];
57
+ return [buildDelta(cfg, from, to)];
58
+ });
59
+ }
60
+ export function resolveYDeltas(allSeries, configs) {
61
+ return configs.flatMap(cfg => {
62
+ const atY = Number(cfg.at);
63
+ const from = resolveYDeltaEndpoint(allSeries, cfg.from, atY);
64
+ const to = cfg.to ? resolveYDeltaEndpoint(allSeries, cfg.to, atY) : 0;
65
+ if (from === null || to === null)
66
+ return [];
67
+ return [buildDelta(cfg, from, to)];
68
+ });
69
+ }
@@ -0,0 +1,33 @@
1
+ import type { AxisValue, Series } from '../../types/plots/common';
2
+ import type { Segment, SegmentsMap, SeriesAwareSegmentsMap, LineSegmentStyle, VisualGroup } from '../../types/plots/styles';
3
+ /** Narrows a {@link SegmentsMap} to its per-series record form. */
4
+ export declare function isSeriesAware<TStyle>(segments: SegmentsMap<TStyle>): segments is SeriesAwareSegmentsMap<TStyle>;
5
+ /**
6
+ * Returns the {@link Segment} list relevant for one series. If `segments` is
7
+ * the global array form every series gets the same list; if it is the record
8
+ * form only the entry keyed by `seriesName` is used (falling back to `[]`).
9
+ */
10
+ export declare function resolveSegmentsForSeries<TStyle>(segments: SegmentsMap<TStyle>, seriesName: string): Segment<TStyle>[];
11
+ /** Returns true when `xVal` falls inside any of a segment's x ranges. */
12
+ export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TStyle>): boolean;
13
+ /**
14
+ * For each segment in `segs`, returns the subset of `data` rows whose x value
15
+ * (from `xAccessor`) falls inside any of the segment's x ranges. Rows are
16
+ * not deduplicated across segments — a row that matches multiple segments
17
+ * appears in each resulting array.
18
+ *
19
+ * Used by component and hover markers with the `'segment'` strategy.
20
+ */
21
+ export declare function getDataForSegments<TData, TStyle>(data: TData[], xAccessor: (d: TData) => AxisValue, segs: Segment<TStyle>[]): TData[][];
22
+ /**
23
+ * Warns about obvious configuration mistakes:
24
+ * - Multiple segments with no `areas` in the same series (they all match
25
+ * everything; only the last style will be visible).
26
+ */
27
+ export declare function validateSegments<TStyle>(segments: SegmentsMap<TStyle>): void;
28
+ /**
29
+ * Builds the full list of visual groups — one per series — ready to render.
30
+ * Each group's `visualSegments` is derived from the series' entry in
31
+ * `segments` (or the global array when `segments` is in array form).
32
+ */
33
+ export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments: SegmentsMap<TStyle>): VisualGroup<S, TStyle>[];
@@ -0,0 +1,153 @@
1
+ import { resolveAccessor } from './accessors';
2
+ /** Narrows a {@link SegmentsMap} to its per-series record form. */
3
+ export function isSeriesAware(segments) {
4
+ return !Array.isArray(segments);
5
+ }
6
+ /**
7
+ * Returns the {@link Segment} list relevant for one series. If `segments` is
8
+ * the global array form every series gets the same list; if it is the record
9
+ * form only the entry keyed by `seriesName` is used (falling back to `[]`).
10
+ */
11
+ export function resolveSegmentsForSeries(segments, seriesName) {
12
+ return Array.isArray(segments) ? segments : (segments[seriesName] ?? []);
13
+ }
14
+ /** Returns true when `xVal` falls inside any of a segment's x ranges. */
15
+ export function matchesSegmentX(xVal, seg) {
16
+ const areas = seg.areas;
17
+ if (!areas || areas.length === 0)
18
+ return true;
19
+ return areas.some((area) => areaMatchesX(xVal, area));
20
+ }
21
+ function areaMatchesX(xVal, area) {
22
+ if (!area.x)
23
+ return true;
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ return (area.x.from === undefined || xVal >= area.x.from) &&
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ (area.x.to === undefined || xVal <= area.x.to);
28
+ }
29
+ function areaMatchesXY(xVal, yVal, area) {
30
+ if (!areaMatchesX(xVal, area))
31
+ return false;
32
+ if (!area.y)
33
+ return true;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ return (area.y.from === undefined || yVal >= area.y.from) &&
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ (area.y.to === undefined || yVal <= area.y.to);
38
+ }
39
+ /**
40
+ * For each segment in `segs`, returns the subset of `data` rows whose x value
41
+ * (from `xAccessor`) falls inside any of the segment's x ranges. Rows are
42
+ * not deduplicated across segments — a row that matches multiple segments
43
+ * appears in each resulting array.
44
+ *
45
+ * Used by component and hover markers with the `'segment'` strategy.
46
+ */
47
+ export function getDataForSegments(data, xAccessor, segs) {
48
+ return segs.map((seg) => {
49
+ const areas = seg.areas;
50
+ if (!areas || areas.length === 0)
51
+ return data;
52
+ return data.filter((d) => areas.some((area) => areaMatchesX(xAccessor(d), area)));
53
+ });
54
+ }
55
+ /**
56
+ * Warns about obvious configuration mistakes:
57
+ * - Multiple segments with no `areas` in the same series (they all match
58
+ * everything; only the last style will be visible).
59
+ */
60
+ export function validateSegments(segments) {
61
+ const check = (label, segs) => {
62
+ const catchAlls = segs.filter((s) => !s.areas || s.areas.length === 0).length;
63
+ if (catchAlls > 1) {
64
+ console.warn(`segments ${label}: ${catchAlls} entries have no areas — ` +
65
+ `they each match every data point; only the last one's style will be visible.`);
66
+ }
67
+ };
68
+ if (Array.isArray(segments)) {
69
+ check('(global)', segments);
70
+ }
71
+ else {
72
+ for (const [name, segs] of Object.entries(segments)) {
73
+ check(`"${name}"`, segs);
74
+ }
75
+ }
76
+ }
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ function resolveSeriesSegments(series, segs) {
79
+ if (!segs.length) {
80
+ return [{ data: series.data, style: {} }];
81
+ }
82
+ const getX = resolveAccessor(series.x);
83
+ const getY = resolveAccessor(series.y);
84
+ // For each data point, find the first matching segment index (-1 = unmatched).
85
+ const matchIdx = series.data.map((d) => {
86
+ const xVal = getX(d);
87
+ const yVal = getY(d);
88
+ return segs.findIndex((seg) => {
89
+ const areas = seg.areas;
90
+ if (!areas || areas.length === 0)
91
+ return true;
92
+ return areas.some((area) => areaMatchesXY(xVal, yVal, area));
93
+ });
94
+ });
95
+ const runs = [];
96
+ for (let i = 0; i < series.data.length; i++) {
97
+ const idx = matchIdx[i];
98
+ const last = runs[runs.length - 1];
99
+ if (last && last.segIdx === idx) {
100
+ last.data.push(series.data[i]);
101
+ }
102
+ else {
103
+ runs.push({ data: [series.data[i]], segIdx: idx });
104
+ }
105
+ }
106
+ const defaultVisual = [];
107
+ const connectors = [];
108
+ const userVisual = [];
109
+ for (let r = 0; r < runs.length; r++) {
110
+ const run = runs[r];
111
+ if (run.segIdx === -1) {
112
+ // Unmatched run: insert bridge points so the line connects to adjacent
113
+ // user segments without a gap.
114
+ const bridged = [];
115
+ if (r > 0)
116
+ bridged.push(runs[r - 1].data[runs[r - 1].data.length - 1]);
117
+ bridged.push(...run.data);
118
+ if (r < runs.length - 1)
119
+ bridged.push(runs[r + 1].data[0]);
120
+ defaultVisual.push({ data: bridged, style: {} });
121
+ }
122
+ else {
123
+ userVisual.push({ data: run.data, style: segs[run.segIdx].style ?? {} });
124
+ // When the next run is also a user segment (no unmatched data between them),
125
+ // insert a line-only connector so the lines join without a gap. Using a
126
+ // separate mark (rather than appending a bridge point to the current run)
127
+ // avoids rendering an extra dot at the shared boundary point.
128
+ const nextRun = runs[r + 1];
129
+ if (nextRun && nextRun.segIdx !== -1) {
130
+ connectors.push({
131
+ data: [run.data[run.data.length - 1], nextRun.data[0]],
132
+ style: {},
133
+ });
134
+ }
135
+ }
136
+ }
137
+ if (defaultVisual.length === 0 && connectors.length === 0)
138
+ return userVisual;
139
+ // Defaults and connectors render first so user segments paint on top at
140
+ // their endpoints, ensuring the correct dot style at boundary points.
141
+ return [...defaultVisual, ...connectors, ...userVisual];
142
+ }
143
+ /**
144
+ * Builds the full list of visual groups — one per series — ready to render.
145
+ * Each group's `visualSegments` is derived from the series' entry in
146
+ * `segments` (or the global array when `segments` is in array form).
147
+ */
148
+ export function buildGroupedSeries(seriesList, segments) {
149
+ return seriesList.map((s) => ({
150
+ series: s,
151
+ visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s.name)),
152
+ }));
153
+ }
@@ -0,0 +1,7 @@
1
+ export declare const isBrowser: boolean;
2
+ export declare const isMobile: import("svelte/store").Readable<boolean>;
3
+ export declare const isTablet: import("svelte/store").Readable<boolean>;
4
+ export declare const isLaptop: import("svelte/store").Readable<boolean>;
5
+ export declare const isDesktop: import("svelte/store").Readable<boolean>;
6
+ export declare const viewportWidth: import("svelte/store").Readable<number>;
7
+ export declare const viewportHeight: import("svelte/store").Readable<number>;
@@ -0,0 +1,39 @@
1
+ import { derived, readable } from 'svelte/store';
2
+ export const isBrowser = typeof window !== 'undefined';
3
+ const viewport = readable({ width: 0, height: 0 }, (set) => {
4
+ if (!isBrowser)
5
+ return;
6
+ let raf = 0;
7
+ let lastWidth = -1;
8
+ let lastHeight = -1;
9
+ const flush = () => {
10
+ raf = 0;
11
+ const width = window.innerWidth;
12
+ const height = window.innerHeight;
13
+ if (width === lastWidth && height === lastHeight)
14
+ return;
15
+ lastWidth = width;
16
+ lastHeight = height;
17
+ set({ width, height });
18
+ };
19
+ const schedule = () => {
20
+ if (raf)
21
+ return;
22
+ raf = requestAnimationFrame(flush);
23
+ };
24
+ flush();
25
+ window.addEventListener('resize', schedule);
26
+ return () => {
27
+ window.removeEventListener('resize', schedule);
28
+ if (raf)
29
+ cancelAnimationFrame(raf);
30
+ };
31
+ });
32
+ export const isMobile = derived(viewport, ($viewport) => $viewport.width < 768);
33
+ export const isTablet = derived(viewport, ($viewport) => ($viewport.width >= 768 &&
34
+ $viewport.width < 1024));
35
+ export const isLaptop = derived(viewport, ($viewport) => ($viewport.width >= 1024 &&
36
+ $viewport.width < 1280));
37
+ export const isDesktop = derived(viewport, ($viewport) => $viewport.width >= 1280);
38
+ export const viewportWidth = derived(viewport, ($viewport) => $viewport.width);
39
+ export const viewportHeight = derived(viewport, ($viewport) => $viewport.height);