hyper-scatter 0.1.0 → 0.2.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.
@@ -39,7 +39,7 @@
39
39
  * is called, because the demo/harness may re-initialize renderers and we
40
40
  * want `init()` to remain side-effect-light.
41
41
  */
42
- import { DEFAULT_COLORS, SELECTION_COLOR, HOVER_COLOR, createIndicesSelectionResult, createGeometrySelectionResult, } from '../core/types.js';
42
+ import { DEFAULT_COLORS, SELECTION_COLOR, HOVER_COLOR, createIndicesSelectionResult, } from '../core/types.js';
43
43
  import { createEuclideanView, projectEuclidean, unprojectEuclidean, panEuclidean, zoomEuclidean, } from '../core/math/euclidean.js';
44
44
  import { createHyperbolicView, projectPoincare, unprojectPoincare, panPoincare, zoomPoincare, } from '../core/math/poincare.js';
45
45
  import { UniformGridIndex, } from './spatial_index.js';
@@ -463,6 +463,14 @@ class WebGLRendererBase {
463
463
  pointRadiusCss = 3;
464
464
  colors = DEFAULT_COLORS;
465
465
  backgroundColor = '#0a0a0a';
466
+ categoryVisibilityMask = new Uint8Array(0);
467
+ hasCategoryVisibilityMask = false;
468
+ categoryAlpha = 1;
469
+ interactionStyle = {
470
+ selectionColor: SELECTION_COLOR,
471
+ hoverColor: HOVER_COLOR,
472
+ hoverFillColor: null,
473
+ };
466
474
  // Hyperbolic backdrop styling (Poincaré disk). Neutral grayscale defaults.
467
475
  // Override per app via InitOptions as needed.
468
476
  poincareDiskFillColor = '#141414';
@@ -747,6 +755,74 @@ class WebGLRendererBase {
747
755
  // Dataset changes don't affect the backdrop, but point DPR might have changed.
748
756
  this.markBackdropDirty();
749
757
  }
758
+ setPalette(colors) {
759
+ this.colors = colors;
760
+ this.paletteDirty = true;
761
+ if (this.gl) {
762
+ this.uploadPaletteUniforms();
763
+ }
764
+ }
765
+ setCategoryVisibility(mask) {
766
+ if (mask == null) {
767
+ this.categoryVisibilityMask = new Uint8Array(0);
768
+ this.hasCategoryVisibilityMask = false;
769
+ }
770
+ else {
771
+ const n = mask.length >>> 0;
772
+ const next = new Uint8Array(n);
773
+ for (let i = 0; i < n; i++) {
774
+ next[i] = mask[i] ? 1 : 0;
775
+ }
776
+ this.categoryVisibilityMask = next;
777
+ this.hasCategoryVisibilityMask = true;
778
+ }
779
+ if (this.hoveredIndex >= 0 && !this.isPointVisibleByCategory(this.hoveredIndex)) {
780
+ this.hoveredIndex = -1;
781
+ }
782
+ this.paletteDirty = true;
783
+ this.selectionDirty = true;
784
+ this.hoverDirty = true;
785
+ if (this.gl) {
786
+ this.uploadPaletteUniforms();
787
+ this.uploadSelectionToGPU();
788
+ this.uploadHoverToGPU();
789
+ }
790
+ }
791
+ setCategoryAlpha(alpha) {
792
+ const next = Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1;
793
+ if (Math.abs(next - this.categoryAlpha) <= 1e-12)
794
+ return;
795
+ this.categoryAlpha = next;
796
+ this.paletteDirty = true;
797
+ if (this.gl) {
798
+ this.uploadPaletteUniforms();
799
+ }
800
+ }
801
+ setInteractionStyle(style) {
802
+ if (typeof style.selectionColor === 'string' && style.selectionColor.length > 0) {
803
+ this.interactionStyle.selectionColor = style.selectionColor;
804
+ }
805
+ if (typeof style.hoverColor === 'string' && style.hoverColor.length > 0) {
806
+ this.interactionStyle.hoverColor = style.hoverColor;
807
+ }
808
+ if (Object.prototype.hasOwnProperty.call(style, 'hoverFillColor')) {
809
+ this.interactionStyle.hoverFillColor = style.hoverFillColor ?? null;
810
+ }
811
+ }
812
+ isCategoryVisible(category) {
813
+ if (!this.hasCategoryVisibilityMask)
814
+ return true;
815
+ const mask = this.categoryVisibilityMask;
816
+ if (category < 0 || category >= mask.length)
817
+ return true;
818
+ return mask[category] !== 0;
819
+ }
820
+ isPointVisibleByCategory(index) {
821
+ const ds = this.dataset;
822
+ if (!ds || index < 0 || index >= ds.n)
823
+ return false;
824
+ return this.isCategoryVisible(ds.labels[index]);
825
+ }
750
826
  resize(width, height) {
751
827
  this.width = width;
752
828
  this.height = height;
@@ -774,7 +850,12 @@ class WebGLRendererBase {
774
850
  return this.selection.size <= 200_000 ? new Set(this.selection) : this.selection;
775
851
  }
776
852
  setHovered(index) {
777
- this.hoveredIndex = index;
853
+ if (index >= 0 && !this.isPointVisibleByCategory(index)) {
854
+ this.hoveredIndex = -1;
855
+ }
856
+ else {
857
+ this.hoveredIndex = index;
858
+ }
778
859
  this.hoverDirty = true;
779
860
  if (this.gl) {
780
861
  this.uploadHoverToGPU();
@@ -894,16 +975,19 @@ class WebGLRendererBase {
894
975
  this.paletteBytes[0] = 255;
895
976
  this.paletteBytes[1] = 255;
896
977
  this.paletteBytes[2] = 255;
897
- this.paletteBytes[3] = 255;
978
+ const visible = this.isCategoryVisible(0);
979
+ this.paletteBytes[3] = visible ? Math.round(255 * this.categoryAlpha) : 0;
898
980
  }
899
981
  else {
900
982
  for (let i = 0; i < size; i++) {
901
983
  const [r, g, b, a] = parseHexColorBytes(this.colors[i]);
984
+ const visible = this.isCategoryVisible(i);
985
+ const alpha = visible ? Math.round(a * this.categoryAlpha) : 0;
902
986
  const o = i * 4;
903
987
  this.paletteBytes[o + 0] = r;
904
988
  this.paletteBytes[o + 1] = g;
905
989
  this.paletteBytes[o + 2] = b;
906
- this.paletteBytes[o + 3] = a;
990
+ this.paletteBytes[o + 3] = alpha;
907
991
  }
908
992
  }
909
993
  if (!this.paletteTex) {
@@ -959,8 +1043,14 @@ class WebGLRendererBase {
959
1043
  const idx = this.dataIndex;
960
1044
  if (!ds || !idx)
961
1045
  return 0;
962
- if (result.indices)
963
- return result.indices.size;
1046
+ if (result.indices) {
1047
+ let visibleCount = 0;
1048
+ for (const i of result.indices) {
1049
+ if (this.isPointVisibleByCategory(i))
1050
+ visibleCount++;
1051
+ }
1052
+ return visibleCount;
1053
+ }
964
1054
  if (result.kind !== 'geometry' || !result.geometry)
965
1055
  return 0;
966
1056
  const polygon = result.geometry.coords;
@@ -1026,6 +1116,8 @@ class WebGLRendererBase {
1026
1116
  const end = offsets[cell + 1];
1027
1117
  for (let k = start; k < end; k++) {
1028
1118
  const i = ids[k];
1119
+ if (!this.isCategoryVisible(ds.labels[i]))
1120
+ continue;
1029
1121
  const x = positions[i * 2];
1030
1122
  const y = positions[i * 2 + 1];
1031
1123
  // Tight AABB prefilter (cells overlap bounds).
@@ -1432,6 +1524,8 @@ class WebGLRendererBase {
1432
1524
  const lab = new Uint16Array(renderCount);
1433
1525
  let k = 0;
1434
1526
  for (const i of this.selection) {
1527
+ if (!this.isPointVisibleByCategory(i))
1528
+ continue;
1435
1529
  pos[k * 2] = ds.positions[i * 2];
1436
1530
  pos[k * 2 + 1] = ds.positions[i * 2 + 1];
1437
1531
  lab[k] = ds.labels[i];
@@ -1461,6 +1555,8 @@ class WebGLRendererBase {
1461
1555
  const indices = new Uint32Array(renderCount);
1462
1556
  let k = 0;
1463
1557
  for (const i of this.selection) {
1558
+ if (!this.isPointVisibleByCategory(i))
1559
+ continue;
1464
1560
  indices[k++] = i;
1465
1561
  if (k >= renderCount)
1466
1562
  break;
@@ -1479,7 +1575,11 @@ class WebGLRendererBase {
1479
1575
  const ds = this.dataset;
1480
1576
  if (!ds || !this.hoverVao || !this.hoverPosBuffer || !this.hoverLabelBuffer)
1481
1577
  return;
1482
- const i = (this.hoveredIndex >= 0 && this.hoveredIndex < ds.n) ? this.hoveredIndex : -1;
1578
+ const i = (this.hoveredIndex >= 0 &&
1579
+ this.hoveredIndex < ds.n &&
1580
+ this.isPointVisibleByCategory(this.hoveredIndex))
1581
+ ? this.hoveredIndex
1582
+ : -1;
1483
1583
  const pos = this.hoverPosScratch;
1484
1584
  const lab = this.hoverLabScratch;
1485
1585
  if (i >= 0) {
@@ -1502,7 +1602,9 @@ class WebGLRendererBase {
1502
1602
  this.hoverDirty = false;
1503
1603
  return;
1504
1604
  }
1505
- const idx = this.hoveredIndex >= 0 ? this.hoveredIndex : 0;
1605
+ const idx = this.hoveredIndex >= 0 && this.isPointVisibleByCategory(this.hoveredIndex)
1606
+ ? this.hoveredIndex
1607
+ : 0;
1506
1608
  const indices = this.hoverIndexScratch;
1507
1609
  indices[0] = idx;
1508
1610
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.hoverEbo);
@@ -1645,7 +1747,7 @@ class WebGLRendererBase {
1645
1747
  fragmentBudget: this.policy.fragmentBudget,
1646
1748
  isInteracting,
1647
1749
  };
1648
- // Selection overlay (still into points buffer)
1750
+ // Selection overlay (ring + label-colored fill, still into points buffer)
1649
1751
  if (!isInteracting && this.selection.size > 0) {
1650
1752
  gl.useProgram(this.programSolid);
1651
1753
  this.bindViewUniformsForProgram(this.programSolid);
@@ -1656,13 +1758,13 @@ class WebGLRendererBase {
1656
1758
  if (this.uPointRadiusSolid)
1657
1759
  gl.uniform1f(this.uPointRadiusSolid, this.pointRadiusCss + 1);
1658
1760
  if (this.uSolidColor) {
1659
- const [r, g, b, a] = parseHexColor(SELECTION_COLOR);
1761
+ const [r, g, b, a] = parseHexColor(this.interactionStyle.selectionColor);
1660
1762
  gl.uniform4f(this.uSolidColor, r, g, b, a);
1661
1763
  }
1662
1764
  if (this.uSolidRingMode)
1663
- gl.uniform1i(this.uSolidRingMode, 0);
1765
+ gl.uniform1i(this.uSolidRingMode, 1);
1664
1766
  if (this.uSolidRingThicknessPx)
1665
- gl.uniform1f(this.uSolidRingThicknessPx, 0);
1767
+ gl.uniform1f(this.uSolidRingThicknessPx, 2);
1666
1768
  if (this.uSolidPointSizePx)
1667
1769
  gl.uniform1f(this.uSolidPointSizePx, (this.pointRadiusCss + 1) * 2 * this.dpr);
1668
1770
  if (this.gpuUsesFullDataset) {
@@ -1675,9 +1777,33 @@ class WebGLRendererBase {
1675
1777
  gl.drawArrays(gl.POINTS, 0, this.selectionOverlayCount);
1676
1778
  gl.bindVertexArray(this.vao);
1677
1779
  }
1780
+ const circlePoints = this.pointsCircle;
1781
+ if (circlePoints) {
1782
+ gl.useProgram(circlePoints.program);
1783
+ this.bindViewUniformsForProgram(circlePoints.program);
1784
+ if (this.paletteDirty)
1785
+ this.uploadPaletteUniforms();
1786
+ this.bindPaletteTexture();
1787
+ if (circlePoints.uCssSize)
1788
+ gl.uniform2f(circlePoints.uCssSize, this.width, this.height);
1789
+ if (circlePoints.uDpr)
1790
+ gl.uniform1f(circlePoints.uDpr, this.dpr);
1791
+ if (circlePoints.uPointRadius)
1792
+ gl.uniform1f(circlePoints.uPointRadius, this.pointRadiusCss);
1793
+ if (this.gpuUsesFullDataset) {
1794
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.selectionEbo);
1795
+ gl.drawElements(gl.POINTS, this.selectionOverlayCount, gl.UNSIGNED_INT, 0);
1796
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
1797
+ }
1798
+ else if (this.selectionVao && this.selectionOverlayCount > 0) {
1799
+ gl.bindVertexArray(this.selectionVao);
1800
+ gl.drawArrays(gl.POINTS, 0, this.selectionOverlayCount);
1801
+ gl.bindVertexArray(this.vao);
1802
+ }
1803
+ }
1678
1804
  }
1679
1805
  // Hover overlay (still into points buffer)
1680
- if (!isInteracting && this.hoveredIndex >= 0 && this.hoveredIndex < ds.n) {
1806
+ if (!isInteracting && this.hoveredIndex >= 0 && this.hoveredIndex < ds.n && this.isPointVisibleByCategory(this.hoveredIndex)) {
1681
1807
  // Ring
1682
1808
  gl.useProgram(this.programSolid);
1683
1809
  this.bindViewUniformsForProgram(this.programSolid);
@@ -1690,7 +1816,7 @@ class WebGLRendererBase {
1690
1816
  if (this.uPointRadiusSolid)
1691
1817
  gl.uniform1f(this.uPointRadiusSolid, ringRadius);
1692
1818
  if (this.uSolidColor) {
1693
- const [r, g, b, a] = parseHexColor(HOVER_COLOR);
1819
+ const [r, g, b, a] = parseHexColor(this.interactionStyle.hoverColor);
1694
1820
  gl.uniform4f(this.uSolidColor, r, g, b, a);
1695
1821
  }
1696
1822
  if (this.uSolidRingMode)
@@ -1708,20 +1834,19 @@ class WebGLRendererBase {
1708
1834
  gl.drawArrays(gl.POINTS, 0, 1);
1709
1835
  gl.bindVertexArray(this.vao);
1710
1836
  }
1711
- // Fill pass (selection color if selected else palette)
1837
+ // Fill pass (selection ring if selected else fill)
1712
1838
  const fillRadius = this.pointRadiusCss + 1;
1713
1839
  if (this.selection.has(this.hoveredIndex)) {
1714
- // Solid red
1715
1840
  if (this.uPointRadiusSolid)
1716
1841
  gl.uniform1f(this.uPointRadiusSolid, fillRadius);
1717
1842
  if (this.uSolidColor) {
1718
- const [r, g, b, a] = parseHexColor(SELECTION_COLOR);
1843
+ const [r, g, b, a] = parseHexColor(this.interactionStyle.selectionColor);
1719
1844
  gl.uniform4f(this.uSolidColor, r, g, b, a);
1720
1845
  }
1721
1846
  if (this.uSolidRingMode)
1722
- gl.uniform1i(this.uSolidRingMode, 0);
1847
+ gl.uniform1i(this.uSolidRingMode, 1);
1723
1848
  if (this.uSolidRingThicknessPx)
1724
- gl.uniform1f(this.uSolidRingThicknessPx, 0);
1849
+ gl.uniform1f(this.uSolidRingThicknessPx, 2);
1725
1850
  if (this.uSolidPointSizePx)
1726
1851
  gl.uniform1f(this.uSolidPointSizePx, fillRadius * 2 * this.dpr);
1727
1852
  if (this.gpuUsesFullDataset) {
@@ -1732,30 +1857,79 @@ class WebGLRendererBase {
1732
1857
  gl.drawArrays(gl.POINTS, 0, 1);
1733
1858
  gl.bindVertexArray(this.vao);
1734
1859
  }
1860
+ const circlePoints = this.pointsCircle;
1861
+ if (circlePoints) {
1862
+ gl.useProgram(circlePoints.program);
1863
+ this.bindViewUniformsForProgram(circlePoints.program);
1864
+ if (this.paletteDirty)
1865
+ this.uploadPaletteUniforms();
1866
+ this.bindPaletteTexture();
1867
+ if (circlePoints.uCssSize)
1868
+ gl.uniform2f(circlePoints.uCssSize, this.width, this.height);
1869
+ if (circlePoints.uDpr)
1870
+ gl.uniform1f(circlePoints.uDpr, this.dpr);
1871
+ if (circlePoints.uPointRadius)
1872
+ gl.uniform1f(circlePoints.uPointRadius, this.pointRadiusCss);
1873
+ if (this.gpuUsesFullDataset) {
1874
+ gl.drawElements(gl.POINTS, 1, gl.UNSIGNED_INT, 0);
1875
+ }
1876
+ else if (this.hoverVao) {
1877
+ gl.bindVertexArray(this.hoverVao);
1878
+ gl.drawArrays(gl.POINTS, 0, 1);
1879
+ gl.bindVertexArray(this.vao);
1880
+ }
1881
+ }
1735
1882
  }
1736
1883
  else {
1737
- // Use palette program for correct label color
1738
- const circlePoints = this.pointsCircle;
1739
- if (!circlePoints)
1740
- return;
1741
- gl.useProgram(circlePoints.program);
1742
- this.bindViewUniformsForProgram(circlePoints.program);
1743
- if (this.paletteDirty)
1744
- this.uploadPaletteUniforms();
1745
- this.bindPaletteTexture();
1746
- if (circlePoints.uCssSize)
1747
- gl.uniform2f(circlePoints.uCssSize, this.width, this.height);
1748
- if (circlePoints.uDpr)
1749
- gl.uniform1f(circlePoints.uDpr, this.dpr);
1750
- if (circlePoints.uPointRadius)
1751
- gl.uniform1f(circlePoints.uPointRadius, fillRadius);
1752
- if (this.gpuUsesFullDataset) {
1753
- gl.drawElements(gl.POINTS, 1, gl.UNSIGNED_INT, 0);
1884
+ const hoverFillColor = this.interactionStyle.hoverFillColor;
1885
+ if (hoverFillColor) {
1886
+ gl.useProgram(this.programSolid);
1887
+ this.bindViewUniformsForProgram(this.programSolid);
1888
+ if (this.uPointRadiusSolid)
1889
+ gl.uniform1f(this.uPointRadiusSolid, fillRadius);
1890
+ if (this.uSolidColor) {
1891
+ const [r, g, b, a] = parseHexColor(hoverFillColor);
1892
+ gl.uniform4f(this.uSolidColor, r, g, b, a);
1893
+ }
1894
+ if (this.uSolidRingMode)
1895
+ gl.uniform1i(this.uSolidRingMode, 0);
1896
+ if (this.uSolidRingThicknessPx)
1897
+ gl.uniform1f(this.uSolidRingThicknessPx, 0);
1898
+ if (this.uSolidPointSizePx)
1899
+ gl.uniform1f(this.uSolidPointSizePx, fillRadius * 2 * this.dpr);
1900
+ if (this.gpuUsesFullDataset) {
1901
+ gl.drawElements(gl.POINTS, 1, gl.UNSIGNED_INT, 0);
1902
+ }
1903
+ else if (this.hoverVao) {
1904
+ gl.bindVertexArray(this.hoverVao);
1905
+ gl.drawArrays(gl.POINTS, 0, 1);
1906
+ gl.bindVertexArray(this.vao);
1907
+ }
1754
1908
  }
1755
- else if (this.hoverVao) {
1756
- gl.bindVertexArray(this.hoverVao);
1757
- gl.drawArrays(gl.POINTS, 0, 1);
1758
- gl.bindVertexArray(this.vao);
1909
+ else {
1910
+ // Use palette program for category color when no hover fill override is set.
1911
+ const circlePoints = this.pointsCircle;
1912
+ if (!circlePoints)
1913
+ return;
1914
+ gl.useProgram(circlePoints.program);
1915
+ this.bindViewUniformsForProgram(circlePoints.program);
1916
+ if (this.paletteDirty)
1917
+ this.uploadPaletteUniforms();
1918
+ this.bindPaletteTexture();
1919
+ if (circlePoints.uCssSize)
1920
+ gl.uniform2f(circlePoints.uCssSize, this.width, this.height);
1921
+ if (circlePoints.uDpr)
1922
+ gl.uniform1f(circlePoints.uDpr, this.dpr);
1923
+ if (circlePoints.uPointRadius)
1924
+ gl.uniform1f(circlePoints.uPointRadius, fillRadius);
1925
+ if (this.gpuUsesFullDataset) {
1926
+ gl.drawElements(gl.POINTS, 1, gl.UNSIGNED_INT, 0);
1927
+ }
1928
+ else if (this.hoverVao) {
1929
+ gl.bindVertexArray(this.hoverVao);
1930
+ gl.drawArrays(gl.POINTS, 0, 1);
1931
+ gl.bindVertexArray(this.vao);
1932
+ }
1759
1933
  }
1760
1934
  }
1761
1935
  if (this.gpuUsesFullDataset) {
@@ -1883,6 +2057,8 @@ export class EuclideanWebGLCandidate extends WebGLRendererBase {
1883
2057
  // Avoid building a potentially large candidates array (push-heavy).
1884
2058
  // Instead iterate overlapping grid cells directly.
1885
2059
  idx.forEachInAABB(dataPt.x - dataRadius, dataPt.y - dataRadius, dataPt.x + dataRadius, dataPt.y + dataRadius, (i) => {
2060
+ if (!this.isCategoryVisible(ds.labels[i]))
2061
+ return;
1886
2062
  const dataX = ds.positions[i * 2];
1887
2063
  const dataY = ds.positions[i * 2 + 1];
1888
2064
  // Fast reject in data space (equivalent up to scale).
@@ -1951,11 +2127,22 @@ export class EuclideanWebGLCandidate extends WebGLRendererBase {
1951
2127
  const bounds = { xMin: minX, yMin: minY, xMax: maxX, yMax: maxY };
1952
2128
  const geometry = { type: 'polygon', coords: dataPolyline, bounds };
1953
2129
  const computeTimeMs = performance.now() - startTime;
1954
- return createGeometrySelectionResult(geometry, ds.positions, computeTimeMs, (px, py, polygon) => {
1955
- if (px < bounds.xMin || px > bounds.xMax || py < bounds.yMin || py > bounds.yMax)
1956
- return false;
1957
- return pointInPolygon(px, py, polygon);
1958
- });
2130
+ return {
2131
+ kind: 'geometry',
2132
+ geometry,
2133
+ computeTimeMs,
2134
+ has: (index) => {
2135
+ if (index < 0 || index >= ds.n)
2136
+ return false;
2137
+ if (!this.isCategoryVisible(ds.labels[index]))
2138
+ return false;
2139
+ const px = ds.positions[index * 2];
2140
+ const py = ds.positions[index * 2 + 1];
2141
+ if (px < bounds.xMin || px > bounds.xMax || py < bounds.yMin || py > bounds.yMax)
2142
+ return false;
2143
+ return pointInPolygon(px, py, dataPolyline);
2144
+ },
2145
+ };
1959
2146
  }
1960
2147
  projectToScreen(dataX, dataY) {
1961
2148
  return projectEuclidean(dataX, dataY, this.view, this.width, this.height);
@@ -2135,6 +2322,8 @@ export class HyperbolicWebGLCandidate extends WebGLRendererBase {
2135
2322
  const ay = view.ay;
2136
2323
  // Avoid building a candidates array; iterate overlapping cells directly.
2137
2324
  idx.forEachInAABB(dataPt.x - queryRadius, dataPt.y - queryRadius, dataPt.x + queryRadius, dataPt.y + queryRadius, (i) => {
2325
+ if (!this.isCategoryVisible(ds.labels[i]))
2326
+ return;
2138
2327
  const dataX = ds.positions[i * 2];
2139
2328
  const dataY = ds.positions[i * 2 + 1];
2140
2329
  // mobiusTransform(z) = (z - a) / (1 - conj(a) * z)
@@ -2260,11 +2449,22 @@ export class HyperbolicWebGLCandidate extends WebGLRendererBase {
2260
2449
  const bounds = { xMin: minX, yMin: minY, xMax: maxX, yMax: maxY };
2261
2450
  const geometry = { type: 'polygon', coords: dataPolyline, bounds };
2262
2451
  const computeTimeMs = performance.now() - startTime;
2263
- return createGeometrySelectionResult(geometry, ds.positions, computeTimeMs, (px, py, polygon) => {
2264
- if (px < bounds.xMin || px > bounds.xMax || py < bounds.yMin || py > bounds.yMax)
2265
- return false;
2266
- return pointInPolygon(px, py, polygon);
2267
- });
2452
+ return {
2453
+ kind: 'geometry',
2454
+ geometry,
2455
+ computeTimeMs,
2456
+ has: (index) => {
2457
+ if (index < 0 || index >= ds.n)
2458
+ return false;
2459
+ if (!this.isCategoryVisible(ds.labels[index]))
2460
+ return false;
2461
+ const px = ds.positions[index * 2];
2462
+ const py = ds.positions[index * 2 + 1];
2463
+ if (px < bounds.xMin || px > bounds.xMax || py < bounds.yMin || py > bounds.yMax)
2464
+ return false;
2465
+ return pointInPolygon(px, py, dataPolyline);
2466
+ },
2467
+ };
2268
2468
  }
2269
2469
  projectToScreen(dataX, dataY) {
2270
2470
  return projectPoincare(dataX, dataY, this.view, this.width, this.height);