@fundar/data-chart-telling 0.0.36 → 0.0.37

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.
@@ -1,308 +1,338 @@
1
- <script lang="ts" generics="TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData">
2
- import type { ComponentProps, Snippet } from 'svelte';
3
- import { Plot, Pointer } from 'svelteplot';
4
- import { getConfiguration } from '../../configuration/config.svelte';
5
- import { AUTO_MARGIN_ESTIMATE, numericMargin } from './margins';
6
- import { buildScale } from '../scales/buildScale';
7
- import ScalesLayout from '../scales/ScalesLayout.svelte';
8
- import GeometryLayout from '../geometry/GeometryLayout.svelte';
9
- import CoordinatesLayout from '../coordinates/CoordinatesLayout.svelte';
10
- import StylesLayout from '../styles/StylesLayout.svelte';
11
- import { getHover, createHover } from '../tooltip/hover.svelte';
12
- import { createHoverController } from '../tooltip/controller.svelte';
13
- import TooltipLayout from '../tooltip/TooltipLayout.svelte';
14
- import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
15
- import type { CoordinatesConfig } from '../../types/layout/coordinates';
16
- import type { StylesLayoutConfig } from '../../types/layout/styles';
17
- import type { ScopedMarkerGroup } from '../../types/layout/geometry';
18
- import type { HoverPoint } from '../../types/layout/tooltip';
19
- import type { MarginConfig, HoverConfig } from '../../types/plots/props';
20
- import type { MarkerConfig } from '../../types/markers/all';
21
- import type { Series } from '../../types/plots/data/common';
22
- import type { GeoFeature } from '../../types/plots/data/geo';
1
+ <script
2
+ lang="ts"
3
+ generics="TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData"
4
+ >
5
+ import type { ComponentProps, Snippet } from 'svelte';
6
+ import { Plot, Pointer } from 'svelteplot';
7
+ import { getConfiguration } from '../../configuration/config.svelte';
8
+ import { AUTO_MARGIN_ESTIMATE, numericMargin } from './margins';
9
+ import { buildScale } from '../scales/buildScale';
10
+ import ScalesLayout from '../scales/ScalesLayout.svelte';
11
+ import GeometryLayout from '../geometry/GeometryLayout.svelte';
12
+ import CoordinatesLayout from '../coordinates/CoordinatesLayout.svelte';
13
+ import StylesLayout from '../styles/StylesLayout.svelte';
14
+ import { getHover, createHover } from '../tooltip/hover.svelte';
15
+ import { createHoverController } from '../tooltip/controller.svelte';
16
+ import TooltipLayout from '../tooltip/TooltipLayout.svelte';
17
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
18
+ import type { CoordinatesConfig } from '../../types/layout/coordinates';
19
+ import type { StylesLayoutConfig } from '../../types/layout/styles';
20
+ import type { ScopedMarkerGroup } from '../../types/layout/geometry';
21
+ import type { HoverPoint } from '../../types/layout/tooltip';
22
+ import type { MarginConfig, HoverConfig } from '../../types/plots/props';
23
+ import type { MarkerConfig } from '../../types/markers/all';
24
+ import type { Series } from '../../types/plots/data/common';
25
+ import type { GeoFeature } from '../../types/plots/data/geo';
23
26
 
24
- type SveltePlotProps = ComponentProps<typeof Plot>;
27
+ type SveltePlotProps = ComponentProps<typeof Plot>;
25
28
 
26
- /**
27
- * Shared chrome around every plot kind's svelteplot markup: the
28
- * pointer-event wrapper, the hover/tooltip state machine, and the floating
29
- * tooltip. Composes the Coordinates and Styles layers around every plot
30
- * kind's own content, plus `GeometryLayout` (which renders nothing extra
31
- * when `markers` is empty/unset). `ScalesLayout` composes in
32
- * independently, only when `scales` is passed. Every plot kind supplies
33
- * its own `<Plot x y projection>` scale config, either directly via
34
- * `x`/`y` or by leaving them unset so this component derives them from
35
- * `scales`.
36
- */
37
- let {
38
- width,
39
- height,
40
- data,
41
- getX,
42
- getY,
43
- hover,
44
- x = undefined,
45
- y = undefined,
46
- scales = undefined,
47
- coordinates = undefined,
48
- margins,
49
- marginEstimate = AUTO_MARGIN_ESTIMATE,
50
- marginRemountKey = undefined,
51
- xTickRotate = undefined,
52
- styles = undefined,
53
- markers = undefined,
54
- seriesData = undefined,
55
- scopedGroups = undefined,
56
- geoAllFeatures = undefined,
57
- containerEl = $bindable(undefined),
58
- children,
59
- }: {
60
- width: number;
61
- height: number;
62
- /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching, and to `CoordinatesLayout` for a `domain: 'data'` projection auto-fit. */
63
- data: TData[];
64
- getX: (d: TData) => AxisValue;
65
- getY: (d: TData) => AxisValue;
66
- /** Every hover/tooltip prop this component's own hover machinery reads — see {@link HoverConfig}. */
67
- hover: HoverConfig<TData>;
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
- /** This plot kind's coordinate system config — `GeoPlot` only (a projection + pan/zoom). */
75
- coordinates?: CoordinatesConfig;
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
- /**
81
- * Forces svelteplot's own `<Plot>` to fully unmount/remount (via
82
- * `{#key}`) whenever this changes, instead of reacting to a `margins`
83
- * prop change on an already-mounted instance. Only needed by a caller
84
- * (line's own `Plot.svelte`) whose `margins` grows *after* mount, from
85
- * measuring rendered content (see `createLabelMarginTracker`) — handing
86
- * svelteplot's `<Plot>` a live post-mount margin change (rather than the
87
- * same final value from its very first mount) was observed to send its
88
- * own internal auto-margin sizing into a permanent one-pixel
89
- * bottom-margin oscillation for line's end-point value labels
90
- * specifically, which in turn kept re-triggering the overflow watcher
91
- * that grew the margin in the first place — an infinite effect loop
92
- * that tripped Svelte's `effect_update_depth_exceeded` guard. A static
93
- * `margins` prop (the common case — every other plot kind) never needs
94
- * this; leave it unset there.
95
- */
96
- marginRemountKey?: string | number;
97
- /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
98
- xTickRotate?: number;
99
- /** This plot kind's own `styles` prop, forwarded to the Styles layer. */
100
- styles?: StylesLayoutConfig;
101
- /**
102
- * This plot kind's own markers array. Accepts a plain array, or a
103
- * builder function for a plot kind whose own marker construction needs
104
- * `setHoverMatch`/`clearHoverMatch`, only available once this
105
- * component's own hover controller is set up.
106
- */
107
- markers?:
108
- | MarkerConfig[]
109
- | ((hover: { setHoverMatch: (rows: TData[]) => void; clearHoverMatch: () => void }) => MarkerConfig[]);
110
- /** All resolved series, forwarded to `GeometryLayout` — axis-based kinds only. Independently generic over `TSeries` (defaults to `TData`). */
111
- seriesData?: Series<TSeries>[];
112
- /** Per-series `GeometryLayout` groups — axis-based kinds only, same `TSeries` as `seriesData`. */
113
- scopedGroups?: ScopedMarkerGroup<TSeries>[];
114
- /** `GeoPlot`'s own untouched, tagged features, forwarded to `GeometryLayout` — `inset` marker only. */
115
- geoAllFeatures?: (GeoFeature<Record<string, unknown>> & { __id: string })[];
116
- /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. `CoordinatesLayout`'s zoom/rotate). */
117
- containerEl?: HTMLDivElement;
118
- /**
119
- * `matchedPoints` carries the source `row` alongside the display fields,
120
- * so a template can resolve it straight off `row` instead of assuming
121
- * which visual axis it landed on. `activePoint` is the one matched point
122
- * under the cursor. `numericMargins` lets a nested layout align itself
123
- * with the plot's own content box. `setHoverMatch`/`clearHoverMatch` let
124
- * a plot kind drive the match itself instead of svelteplot's
125
- * distance-based `<Pointer>` see `hover.pointMatching`. When `markers`
126
- * is given, this is `GeometryLayout`'s own `children` instead of raw
127
- * plot content. Optional — a plot kind whose every mark is a `markers`
128
- * entry has nothing left to render here.
129
- */
130
- children?: Snippet<
131
- [{
132
- matchedPoints: HoverPoint<TData>[];
133
- activePoint: HoverPoint<TData> | null;
134
- impliesCrosshairX: boolean;
135
- impliesCrosshairY: boolean;
136
- numericMargins: { top: number; right: number; bottom: number; left: number };
137
- setHoverMatch: (rows: TData[]) => void;
138
- clearHoverMatch: () => void;
139
- }]
140
- >;
141
- } = $props();
29
+ /**
30
+ * Shared chrome around every plot kind's svelteplot markup: the
31
+ * pointer-event wrapper, the hover/tooltip state machine, and the floating
32
+ * tooltip. Composes the Coordinates and Styles layers around every plot
33
+ * kind's own content, plus `GeometryLayout` (which renders nothing extra
34
+ * when `markers` is empty/unset). `ScalesLayout` composes in
35
+ * independently, only when `scales` is passed. Every plot kind supplies
36
+ * its own `<Plot x y projection>` scale config, either directly via
37
+ * `x`/`y` or by leaving them unset so this component derives them from
38
+ * `scales`.
39
+ */
40
+ let {
41
+ width,
42
+ height,
43
+ data,
44
+ getX,
45
+ getY,
46
+ hover,
47
+ x = undefined,
48
+ y = undefined,
49
+ scales = undefined,
50
+ coordinates = undefined,
51
+ margins,
52
+ marginEstimate = AUTO_MARGIN_ESTIMATE,
53
+ marginRemountKey = undefined,
54
+ xTickRotate = undefined,
55
+ styles = undefined,
56
+ markers = undefined,
57
+ seriesData = undefined,
58
+ scopedGroups = undefined,
59
+ geoAllFeatures = undefined,
60
+ containerEl = $bindable(undefined),
61
+ children
62
+ }: {
63
+ width: number;
64
+ height: number;
65
+ /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching, and to `CoordinatesLayout` for a `domain: 'data'` projection auto-fit. */
66
+ data: TData[];
67
+ getX: (d: TData) => AxisValue;
68
+ getY: (d: TData) => AxisValue;
69
+ /** Every hover/tooltip prop this component's own hover machinery reads — see {@link HoverConfig}. */
70
+ hover: HoverConfig<TData>;
71
+ /** Explicit `<Plot x>` override — bypasses `scales`-derived resolution (e.g. GeoPlot's fixed lon/lat domain). */
72
+ x?: SveltePlotProps['x'];
73
+ /** Explicit `<Plot y>` override — see `x`. */
74
+ y?: SveltePlotProps['y'];
75
+ /** Scale config `x`/`y` are derived from when not given directly. */
76
+ scales?: AxisBasedScalesConfig<TData>;
77
+ /** This plot kind's coordinate system config — `GeoPlot` only (a projection + pan/zoom). */
78
+ coordinates?: CoordinatesConfig;
79
+ /** Partial margin override — merged on top of the theme. */
80
+ margins?: MarginConfig;
81
+ /** Numeric fallback for a margin side that resolved to `'auto'` — see `numericMargin`'s doc comment. */
82
+ marginEstimate?: Record<'top' | 'right' | 'bottom' | 'left', number>;
83
+ /**
84
+ * Forces svelteplot's own `<Plot>` to fully unmount/remount (via
85
+ * `{#key}`) whenever this changes, instead of reacting to a `margins`
86
+ * prop change on an already-mounted instance. Only needed by a caller
87
+ * (line's own `Plot.svelte`) whose `margins` grows *after* mount, from
88
+ * measuring rendered content (see `createLabelMarginTracker`) — handing
89
+ * svelteplot's `<Plot>` a live post-mount margin change (rather than the
90
+ * same final value from its very first mount) was observed to send its
91
+ * own internal auto-margin sizing into a permanent one-pixel
92
+ * bottom-margin oscillation for line's end-point value labels
93
+ * specifically, which in turn kept re-triggering the overflow watcher
94
+ * that grew the margin in the first place — an infinite effect loop
95
+ * that tripped Svelte's `effect_update_depth_exceeded` guard. A static
96
+ * `margins` prop (the common case — every other plot kind) never needs
97
+ * this; leave it unset there.
98
+ */
99
+ marginRemountKey?: string | number;
100
+ /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
101
+ xTickRotate?: number;
102
+ /** This plot kind's own `styles` prop, forwarded to the Styles layer. */
103
+ styles?: StylesLayoutConfig;
104
+ /**
105
+ * This plot kind's own markers array. Accepts a plain array, or a
106
+ * builder function for a plot kind whose own marker construction needs
107
+ * `setHoverMatch`/`clearHoverMatch`, only available once this
108
+ * component's own hover controller is set up.
109
+ */
110
+ markers?:
111
+ | MarkerConfig[]
112
+ | ((hover: {
113
+ setHoverMatch: (rows: TData[]) => void;
114
+ clearHoverMatch: () => void;
115
+ }) => MarkerConfig[]);
116
+ /** All resolved series, forwarded to `GeometryLayout` — axis-based kinds only. Independently generic over `TSeries` (defaults to `TData`). */
117
+ seriesData?: Series<TSeries>[];
118
+ /** Per-series `GeometryLayout` groups — axis-based kinds only, same `TSeries` as `seriesData`. */
119
+ scopedGroups?: ScopedMarkerGroup<TSeries>[];
120
+ /** `GeoPlot`'s own untouched, tagged features, forwarded to `GeometryLayout` — `inset` marker only. */
121
+ geoAllFeatures?: (GeoFeature<Record<string, unknown>> & { __id: string })[];
122
+ /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. `CoordinatesLayout`'s zoom/rotate). */
123
+ containerEl?: HTMLDivElement;
124
+ /**
125
+ * `matchedPoints` carries the source `row` alongside the display fields,
126
+ * so a template can resolve it straight off `row` instead of assuming
127
+ * which visual axis it landed on. `activePoint` is the one matched point
128
+ * under the cursor. `numericMargins` lets a nested layout align itself
129
+ * with the plot's own content box. `setHoverMatch`/`clearHoverMatch` let
130
+ * a plot kind drive the match itself instead of svelteplot's
131
+ * distance-based `<Pointer>` see `hover.pointMatching`. When `markers`
132
+ * is given, this is `GeometryLayout`'s own `children` instead of raw
133
+ * plot content. Optional — a plot kind whose every mark is a `markers`
134
+ * entry has nothing left to render here.
135
+ */
136
+ children?: Snippet<
137
+ [
138
+ {
139
+ matchedPoints: HoverPoint<TData>[];
140
+ activePoint: HoverPoint<TData> | null;
141
+ impliesCrosshairX: boolean;
142
+ impliesCrosshairY: boolean;
143
+ numericMargins: { top: number; right: number; bottom: number; left: number };
144
+ setHoverMatch: (rows: TData[]) => void;
145
+ clearHoverMatch: () => void;
146
+ }
147
+ ]
148
+ >;
149
+ } = $props();
142
150
 
143
- const cfg = $derived(getConfiguration());
151
+ const cfg = $derived(getConfiguration());
144
152
 
145
- /**
146
- * Margins passed straight to `<Plot margin={…}>`. Each side defaults to
147
- * `'auto'` (from the theme), which lets svelteplot size it to whatever it
148
- * actually renders there — tick labels, an axis title, rotated ticks —
149
- * rather than a fixed guess that either clips content or wastes space.
150
- * `margins`/`cfg.margins` can still pin a side to an exact number.
151
- */
152
- const computedMargins = $derived.by(() => {
153
- const base = { ...cfg.margins, ...margins };
154
- // Auto margins already measure the real (rotated) label bounding box, so
155
- // this manual boost is only needed when the caller pinned bottom to a
156
- // fixed number, which bypasses that measurement.
157
- if (!xTickRotate || typeof base.bottom !== 'number') return base;
158
- const rad = Math.abs(xTickRotate) * Math.PI / 180;
159
- return {
160
- ...base,
161
- bottom: base.bottom + Math.ceil(Math.sin(rad) * 20),
162
- };
163
- });
153
+ /**
154
+ * Margins passed straight to `<Plot margin={…}>`. Each side defaults to
155
+ * `'auto'` (from the theme), which lets svelteplot size it to whatever it
156
+ * actually renders there — tick labels, an axis title, rotated ticks —
157
+ * rather than a fixed guess that either clips content or wastes space.
158
+ * `margins`/`cfg.margins` can still pin a side to an exact number.
159
+ */
160
+ const computedMargins = $derived.by(() => {
161
+ const base = { ...cfg.margins, ...margins };
162
+ // Auto margins already measure the real (rotated) label bounding box, so
163
+ // this manual boost is only needed when the caller pinned bottom to a
164
+ // fixed number, which bypasses that measurement.
165
+ if (!xTickRotate || typeof base.bottom !== 'number') return base;
166
+ const rad = (Math.abs(xTickRotate) * Math.PI) / 180;
167
+ return {
168
+ ...base,
169
+ bottom: base.bottom + Math.ceil(Math.sin(rad) * 20)
170
+ };
171
+ });
164
172
 
165
- const numericMargins = $derived({
166
- top: numericMargin('top', computedMargins.top, marginEstimate),
167
- right: numericMargin('right', computedMargins.right, marginEstimate),
168
- bottom: numericMargin('bottom', computedMargins.bottom, marginEstimate),
169
- left: numericMargin('left', computedMargins.left, marginEstimate),
170
- });
173
+ const numericMargins = $derived({
174
+ top: numericMargin('top', computedMargins.top, marginEstimate),
175
+ right: numericMargin('right', computedMargins.right, marginEstimate),
176
+ bottom: numericMargin('bottom', computedMargins.bottom, marginEstimate),
177
+ left: numericMargin('left', computedMargins.left, marginEstimate)
178
+ });
171
179
 
172
- // Cast to svelteplot's own (narrower, axis-specific) scale option type:
173
- // buildScale has already stripped the fields that don't exist there
174
- // (grid, the vertical label config, BarPlot's series-layout knobs).
175
- const resolvedX = $derived(x !== undefined ? x : (buildScale(scales?.x, data, getX) as SveltePlotProps['x']));
176
- const resolvedY = $derived(y !== undefined ? y : (buildScale(scales?.y, data, getY) as SveltePlotProps['y']));
180
+ // Cast to svelteplot's own (narrower, axis-specific) scale option type:
181
+ // buildScale has already stripped the fields that don't exist there
182
+ // (grid, the vertical label config, BarPlot's series-layout knobs).
183
+ const resolvedX = $derived(
184
+ x !== undefined ? x : (buildScale(scales?.x, data, getX) as SveltePlotProps['x'])
185
+ );
186
+ const resolvedY = $derived(
187
+ y !== undefined ? y : (buildScale(scales?.y, data, getY) as SveltePlotProps['y'])
188
+ );
177
189
 
178
- /** Only ever meaningful for `GeoPlot` every other kind leaves `coordinates` unset. */
179
- const touchAction = $derived(coordinates?.zoom !== false && coordinates?.zoom != null ? 'none' : undefined);
190
+ /** Only ever meaningful for `GeoPlot`. `none` while any interactive gesture (`zoom`/`pan`/`rotate`) is on. */
191
+ const touchAction = $derived(
192
+ (coordinates?.zoom !== false && coordinates?.zoom != null) ||
193
+ coordinates?.pan ||
194
+ coordinates?.rotate
195
+ ? 'none'
196
+ : undefined
197
+ );
180
198
 
181
- // ── Hover / tooltip ───────────────────────────────────────────────────────
182
- const sharedHover = getHover();
183
- const localHover = createHover();
184
- const hoverStore = $derived(sharedHover ?? localHover);
199
+ // ── Hover / tooltip ───────────────────────────────────────────────────────
200
+ const sharedHover = getHover();
201
+ const localHover = createHover();
202
+ const hoverStore = $derived(sharedHover ?? localHover);
185
203
 
186
- /** Declared intent, defaulted — see {@link HoverConfig.pointMatching}. */
187
- const pointMatchingStrategy = $derived(hover.pointMatching ?? 'nearest');
204
+ /** Declared intent, defaulted — see {@link HoverConfig.pointMatching}. */
205
+ const pointMatchingStrategy = $derived(hover.pointMatching ?? 'nearest');
188
206
 
189
- /**
190
- * Which axes svelteplot's `<Pointer>` should search along, derived from
191
- * the active strategy. `undefined` for an axis collapses every point to
192
- * the same coordinate on that axis, excluding it from the match entirely.
193
- */
194
- const pointerMatchX = $derived(hoverStore.strategy === 'y' ? undefined : getX);
195
- const pointerMatchY = $derived(hoverStore.strategy === 'x' ? undefined : getY);
207
+ /**
208
+ * Which axes svelteplot's `<Pointer>` should search along, derived from
209
+ * the active strategy. `undefined` for an axis collapses every point to
210
+ * the same coordinate on that axis, excluding it from the match entirely.
211
+ */
212
+ const pointerMatchX = $derived(hoverStore.strategy === 'y' ? undefined : getX);
213
+ const pointerMatchY = $derived(hoverStore.strategy === 'x' ? undefined : getY);
196
214
 
197
- $effect(() => {
198
- if (sharedHover) return;
199
- localHover.strategy = hover.strategy ?? 'x';
200
- localHover.sync = hover.sync ?? false;
201
- });
215
+ $effect(() => {
216
+ if (sharedHover) return;
217
+ localHover.strategy = hover.strategy ?? 'x';
218
+ localHover.sync = hover.sync ?? false;
219
+ });
202
220
 
203
- const ctrl = createHoverController<TData>({
204
- hover: () => hoverStore,
205
- facetId: () => hover.tooltip?.facetId ?? '',
206
- points: () => hover.points,
207
- getX: () => getX,
208
- getY: () => getY,
209
- label: () => hover.label,
210
- seriesFilter: () => hover.tooltip?.series,
211
- width: () => width,
212
- height: () => height,
213
- });
221
+ const ctrl = createHoverController<TData>({
222
+ hover: () => hoverStore,
223
+ facetId: () => hover.tooltip?.facetId ?? '',
224
+ points: () => hover.points,
225
+ getX: () => getX,
226
+ getY: () => getY,
227
+ label: () => hover.label,
228
+ seriesFilter: () => hover.tooltip?.series,
229
+ width: () => width,
230
+ height: () => height
231
+ });
214
232
 
215
- function onMove(e: PointerEvent) {
216
- if (hover.exclude?.(e)) {
217
- ctrl.onLeave();
218
- return;
219
- }
220
- ctrl.onMove(e);
221
- }
233
+ function onMove(e: PointerEvent) {
234
+ if (hover.exclude?.(e)) {
235
+ ctrl.onLeave();
236
+ return;
237
+ }
238
+ ctrl.onMove(e);
239
+ }
222
240
 
223
- const resolvedMarkers = $derived(
224
- typeof markers === 'function' ? markers({ setHoverMatch: ctrl.onPointer, clearHoverMatch: ctrl.onLeave }) : markers,
225
- );
241
+ const resolvedMarkers = $derived(
242
+ typeof markers === 'function'
243
+ ? markers({ setHoverMatch: ctrl.onPointer, clearHoverMatch: ctrl.onLeave })
244
+ : markers
245
+ );
226
246
 
227
- /** `hover.crosshair === false` forces both off, overriding the strategy-implied default — see {@link HoverConfig.crosshair}. */
228
- const effectiveImpliesCrosshairX = $derived(hover.crosshair === false ? false : ctrl.impliesCrosshairX);
229
- const effectiveImpliesCrosshairY = $derived(hover.crosshair === false ? false : ctrl.impliesCrosshairY);
247
+ /** `hover.crosshair === false` forces both off, overriding the strategy-implied default — see {@link HoverConfig.crosshair}. */
248
+ const effectiveImpliesCrosshairX = $derived(
249
+ hover.crosshair === false ? false : ctrl.impliesCrosshairX
250
+ );
251
+ const effectiveImpliesCrosshairY = $derived(
252
+ hover.crosshair === false ? false : ctrl.impliesCrosshairY
253
+ );
230
254
  </script>
231
255
 
232
256
  <!-- svelte-ignore a11y_no_static_element_interactions -->
233
257
  <div
234
- class="dct-plot"
235
- bind:this={containerEl}
236
- style:color={cfg.axis.color}
237
- style:touch-action={touchAction}
238
- onpointermove={hover.active ? onMove : undefined}
239
- onpointerleave={hover.active ? ctrl.onLeave : undefined}
258
+ class="dct-plot"
259
+ bind:this={containerEl}
260
+ style:color={cfg.axis.color}
261
+ style:touch-action={touchAction}
262
+ onpointermove={hover.active ? onMove : undefined}
263
+ onpointerleave={hover.active ? ctrl.onLeave : undefined}
240
264
  >
241
- <CoordinatesLayout
242
- {width}
243
- {height}
244
- margin={computedMargins}
245
- x={resolvedX}
246
- y={resolvedY}
247
- {coordinates}
248
- {data}
249
- {containerEl}
250
- remountKey={marginRemountKey}
251
- >
252
- <StylesLayout {styles} {width} {height} {numericMargins} />
253
- {#if scales !== undefined}
254
- <ScalesLayout {data} {getX} {getY} {width} {height} {numericMargins} {scales} />
255
- {/if}
256
- <GeometryLayout
257
- markers={resolvedMarkers}
258
- seriesData={seriesData ?? []}
259
- scopedGroups={scopedGroups ?? []}
260
- matchedPoints={ctrl.points}
261
- activePoint={ctrl.activePoint}
262
- impliesCrosshairX={effectiveImpliesCrosshairX}
263
- impliesCrosshairY={effectiveImpliesCrosshairY}
264
- {width}
265
- {height}
266
- {numericMargins}
267
- {data}
268
- {geoAllFeatures}
269
- tooltipAnchorX={hover.tooltip?.anchorX}
270
- tooltipAnchorY={hover.tooltip?.anchorY}
271
- >
272
- {@render children?.({
273
- matchedPoints: ctrl.points,
274
- activePoint: ctrl.activePoint,
275
- impliesCrosshairX: effectiveImpliesCrosshairX,
276
- impliesCrosshairY: effectiveImpliesCrosshairY,
277
- numericMargins,
278
- setHoverMatch: ctrl.onPointer,
279
- clearHoverMatch: ctrl.onLeave,
280
- })}
281
- </GeometryLayout>
265
+ <CoordinatesLayout
266
+ {width}
267
+ {height}
268
+ margin={computedMargins}
269
+ x={resolvedX}
270
+ y={resolvedY}
271
+ {coordinates}
272
+ {data}
273
+ {containerEl}
274
+ remountKey={marginRemountKey}
275
+ >
276
+ <StylesLayout {styles} {width} {height} {numericMargins} />
277
+ {#if scales !== undefined}
278
+ <ScalesLayout {data} {getX} {getY} {width} {height} {numericMargins} {scales} />
279
+ {/if}
280
+ <GeometryLayout
281
+ markers={resolvedMarkers}
282
+ seriesData={seriesData ?? []}
283
+ scopedGroups={scopedGroups ?? []}
284
+ matchedPoints={ctrl.points}
285
+ activePoint={ctrl.activePoint}
286
+ impliesCrosshairX={effectiveImpliesCrosshairX}
287
+ impliesCrosshairY={effectiveImpliesCrosshairY}
288
+ {width}
289
+ {height}
290
+ {numericMargins}
291
+ {data}
292
+ {geoAllFeatures}
293
+ tooltipAnchorX={hover.tooltip?.anchorX}
294
+ tooltipAnchorY={hover.tooltip?.anchorY}
295
+ >
296
+ {@render children?.({
297
+ matchedPoints: ctrl.points,
298
+ activePoint: ctrl.activePoint,
299
+ impliesCrosshairX: effectiveImpliesCrosshairX,
300
+ impliesCrosshairY: effectiveImpliesCrosshairY,
301
+ numericMargins,
302
+ setHoverMatch: ctrl.onPointer,
303
+ clearHoverMatch: ctrl.onLeave
304
+ })}
305
+ </GeometryLayout>
282
306
 
283
- {#if hover.active && pointMatchingStrategy === 'nearest'}
284
- <Pointer {data} x={pointerMatchX} y={pointerMatchY} maxDistance={400} onupdate={ctrl.onPointer} />
285
- {/if}
286
- </CoordinatesLayout>
307
+ {#if hover.active && pointMatchingStrategy === 'nearest'}
308
+ <Pointer
309
+ {data}
310
+ x={pointerMatchX}
311
+ y={pointerMatchY}
312
+ maxDistance={400}
313
+ onupdate={ctrl.onPointer}
314
+ />
315
+ {/if}
316
+ </CoordinatesLayout>
287
317
 
288
- {#if hover.tooltip}
289
- <TooltipLayout
290
- visible={ctrl.showTooltip && ctrl.tooltipPosition != null}
291
- x={ctrl.tooltipPosition?.x ?? 0}
292
- y={ctrl.tooltipPosition?.y ?? 0}
293
- bounds={{ width, height }}
294
- rows={ctrl.matchedRows.map((p) => p.row)}
295
- format={hover.tooltip.format}
296
- content={hover.tooltip.content}
297
- anchorX={hover.tooltip.anchorX}
298
- anchorY={hover.tooltip.anchorY}
299
- />
300
- {/if}
318
+ {#if hover.tooltip}
319
+ <TooltipLayout
320
+ visible={ctrl.showTooltip && ctrl.tooltipPosition != null}
321
+ x={ctrl.tooltipPosition?.x ?? 0}
322
+ y={ctrl.tooltipPosition?.y ?? 0}
323
+ bounds={{ width, height }}
324
+ rows={ctrl.matchedRows.map((p) => p.row)}
325
+ format={hover.tooltip.format}
326
+ content={hover.tooltip.content}
327
+ anchorX={hover.tooltip.anchorX}
328
+ anchorY={hover.tooltip.anchorY}
329
+ />
330
+ {/if}
301
331
  </div>
302
332
 
303
333
  <style>
304
- .dct-plot {
305
- position: relative;
306
- line-height: 0;
307
- }
334
+ .dct-plot {
335
+ position: relative;
336
+ line-height: 0;
337
+ }
308
338
  </style>