svelteplot 0.3.9-pr-150.0 → 0.3.10-pr-151.0

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}
@@ -9,6 +9,11 @@
9
9
  * maximum cursor distance to select data points
10
10
  */
11
11
  maxDistance?: number;
12
+ /**
13
+ * tolerance for considering points as "the same" when sharing x or y values
14
+ * defaults to 0 pixel
15
+ */
16
+ tolerance?: number;
12
17
  /**
13
18
  * called whenever the selection changes
14
19
  * @param data
@@ -28,6 +33,9 @@
28
33
  const { getPlotState } = getContext<PlotContext>('svelteplot');
29
34
  const plot = $derived(getPlotState());
30
35
 
36
+ const POINTER_X = Symbol('pointerX');
37
+ const POINTER_Y = Symbol('pointerY');
38
+
31
39
  let markProps: PointerMarkProps = $props();
32
40
 
33
41
  const DEFAULTS = {
@@ -41,6 +49,7 @@
41
49
  y,
42
50
  z,
43
51
  maxDistance = 15,
52
+ tolerance = Number.NEGATIVE_INFINITY,
44
53
  onupdate = null
45
54
  }: PointerMarkProps = $derived({
46
55
  ...DEFAULTS,
@@ -49,27 +58,21 @@
49
58
 
50
59
  let selectedData = $state([]);
51
60
 
52
- function onMouseMove(evt: MouseEvent) {
53
- updateSelection(evt.layerX, evt.layerY);
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
- }
61
+ function onPointerMove(evt: MouseEvent) {
62
+ let facetEl = evt.target as SVGElement;
63
+ while (facetEl && !facetEl.classList.contains('facet')) {
64
+ facetEl = facetEl.parentElement;
69
65
  }
66
+ const facetRect = (facetEl?.firstChild ?? plot.body).getBoundingClientRect();
67
+
68
+ const relativeX = evt.clientX - facetRect.left + (plot.options.marginLeft ?? 0);
69
+ const relativeY = evt.clientY - facetRect.top + (plot.options.marginTop ?? 0);
70
+
71
+ // console.log({ relativeX, relativeY }, evt);
72
+ updateSelection(relativeX, relativeY);
70
73
  }
71
74
 
72
- function onMouseLeave() {
75
+ function onPointerLeave() {
73
76
  selectedData = [];
74
77
  if (onupdate) onupdate(selectedData);
75
78
  }
@@ -79,19 +82,30 @@
79
82
  const points = trees.map((tree) =>
80
83
  tree.find(x != null ? ex : 0, y != null ? ey : 0, maxDistance)
81
84
  );
82
- selectedData = points.filter((d) => d != null);
85
+ // also include other points that share the same x or y value
86
+ const otherPoints = trees.flatMap((tree, i) => {
87
+ return tree
88
+ .data()
89
+ .filter((d) => d !== points[i])
90
+ .filter(
91
+ (d) =>
92
+ (!isFinite(d[POINTER_X]) ||
93
+ Math.abs(d[POINTER_X] - points[i]?.[POINTER_X]) < tolerance) &&
94
+ (!isFinite(d[POINTER_Y]) ||
95
+ Math.abs(d[POINTER_Y] - points[i]?.[POINTER_Y]) < tolerance)
96
+ );
97
+ });
98
+ selectedData = [...points, ...otherPoints].filter((d) => d != null);
83
99
  if (onupdate) onupdate(selectedData);
84
100
  }
85
101
 
86
102
  $effect(() => {
87
- plot.body?.addEventListener('mousemove', onMouseMove);
88
- plot.body?.addEventListener('mouseleave', onMouseLeave);
89
- plot.body?.addEventListener('touchmove', onTouchMove);
103
+ plot.body?.addEventListener('pointermove', onPointerMove);
104
+ plot.body?.addEventListener('pointerleave', onPointerLeave);
90
105
 
91
106
  return () => {
92
- plot.body?.removeEventListener('mousemove', onMouseMove);
93
- plot.body?.removeEventListener('mouseleave', onMouseLeave);
94
- plot.body?.removeEventListener('touchmove', onTouchMove);
107
+ plot.body?.removeEventListener('pointermove', onPointerMove);
108
+ plot.body?.removeEventListener('pointerleave', onPointerLeave);
95
109
  };
96
110
  });
97
111
 
@@ -102,8 +116,8 @@
102
116
  const trees = $derived(
103
117
  groups.map(([, items]) =>
104
118
  quadtree()
105
- .x(x != null ? (d) => d.__pointerX : () => 0)
106
- .y(y != null ? (d) => d.__pointerY : () => 0)
119
+ .x(x != null ? (d) => d[POINTER_X] : () => 0)
120
+ .y(y != null ? (d) => d[POINTER_Y] : () => 0)
107
121
  .addAll(
108
122
  items?.map((d) => {
109
123
  const [px, py] = projectXY(
@@ -115,8 +129,8 @@
115
129
  );
116
130
  return {
117
131
  ...(isDataRecord(d) ? d : { [RAW_VALUE]: d }),
118
- __pointerX: px,
119
- __pointerY: py
132
+ [POINTER_X]: px,
133
+ [POINTER_Y]: py
120
134
  };
121
135
  }) ?? []
122
136
  )
@@ -13,6 +13,11 @@ declare class __sveltets_Render<Datum extends DataRow> {
13
13
  * maximum cursor distance to select data points
14
14
  */
15
15
  maxDistance?: number;
16
+ /**
17
+ * tolerance for considering points as "the same" when sharing x or y values
18
+ * defaults to 0 pixel
19
+ */
20
+ tolerance?: number;
16
21
  /**
17
22
  * called whenever the selection changes
18
23
  * @param data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.3.9-pr-150.0",
3
+ "version": "0.3.10-pr-151.0",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",