svelteplot 0.3.10 → 0.3.11
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.
|
@@ -64,6 +64,14 @@
|
|
|
64
64
|
transform="translate({useFacetX ? facetXScale(facetX) : 0}, {useFacetY
|
|
65
65
|
? facetYScale(facetY)
|
|
66
66
|
: 0})">
|
|
67
|
+
<!-- facets need invisible rect -->
|
|
68
|
+
<rect
|
|
69
|
+
x={plot.options.marginLeft}
|
|
70
|
+
y={plot.options.marginTop}
|
|
71
|
+
width={facetWidth}
|
|
72
|
+
height={facetHeight}
|
|
73
|
+
fill="transparent"
|
|
74
|
+
pointer-events="all" />
|
|
67
75
|
<Facet
|
|
68
76
|
fx={facetX}
|
|
69
77
|
fy={facetY}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
<script lang="ts" generics="Datum extends DataRow">
|
|
2
2
|
interface PointerMarkProps {
|
|
3
3
|
data: Datum[];
|
|
4
|
-
children
|
|
4
|
+
children?: Snippet<[{ data: Datum[] }]>;
|
|
5
5
|
x?: ChannelAccessor<Datum>;
|
|
6
6
|
y?: ChannelAccessor<Datum>;
|
|
7
7
|
z?: ChannelAccessor<Datum>;
|
|
8
|
+
fx?: ChannelAccessor<Datum>;
|
|
9
|
+
fy?: ChannelAccessor<Datum>;
|
|
8
10
|
/**
|
|
9
11
|
* maximum cursor distance to select data points
|
|
10
12
|
*/
|
|
11
13
|
maxDistance?: number;
|
|
14
|
+
/**
|
|
15
|
+
* tolerance for considering points as "the same" when sharing x or y values
|
|
16
|
+
* defaults to 0 pixel
|
|
17
|
+
*/
|
|
18
|
+
tolerance?: number;
|
|
12
19
|
/**
|
|
13
20
|
* called whenever the selection changes
|
|
14
21
|
* @param data
|
|
@@ -24,10 +31,14 @@
|
|
|
24
31
|
import { projectXY } from '../helpers/scales.js';
|
|
25
32
|
import isDataRecord from '../helpers/isDataRecord.js';
|
|
26
33
|
import { RAW_VALUE } from '../transforms/recordize.js';
|
|
34
|
+
import { groupFacetsAndZ } from '../helpers/group.js';
|
|
27
35
|
|
|
28
36
|
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
29
37
|
const plot = $derived(getPlotState());
|
|
30
38
|
|
|
39
|
+
const POINTER_X = Symbol('pointerX');
|
|
40
|
+
const POINTER_Y = Symbol('pointerY');
|
|
41
|
+
|
|
31
42
|
let markProps: PointerMarkProps = $props();
|
|
32
43
|
|
|
33
44
|
const DEFAULTS = {
|
|
@@ -40,7 +51,10 @@
|
|
|
40
51
|
x,
|
|
41
52
|
y,
|
|
42
53
|
z,
|
|
54
|
+
fx,
|
|
55
|
+
fy,
|
|
43
56
|
maxDistance = 15,
|
|
57
|
+
tolerance = Number.NEGATIVE_INFINITY,
|
|
44
58
|
onupdate = null
|
|
45
59
|
}: PointerMarkProps = $derived({
|
|
46
60
|
...DEFAULTS,
|
|
@@ -49,27 +63,21 @@
|
|
|
49
63
|
|
|
50
64
|
let selectedData = $state([]);
|
|
51
65
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
function onTouchMove(evt: TouchEvent) {
|
|
57
|
-
if (evt.touches) {
|
|
58
|
-
const rect = (evt.target as HTMLElement).getBoundingClientRect();
|
|
59
|
-
const pageTop = window.scrollY || document.documentElement.scrollTop;
|
|
60
|
-
const ox = rect.left;
|
|
61
|
-
const oy = rect.top + pageTop;
|
|
62
|
-
|
|
63
|
-
const touch = evt.touches[0] || evt.changedTouches[0];
|
|
64
|
-
if (touch) {
|
|
65
|
-
const ex = touch.pageX - ox;
|
|
66
|
-
const ey = touch.pageY - oy;
|
|
67
|
-
updateSelection(ex, ey);
|
|
68
|
-
}
|
|
66
|
+
function onPointerMove(evt: MouseEvent) {
|
|
67
|
+
let facetEl = evt.target as SVGElement;
|
|
68
|
+
while (facetEl && !facetEl.classList.contains('facet')) {
|
|
69
|
+
facetEl = facetEl.parentElement;
|
|
69
70
|
}
|
|
71
|
+
const facetRect = (facetEl?.firstChild ?? plot.body).getBoundingClientRect();
|
|
72
|
+
|
|
73
|
+
const relativeX = evt.clientX - facetRect.left + (plot.options.marginLeft ?? 0);
|
|
74
|
+
const relativeY = evt.clientY - facetRect.top + (plot.options.marginTop ?? 0);
|
|
75
|
+
|
|
76
|
+
// console.log({ relativeX, relativeY }, evt);
|
|
77
|
+
updateSelection(relativeX, relativeY);
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
function
|
|
80
|
+
function onPointerLeave() {
|
|
73
81
|
selectedData = [];
|
|
74
82
|
if (onupdate) onupdate(selectedData);
|
|
75
83
|
}
|
|
@@ -79,31 +87,44 @@
|
|
|
79
87
|
const points = trees.map((tree) =>
|
|
80
88
|
tree.find(x != null ? ex : 0, y != null ? ey : 0, maxDistance)
|
|
81
89
|
);
|
|
82
|
-
|
|
90
|
+
// also include other points that share the same x or y value
|
|
91
|
+
const otherPoints = trees.flatMap((tree, i) => {
|
|
92
|
+
return tree
|
|
93
|
+
.data()
|
|
94
|
+
.filter((d) => d !== points[i])
|
|
95
|
+
.filter(
|
|
96
|
+
(d) =>
|
|
97
|
+
(!isFinite(d[POINTER_X]) ||
|
|
98
|
+
Math.abs(d[POINTER_X] - points[i]?.[POINTER_X]) < tolerance) &&
|
|
99
|
+
(!isFinite(d[POINTER_Y]) ||
|
|
100
|
+
Math.abs(d[POINTER_Y] - points[i]?.[POINTER_Y]) < tolerance)
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
selectedData = [...points, ...otherPoints].filter((d) => d != null);
|
|
83
104
|
if (onupdate) onupdate(selectedData);
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
$effect(() => {
|
|
87
|
-
plot.body?.addEventListener('
|
|
88
|
-
plot.body?.addEventListener('
|
|
89
|
-
plot.body?.addEventListener('touchmove', onTouchMove);
|
|
108
|
+
plot.body?.addEventListener('pointermove', onPointerMove);
|
|
109
|
+
plot.body?.addEventListener('pointerleave', onPointerLeave);
|
|
90
110
|
|
|
91
111
|
return () => {
|
|
92
|
-
plot.body?.removeEventListener('
|
|
93
|
-
plot.body?.removeEventListener('
|
|
94
|
-
plot.body?.removeEventListener('touchmove', onTouchMove);
|
|
112
|
+
plot.body?.removeEventListener('pointermove', onPointerMove);
|
|
113
|
+
plot.body?.removeEventListener('pointerleave', onPointerLeave);
|
|
95
114
|
};
|
|
96
115
|
});
|
|
97
116
|
|
|
98
|
-
const groups = $derived(
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
const groups = $derived.by(() => {
|
|
118
|
+
const groups = [];
|
|
119
|
+
groupFacetsAndZ(data, { x, y, z, fx, fy }, (d) => groups.push(d));
|
|
120
|
+
return groups;
|
|
121
|
+
});
|
|
101
122
|
|
|
102
123
|
const trees = $derived(
|
|
103
|
-
groups.map((
|
|
124
|
+
groups.map((items) =>
|
|
104
125
|
quadtree()
|
|
105
|
-
.x(x != null ? (d) => d
|
|
106
|
-
.y(y != null ? (d) => d
|
|
126
|
+
.x(x != null ? (d) => d[POINTER_X] : () => 0)
|
|
127
|
+
.y(y != null ? (d) => d[POINTER_Y] : () => 0)
|
|
107
128
|
.addAll(
|
|
108
129
|
items?.map((d) => {
|
|
109
130
|
const [px, py] = projectXY(
|
|
@@ -115,8 +136,8 @@
|
|
|
115
136
|
);
|
|
116
137
|
return {
|
|
117
138
|
...(isDataRecord(d) ? d : { [RAW_VALUE]: d }),
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
[POINTER_X]: px,
|
|
140
|
+
[POINTER_Y]: py
|
|
120
141
|
};
|
|
121
142
|
}) ?? []
|
|
122
143
|
)
|
|
@@ -3,16 +3,23 @@ import type { ChannelAccessor, DataRow } from '../types/index.js';
|
|
|
3
3
|
declare class __sveltets_Render<Datum extends DataRow> {
|
|
4
4
|
props(): {
|
|
5
5
|
data: Datum[];
|
|
6
|
-
children
|
|
6
|
+
children?: Snippet<[{
|
|
7
7
|
data: Datum[];
|
|
8
|
-
}]
|
|
8
|
+
}]> | undefined;
|
|
9
9
|
x?: ChannelAccessor<Datum>;
|
|
10
10
|
y?: ChannelAccessor<Datum>;
|
|
11
11
|
z?: ChannelAccessor<Datum>;
|
|
12
|
+
fx?: ChannelAccessor<Datum>;
|
|
13
|
+
fy?: ChannelAccessor<Datum>;
|
|
12
14
|
/**
|
|
13
15
|
* maximum cursor distance to select data points
|
|
14
16
|
*/
|
|
15
17
|
maxDistance?: number;
|
|
18
|
+
/**
|
|
19
|
+
* tolerance for considering points as "the same" when sharing x or y values
|
|
20
|
+
* defaults to 0 pixel
|
|
21
|
+
*/
|
|
22
|
+
tolerance?: number;
|
|
16
23
|
/**
|
|
17
24
|
* called whenever the selection changes
|
|
18
25
|
* @param data
|