@fundar/data-chart-telling 0.0.14 → 0.0.16

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 (148) hide show
  1. package/README.md +121 -17
  2. package/dist/charts/{Chart.svelte → BaseChart.svelte} +2 -0
  3. package/dist/charts/{Chart.svelte.d.ts → BaseChart.svelte.d.ts} +3 -3
  4. package/dist/charts/bar/Chart.svelte +5 -5
  5. package/dist/charts/bar/Chart.svelte.d.ts +1 -2
  6. package/dist/charts/heatmap/Chart.svelte +8 -7
  7. package/dist/charts/heatmap/Chart.svelte.d.ts +2 -3
  8. package/dist/charts/line/Chart.svelte +5 -5
  9. package/dist/charts/line/Chart.svelte.d.ts +1 -2
  10. package/dist/charts/pyramid/Chart.svelte +7 -6
  11. package/dist/charts/pyramid/Chart.svelte.d.ts +2 -3
  12. package/dist/configuration/config.svelte.js +2 -1
  13. package/dist/configuration/themes/index.d.ts +60 -40
  14. package/dist/index.d.ts +27 -13
  15. package/dist/index.js +12 -7
  16. package/dist/layout/facet/FacetLayout.svelte +1 -1
  17. package/dist/layout/facet/FacetLayout.svelte.d.ts +1 -1
  18. package/dist/layout/geo/GeoLayout.svelte +47 -0
  19. package/dist/layout/geo/GeoLayout.svelte.d.ts +22 -0
  20. package/dist/layout/geo/resolveProjection.d.ts +10 -0
  21. package/dist/layout/geo/resolveProjection.js +48 -0
  22. package/dist/layout/legend/ContinuousSection.svelte +158 -17
  23. package/dist/layout/legend/DiscreteSection.svelte +56 -4
  24. package/dist/layout/legend/interaction.svelte.d.ts +7 -0
  25. package/dist/layout/legend/interaction.svelte.js +24 -0
  26. package/dist/layout/plot/AxisLayout.svelte +95 -0
  27. package/dist/layout/plot/AxisLayout.svelte.d.ts +39 -0
  28. package/dist/layout/plot/BasePlotLayout.svelte +302 -0
  29. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +95 -0
  30. package/dist/layout/plot/GridLayout.svelte +30 -0
  31. package/dist/layout/plot/GridLayout.svelte.d.ts +27 -0
  32. package/dist/layout/plot/RuleLayout.svelte +111 -0
  33. package/dist/layout/plot/RuleLayout.svelte.d.ts +40 -0
  34. package/dist/layout/plot/margins.d.ts +34 -0
  35. package/dist/layout/plot/margins.js +26 -0
  36. package/dist/layout/timeline/TimelineLayout.svelte +1 -1
  37. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
  38. package/dist/layout/tooltip/Tooltip.svelte +15 -0
  39. package/dist/layout/tooltip/Tooltip.svelte.d.ts +7 -0
  40. package/dist/layout/tooltip/TooltipLayout.svelte +45 -0
  41. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +41 -0
  42. package/dist/layout/tooltip/controller.svelte.d.ts +4 -3
  43. package/dist/layout/tooltip/controller.svelte.js +5 -6
  44. package/dist/layout/tooltip/hover.svelte.d.ts +1 -1
  45. package/dist/layout/tooltip/hover.svelte.js +1 -1
  46. package/dist/layout/tooltip/utils.d.ts +2 -2
  47. package/dist/markers/ContourClipMarker.svelte +77 -0
  48. package/dist/markers/ContourClipMarker.svelte.d.ts +10 -0
  49. package/dist/{plots/markers → markers}/DeltaMarker.svelte +4 -4
  50. package/dist/{plots/markers → markers}/DeltaMarker.svelte.d.ts +3 -3
  51. package/dist/{plots/markers → markers}/DotMarker.svelte +3 -3
  52. package/dist/{plots/markers → markers}/DotMarker.svelte.d.ts +3 -3
  53. package/dist/markers/HoverMarker.svelte +136 -0
  54. package/dist/{plots/markers → markers}/HoverMarker.svelte.d.ts +4 -4
  55. package/dist/{plots/markers → markers}/PeakMarker.svelte +2 -2
  56. package/dist/{plots/markers → markers}/PeakMarker.svelte.d.ts +2 -2
  57. package/dist/{plots/markers → markers}/TextMarker.svelte +11 -4
  58. package/dist/{plots/markers → markers}/TextMarker.svelte.d.ts +3 -3
  59. package/dist/markers/inset/InsetMarker.svelte +170 -0
  60. package/dist/markers/inset/InsetMarker.svelte.d.ts +51 -0
  61. package/dist/markers/inset/insetLayout.d.ts +25 -0
  62. package/dist/markers/inset/insetLayout.js +39 -0
  63. package/dist/markers/inset/insetProjection.d.ts +8 -0
  64. package/dist/markers/inset/insetProjection.js +39 -0
  65. package/dist/plots/bar/BarSegments.svelte +69 -0
  66. package/dist/plots/bar/BarSegments.svelte.d.ts +40 -0
  67. package/dist/plots/bar/Plot.svelte +140 -74
  68. package/dist/plots/bar/Plot.svelte.d.ts +7 -3
  69. package/dist/plots/bar/ValueLabels.svelte +89 -0
  70. package/dist/plots/bar/ValueLabels.svelte.d.ts +39 -0
  71. package/dist/plots/bar/hoverPoints.d.ts +15 -0
  72. package/dist/plots/bar/hoverPoints.js +35 -0
  73. package/dist/plots/bar/layout.svelte.d.ts +37 -0
  74. package/dist/plots/bar/layout.svelte.js +132 -0
  75. package/dist/plots/geo/Plot.svelte +577 -0
  76. package/dist/plots/geo/Plot.svelte.d.ts +29 -0
  77. package/dist/plots/geo/TileLayer.svelte +77 -0
  78. package/dist/plots/geo/TileLayer.svelte.d.ts +11 -0
  79. package/dist/plots/geo/projections/argentina.d.ts +8 -0
  80. package/dist/plots/geo/projections/argentina.js +35 -0
  81. package/dist/plots/geo/projections/fit.d.ts +8 -0
  82. package/dist/plots/geo/projections/fit.js +14 -0
  83. package/dist/plots/geo/rotate.svelte.d.ts +26 -0
  84. package/dist/plots/geo/rotate.svelte.js +79 -0
  85. package/dist/plots/geo/zoom.svelte.d.ts +32 -0
  86. package/dist/plots/geo/zoom.svelte.js +50 -0
  87. package/dist/plots/heatmap/Plot.svelte +171 -58
  88. package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
  89. package/dist/plots/line/Plot.svelte +67 -30
  90. package/dist/plots/line/Plot.svelte.d.ts +7 -3
  91. package/dist/plots/pyramid/Plot.svelte +88 -35
  92. package/dist/plots/pyramid/Plot.svelte.d.ts +7 -3
  93. package/dist/plots/utils/delta.d.ts +1 -1
  94. package/dist/plots/utils/geoAccessors.d.ts +64 -0
  95. package/dist/plots/utils/geoAccessors.js +188 -0
  96. package/dist/plots/utils/geoSegments.d.ts +14 -0
  97. package/dist/plots/utils/geoSegments.js +17 -0
  98. package/dist/plots/utils/legendDisabled.d.ts +15 -0
  99. package/dist/plots/utils/legendDisabled.js +41 -0
  100. package/dist/plots/utils/segments.d.ts +14 -5
  101. package/dist/plots/utils/segments.js +25 -13
  102. package/dist/plots/utils/tiles.d.ts +89 -0
  103. package/dist/plots/utils/tiles.js +159 -0
  104. package/dist/plots/utils/topojson.d.ts +9 -0
  105. package/dist/plots/utils/topojson.js +30 -0
  106. package/dist/types/charts/common.d.ts +2 -1
  107. package/dist/types/charts/legend.d.ts +32 -1
  108. package/dist/types/charts/props.d.ts +9 -6
  109. package/dist/types/configuration/styling.d.ts +13 -5
  110. package/dist/types/layout/legend.d.ts +26 -0
  111. package/dist/types/layout/tooltip.d.ts +14 -21
  112. package/dist/types/{plots/markers.d.ts → markers/common.d.ts} +2 -18
  113. package/dist/types/markers/geo.d.ts +237 -0
  114. package/dist/types/markers/props.d.ts +11 -0
  115. package/dist/types/markers/props.js +1 -0
  116. package/dist/types/plots/axis.d.ts +55 -0
  117. package/dist/types/plots/axis.js +1 -0
  118. package/dist/types/plots/constants.d.ts +30 -0
  119. package/dist/types/plots/constants.js +1 -0
  120. package/dist/types/plots/data/common.d.ts +40 -0
  121. package/dist/types/plots/data/common.js +1 -0
  122. package/dist/types/plots/data/geo.d.ts +74 -0
  123. package/dist/types/plots/data/geo.js +1 -0
  124. package/dist/types/plots/delta.d.ts +2 -2
  125. package/dist/types/plots/props.d.ts +105 -16
  126. package/dist/types/plots/scales/geo.d.ts +44 -0
  127. package/dist/types/plots/scales/geo.js +1 -0
  128. package/dist/types/plots/segments/common.d.ts +25 -0
  129. package/dist/types/plots/segments/common.js +1 -0
  130. package/dist/types/plots/segments/config.d.ts +51 -0
  131. package/dist/types/plots/segments/config.js +1 -0
  132. package/dist/types/plots/styles/common.d.ts +37 -0
  133. package/dist/types/plots/styles/common.js +1 -0
  134. package/dist/types/plots/styles/geo.d.ts +42 -0
  135. package/dist/types/plots/styles/geo.js +1 -0
  136. package/dist/types/plots/styling.d.ts +45 -0
  137. package/dist/types/plots/styling.js +1 -0
  138. package/dist/utils/grouping.d.ts +2 -1
  139. package/dist/utils/interpolate.d.ts +2 -1
  140. package/package.json +27 -13
  141. package/dist/layout/plot/PlotLayout.svelte +0 -294
  142. package/dist/layout/plot/PlotLayout.svelte.d.ts +0 -70
  143. package/dist/plots/markers/HoverMarker.svelte +0 -98
  144. package/dist/types/plots/common.d.ts +0 -97
  145. package/dist/types/plots/styles.d.ts +0 -187
  146. /package/dist/types/{plots/common.js → layout/legend.js} +0 -0
  147. /package/dist/types/{plots/markers.js → markers/common.js} +0 -0
  148. /package/dist/types/{plots/styles.js → markers/geo.js} +0 -0
@@ -0,0 +1,302 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ import type { ComponentProps, Snippet } from 'svelte';
3
+ import { SvelteSet } from 'svelte/reactivity';
4
+ import { Plot, Pointer } from 'svelteplot';
5
+ import { getConfiguration } from '../../configuration/config.svelte';
6
+ import { AUTO_MARGIN_ESTIMATE, numericMargin } from './margins';
7
+ import { getHover, createHover } from '../tooltip/hover.svelte';
8
+ import { createHoverController } from '../tooltip/controller.svelte';
9
+ import TooltipLayout from '../tooltip/TooltipLayout.svelte';
10
+ import type { AxisValue, AxisScale } from '../../types/plots/axis';
11
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
12
+ import type { GeoProjectionConfig } from '../../types/plots/scales/geo';
13
+ import type { HoverStrategy, HoverPoint } from '../../types/layout/tooltip';
14
+ import type { MarginConfig, TooltipConfig } from '../../types/plots/props';
15
+
16
+ type SveltePlotProps = ComponentProps<typeof Plot>;
17
+
18
+ /**
19
+ * Shared chrome around every plot kind's svelteplot markup: the pointer-event
20
+ * wrapper, the hover/tooltip state machine, the `<Plot>` element itself, and
21
+ * the floating tooltip. Deliberately marker/axis-agnostic — axis-having plot
22
+ * kinds (line/bar/heatmap/pyramid) additionally compose `AxisLayout` /
23
+ * `GridLayout` / `RuleLayout` inside `children`; `GeoPlot` composes `GeoLayout`
24
+ * instead, since a map has no x/y axis pair. Every plot kind supplies its own
25
+ * `<Plot x y projection>` scale config (either directly via `x`/`y`/`projection`,
26
+ * or by leaving `x`/`y` unset so this component derives them from `scales`) and
27
+ * its own marks/markers through `children`.
28
+ */
29
+ let {
30
+ width,
31
+ height,
32
+ data,
33
+ getX,
34
+ getY,
35
+ hoverPoints,
36
+ label,
37
+ tooltip = undefined,
38
+ hoverActive,
39
+ hoverStrategy = undefined,
40
+ hoverSync = undefined,
41
+ x = undefined,
42
+ y = undefined,
43
+ scales = undefined,
44
+ projection = undefined,
45
+ margins,
46
+ marginEstimate = AUTO_MARGIN_ESTIMATE,
47
+ xTickRotate = undefined,
48
+ excludeHover = undefined,
49
+ touchAction = undefined,
50
+ containerEl = $bindable(undefined),
51
+ children,
52
+ }: {
53
+ width: number;
54
+ height: number;
55
+ /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching. */
56
+ data: TData[];
57
+ getX: (d: TData) => AxisValue;
58
+ getY: (d: TData) => AxisValue;
59
+ hoverPoints: HoverPoint<TData>[];
60
+ label: (row: TData) => string;
61
+ tooltip?: TooltipConfig<TData>;
62
+ /** Whether the pointer/hover/tooltip machinery should mount at all. */
63
+ hoverActive: boolean;
64
+ /** Resolved hover strategy for the *local* hover store (ignored once a shared store is in scope). */
65
+ hoverStrategy?: HoverStrategy;
66
+ /** Resolved sync flag for the *local* hover store (ignored once a shared store is in scope). */
67
+ hoverSync?: boolean;
68
+ /** Explicit `<Plot x>` override — bypasses `scales`-derived resolution (e.g. GeoPlot's fixed lon/lat domain). */
69
+ x?: SveltePlotProps['x'];
70
+ /** Explicit `<Plot y>` override — see `x`. */
71
+ y?: SveltePlotProps['y'];
72
+ /** Scale config `x`/`y` are derived from when not given directly. */
73
+ scales?: AxisBasedScalesConfig<TData>;
74
+ /** Forwarded to `<Plot projection>` — GeoPlot only. */
75
+ projection?: GeoProjectionConfig;
76
+ /** Partial margin override — merged on top of the theme. */
77
+ margins?: MarginConfig;
78
+ /** Numeric fallback for a margin side that resolved to `'auto'` — see `numericMargin`'s doc comment. */
79
+ marginEstimate?: Record<'top' | 'right' | 'bottom' | 'left', number>;
80
+ /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
81
+ xTickRotate?: number;
82
+ /**
83
+ * When it returns `true` for a pointer event, that event is treated as a
84
+ * leave instead of a move — lets a nested layout (e.g. GeoLayout's inset
85
+ * markers) opt specific screen regions out of this plot's own hover
86
+ * matching without this component needing to know why.
87
+ */
88
+ excludeHover?: (e: PointerEvent) => boolean;
89
+ /** Forwarded to the wrapper div's `touch-action` CSS — GeoPlot's zoom/pan only. */
90
+ touchAction?: string;
91
+ /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. GeoLayout's zoom/rotate). */
92
+ containerEl?: HTMLDivElement;
93
+ /**
94
+ * `matchedPoints` carries the source `row` alongside the display fields
95
+ * — needed by plot kinds whose semantic value isn't tied to a fixed
96
+ * visual axis (e.g. `BarPlot`'s category, which can be drawn on either
97
+ * axis depending on orientation), so a template can resolve it straight
98
+ * off `row` instead of assuming which of `.x`/`.y` it landed on.
99
+ * `impliesRuleX`/`impliesRuleY` tell the plot kind whether the active
100
+ * strategy implies a crosshair rule on each axis by default.
101
+ * `numericMargins` lets a nested layout align itself with the plot's own
102
+ * content box without recomputing margins itself.
103
+ */
104
+ children: Snippet<
105
+ [{
106
+ matchedPoints: HoverPoint<TData>[];
107
+ ruleX: AxisValue | null;
108
+ ruleY: AxisValue | null;
109
+ impliesRuleX: boolean;
110
+ impliesRuleY: boolean;
111
+ numericMargins: { top: number; right: number; bottom: number; left: number };
112
+ }]
113
+ >;
114
+ } = $props();
115
+
116
+ const cfg = $derived(getConfiguration());
117
+
118
+ /**
119
+ * Margins passed straight to `<Plot margin={…}>`. Each side defaults to
120
+ * `'auto'` (from the theme), which lets svelteplot size it to whatever it
121
+ * actually renders there — tick labels, an axis title, rotated ticks —
122
+ * rather than a fixed guess that either clips content or wastes space.
123
+ * `margins`/`cfg.margins` can still pin a side to an exact number.
124
+ */
125
+ const computedMargins = $derived.by(() => {
126
+ const base = { ...cfg.margins, ...margins };
127
+ // Auto margins already measure the real (rotated) label bounding box, so
128
+ // this manual boost is only needed when the caller pinned bottom to a
129
+ // fixed number, which bypasses that measurement.
130
+ if (!xTickRotate || typeof base.bottom !== 'number') return base;
131
+ const rad = Math.abs(xTickRotate) * Math.PI / 180;
132
+ return {
133
+ ...base,
134
+ bottom: base.bottom + Math.ceil(Math.sin(rad) * 20),
135
+ };
136
+ });
137
+
138
+ const numericMargins = $derived({
139
+ top: numericMargin('top', computedMargins.top, marginEstimate),
140
+ right: numericMargin('right', computedMargins.right, marginEstimate),
141
+ bottom: numericMargin('bottom', computedMargins.bottom, marginEstimate),
142
+ left: numericMargin('left', computedMargins.left, marginEstimate),
143
+ });
144
+
145
+ /** One representative row per unique axis value (first occurrence in `data`). */
146
+ function uniqueRows(rows: TData[], getAxisValue: (d: TData) => AxisValue): TData[] {
147
+ const seen = new SvelteSet<AxisValue>();
148
+ const out: TData[] = [];
149
+ for (const row of rows) {
150
+ const v = getAxisValue(row);
151
+ if (seen.has(v)) continue;
152
+ seen.add(v);
153
+ out.push(row);
154
+ }
155
+ return out;
156
+ }
157
+
158
+ /**
159
+ * svelteplot's own `sort` scale option only toggles a fixed ascending sort
160
+ * (lexicographic for strings) — it can't take a custom comparator. When the
161
+ * caller supplies one via `scales.x.sort`/`scales.y.sort` for a string-valued
162
+ * axis (e.g. dates formatted as text, or categories ordered by another
163
+ * field), compute the ordered domain ourselves and hand it to the scale as
164
+ * an explicit `domain`, which svelteplot does respect as-is for ordinal
165
+ * scales. The comparator receives one representative row per unique axis
166
+ * value, not just the axis value itself, so it can sort by this axis's own
167
+ * value or by any other field on the row.
168
+ */
169
+ function sortedScale(scale: AxisScale<TData> | undefined, rows: TData[], getAxisValue: (d: TData) => AxisValue): AxisScale<TData> | undefined {
170
+ const sort = scale?.sort;
171
+ if (!sort || scale?.domain) return scale;
172
+ const reps = uniqueRows(rows, getAxisValue);
173
+ if (reps.length === 0 || !reps.every((r) => typeof getAxisValue(r) === 'string')) return scale;
174
+ const domain = [...reps].sort(sort).map(getAxisValue);
175
+ return { ...scale, domain };
176
+ }
177
+
178
+ /**
179
+ * `grid`/`seriesLayout`/`seriesPadding` and a vertical `label` are rendered
180
+ * by `GridLayout`/`AxisLayout` themselves rather than passed through to
181
+ * svelteplot (which only draws a fixed-style implicit grid, and never
182
+ * rotates the axis title) — stripped here so they don't also trigger
183
+ * svelteplot's own (duplicate, unstyled) rendering of them. Also the single
184
+ * place a plot kind's fully-resolved `scales.x`/`scales.y` (defaults
185
+ * already merged in by the plot itself) turns into what svelteplot's own,
186
+ * narrower scale option type actually accepts.
187
+ */
188
+ function finalizeScale(scale: AxisScale<TData> | undefined, rows: TData[], getAxisValue: (d: TData) => AxisValue): AxisScale<TData> | undefined {
189
+ const sorted = sortedScale(scale, rows, getAxisValue);
190
+ if (!sorted) return sorted;
191
+ const out = { ...sorted };
192
+ delete out.grid;
193
+ delete out.seriesLayout;
194
+ delete out.seriesPadding;
195
+ if (scale?.labelOrientation === 'vertical') delete out.label;
196
+ return out;
197
+ }
198
+
199
+ // Cast to svelteplot's own (narrower, axis-specific) scale option type:
200
+ // finalizeScale has already stripped the fields that don't exist there
201
+ // (grid, the vertical label config, BarPlot's series-layout knobs).
202
+ const resolvedX = $derived(x !== undefined ? x : (finalizeScale(scales?.x, data, getX) as SveltePlotProps['x']));
203
+ const resolvedY = $derived(y !== undefined ? y : (finalizeScale(scales?.y, data, getY) as SveltePlotProps['y']));
204
+
205
+ // ── Hover / tooltip ───────────────────────────────────────────────────────
206
+ const sharedHover = getHover();
207
+ const localHover = createHover();
208
+ const hover = $derived(sharedHover ?? localHover);
209
+
210
+ /**
211
+ * Which axes svelteplot's `<Pointer>` should search along — derived from
212
+ * the *active* strategy rather than taken as separate props. Passing
213
+ * `undefined` for an axis collapses every point to the same coordinate on
214
+ * that axis in the search quadtree, making the nearest-neighbor match
215
+ * ignore it entirely: `'x'` (line/bar) searches x only, `'y'` (pyramid —
216
+ * its category sits on y, and a bar's own magnitude on x shouldn't pull in
217
+ * a *different* row just because that row's bar tip is nearer in x)
218
+ * searches y only, and `'punctual'` (heatmap/geo's 2D grid) needs both.
219
+ * Reactive to `hover.strategy`, so a caller overriding `tooltip.strategy`/a
220
+ * hover marker's `strategy` away from the plot kind's default gets
221
+ * correctly-dimensioned matching too.
222
+ */
223
+ const pointerMatchX = $derived(hover.strategy === 'y' ? undefined : getX);
224
+ const pointerMatchY = $derived(hover.strategy === 'x' ? undefined : getY);
225
+
226
+ $effect(() => {
227
+ if (sharedHover) return;
228
+ localHover.strategy = hoverStrategy ?? 'x';
229
+ localHover.sync = hoverSync ?? false;
230
+ });
231
+
232
+ const ctrl = createHoverController<TData>({
233
+ hover: () => hover,
234
+ facetId: () => tooltip?.facetId ?? '',
235
+ points: () => hoverPoints,
236
+ getX: () => getX,
237
+ getY: () => getY,
238
+ label: () => label,
239
+ seriesFilter: () => tooltip?.series,
240
+ width: () => width,
241
+ height: () => height,
242
+ });
243
+
244
+ function onMove(e: PointerEvent) {
245
+ if (excludeHover?.(e)) {
246
+ ctrl.onLeave();
247
+ return;
248
+ }
249
+ ctrl.onMove(e);
250
+ }
251
+ </script>
252
+
253
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
254
+ <div
255
+ class="dct-plot"
256
+ bind:this={containerEl}
257
+ style:color={cfg.axis.color}
258
+ style:touch-action={touchAction}
259
+ onpointermove={hoverActive ? onMove : undefined}
260
+ onpointerleave={hoverActive ? ctrl.onLeave : undefined}
261
+ >
262
+ <Plot
263
+ {width}
264
+ {height}
265
+ margin={computedMargins}
266
+ x={resolvedX}
267
+ y={resolvedY}
268
+ projection={projection as SveltePlotProps['projection']}
269
+ >
270
+ {@render children({
271
+ matchedPoints: ctrl.points,
272
+ ruleX: ctrl.ruleX,
273
+ ruleY: ctrl.ruleY,
274
+ impliesRuleX: ctrl.impliesRuleX,
275
+ impliesRuleY: ctrl.impliesRuleY,
276
+ numericMargins,
277
+ })}
278
+
279
+ {#if hoverActive}
280
+ <Pointer {data} x={pointerMatchX} y={pointerMatchY} maxDistance={400} onupdate={ctrl.onPointer} />
281
+ {/if}
282
+ </Plot>
283
+
284
+ {#if tooltip}
285
+ <TooltipLayout
286
+ visible={ctrl.showTooltip && ctrl.tooltipPosition != null}
287
+ x={ctrl.tooltipPosition?.x ?? 0}
288
+ y={ctrl.tooltipPosition?.y ?? 0}
289
+ bounds={{ width, height }}
290
+ rows={ctrl.matchedRows.map((p) => p.row)}
291
+ format={tooltip.format}
292
+ content={tooltip.content}
293
+ />
294
+ {/if}
295
+ </div>
296
+
297
+ <style>
298
+ .dct-plot {
299
+ position: relative;
300
+ line-height: 0;
301
+ }
302
+ </style>
@@ -0,0 +1,95 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { AxisValue } from '../../types/plots/axis';
3
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
+ import type { GeoProjectionConfig } from '../../types/plots/scales/geo';
5
+ import type { HoverStrategy, HoverPoint } from '../../types/layout/tooltip';
6
+ import type { MarginConfig, TooltipConfig } from '../../types/plots/props';
7
+ declare function $$render<TData extends Record<string, unknown>>(): {
8
+ props: {
9
+ width: number;
10
+ height: number;
11
+ /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching. */
12
+ data: TData[];
13
+ getX: (d: TData) => AxisValue;
14
+ getY: (d: TData) => AxisValue;
15
+ hoverPoints: HoverPoint<TData>[];
16
+ label: (row: TData) => string;
17
+ tooltip?: TooltipConfig<TData>;
18
+ /** Whether the pointer/hover/tooltip machinery should mount at all. */
19
+ hoverActive: boolean;
20
+ /** Resolved hover strategy for the *local* hover store (ignored once a shared store is in scope). */
21
+ hoverStrategy?: HoverStrategy;
22
+ /** Resolved sync flag for the *local* hover store (ignored once a shared store is in scope). */
23
+ hoverSync?: boolean;
24
+ /** Explicit `<Plot x>` override — bypasses `scales`-derived resolution (e.g. GeoPlot's fixed lon/lat domain). */
25
+ x?: false | Partial<import("svelteplot/types/scale.js").XScaleOptions> | import("svelteplot/types/data.js").RawValue[] | undefined;
26
+ /** Explicit `<Plot y>` override — see `x`. */
27
+ y?: false | import("svelteplot/types/data.js").RawValue[] | Partial<import("svelteplot/types/scale.js").YScaleOptions> | undefined;
28
+ /** Scale config `x`/`y` are derived from when not given directly. */
29
+ scales?: AxisBasedScalesConfig<TData>;
30
+ /** Forwarded to `<Plot projection>` — GeoPlot only. */
31
+ projection?: GeoProjectionConfig;
32
+ /** Partial margin override — merged on top of the theme. */
33
+ margins?: MarginConfig;
34
+ /** Numeric fallback for a margin side that resolved to `'auto'` — see `numericMargin`'s doc comment. */
35
+ marginEstimate?: Record<"top" | "right" | "bottom" | "left", number>;
36
+ /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
37
+ xTickRotate?: number;
38
+ /**
39
+ * When it returns `true` for a pointer event, that event is treated as a
40
+ * leave instead of a move — lets a nested layout (e.g. GeoLayout's inset
41
+ * markers) opt specific screen regions out of this plot's own hover
42
+ * matching without this component needing to know why.
43
+ */
44
+ excludeHover?: (e: PointerEvent) => boolean;
45
+ /** Forwarded to the wrapper div's `touch-action` CSS — GeoPlot's zoom/pan only. */
46
+ touchAction?: string;
47
+ /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. GeoLayout's zoom/rotate). */
48
+ containerEl?: HTMLDivElement;
49
+ /**
50
+ * `matchedPoints` carries the source `row` alongside the display fields
51
+ * — needed by plot kinds whose semantic value isn't tied to a fixed
52
+ * visual axis (e.g. `BarPlot`'s category, which can be drawn on either
53
+ * axis depending on orientation), so a template can resolve it straight
54
+ * off `row` instead of assuming which of `.x`/`.y` it landed on.
55
+ * `impliesRuleX`/`impliesRuleY` tell the plot kind whether the active
56
+ * strategy implies a crosshair rule on each axis by default.
57
+ * `numericMargins` lets a nested layout align itself with the plot's own
58
+ * content box without recomputing margins itself.
59
+ */
60
+ children: Snippet<[{
61
+ matchedPoints: HoverPoint<TData>[];
62
+ ruleX: AxisValue | null;
63
+ ruleY: AxisValue | null;
64
+ impliesRuleX: boolean;
65
+ impliesRuleY: boolean;
66
+ numericMargins: {
67
+ top: number;
68
+ right: number;
69
+ bottom: number;
70
+ left: number;
71
+ };
72
+ }]>;
73
+ };
74
+ exports: {};
75
+ bindings: "containerEl";
76
+ slots: {};
77
+ events: {};
78
+ };
79
+ declare class __sveltets_Render<TData extends Record<string, unknown>> {
80
+ props(): ReturnType<typeof $$render<TData>>['props'];
81
+ events(): ReturnType<typeof $$render<TData>>['events'];
82
+ slots(): ReturnType<typeof $$render<TData>>['slots'];
83
+ bindings(): "containerEl";
84
+ exports(): {};
85
+ }
86
+ interface $$IsomorphicComponent {
87
+ 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']>> & {
88
+ $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
89
+ } & ReturnType<__sveltets_Render<TData>['exports']>;
90
+ <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
91
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
92
+ }
93
+ declare const BasePlotLayout: $$IsomorphicComponent;
94
+ type BasePlotLayout<TData extends Record<string, unknown>> = InstanceType<typeof BasePlotLayout<TData>>;
95
+ export default BasePlotLayout;
@@ -0,0 +1,30 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ import { GridX, GridY } from 'svelteplot';
3
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
+
5
+ /**
6
+ * Grid lines for the plot kinds that have an x/y axis pair — opt-in via
7
+ * `scales.x.grid`/`scales.y.grid` (svelteplot's own implicit grid is a
8
+ * fixed, unstyled default, so this is drawn instead of that, not on top of
9
+ * it — see `BasePlotLayout`'s `finalizeScale`, which strips `grid` from what
10
+ * actually reaches `<Plot x y>`).
11
+ */
12
+ let { scales }: { scales?: AxisBasedScalesConfig<TData> } = $props();
13
+ </script>
14
+
15
+ {#if scales?.x?.grid}
16
+ <GridX
17
+ stroke={typeof scales.x.grid === 'object' ? scales.x.grid.stroke : undefined}
18
+ strokeWidth={typeof scales.x.grid === 'object' ? scales.x.grid.strokeWidth : undefined}
19
+ strokeOpacity={typeof scales.x.grid === 'object' ? scales.x.grid.strokeOpacity : undefined}
20
+ strokeDasharray={typeof scales.x.grid === 'object' ? scales.x.grid.strokeDasharray : undefined}
21
+ />
22
+ {/if}
23
+ {#if scales?.y?.grid}
24
+ <GridY
25
+ stroke={typeof scales.y.grid === 'object' ? scales.y.grid.stroke : undefined}
26
+ strokeWidth={typeof scales.y.grid === 'object' ? scales.y.grid.strokeWidth : undefined}
27
+ strokeOpacity={typeof scales.y.grid === 'object' ? scales.y.grid.strokeOpacity : undefined}
28
+ strokeDasharray={typeof scales.y.grid === 'object' ? scales.y.grid.strokeDasharray : undefined}
29
+ />
30
+ {/if}
@@ -0,0 +1,27 @@
1
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
2
+ declare function $$render<TData extends Record<string, unknown>>(): {
3
+ props: {
4
+ scales?: AxisBasedScalesConfig<TData>;
5
+ };
6
+ exports: {};
7
+ bindings: "";
8
+ slots: {};
9
+ events: {};
10
+ };
11
+ declare class __sveltets_Render<TData extends Record<string, unknown>> {
12
+ props(): ReturnType<typeof $$render<TData>>['props'];
13
+ events(): ReturnType<typeof $$render<TData>>['events'];
14
+ slots(): ReturnType<typeof $$render<TData>>['slots'];
15
+ bindings(): "";
16
+ exports(): {};
17
+ }
18
+ interface $$IsomorphicComponent {
19
+ 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']>> & {
20
+ $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
21
+ } & ReturnType<__sveltets_Render<TData>['exports']>;
22
+ <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
23
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
24
+ }
25
+ declare const GridLayout: $$IsomorphicComponent;
26
+ type GridLayout<TData extends Record<string, unknown>> = InstanceType<typeof GridLayout<TData>>;
27
+ export default GridLayout;
@@ -0,0 +1,111 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ import type { Snippet } from 'svelte';
3
+ import { RuleX, RuleY } from 'svelteplot';
4
+ import HoverMarker from '../../markers/HoverMarker.svelte';
5
+ import type { AxisValue } from '../../types/plots/axis';
6
+ import type { Series } from '../../types/plots/data/common';
7
+ import type { Marker } from '../../types/markers/common';
8
+ import type { HoverPoint } from '../../types/layout/tooltip';
9
+
10
+ /**
11
+ * Standalone rule markers (`ruleX`/`ruleY`/un-scoped `component`) plus the
12
+ * unscoped hover crosshair+dot+label — the plot kinds with an x/y axis pair
13
+ * only (line/bar/heatmap/pyramid); `GeoPlot` has no rule/crosshair concept
14
+ * of its own. Wraps `children` (the plot kind's actual marks) so both draw
15
+ * in the same order as before this was split out of `BasePlotLayout`: marks
16
+ * first, then rules/hover marker on top of them.
17
+ */
18
+ let {
19
+ markers = [],
20
+ seriesData = [],
21
+ matchedPoints,
22
+ ruleX,
23
+ ruleY,
24
+ impliesRuleX,
25
+ impliesRuleY,
26
+ showHoverLabels = true,
27
+ children,
28
+ }: {
29
+ markers?: Marker[];
30
+ /** All resolved series, forwarded to unscoped component markers (e.g. DeltaMarker). */
31
+ seriesData?: Series<TData>[];
32
+ matchedPoints: HoverPoint<TData>[];
33
+ ruleX: AxisValue | null;
34
+ ruleY: AxisValue | null;
35
+ impliesRuleX: boolean;
36
+ impliesRuleY: boolean;
37
+ showHoverLabels?: boolean;
38
+ children: Snippet;
39
+ } = $props();
40
+
41
+ const topLevelMarkers = $derived(
42
+ markers.filter(
43
+ (marker) =>
44
+ marker.type === 'ruleX' ||
45
+ marker.type === 'ruleY' ||
46
+ (marker.type === 'component' && !marker.series),
47
+ ),
48
+ );
49
+
50
+ /** The unscoped (no `series`) hover marker, if any. */
51
+ const unscopedHoverMarker = $derived(
52
+ markers.find((m) => m.type === 'hover' && !m.series) as
53
+ | Extract<Marker, { type: 'hover' }>
54
+ | undefined,
55
+ );
56
+
57
+ /**
58
+ * Gate ruleX display: shows when the strategy implies it (default) or the
59
+ * marker explicitly requests it (`ruleX: true`), and is never suppressed
60
+ * unless the marker explicitly disables it (`ruleX: false`).
61
+ */
62
+ const showRuleX = $derived(
63
+ unscopedHoverMarker != null &&
64
+ unscopedHoverMarker.ruleX !== false &&
65
+ (impliesRuleX || unscopedHoverMarker.ruleX === true),
66
+ );
67
+ const showRuleY = $derived(
68
+ unscopedHoverMarker != null &&
69
+ unscopedHoverMarker.ruleY !== false &&
70
+ (impliesRuleY || unscopedHoverMarker.ruleY === true),
71
+ );
72
+ </script>
73
+
74
+ {@render children()}
75
+
76
+ {#each topLevelMarkers as marker, i (marker.type === 'ruleX' ? `${marker.type}-${marker.x}` : marker.type === 'ruleY' ? `${marker.type}-${marker.y}` : `${marker.type}-${i}`)}
77
+ {#if marker.type === 'ruleX'}
78
+ <RuleX
79
+ data={[{ xValue: marker.x }]}
80
+ x={(d) => d.xValue}
81
+ stroke={marker.style?.stroke ?? 'currentColor'}
82
+ strokeWidth={marker.style?.strokeWidth ?? 1}
83
+ strokeOpacity={marker.style?.strokeOpacity ?? 1}
84
+ strokeDasharray={marker.style?.strokeDasharray}
85
+ />
86
+ {:else if marker.type === 'ruleY'}
87
+ <RuleY
88
+ data={[{ yValue: marker.y }]}
89
+ y={(d) => d.yValue}
90
+ stroke={marker.style?.stroke ?? 'currentColor'}
91
+ strokeWidth={marker.style?.strokeWidth ?? 1}
92
+ strokeOpacity={marker.style?.strokeOpacity ?? 1}
93
+ strokeDasharray={marker.style?.strokeDasharray}
94
+ />
95
+ {:else if marker.type === 'component'}
96
+ <marker.component {...marker.props} data={marker.data ?? seriesData} />
97
+ {/if}
98
+ {/each}
99
+
100
+ {#if unscopedHoverMarker}
101
+ <HoverMarker
102
+ data={matchedPoints}
103
+ ruleX={showRuleX ? ruleX : null}
104
+ ruleY={showRuleY ? ruleY : null}
105
+ showLabels={unscopedHoverMarker.showLabels ?? showHoverLabels}
106
+ format={unscopedHoverMarker.format}
107
+ ruleStyle={unscopedHoverMarker.ruleStyle}
108
+ dotStyle={unscopedHoverMarker.dotStyle}
109
+ fontStyle={unscopedHoverMarker.fontStyle}
110
+ />
111
+ {/if}
@@ -0,0 +1,40 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { AxisValue } from '../../types/plots/axis';
3
+ import type { Series } from '../../types/plots/data/common';
4
+ import type { Marker } from '../../types/markers/common';
5
+ import type { HoverPoint } from '../../types/layout/tooltip';
6
+ declare function $$render<TData extends Record<string, unknown>>(): {
7
+ props: {
8
+ markers?: Marker[];
9
+ /** All resolved series, forwarded to unscoped component markers (e.g. DeltaMarker). */
10
+ seriesData?: Series<TData>[];
11
+ matchedPoints: HoverPoint<TData>[];
12
+ ruleX: AxisValue | null;
13
+ ruleY: AxisValue | null;
14
+ impliesRuleX: boolean;
15
+ impliesRuleY: boolean;
16
+ showHoverLabels?: boolean;
17
+ children: Snippet;
18
+ };
19
+ exports: {};
20
+ bindings: "";
21
+ slots: {};
22
+ events: {};
23
+ };
24
+ declare class __sveltets_Render<TData extends Record<string, unknown>> {
25
+ props(): ReturnType<typeof $$render<TData>>['props'];
26
+ events(): ReturnType<typeof $$render<TData>>['events'];
27
+ slots(): ReturnType<typeof $$render<TData>>['slots'];
28
+ bindings(): "";
29
+ exports(): {};
30
+ }
31
+ interface $$IsomorphicComponent {
32
+ 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']>> & {
33
+ $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
34
+ } & ReturnType<__sveltets_Render<TData>['exports']>;
35
+ <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
36
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
37
+ }
38
+ declare const RuleLayout: $$IsomorphicComponent;
39
+ type RuleLayout<TData extends Record<string, unknown>> = InstanceType<typeof RuleLayout<TData>>;
40
+ export default RuleLayout;
@@ -0,0 +1,34 @@
1
+ export declare const AUTO_MARGIN_ESTIMATE: {
2
+ readonly top: 20;
3
+ readonly right: 20;
4
+ readonly bottom: 30;
5
+ readonly left: 40;
6
+ };
7
+ /**
8
+ * GeoPlot's own margin default — a small, uniform gap on every side, unlike
9
+ * `AUTO_MARGIN_ESTIMATE`'s asymmetric axis-tick-label estimate (extra room
10
+ * on the left/bottom for y tick labels and an x-axis title). A map has no
11
+ * axes to make room for, so borrowing that estimate just left GeoPlot with
12
+ * a lopsided, oversized margin — most visibly a much taller gap under the
13
+ * map than an `inset` marker's own default `margin` (see `insetLayout.ts`),
14
+ * even though both are meant to read as the same edge gap. Matching this
15
+ * constant to that 8px inset default keeps the two visually aligned.
16
+ */
17
+ export declare const GEO_MARGIN_ESTIMATE: {
18
+ readonly top: 8;
19
+ readonly right: 8;
20
+ readonly bottom: 8;
21
+ readonly left: 8;
22
+ };
23
+ /**
24
+ * Best-effort numeric fallback for a margin side that resolved to `'auto'`.
25
+ * Used for layout heuristics that need *a* number before svelteplot has
26
+ * measured the real rendered content (tick labels, axis title) — e.g.
27
+ * capping tick count, or estimating a band's pixel width for the bar plot's
28
+ * 'grouped' series layout. Not exact: an `'auto'` side can end up wider than
29
+ * this once svelteplot measures unusually long tick labels.
30
+ *
31
+ * `estimate` defaults to `AUTO_MARGIN_ESTIMATE`; pass `GEO_MARGIN_ESTIMATE`
32
+ * for GeoPlot, which has no axes for that estimate's asymmetry to apply to.
33
+ */
34
+ export declare function numericMargin(side: keyof typeof AUTO_MARGIN_ESTIMATE, value: number | 'auto' | undefined, estimate?: Record<keyof typeof AUTO_MARGIN_ESTIMATE, number>): number;
@@ -0,0 +1,26 @@
1
+ export const AUTO_MARGIN_ESTIMATE = { top: 20, right: 20, bottom: 30, left: 40 };
2
+ /**
3
+ * GeoPlot's own margin default — a small, uniform gap on every side, unlike
4
+ * `AUTO_MARGIN_ESTIMATE`'s asymmetric axis-tick-label estimate (extra room
5
+ * on the left/bottom for y tick labels and an x-axis title). A map has no
6
+ * axes to make room for, so borrowing that estimate just left GeoPlot with
7
+ * a lopsided, oversized margin — most visibly a much taller gap under the
8
+ * map than an `inset` marker's own default `margin` (see `insetLayout.ts`),
9
+ * even though both are meant to read as the same edge gap. Matching this
10
+ * constant to that 8px inset default keeps the two visually aligned.
11
+ */
12
+ export const GEO_MARGIN_ESTIMATE = { top: 8, right: 8, bottom: 8, left: 8 };
13
+ /**
14
+ * Best-effort numeric fallback for a margin side that resolved to `'auto'`.
15
+ * Used for layout heuristics that need *a* number before svelteplot has
16
+ * measured the real rendered content (tick labels, axis title) — e.g.
17
+ * capping tick count, or estimating a band's pixel width for the bar plot's
18
+ * 'grouped' series layout. Not exact: an `'auto'` side can end up wider than
19
+ * this once svelteplot measures unusually long tick labels.
20
+ *
21
+ * `estimate` defaults to `AUTO_MARGIN_ESTIMATE`; pass `GEO_MARGIN_ESTIMATE`
22
+ * for GeoPlot, which has no axes for that estimate's asymmetry to apply to.
23
+ */
24
+ export function numericMargin(side, value, estimate = AUTO_MARGIN_ESTIMATE) {
25
+ return typeof value === 'number' ? value : estimate[side];
26
+ }