svelteplot 0.11.0 → 0.11.1-pr-520.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.
Files changed (94) hide show
  1. package/dist/Mark.svelte +6 -1
  2. package/dist/Mark.svelte.d.ts +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/core/Plot.svelte +32 -3
  5. package/dist/helpers/arrowPath.js +10 -5
  6. package/dist/helpers/autoScales.js +7 -3
  7. package/dist/helpers/autoTicks.js +4 -4
  8. package/dist/helpers/autoTimeFormat.js +22 -12
  9. package/dist/helpers/colors.d.ts +4 -4
  10. package/dist/helpers/facets.d.ts +42 -1
  11. package/dist/helpers/facets.js +83 -0
  12. package/dist/helpers/math.js +1 -1
  13. package/dist/helpers/noise.js +1 -1
  14. package/dist/helpers/rasterInterpolate.d.ts +26 -0
  15. package/dist/helpers/rasterInterpolate.js +220 -0
  16. package/dist/helpers/roundedRect.js +1 -1
  17. package/dist/helpers/scales.d.ts +1 -0
  18. package/dist/helpers/scales.js +8 -5
  19. package/dist/helpers/time.js +1 -1
  20. package/dist/helpers/typeChecks.d.ts +1 -0
  21. package/dist/helpers/typeChecks.js +3 -0
  22. package/dist/marks/Area.svelte.d.ts +1 -1
  23. package/dist/marks/AreaX.svelte.d.ts +1 -1
  24. package/dist/marks/AreaY.svelte.d.ts +1 -1
  25. package/dist/marks/Arrow.svelte.d.ts +1 -1
  26. package/dist/marks/AxisX.svelte +8 -3
  27. package/dist/marks/AxisX.svelte.d.ts +1 -1
  28. package/dist/marks/AxisY.svelte +8 -3
  29. package/dist/marks/AxisY.svelte.d.ts +1 -1
  30. package/dist/marks/BarX.svelte.d.ts +1 -1
  31. package/dist/marks/BarY.svelte.d.ts +1 -1
  32. package/dist/marks/BollingerX.svelte.d.ts +1 -1
  33. package/dist/marks/BollingerY.svelte.d.ts +1 -1
  34. package/dist/marks/BoxY.svelte.d.ts +1 -1
  35. package/dist/marks/Brush.svelte.d.ts +1 -1
  36. package/dist/marks/Cell.svelte.d.ts +1 -1
  37. package/dist/marks/CellX.svelte.d.ts +1 -1
  38. package/dist/marks/CellY.svelte.d.ts +1 -1
  39. package/dist/marks/CustomMark.svelte.d.ts +1 -1
  40. package/dist/marks/DifferenceY.svelte.d.ts +1 -1
  41. package/dist/marks/Dot.svelte.d.ts +1 -1
  42. package/dist/marks/DotX.svelte.d.ts +1 -1
  43. package/dist/marks/DotY.svelte.d.ts +1 -1
  44. package/dist/marks/Frame.svelte.d.ts +1 -1
  45. package/dist/marks/Geo.svelte.d.ts +1 -1
  46. package/dist/marks/GridX.svelte.d.ts +1 -1
  47. package/dist/marks/GridY.svelte.d.ts +1 -1
  48. package/dist/marks/HTMLTooltip.svelte +28 -25
  49. package/dist/marks/Image.svelte.d.ts +1 -1
  50. package/dist/marks/Line.svelte +52 -15
  51. package/dist/marks/Line.svelte.d.ts +1 -1
  52. package/dist/marks/LineX.svelte.d.ts +1 -1
  53. package/dist/marks/LineY.svelte.d.ts +1 -1
  54. package/dist/marks/Link.svelte.d.ts +1 -1
  55. package/dist/marks/Pointer.svelte +31 -29
  56. package/dist/marks/Raster.svelte +414 -0
  57. package/dist/marks/Raster.svelte.d.ts +94 -0
  58. package/dist/marks/Rect.svelte.d.ts +1 -1
  59. package/dist/marks/RectX.svelte.d.ts +1 -1
  60. package/dist/marks/RectY.svelte.d.ts +1 -1
  61. package/dist/marks/RuleX.svelte.d.ts +1 -1
  62. package/dist/marks/RuleY.svelte.d.ts +1 -1
  63. package/dist/marks/Spike.svelte.d.ts +1 -1
  64. package/dist/marks/Text.svelte +7 -5
  65. package/dist/marks/Text.svelte.d.ts +2 -2
  66. package/dist/marks/TickX.svelte.d.ts +1 -1
  67. package/dist/marks/TickY.svelte.d.ts +1 -1
  68. package/dist/marks/Trail.svelte.d.ts +1 -1
  69. package/dist/marks/Vector.svelte.d.ts +1 -1
  70. package/dist/marks/WaffleX.svelte.d.ts +1 -1
  71. package/dist/marks/WaffleY.svelte.d.ts +1 -1
  72. package/dist/marks/helpers/Box.svelte.d.ts +1 -1
  73. package/dist/marks/helpers/MarkerPath.svelte.d.ts +1 -1
  74. package/dist/marks/helpers/Regression.svelte +2 -1
  75. package/dist/marks/helpers/trail.js +1 -1
  76. package/dist/marks/helpers/waffle.js +1 -1
  77. package/dist/marks/index.d.ts +1 -0
  78. package/dist/marks/index.js +1 -0
  79. package/dist/regression/polynomial.js +2 -2
  80. package/dist/transforms/bollinger.js +6 -3
  81. package/dist/transforms/density.js +3 -3
  82. package/dist/transforms/group.d.ts +1 -1
  83. package/dist/transforms/interval.d.ts +2 -2
  84. package/dist/transforms/jitter.d.ts +3 -3
  85. package/dist/transforms/map.d.ts +3 -3
  86. package/dist/transforms/map.js +2 -2
  87. package/dist/transforms/normalize.d.ts +2 -2
  88. package/dist/transforms/normalize.js +1 -1
  89. package/dist/transforms/select.d.ts +7 -7
  90. package/dist/transforms/window.d.ts +2 -2
  91. package/dist/types/mark.d.ts +3 -3
  92. package/dist/types/plot.d.ts +8 -1
  93. package/dist/types/scale.d.ts +2 -2
  94. package/package.json +183 -179
@@ -12,7 +12,7 @@ declare function $$render<Datum = RawValue>(): {
12
12
  fill: ChannelAccessor<Datum>;
13
13
  fillOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
14
14
  fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
15
- fontSize: import("../types/index.js").ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
16
16
  fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
17
17
  fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
18
18
  fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
@@ -12,7 +12,7 @@ declare function $$render<Datum = RawValue>(): {
12
12
  fill: ChannelAccessor<Datum>;
13
13
  fillOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
14
14
  fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
15
- fontSize: import("../types/index.js").ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
16
16
  fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
17
17
  fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
18
18
  fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
@@ -27,6 +27,8 @@
27
27
  import { quadtree } from 'd3-quadtree';
28
28
  import { projectX, projectY } from '../helpers/scales.js';
29
29
  import { groupFacetsAndZ } from '../helpers/group.js';
30
+ import { detectFacet, facetKey } from '../helpers/facets.js';
31
+ import { SvelteMap } from 'svelte/reactivity';
30
32
 
31
33
  const plot = usePlot();
32
34
 
@@ -40,21 +42,19 @@
40
42
  let facetOffsetY = $state(0);
41
43
 
42
44
  function onPointerMove(evt: MouseEvent) {
43
- const plotRect = plot.body.getBoundingClientRect();
44
- let facetEl: Element | null = evt.target as Element;
45
- while (facetEl && !facetEl.classList.contains('facet')) {
46
- facetEl = facetEl.parentElement as Element | null;
47
- }
48
- const facetIndex = +((facetEl as HTMLElement)?.dataset?.facet ?? 0);
49
- const facetRect = ((facetEl?.firstChild as Element) ?? plot.body).getBoundingClientRect();
45
+ const { fxValue, fyValue, offsetX, offsetY } = detectFacet(evt, plot);
46
+ const bodyRect = plot.body.getBoundingClientRect();
47
+
48
+ facetOffsetX = offsetX;
49
+ facetOffsetY = offsetY;
50
50
 
51
- facetOffsetX = facetRect.left - plotRect.left - plot.options.marginLeft;
52
- facetOffsetY = facetRect.top - plotRect.top - plot.options.marginTop;
51
+ const relativeX = evt.clientX - bodyRect.left - offsetX;
52
+ const relativeY = evt.clientY - bodyRect.top - offsetY;
53
53
 
54
- const relativeX = evt.clientX - facetRect.left + (plot.options.marginLeft ?? 0);
55
- const relativeY = evt.clientY - facetRect.top + (plot.options.marginTop ?? 0);
54
+ const key = facetKey(fxValue, fyValue);
55
+ const facetTrees = treeMap.get(key) ?? [];
56
+ const pt = facetTrees.length > 0 ? facetTrees[0].find(relativeX, relativeY, 25) : null;
56
57
 
57
- const pt = trees[facetIndex].find(relativeX, relativeY, 25);
58
58
  if (pt) {
59
59
  tooltipX = resolveChannel('x', pt, { x, y, r });
60
60
  tooltipY = resolveChannel('y', pt, { x, y, r });
@@ -73,25 +73,28 @@
73
73
  plot.body?.addEventListener('pointermove', onPointerMove);
74
74
 
75
75
  return () => {
76
- plot.body?.removeEventListener('mouseleave', onPointerLeave);
76
+ plot.body?.removeEventListener('pointerleave', onPointerLeave);
77
77
  plot.body?.removeEventListener('pointermove', onPointerMove);
78
78
  };
79
79
  });
80
80
 
81
- const groups = $derived.by(() => {
82
- const groups: Datum[][] = [];
83
- groupFacetsAndZ(data, { fx, fy }, (d) => groups.push(d));
84
- return groups;
85
- });
86
-
87
- const trees = $derived(
88
- groups.map((items) =>
89
- quadtree<Datum>()
81
+ const treeMap = $derived.by(() => {
82
+ const map = new SvelteMap<string, ReturnType<typeof quadtree<Datum>>[]>();
83
+ groupFacetsAndZ(data, { fx, fy }, (items) => {
84
+ if (!items.length) return;
85
+ const fxVal = fx ? resolveChannel('fx', items[0], { fx }) : true;
86
+ const fyVal = fy ? resolveChannel('fy', items[0], { fy }) : true;
87
+ const key = facetKey(fxVal, fyVal);
88
+ const tree = quadtree<Datum>()
90
89
  .x((d) => projectX('x', plot.scales, resolveChannel('x', d, { x, y, r })))
91
90
  .y((d) => projectY('y', plot.scales, resolveChannel('y', d, { x, y, r })))
92
- .addAll(items)
93
- )
94
- );
91
+ .addAll(items);
92
+ const existing = map.get(key) ?? [];
93
+ existing.push(tree);
94
+ map.set(key, existing);
95
+ });
96
+ return map;
97
+ });
95
98
  </script>
96
99
 
97
100
  <div
@@ -12,7 +12,7 @@ declare function $$render<Datum extends DataRecord>(): {
12
12
  fill: ChannelAccessor<Datum>;
13
13
  fillOpacity: ConstantAccessor<number, Datum>;
14
14
  fontFamily: ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
15
- fontSize: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
15
+ fontSize: ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
16
16
  fontStyle: ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
17
17
  fontVariant: ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
18
18
  fontWeight: ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
@@ -94,28 +94,65 @@
94
94
  const args = $derived(sort(recordizeXY({ data, ...options })));
95
95
 
96
96
  /**
97
- * Groups the data by the specified key
97
+ * Groups the data by the specified key (and optionally a secondary key).
98
+ * When a secondary key is provided, each primary group is further split by
99
+ * the secondary key, with each sub-segment extended to include the first point
100
+ * of the next sub-segment so consecutive segments share an endpoint (enabling
101
+ * multi-colored lines without gaps).
98
102
  */
99
- function groupIndex(data: ScaledDataRecord[], groupByKey: ChannelAccessor<Datum> | null) {
100
- if (!groupByKey) return [data];
101
- let group: ScaledDataRecord[] = [];
102
- const groups: ScaledDataRecord[][] = [group];
103
- let lastGroupValue;
103
+ function groupIndex(
104
+ data: ScaledDataRecord[],
105
+ groupByKey: ChannelAccessor<Datum> | null,
106
+ secondaryKey: ChannelAccessor<Datum> | null = null
107
+ ) {
108
+ if (!groupByKey && !secondaryKey) return [data];
109
+
110
+ // Group by the primary key
111
+ const primaryGroups: ScaledDataRecord[][] = [];
112
+ let primaryGroup: ScaledDataRecord[] = [];
113
+ let lastPrimaryValue: unknown;
104
114
  for (const d of data) {
105
- const groupValue = resolveProp(groupByKey, d.datum);
106
- if (groupValue === lastGroupValue) {
107
- group.push(d);
115
+ const primaryValue = resolveProp(groupByKey!, d.datum);
116
+ if (primaryValue === lastPrimaryValue) {
117
+ primaryGroup.push(d);
108
118
  } else {
109
- // new group
110
- group = [d];
111
- groups.push(group);
112
- lastGroupValue = groupValue;
119
+ primaryGroup = [d];
120
+ primaryGroups.push(primaryGroup);
121
+ lastPrimaryValue = primaryValue;
122
+ }
123
+ }
124
+
125
+ if (!secondaryKey) return primaryGroups;
126
+
127
+ // Further split each primary group by the secondary key. Each sub-segment is
128
+ // extended to include the first point of the next sub-segment so that
129
+ // consecutive segments share an endpoint (no gaps in multi-colored lines).
130
+ const result: ScaledDataRecord[][] = [];
131
+ for (const pGroup of primaryGroups) {
132
+ if (pGroup.length === 0) continue;
133
+ let subGroup: ScaledDataRecord[] = [pGroup[0]];
134
+ let lastSecondaryValue = resolveProp(secondaryKey, pGroup[0].datum);
135
+ for (let i = 1; i < pGroup.length; i++) {
136
+ const d = pGroup[i];
137
+ const secondaryValue = resolveProp(secondaryKey, d.datum);
138
+ if (secondaryValue === lastSecondaryValue) {
139
+ subGroup.push(d);
140
+ } else {
141
+ subGroup.push(d); // extend to connect to next sub-segment
142
+ result.push(subGroup);
143
+ subGroup = [d]; // new sub-segment begins here
144
+ lastSecondaryValue = secondaryValue;
145
+ }
113
146
  }
147
+ result.push(subGroup);
114
148
  }
115
- return groups;
149
+ return result;
116
150
  }
117
151
 
118
152
  const groupByKey = $derived(args.z || args.stroke) as ChannelAccessor<Datum> | null;
153
+ const secondaryKey = $derived(
154
+ args.z && args.stroke ? args.stroke : null
155
+ ) as ChannelAccessor<Datum> | null;
119
156
 
120
157
  const plot = usePlot();
121
158
 
@@ -155,7 +192,7 @@
155
192
  {...args}>
156
193
  {#snippet children({ mark, usedScales, scaledData })}
157
194
  {#if scaledData.length > 0}
158
- {@const groupedLineData = groupIndex(scaledData, groupByKey)}
195
+ {@const groupedLineData = groupIndex(scaledData, groupByKey, secondaryKey)}
159
196
  {#if canvas}
160
197
  <LineCanvas {groupedLineData} {mark} {usedScales} {linePath} {groupByKey} />
161
198
  {:else}
@@ -14,7 +14,7 @@ declare function $$render<Datum extends DataRecord>(): {
14
14
  fill: ChannelAccessor<Datum>;
15
15
  fillOpacity: ConstantAccessor<number, Datum>;
16
16
  fontFamily: ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
17
- fontSize: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
17
+ fontSize: ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
18
18
  fontStyle: ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
19
19
  fontVariant: ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
20
20
  fontWeight: ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
@@ -12,7 +12,7 @@ declare function $$render<Datum extends DataRow>(): {
12
12
  fill: import("../types/index.js").ChannelAccessor<Record<string | symbol, import("../types/index.js").RawValue>>;
13
13
  fillOpacity: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/index.js").RawValue>>;
14
14
  fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Record<string | symbol, import("../types/index.js").RawValue>>;
15
- fontSize: import("../types/index.js").ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Record<string | symbol, import("../types/index.js").RawValue>>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Record<string | symbol, import("../types/index.js").RawValue>>;
16
16
  fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Record<string | symbol, import("../types/index.js").RawValue>>;
17
17
  fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Record<string | symbol, import("../types/index.js").RawValue>>;
18
18
  fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Record<string | symbol, import("../types/index.js").RawValue>>;
@@ -12,7 +12,7 @@ declare function $$render<Datum extends DataRow>(): {
12
12
  fill: import("../types/index.js").ChannelAccessor<Record<string | symbol, import("../types/index.js").RawValue>>;
13
13
  fillOpacity: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/index.js").RawValue>>;
14
14
  fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Record<string | symbol, import("../types/index.js").RawValue>>;
15
- fontSize: import("../types/index.js").ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Record<string | symbol, import("../types/index.js").RawValue>>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Record<string | symbol, import("../types/index.js").RawValue>>;
16
16
  fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Record<string | symbol, import("../types/index.js").RawValue>>;
17
17
  fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Record<string | symbol, import("../types/index.js").RawValue>>;
18
18
  fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Record<string | symbol, import("../types/index.js").RawValue>>;
@@ -13,7 +13,7 @@ declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
13
13
  fill: ChannelAccessor<Datum>;
14
14
  fillOpacity: ConstantAccessor<number, Datum>;
15
15
  fontFamily: ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
16
- fontSize: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | "math" | (string & {}) | "large" | "medium" | "small" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
16
+ fontSize: ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
17
17
  fontStyle: ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
18
18
  fontVariant: ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
19
19
  fontWeight: ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
@@ -40,6 +40,8 @@
40
40
  import { groupFacetsAndZ } from '../helpers/group.js';
41
41
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
42
42
  import { usePlot } from '../hooks/usePlot.svelte.js';
43
+ import { detectFacet, facetKey } from '../helpers/facets.js';
44
+ import { SvelteMap } from 'svelte/reactivity';
43
45
 
44
46
  const plot = usePlot();
45
47
 
@@ -71,17 +73,14 @@
71
73
  let selectedData = $state<any[]>([]);
72
74
 
73
75
  function onPointerMove(evt: MouseEvent) {
74
- let facetEl: Element | null = evt.target as Element;
75
- while (facetEl && !facetEl.classList.contains('facet')) {
76
- facetEl = facetEl.parentElement as Element | null;
77
- }
78
- const facetRect = ((facetEl?.firstChild as Element) ?? plot.body).getBoundingClientRect();
76
+ const { fxValue, fyValue, offsetX, offsetY } = detectFacet(evt, plot);
77
+ const bodyRect = plot.body.getBoundingClientRect();
79
78
 
80
- const relativeX = evt.clientX - facetRect.left + (plot.options.marginLeft ?? 0);
81
- const relativeY = evt.clientY - facetRect.top + (plot.options.marginTop ?? 0);
79
+ const relativeX = evt.clientX - bodyRect.left - offsetX;
80
+ const relativeY = evt.clientY - bodyRect.top - offsetY;
82
81
 
83
- // console.log({ relativeX, relativeY }, evt);
84
- updateSelection(relativeX, relativeY);
82
+ const key = facetKey(fxValue, fyValue);
83
+ updateSelection(relativeX, relativeY, key);
85
84
  }
86
85
 
87
86
  function onPointerLeave() {
@@ -89,13 +88,14 @@
89
88
  if (onupdate) onupdate(selectedData);
90
89
  }
91
90
 
92
- function updateSelection(ex: number, ey: number) {
93
- // find data row with minimum distance to
94
- const points = trees.map((tree) =>
91
+ function updateSelection(ex: number, ey: number, key: string) {
92
+ const facetTrees = treeMap.get(key) ?? [];
93
+ // find data row with minimum distance to cursor
94
+ const points = facetTrees.map((tree) =>
95
95
  tree.find(x != null ? ex : 0, y != null ? ey : 0, maxDistance)
96
96
  );
97
97
  // also include other points that share the same x or y value
98
- const otherPoints = trees.flatMap((tree, i) => {
98
+ const otherPoints = facetTrees.flatMap((tree, i) => {
99
99
  return tree
100
100
  .data()
101
101
  .filter((d) => d !== points[i])
@@ -121,21 +121,19 @@
121
121
  };
122
122
  });
123
123
 
124
- const groups = $derived.by(() => {
125
- const groups: any[][] = [];
126
- groupFacetsAndZ(indexData(data as object[]) as any, { x, y, z, fx, fy }, (d) =>
127
- groups.push(d)
128
- );
129
- return groups;
130
- });
131
-
132
- const trees = $derived(
133
- groups.map((items) =>
134
- quadtree<any>()
124
+ const treeMap = $derived.by(() => {
125
+ const map = new SvelteMap<string, ReturnType<typeof quadtree<any>>[]>();
126
+ groupFacetsAndZ(indexData(data as object[]) as any, { x, y, z, fx, fy }, (items) => {
127
+ if (!items.length) return;
128
+ // Recover fx/fy values from the first datum in the group
129
+ const fxVal = fx ? resolveChannel('fx', items[0], { fx }) : true;
130
+ const fyVal = fy ? resolveChannel('fy', items[0], { fy }) : true;
131
+ const key = facetKey(fxVal, fyVal);
132
+ const tree = quadtree<any>()
135
133
  .x(x != null ? (d: any) => d[POINTER_X] : () => 0)
136
134
  .y(y != null ? (d: any) => d[POINTER_Y] : () => 0)
137
135
  .addAll(
138
- items?.map((d: any) => {
136
+ items.map((d: any) => {
139
137
  const [px, py] = projectXY(
140
138
  plot.scales,
141
139
  resolveChannel('x', d, { x }),
@@ -148,10 +146,14 @@
148
146
  [POINTER_X]: px,
149
147
  [POINTER_Y]: py
150
148
  };
151
- }) ?? []
152
- )
153
- )
154
- );
149
+ })
150
+ );
151
+ const existing = map.get(key) ?? [];
152
+ existing.push(tree);
153
+ map.set(key, existing);
154
+ });
155
+ return map;
156
+ });
155
157
  </script>
156
158
 
157
159
  {#if children}