@fundar/data-chart-telling 0.0.53 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/layout/geometry/GeometryLayout.svelte +19 -17
- package/dist/layout/geometry/GeometryLayout.svelte.d.ts +4 -6
- package/dist/layout/plot/BasePlotLayout.svelte +5 -15
- package/dist/layout/tooltip/controller.svelte.d.ts +8 -0
- package/dist/layout/tooltip/controller.svelte.js +11 -3
- package/dist/markers/BarMarker.svelte +8 -6
- package/dist/markers/HoverMarker.svelte +39 -2
- package/dist/markers/HoverMarker.svelte.d.ts +5 -0
- package/dist/plots/bar/hoverPoints.d.ts +1 -1
- package/dist/plots/bar/hoverPoints.js +2 -0
- package/dist/types/layout/tooltip.d.ts +5 -0
- package/dist/types/markers/common.d.ts +2 -1
- package/package.json +1 -1
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import type { Series } from '../../types/plots/data/common';
|
|
24
24
|
import type { HoverMarkerConfig, CustomComponentMarkerConfig } from '../../types/markers/common';
|
|
25
25
|
import type { MarkerConfig } from '../../types/markers/all';
|
|
26
|
-
import type {
|
|
26
|
+
import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
|
|
27
|
+
import type { HoverController } from '../tooltip/controller.svelte';
|
|
27
28
|
import type { ScopedMarkerGroup } from '../../types/layout/geometry';
|
|
28
29
|
import type { GeoFeature } from '../../types/plots/data/geo';
|
|
29
30
|
|
|
@@ -49,10 +50,7 @@
|
|
|
49
50
|
markers = [],
|
|
50
51
|
seriesData = [],
|
|
51
52
|
scopedGroups = [],
|
|
52
|
-
|
|
53
|
-
activePoint,
|
|
54
|
-
impliesCrosshairX,
|
|
55
|
-
impliesCrosshairY,
|
|
53
|
+
ctrl,
|
|
56
54
|
numericMargins,
|
|
57
55
|
data = [],
|
|
58
56
|
geoAllFeatures = [],
|
|
@@ -68,11 +66,8 @@
|
|
|
68
66
|
seriesData?: Series<TSeries>[];
|
|
69
67
|
/** One entry per series that can own a `series`-scoped marker. Same `TSeries` as `seriesData`. */
|
|
70
68
|
scopedGroups?: ScopedMarkerGroup<TSeries>[];
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
activePoint: HoverPoint<TData> | null;
|
|
74
|
-
impliesCrosshairX: boolean;
|
|
75
|
-
impliesCrosshairY: boolean;
|
|
69
|
+
/** `BasePlotLayout`'s own hover controller. */
|
|
70
|
+
ctrl: HoverController<TData>;
|
|
76
71
|
/** The plot's own resolved margins — `inset` marker only, to place its content box. */
|
|
77
72
|
numericMargins?: { top: number; right: number; bottom: number; left: number };
|
|
78
73
|
/** `BasePlotLayout`'s own rows — `contour` (clip) and unscoped `geo-component` fallback data. */
|
|
@@ -171,9 +166,16 @@
|
|
|
171
166
|
{:else if marker.type === 'geo-component'}
|
|
172
167
|
<marker.component {...marker.props} data={marker.data ?? data} />
|
|
173
168
|
{:else if marker.type === 'hover'}
|
|
174
|
-
<HoverMarker
|
|
169
|
+
<HoverMarker
|
|
170
|
+
{marker}
|
|
171
|
+
data={ctrl.points}
|
|
172
|
+
activePoint={ctrl.activePoint}
|
|
173
|
+
impliesCrosshairX={ctrl.impliesCrosshairX}
|
|
174
|
+
impliesCrosshairY={ctrl.impliesCrosshairY}
|
|
175
|
+
onActivePointPixel={ctrl.reportActivePointPixel}
|
|
176
|
+
/>
|
|
175
177
|
{:else if marker.type === 'geo-hover'}
|
|
176
|
-
<GeoHoverMarker {marker} data={
|
|
178
|
+
<GeoHoverMarker {marker} data={ctrl.points} />
|
|
177
179
|
{:else if marker.type === 'delta'}
|
|
178
180
|
<DeltaMarker {marker} data={seriesData} />
|
|
179
181
|
{/if}
|
|
@@ -182,7 +184,7 @@
|
|
|
182
184
|
{#each scopedGroups as group (group.name)}
|
|
183
185
|
{#each scopedMarkersByName.get(group.name) ?? [] as marker, i (`${group.name}-${marker.type}-${i}`)}
|
|
184
186
|
{#if marker.type === 'hover'}
|
|
185
|
-
{@const seriesPoints =
|
|
187
|
+
{@const seriesPoints = ctrl.points.filter((p) => p.series === group.name)}
|
|
186
188
|
{#if marker.scope === 'segment'}
|
|
187
189
|
{#each group.segments as seg, segIdx (`${group.name}-hover-segment-${segIdx}`)}
|
|
188
190
|
<!-- p.row (TData) is always a superset of TSeries in practice, not provable across two independent generics. -->
|
|
@@ -192,8 +194,8 @@
|
|
|
192
194
|
{marker}
|
|
193
195
|
data={segPoints}
|
|
194
196
|
activePoint={segPoints[0] ?? null}
|
|
195
|
-
{impliesCrosshairX}
|
|
196
|
-
{impliesCrosshairY}
|
|
197
|
+
impliesCrosshairX={ctrl.impliesCrosshairX}
|
|
198
|
+
impliesCrosshairY={ctrl.impliesCrosshairY}
|
|
197
199
|
seriesColor={seg.color}
|
|
198
200
|
/>
|
|
199
201
|
{/if}
|
|
@@ -203,8 +205,8 @@
|
|
|
203
205
|
{marker}
|
|
204
206
|
data={seriesPoints}
|
|
205
207
|
activePoint={seriesPoints[0] ?? null}
|
|
206
|
-
{impliesCrosshairX}
|
|
207
|
-
{impliesCrosshairY}
|
|
208
|
+
impliesCrosshairX={ctrl.impliesCrosshairX}
|
|
209
|
+
impliesCrosshairY={ctrl.impliesCrosshairY}
|
|
208
210
|
seriesColor={group.segments[0]?.color ?? group.color}
|
|
209
211
|
/>
|
|
210
212
|
{/if}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { Series } from '../../types/plots/data/common';
|
|
3
3
|
import type { MarkerConfig } from '../../types/markers/all';
|
|
4
|
-
import type {
|
|
4
|
+
import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
|
|
5
|
+
import type { HoverController } from '../tooltip/controller.svelte';
|
|
5
6
|
import type { ScopedMarkerGroup } from '../../types/layout/geometry';
|
|
6
7
|
import type { GeoFeature } from '../../types/plots/data/geo';
|
|
7
8
|
declare function $$render<TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData>(): {
|
|
@@ -14,11 +15,8 @@ declare function $$render<TData extends Record<string, unknown>, TSeries extends
|
|
|
14
15
|
seriesData?: Series<TSeries>[];
|
|
15
16
|
/** One entry per series that can own a `series`-scoped marker. Same `TSeries` as `seriesData`. */
|
|
16
17
|
scopedGroups?: ScopedMarkerGroup<TSeries>[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
activePoint: HoverPoint<TData> | null;
|
|
20
|
-
impliesCrosshairX: boolean;
|
|
21
|
-
impliesCrosshairY: boolean;
|
|
18
|
+
/** `BasePlotLayout`'s own hover controller. */
|
|
19
|
+
ctrl: HoverController<TData>;
|
|
22
20
|
/** The plot's own resolved margins — `inset` marker only, to place its content box. */
|
|
23
21
|
numericMargins?: {
|
|
24
22
|
top: number;
|
|
@@ -227,7 +227,8 @@
|
|
|
227
227
|
label: () => hover.label,
|
|
228
228
|
seriesFilter: () => hover.tooltip?.series,
|
|
229
229
|
width: () => width,
|
|
230
|
-
height: () => height
|
|
230
|
+
height: () => height,
|
|
231
|
+
crosshair: () => hover.crosshair
|
|
231
232
|
});
|
|
232
233
|
|
|
233
234
|
function onMove(e: PointerEvent) {
|
|
@@ -243,14 +244,6 @@
|
|
|
243
244
|
? markers({ setHoverMatch: ctrl.onPointer, clearHoverMatch: ctrl.onLeave })
|
|
244
245
|
: markers
|
|
245
246
|
);
|
|
246
|
-
|
|
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
|
-
);
|
|
254
247
|
</script>
|
|
255
248
|
|
|
256
249
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -281,10 +274,7 @@
|
|
|
281
274
|
markers={resolvedMarkers}
|
|
282
275
|
seriesData={seriesData ?? []}
|
|
283
276
|
scopedGroups={scopedGroups ?? []}
|
|
284
|
-
|
|
285
|
-
activePoint={ctrl.activePoint}
|
|
286
|
-
impliesCrosshairX={effectiveImpliesCrosshairX}
|
|
287
|
-
impliesCrosshairY={effectiveImpliesCrosshairY}
|
|
277
|
+
{ctrl}
|
|
288
278
|
{width}
|
|
289
279
|
{height}
|
|
290
280
|
{numericMargins}
|
|
@@ -296,8 +286,8 @@
|
|
|
296
286
|
{@render children?.({
|
|
297
287
|
matchedPoints: ctrl.points,
|
|
298
288
|
activePoint: ctrl.activePoint,
|
|
299
|
-
impliesCrosshairX:
|
|
300
|
-
impliesCrosshairY:
|
|
289
|
+
impliesCrosshairX: ctrl.impliesCrosshairX,
|
|
290
|
+
impliesCrosshairY: ctrl.impliesCrosshairY,
|
|
301
291
|
numericMargins,
|
|
302
292
|
setHoverMatch: ctrl.onPointer,
|
|
303
293
|
clearHoverMatch: ctrl.onLeave
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { HoverStore, HoverPoint } from '../../types/layout/tooltip';
|
|
2
2
|
import type { AxisValue } from '../../types/layout/scales';
|
|
3
|
+
/** Return type of {@link createHoverController}. */
|
|
4
|
+
export type HoverController<T extends Record<string, unknown>> = ReturnType<typeof createHoverController<T>>;
|
|
3
5
|
/**
|
|
4
6
|
* The plot-agnostic hover state machine shared by every plot. Given reactive
|
|
5
7
|
* accessors for the current hover store, this facet's id, and the local points,
|
|
@@ -23,10 +25,16 @@ export declare function createHoverController<T extends Record<string, unknown>>
|
|
|
23
25
|
/** This facet's own box, used to convert the cursor to/from a sync-broadcast fraction. */
|
|
24
26
|
width: () => number;
|
|
25
27
|
height: () => number;
|
|
28
|
+
/** Forces the crosshair rule(s) off entirely — see {@link import('../../types/plots/props').HoverConfig.crosshair}. */
|
|
29
|
+
crosshair?: () => boolean | undefined;
|
|
26
30
|
}): {
|
|
27
31
|
onPointer: (data: T[]) => void;
|
|
28
32
|
onMove: (e: PointerEvent) => void;
|
|
29
33
|
onLeave: () => void;
|
|
34
|
+
reportActivePointPixel(pos: {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
} | null): void;
|
|
30
38
|
readonly points: {
|
|
31
39
|
x: AxisValue;
|
|
32
40
|
y: AxisValue;
|
|
@@ -11,6 +11,8 @@ import { eq, matchByStrategy, impliesCrosshairX, impliesCrosshairY, filterBySeri
|
|
|
11
11
|
*/
|
|
12
12
|
export function createHoverController(args) {
|
|
13
13
|
let cursor = $state(null);
|
|
14
|
+
// This facet's own `activePoint`, as a pixel position reported by `HoverMarker`.
|
|
15
|
+
let activePointPixel = $state(null);
|
|
14
16
|
// Finds the matching hoverPoint for a hit row — by identity first, then
|
|
15
17
|
// by x/y value (for svelteplot's `Pointer`, which hands back clones).
|
|
16
18
|
const pointForRow = (row) => {
|
|
@@ -79,9 +81,9 @@ export function createHoverController(args) {
|
|
|
79
81
|
return candidates[0];
|
|
80
82
|
return candidates.find((p) => eq(p.x, activeX) && eq(p.y, activeY)) ?? candidates[0];
|
|
81
83
|
});
|
|
82
|
-
// Whether the current strategy implies a crosshair rule on each axis
|
|
83
|
-
const _impliesCrosshairX = $derived(impliesCrosshairX(args.hover().strategy));
|
|
84
|
-
const _impliesCrosshairY = $derived(impliesCrosshairY(args.hover().strategy));
|
|
84
|
+
// Whether the current strategy implies a crosshair rule on each axis, unless `crosshair: false`.
|
|
85
|
+
const _impliesCrosshairX = $derived(args.crosshair?.() === false ? false : impliesCrosshairX(args.hover().strategy));
|
|
86
|
+
const _impliesCrosshairY = $derived(args.crosshair?.() === false ? false : impliesCrosshairY(args.hover().strategy));
|
|
85
87
|
const showTooltip = $derived.by(() => {
|
|
86
88
|
if (matchedRows.length === 0)
|
|
87
89
|
return false;
|
|
@@ -127,9 +129,12 @@ export function createHoverController(args) {
|
|
|
127
129
|
h.source = null;
|
|
128
130
|
}
|
|
129
131
|
}
|
|
132
|
+
/** This facet's tooltip position: the cursor, else `activePointPixel`, else the source facet's cursor fraction reprojected onto this box. */
|
|
130
133
|
const tooltipPosition = $derived.by(() => {
|
|
131
134
|
if (cursor)
|
|
132
135
|
return cursor;
|
|
136
|
+
if (activePointPixel)
|
|
137
|
+
return activePointPixel;
|
|
133
138
|
const frac = args.hover().cursorFrac;
|
|
134
139
|
return frac ? { x: frac.x * args.width(), y: frac.y * args.height() } : null;
|
|
135
140
|
});
|
|
@@ -137,6 +142,9 @@ export function createHoverController(args) {
|
|
|
137
142
|
onPointer,
|
|
138
143
|
onMove,
|
|
139
144
|
onLeave,
|
|
145
|
+
reportActivePointPixel(pos) {
|
|
146
|
+
activePointPixel = pos;
|
|
147
|
+
},
|
|
140
148
|
get points() {
|
|
141
149
|
return highlight;
|
|
142
150
|
},
|
|
@@ -78,14 +78,14 @@
|
|
|
78
78
|
{#if categoryScale?.type === 'band'}
|
|
79
79
|
{@const bandwidth = categoryScale.fn.bandwidth()}
|
|
80
80
|
{@const bandStart = Number(categoryScale.fn(marker.category as never) ?? 0)}
|
|
81
|
+
{@const insetLeft = (marker.insetLeft ?? 0) * bandwidth}
|
|
82
|
+
{@const insetRight = (marker.insetRight ?? 0) * bandwidth}
|
|
83
|
+
{@const insetTop = (marker.insetTop ?? 0) * bandwidth}
|
|
84
|
+
{@const insetBottom = (marker.insetBottom ?? 0) * bandwidth}
|
|
81
85
|
{#if marker.role === 'hitArea'}
|
|
82
86
|
{@const [rangeMin, rangeMax] = marker.isHorizontal
|
|
83
87
|
? hitAreaValueRangeFor(marker.isHorizontal, marker.x1, marker.x2)
|
|
84
88
|
: hitAreaValueRangeFor(marker.isHorizontal, marker.y1, marker.y2)}
|
|
85
|
-
{@const insetLeft = (marker.insetLeft ?? 0) * bandwidth}
|
|
86
|
-
{@const insetRight = (marker.insetRight ?? 0) * bandwidth}
|
|
87
|
-
{@const insetTop = (marker.insetTop ?? 0) * bandwidth}
|
|
88
|
-
{@const insetBottom = (marker.insetBottom ?? 0) * bandwidth}
|
|
89
89
|
{#if marker.isHorizontal}
|
|
90
90
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
91
91
|
<rect
|
|
@@ -113,9 +113,9 @@
|
|
|
113
113
|
{/if}
|
|
114
114
|
{:else}
|
|
115
115
|
{@const [valueMin, valueMax] = valueExtentFor(marker.isHorizontal)}
|
|
116
|
-
{@const thickness = bandwidth + 2}
|
|
117
|
-
{@const center = bandStart + bandwidth / 2}
|
|
118
116
|
{#if marker.isHorizontal}
|
|
117
|
+
{@const thickness = Math.max(0, bandwidth - insetTop - insetBottom) + 2}
|
|
118
|
+
{@const center = bandStart + insetTop + (bandwidth - insetTop - insetBottom) / 2}
|
|
119
119
|
<rect
|
|
120
120
|
class="hover-area"
|
|
121
121
|
x={valueMin}
|
|
@@ -132,6 +132,8 @@
|
|
|
132
132
|
pointer-events="none"
|
|
133
133
|
/>
|
|
134
134
|
{:else}
|
|
135
|
+
{@const thickness = Math.max(0, bandwidth - insetLeft - insetRight) + 2}
|
|
136
|
+
{@const center = bandStart + insetLeft + (bandwidth - insetLeft - insetRight) / 2}
|
|
135
137
|
<rect
|
|
136
138
|
class="hover-area"
|
|
137
139
|
x={center - thickness / 2}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { usePlot } from 'svelteplot';
|
|
2
3
|
import DotMarker from './DotMarker.svelte';
|
|
3
4
|
import TextMarker from './TextMarker.svelte';
|
|
4
5
|
import RuleMarker from './RuleMarker.svelte';
|
|
@@ -39,6 +40,8 @@
|
|
|
39
40
|
activePoint?: HoverDisplayPoint | null;
|
|
40
41
|
impliesCrosshairX?: boolean;
|
|
41
42
|
impliesCrosshairY?: boolean;
|
|
43
|
+
/** Reports `activePoint`'s real pixel position within this facet's own box, or `null` when nothing's matched. */
|
|
44
|
+
onActivePointPixel?: (pos: { x: number; y: number } | null) => void;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
let {
|
|
@@ -48,9 +51,35 @@
|
|
|
48
51
|
impliesCrosshairX = false,
|
|
49
52
|
impliesCrosshairY = false,
|
|
50
53
|
seriesColor,
|
|
54
|
+
onActivePointPixel,
|
|
51
55
|
}: Props = $props();
|
|
52
56
|
|
|
53
57
|
const cfg = $derived(getConfiguration());
|
|
58
|
+
const plot = usePlot();
|
|
59
|
+
|
|
60
|
+
/** Scaled pixel position of `value`, centered within its band for a band scale. */
|
|
61
|
+
function scaledPixel(scale: { type?: string; fn?: (v: never) => unknown } | undefined, value: unknown): number | null {
|
|
62
|
+
if (!scale?.fn || value == null) return null;
|
|
63
|
+
const raw = Number(scale.fn(value as never));
|
|
64
|
+
if (!Number.isFinite(raw)) return null;
|
|
65
|
+
if (scale.type === 'band') {
|
|
66
|
+
const bandwidth = (scale.fn as { bandwidth?: () => number }).bandwidth?.() ?? 0;
|
|
67
|
+
return raw + bandwidth / 2;
|
|
68
|
+
}
|
|
69
|
+
return raw;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const activePixel = $derived.by((): { x: number; y: number } | null => {
|
|
73
|
+
if (!activePoint) return null;
|
|
74
|
+
const x = scaledPixel(plot.scales.x, activePoint.x);
|
|
75
|
+
const y = scaledPixel(plot.scales.y, activePoint.y);
|
|
76
|
+
if (x == null || y == null) return null;
|
|
77
|
+
return { x: x + (activePoint.dx ?? 0), y: y + (activePoint.dy ?? 0) };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
$effect(() => {
|
|
81
|
+
onActivePointPixel?.(activePixel);
|
|
82
|
+
});
|
|
54
83
|
|
|
55
84
|
const showCrosshairX = $derived(marker.ruleX !== false && (impliesCrosshairX || marker.ruleX === true));
|
|
56
85
|
const showCrosshairY = $derived(marker.ruleY !== false && (impliesCrosshairY || marker.ruleY === true));
|
|
@@ -115,6 +144,8 @@
|
|
|
115
144
|
isHorizontal: false,
|
|
116
145
|
data: [],
|
|
117
146
|
category: areaX,
|
|
147
|
+
insetLeft: activePoint?.areaInset?.start,
|
|
148
|
+
insetRight: activePoint?.areaInset?.end,
|
|
118
149
|
style: areaStyle,
|
|
119
150
|
}}
|
|
120
151
|
/>
|
|
@@ -128,17 +159,23 @@
|
|
|
128
159
|
isHorizontal: true,
|
|
129
160
|
data: [],
|
|
130
161
|
category: areaY,
|
|
162
|
+
insetTop: activePoint?.areaInset?.start,
|
|
163
|
+
insetBottom: activePoint?.areaInset?.end,
|
|
131
164
|
style: areaStyle,
|
|
132
165
|
}}
|
|
133
166
|
/>
|
|
134
167
|
{/if}
|
|
135
168
|
|
|
136
169
|
{#if ruleX != null}
|
|
137
|
-
<
|
|
170
|
+
<g transform="translate({activePoint?.dx ?? 0}, {activePoint?.dy ?? 0})">
|
|
171
|
+
<RuleMarker axis="x" value={ruleX} style={{ ...cfg.hover.rule, ...ruleStyle }} />
|
|
172
|
+
</g>
|
|
138
173
|
{/if}
|
|
139
174
|
|
|
140
175
|
{#if ruleY != null}
|
|
141
|
-
<
|
|
176
|
+
<g transform="translate({activePoint?.dx ?? 0}, {activePoint?.dy ?? 0})">
|
|
177
|
+
<RuleMarker axis="y" value={ruleY} style={{ ...cfg.hover.rule, ...ruleStyle }} />
|
|
178
|
+
</g>
|
|
142
179
|
{/if}
|
|
143
180
|
|
|
144
181
|
{#if data.length > 0}
|
|
@@ -29,6 +29,11 @@ interface Props extends MarkerProps<HoverDisplayPoint> {
|
|
|
29
29
|
activePoint?: HoverDisplayPoint | null;
|
|
30
30
|
impliesCrosshairX?: boolean;
|
|
31
31
|
impliesCrosshairY?: boolean;
|
|
32
|
+
/** Reports `activePoint`'s real pixel position within this facet's own box, or `null` when nothing's matched. */
|
|
33
|
+
onActivePointPixel?: (pos: {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
} | null) => void;
|
|
32
37
|
}
|
|
33
38
|
declare const HoverMarker: import("svelte").Component<Props, {}, "">;
|
|
34
39
|
type HoverMarker = ReturnType<typeof HoverMarker>;
|
|
@@ -12,4 +12,4 @@ import type { BarLayout } from './layout.svelte';
|
|
|
12
12
|
* height instead of `dx`/`dy`, since that offset is representable as a
|
|
13
13
|
* regular data value (unlike 'grouped''s pixel-only slice offset).
|
|
14
14
|
*/
|
|
15
|
-
export declare function buildHoverPoints<TData extends Record<string, unknown>>(resolvedSeries: Series<TData>[], isHorizontal: boolean, geometry: Pick<BarLayout<TData>, 'layout' | 'stackedBaseline' | 'groupOffset'>, colorFor: (seriesName: string) => string): HoverPoint<TData>[];
|
|
15
|
+
export declare function buildHoverPoints<TData extends Record<string, unknown>>(resolvedSeries: Series<TData>[], isHorizontal: boolean, geometry: Pick<BarLayout<TData>, 'layout' | 'stackedBaseline' | 'groupOffset' | 'groupInsetFraction'>, colorFor: (seriesName: string) => string): HoverPoint<TData>[];
|
|
@@ -15,6 +15,7 @@ export function buildHoverPoints(resolvedSeries, isHorizontal, geometry, colorFo
|
|
|
15
15
|
return resolvedSeries.flatMap((s, idx) => {
|
|
16
16
|
const sx = resolveAccessor(s.x);
|
|
17
17
|
const sy = resolveAccessor(s.y);
|
|
18
|
+
const areaInset = geometry.layout === 'grouped' ? geometry.groupInsetFraction(idx) : undefined;
|
|
18
19
|
return s.data.filter((d) => !isGapValue(sy(d))).map((d) => {
|
|
19
20
|
const category = sx(d);
|
|
20
21
|
const rawValue = Number(sy(d));
|
|
@@ -25,6 +26,7 @@ export function buildHoverPoints(resolvedSeries, isHorizontal, geometry, colorFo
|
|
|
25
26
|
y: isHorizontal ? category : value,
|
|
26
27
|
dx,
|
|
27
28
|
dy,
|
|
29
|
+
areaInset,
|
|
28
30
|
row: d,
|
|
29
31
|
label: String(sy(d)),
|
|
30
32
|
series: s.name,
|
|
@@ -96,6 +96,11 @@ export type HoverDisplayPoint = {
|
|
|
96
96
|
*/
|
|
97
97
|
dx?: number;
|
|
98
98
|
dy?: number;
|
|
99
|
+
/** A 'grouped' series' own slice of the category band, as start/end fractions (0–1) of the whole band. `undefined` means the whole band. */
|
|
100
|
+
areaInset?: {
|
|
101
|
+
start: number;
|
|
102
|
+
end: number;
|
|
103
|
+
};
|
|
99
104
|
};
|
|
100
105
|
/** A candidate point for hover matching/highlighting — also carries its source row. */
|
|
101
106
|
export type HoverPoint<T> = HoverDisplayPoint & {
|
|
@@ -52,7 +52,8 @@ export type LineMarkerConfig = {
|
|
|
52
52
|
* for hover, resolved against the live scale at render time; `role:
|
|
53
53
|
* 'highlight'` is `HoverMarkerConfig`'s `areaX`/`areaY` highlight — a
|
|
54
54
|
* visible, non-interactive rect centered on `category`, spanning the full
|
|
55
|
-
* opposite axis, sized to the category band's own width plus 2px
|
|
55
|
+
* opposite axis, sized to the category band's own width plus 2px (or a
|
|
56
|
+
* single series' own slice, via `insetTop`/etc.).
|
|
56
57
|
*/
|
|
57
58
|
export type BarMarkerConfig = {
|
|
58
59
|
type: 'bar';
|