@haniffalab/cherita-react 2.0.0 → 2.1.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 (67) hide show
  1. package/dist/cjs/components/dotplot/Dotplot.js +4 -5
  2. package/dist/cjs/components/dotplot/DotplotControls.js +7 -3
  3. package/dist/cjs/components/heatmap/Heatmap.js +4 -5
  4. package/dist/cjs/components/icons/HeatmapIcon.js +2 -2
  5. package/dist/cjs/components/matrixplot/Matrixplot.js +4 -5
  6. package/dist/cjs/components/obs-list/ObsItem.js +7 -7
  7. package/dist/cjs/components/offcanvas/OffCanvas.js +7 -4
  8. package/dist/cjs/components/plot/PlotTypeSelector.js +49 -10
  9. package/dist/cjs/components/pseudospatial/Pseudospatial.js +8 -5
  10. package/dist/cjs/components/scatterplot/Scatterplot.js +134 -136
  11. package/dist/cjs/components/scatterplot/ScatterplotLayer.js +144 -0
  12. package/dist/cjs/components/scatterplot/SpatialControls.js +7 -4
  13. package/dist/cjs/components/scatterplot/Toolbox.js +8 -4
  14. package/dist/cjs/components/search-bar/SearchBar.js +51 -29
  15. package/dist/cjs/components/search-bar/SearchInfo.js +2 -2
  16. package/dist/cjs/components/search-bar/SearchResults.js +9 -6
  17. package/dist/cjs/components/toolbar/Toolbar.js +10 -65
  18. package/dist/cjs/components/var-list/VarItem.js +4 -6
  19. package/dist/cjs/components/var-list/VarList.js +17 -9
  20. package/dist/cjs/components/var-list/VarListToolbar.js +1 -1
  21. package/dist/cjs/components/var-list/VarSet.js +7 -5
  22. package/dist/cjs/components/violin/Violin.js +6 -7
  23. package/dist/cjs/constants/constants.js +6 -3
  24. package/dist/cjs/context/DatasetContext.js +11 -2
  25. package/dist/cjs/context/SettingsContext.js +27 -2
  26. package/dist/cjs/helpers/color-helper.js +17 -12
  27. package/dist/cjs/index.js +0 -7
  28. package/dist/cjs/utils/Legend.js +6 -5
  29. package/dist/cjs/utils/requests.js +2 -2
  30. package/dist/cjs/views/ObservationFeature/StandardView.js +1 -1
  31. package/dist/cjs/views/PerturbationMap/ObsExplorer.js +11 -9
  32. package/dist/cjs/views/PerturbationMap/StandardView.js +2 -1
  33. package/dist/cjs/workers/scatterplotData.js +16 -0
  34. package/dist/esm/components/dotplot/Dotplot.js +5 -6
  35. package/dist/esm/components/dotplot/DotplotControls.js +6 -3
  36. package/dist/esm/components/heatmap/Heatmap.js +5 -6
  37. package/dist/esm/components/icons/HeatmapIcon.js +2 -2
  38. package/dist/esm/components/matrixplot/Matrixplot.js +5 -6
  39. package/dist/esm/components/obs-list/ObsItem.js +7 -7
  40. package/dist/esm/components/offcanvas/OffCanvas.js +7 -4
  41. package/dist/esm/components/plot/PlotTypeSelector.js +49 -10
  42. package/dist/esm/components/pseudospatial/Pseudospatial.js +8 -5
  43. package/dist/esm/components/scatterplot/Scatterplot.js +132 -134
  44. package/dist/esm/components/scatterplot/ScatterplotLayer.js +137 -0
  45. package/dist/esm/components/scatterplot/SpatialControls.js +7 -4
  46. package/dist/esm/components/scatterplot/Toolbox.js +8 -4
  47. package/dist/esm/components/search-bar/SearchBar.js +52 -30
  48. package/dist/esm/components/search-bar/SearchInfo.js +2 -2
  49. package/dist/esm/components/search-bar/SearchResults.js +9 -6
  50. package/dist/esm/components/toolbar/Toolbar.js +9 -63
  51. package/dist/esm/components/var-list/VarItem.js +4 -6
  52. package/dist/esm/components/var-list/VarList.js +17 -9
  53. package/dist/esm/components/var-list/VarListToolbar.js +1 -1
  54. package/dist/esm/components/var-list/VarSet.js +7 -5
  55. package/dist/esm/components/violin/Violin.js +7 -8
  56. package/dist/esm/constants/constants.js +5 -2
  57. package/dist/esm/context/DatasetContext.js +11 -2
  58. package/dist/esm/context/SettingsContext.js +27 -2
  59. package/dist/esm/helpers/color-helper.js +17 -12
  60. package/dist/esm/index.js +0 -1
  61. package/dist/esm/utils/Legend.js +6 -5
  62. package/dist/esm/utils/requests.js +2 -2
  63. package/dist/esm/views/ObservationFeature/StandardView.js +1 -1
  64. package/dist/esm/views/PerturbationMap/ObsExplorer.js +11 -9
  65. package/dist/esm/views/PerturbationMap/StandardView.js +2 -1
  66. package/dist/esm/workers/scatterplotData.js +10 -0
  67. package/package.json +6 -3
@@ -6,14 +6,14 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Scatterplot = Scatterplot;
7
7
  var _react = require("react");
8
8
  var _core = require("@deck.gl/core");
9
- var _layers = require("@deck.gl/layers");
10
9
  var _react2 = require("@deck.gl/react");
11
10
  var _freeSolidSvgIcons = require("@fortawesome/free-solid-svg-icons");
12
11
  var _reactFontawesome = require("@fortawesome/react-fontawesome");
13
12
  var _editModes = require("@nebula.gl/edit-modes");
14
- var _layers2 = require("@nebula.gl/layers");
13
+ var _layers = require("@nebula.gl/layers");
15
14
  var _lodash = _interopRequireDefault(require("lodash"));
16
15
  var _reactBootstrap = require("react-bootstrap");
16
+ var _ScatterplotLayer = require("./ScatterplotLayer");
17
17
  var _SpatialControls = require("./SpatialControls");
18
18
  var _Toolbox = require("./Toolbox");
19
19
  var _constants = require("../../constants/constants");
@@ -29,6 +29,7 @@ var _Resolver = require("../../utils/Resolver");
29
29
  var _string = require("../../utils/string");
30
30
  var _usePlotVisibility = _interopRequireDefault(require("../../utils/usePlotVisibility"));
31
31
  var _zarrData = require("../../utils/zarrData");
32
+ var _scatterplotData = require("../../workers/scatterplotData.js");
32
33
  var _PlotAlert = require("../plot/PlotAlert");
33
34
  var _jsxRuntime = require("react/jsx-runtime");
34
35
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -55,15 +56,15 @@ const getRadiusScale = bounds => {
55
56
  return rs;
56
57
  };
57
58
  function Scatterplot(_ref) {
58
- var _features$features2, _obsmData$serverError, _labelObsData$serverE, _settings$selectedVar, _data$positions2;
59
+ var _data$positions5, _features$features2, _obsmData$serverError, _labelObsData$serverE, _settings$selectedVar, _data$positions6;
59
60
  let {
61
+ pointInteractionEnabled = false,
62
+ showSpatialControls = true,
60
63
  setShowCategories,
61
64
  setShowSearch,
62
- plotType,
63
65
  setPlotType,
64
66
  isFullscreen = false,
65
- pointInteractionEnabled = false,
66
- showSpatialControls = true
67
+ isSearchObs
67
68
  } = _ref;
68
69
  const {
69
70
  useUnsColors
@@ -76,9 +77,6 @@ function Scatterplot(_ref) {
76
77
  slicedLength
77
78
  } = (0, _FilterContext.useFilteredData)();
78
79
  const dispatch = (0, _SettingsContext.useSettingsDispatch)();
79
- const {
80
- getColor
81
- } = (0, _colorHelper.useColor)();
82
80
  const deckRef = (0, _react.useRef)(null);
83
81
  const [viewport, setViewport] = (0, _react.useState)(null);
84
82
  const [viewState, setViewState] = (0, _react.useState)(INITIAL_VIEW_STATE);
@@ -94,8 +92,6 @@ function Scatterplot(_ref) {
94
92
  const radiusScale = settings.controls.radiusScale[settings.selectedObsm] || 1;
95
93
  const selectedObs = (0, _Resolver.useSelectedObs)();
96
94
  const selectedObsIndex = settings.selectedObsIndex;
97
- const [hoveredIndex, setHoveredIndex] = (0, _react.useState)(null);
98
- const [isHoveringPoint, setIsHoveringPoint] = (0, _react.useState)(false);
99
95
  const {
100
96
  showSearchBtn
101
97
  } = (0, _usePlotVisibility.default)(isFullscreen);
@@ -114,6 +110,22 @@ function Scatterplot(_ref) {
114
110
  } = (0, _ZarrDataContext.useZarrData)();
115
111
  const labelObsData = (0, _zarrData.useLabelObsData)();
116
112
  const clickedInsideRef = (0, _react.useRef)(false);
113
+ const workerRef = (0, _react.useRef)(null);
114
+ const [scatterplotAttributes, setScatterplotAttributes] = (0, _react.useState)(null);
115
+ const isCategorical = (0, _react.useMemo)(() => {
116
+ if (settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS) {
117
+ return (selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type) === _constants.OBS_TYPES.CATEGORICAL || (selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type) === _constants.OBS_TYPES.BOOLEAN;
118
+ } else {
119
+ return false;
120
+ }
121
+ }, [settings.colorEncoding, selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type]);
122
+ const {
123
+ colormap,
124
+ getColor
125
+ } = (0, _colorHelper.useColor)({
126
+ isCategorical,
127
+ colorscale: useUnsColors && settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS ? selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.colors : null
128
+ });
117
129
 
118
130
  // @TODO: assert length of obsmData, xData, obsData is equal
119
131
 
@@ -163,7 +175,8 @@ function Scatterplot(_ref) {
163
175
  }
164
176
  }, [obsData.data, obsData.isPending, obsData.serverError, obsmData.data, obsmData.isPending, obsmData.serverError, settings.colorEncoding, xData.data, xData.isPending, xData.serverError]);
165
177
  (0, _react.useEffect)(() => {
166
- if (data.positions && data.positions.length) {
178
+ var _data$positions;
179
+ if ((_data$positions = data.positions) !== null && _data$positions !== void 0 && _data$positions.length) {
167
180
  var _deckRef$current, _deckRef$current2;
168
181
  const mapHelper = new _mapHelper.MapHelper();
169
182
  const {
@@ -198,10 +211,10 @@ function Scatterplot(_ref) {
198
211
  }
199
212
  }, [dispatch, settings.controls.radiusScale, settings.selectedObsm, viewport === null || viewport === void 0 ? void 0 : viewport.bounds]);
200
213
  (0, _react.useEffect)(() => {
201
- var _data$positions;
214
+ var _data$positions2;
202
215
  if (!pointInteractionEnabled) return;
203
216
  if (selectedObsIndex == null) return;
204
- if (!((_data$positions = data.positions) !== null && _data$positions !== void 0 && _data$positions.length)) return;
217
+ if (!((_data$positions2 = data.positions) !== null && _data$positions2 !== void 0 && _data$positions2.length)) return;
205
218
 
206
219
  // If the selection came from a click inside this plot, skip recentering
207
220
  if (clickedInsideRef.current) {
@@ -236,116 +249,110 @@ function Scatterplot(_ref) {
236
249
  zoom
237
250
  };
238
251
  }, [data.positions]);
239
-
240
- // Make stable references for getOriginalIndex and sortedIndexMap
241
- const identityGetOriginalIndex = (0, _react.useCallback)(i => i, []);
242
- const identitySortedIndexMap = (0, _react.useMemo)(() => ({
243
- get: key => key
244
- }), []);
245
- const {
246
- sortedData,
247
- getOriginalIndex,
248
- sortedIndexMap
249
- } = (0, _react.useMemo)(() => {
250
- if ((settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR || settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS && (selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type) === _constants.OBS_TYPES.CONTINUOUS) && data.positions && data.values && data.positions.length === data.values.length) {
251
- const sortedIndices = _lodash.default.map(data.values, (_v, i) => i).sort((a, b) => data.values[a] - data.values[b]);
252
- const sortedIndexMap = new Map(_lodash.default.map(sortedIndices, (originalIndex, sortedIndex) => [originalIndex, sortedIndex]));
253
- return {
254
- sortedData: _lodash.default.mapValues(data, (v, _k) => {
255
- return v ? _lodash.default.at(v, sortedIndices) : v;
256
- }),
257
- getOriginalIndex: i => sortedIndices[i],
258
- sortedIndexMap: sortedIndexMap
259
- };
260
- }
261
- return {
262
- sortedData: data,
263
- getOriginalIndex: identityGetOriginalIndex,
264
- // return original index
265
- sortedIndexMap: identitySortedIndexMap // return original index
266
- };
267
- }, [data, identityGetOriginalIndex, identitySortedIndexMap, selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type, settings.colorEncoding]);
268
- const hoverLayer = typeof hoveredIndex === 'number' && Array.isArray(sortedData === null || sortedData === void 0 ? void 0 : sortedData.positions) && hoveredIndex < sortedData.positions.length ? new _layers.ScatterplotLayer({
269
- id: 'hover-highlight',
270
- data: [sortedData.positions[hoveredIndex]],
271
- getPosition: d => d,
272
- getFillColor: [255, 215, 0, 180],
273
- getRadius: 10,
274
- radiusMinPixels: 15,
275
- radiusScale: 1,
276
- pointSizeUnits: 'pixels',
277
- pickable: false,
278
- parameters: {
279
- depthTest: false
280
- }
281
- }) : null;
282
- const sortedObsIndices = (0, _react.useMemo)(() => {
283
- return obsIndices ? new Set(Array.from(obsIndices, i => sortedIndexMap.get(i))) : obsIndices;
284
- }, [obsIndices, sortedIndexMap]);
285
- const isCategorical = (0, _react.useMemo)(() => {
286
- if (settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS) {
287
- return (selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type) === _constants.OBS_TYPES.CATEGORICAL || (selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type) === _constants.OBS_TYPES.BOOLEAN;
288
- } else {
289
- return false;
290
- }
291
- }, [settings.colorEncoding, selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.type]);
292
252
  const {
293
253
  min,
294
254
  max
295
- } = {
255
+ } = (0, _react.useMemo)(() => ({
296
256
  min: settings.controls.range[0] * (valueMax - valueMin) + valueMin,
297
257
  max: settings.controls.range[1] * (valueMax - valueMin) + valueMin
298
- };
299
- const getFillColor = (0, _react.useCallback)((_d, _ref2) => {
300
- let {
301
- index
302
- } = _ref2;
303
- const grayOut = isPending || sortedObsIndices && !sortedObsIndices.has(index);
304
- if (pointInteractionEnabled && getOriginalIndex(index) === selectedObsIndex) {
305
- return [255, 215, 0, 255];
258
+ }), [settings.controls.range, valueMax, valueMin]);
259
+ (0, _react.useEffect)(() => {
260
+ workerRef.current = (0, _scatterplotData.createScatterplotWorker)();
261
+ workerRef.current.onmessage = _ref2 => {
262
+ let {
263
+ data
264
+ } = _ref2;
265
+ setScatterplotAttributes(p => _objectSpread(_objectSpread({}, p), data));
266
+ };
267
+ workerRef.current.onerror = e => {
268
+ console.error('Worker error:', e);
269
+ };
270
+ return () => {
271
+ var _workerRef$current;
272
+ (_workerRef$current = workerRef.current) === null || _workerRef$current === void 0 || _workerRef$current.terminate();
273
+ };
274
+ }, []);
275
+ (0, _react.useEffect)(() => {
276
+ var _data$positions3;
277
+ if (workerRef.current && (_data$positions3 = data.positions) !== null && _data$positions3 !== void 0 && _data$positions3.length) {
278
+ workerRef.current.postMessage({
279
+ positions: data.positions
280
+ });
306
281
  }
307
- return getColor(_objectSpread({
308
- value: (sortedData.values[index] - min) / (max - min),
309
- categorical: isCategorical,
310
- grayOut: grayOut
311
- }, useUnsColors && settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS && selectedObs !== null && selectedObs !== void 0 && selectedObs.colors ? {
312
- colorscale: selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.colors
313
- } : {})) || [0, 0, 0, 100];
314
- }, [isPending, sortedObsIndices, pointInteractionEnabled, getOriginalIndex, selectedObsIndex, getColor, sortedData.values, min, max, isCategorical, useUnsColors, settings.colorEncoding, selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.colors]);
315
-
316
- // @TODO: add support for pseudospatial hover to reflect in radius
317
- const getRadius = (0, _react.useCallback)((_d, _ref3) => {
282
+ }, [data.positions]);
283
+ (0, _react.useEffect)(() => {
284
+ var _data$values;
285
+ if (workerRef.current && (_data$values = data.values) !== null && _data$values !== void 0 && _data$values.length) {
286
+ workerRef.current.postMessage({
287
+ values: data.values
288
+ });
289
+ }
290
+ }, [data.values]);
291
+ (0, _react.useEffect)(() => {
292
+ var _data$positions4;
293
+ if (workerRef.current && (_data$positions4 = data.positions) !== null && _data$positions4 !== void 0 && _data$positions4.length && obsIndices !== undefined) {
294
+ workerRef.current.postMessage({
295
+ obsIndices,
296
+ length: data.positions.length
297
+ });
298
+ }
299
+ }, [obsIndices, (_data$positions5 = data.positions) === null || _data$positions5 === void 0 ? void 0 : _data$positions5.length]);
300
+ const getFillColor = (0, _react.useCallback)((_d, _ref3) => {
318
301
  let {
319
302
  index
320
303
  } = _ref3;
321
- const grayOut = sortedObsIndices && !sortedObsIndices.has(index);
322
- if (pointInteractionEnabled && getOriginalIndex(index) === selectedObsIndex) {
323
- return 50;
304
+ const grayOut = isPending || obsIndices && !obsIndices.has(index);
305
+ if (pointInteractionEnabled && index === selectedObsIndex) {
306
+ return [255, 215, 0, 255];
324
307
  }
325
- return (grayOut ? 1 : 3) * (pointInteractionEnabled ? 26 : 1);
326
- }, [getOriginalIndex, pointInteractionEnabled, selectedObsIndex, sortedObsIndices]);
308
+ return getColor({
309
+ value: (data.values[index] - min) / Math.max(max - min, 1e-6),
310
+ grayOut: grayOut
311
+ }) || [0, 0, 0, 100];
312
+ }, [isPending, obsIndices, pointInteractionEnabled, selectedObsIndex, getColor, data.values, min, max]);
327
313
  const memoizedLayers = (0, _react.useMemo)(() => {
328
- return [new _layers.ScatterplotLayer({
314
+ const hasSelection = settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR && settings.selectedVar || settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS && selectedObs;
315
+ return [new _ScatterplotLayer.ScatterplotLayer({
329
316
  id: 'cherita-layer-scatterplot',
330
317
  pickable: true,
331
- data: sortedData.positions,
318
+ autoHighlight: true,
319
+ highlightColor: pointInteractionEnabled ? [255, 215, 0, 255] : [0, 0, 0, 0],
320
+ data: {
321
+ length: (scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.count) || 0,
322
+ attributes: {
323
+ getPosition: {
324
+ value: (scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.positions) || new Float32Array(0),
325
+ size: 2
326
+ },
327
+ getValues: {
328
+ value: scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.values,
329
+ size: 1
330
+ },
331
+ getEnabled: {
332
+ value: scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.indexEnabledBitmask,
333
+ size: 1
334
+ }
335
+ }
336
+ },
332
337
  radiusScale: radiusScale,
333
338
  radiusMinPixels: 1,
334
- getPosition: d => d,
335
- getFillColor: getFillColor,
336
- getRadius: getRadius,
337
339
  updateTriggers: {
338
- getFillColor: getFillColor,
339
- getRadius: [getRadius, hoveredIndex, selectedObsIndex]
340
+ colormap: [colormap]
340
341
  },
341
- transitions: {
342
- getRadius: 200,
343
- getFillColor: 200
344
- }
345
- }), new _layers2.EditableGeoJsonLayer({
342
+ colormap: isPending ? ["".concat((0, _colorHelper.rgbToHex)(_constants.GRAY), "aa")] : hasSelection ? colormap : ['#000000aa'],
343
+ isCategorical,
344
+ valueMin: min,
345
+ valueMax: max,
346
+ selectedIndex: pointInteractionEnabled ? selectedObsIndex !== null && selectedObsIndex !== void 0 ? selectedObsIndex : -1 : -1,
347
+ pointInteractionEnabled,
348
+ highlightMultiplier: pointInteractionEnabled ? 1.5 : 1.0
349
+ }), new _layers.EditableGeoJsonLayer({
346
350
  id: 'cherita-layer-draw',
347
351
  data: features,
348
352
  mode: mode,
353
+ parameters: {
354
+ depthTest: false
355
+ },
349
356
  selectedFeatureIndexes,
350
357
  onEdit: _ref4 => {
351
358
  let {
@@ -376,12 +383,9 @@ function Scatterplot(_ref) {
376
383
  }
377
384
  }
378
385
  })];
379
- }, [sortedData.positions, features, getFillColor, getRadius, hoveredIndex, mode, radiusScale, selectedFeatureIndexes, selectedObsIndex]);
386
+ }, [settings.colorEncoding, settings.selectedVar, selectedObs, pointInteractionEnabled, scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.count, scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.positions, scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.values, scatterplotAttributes === null || scatterplotAttributes === void 0 ? void 0 : scatterplotAttributes.indexEnabledBitmask, radiusScale, colormap, isPending, isCategorical, min, max, selectedObsIndex, features, mode, selectedFeatureIndexes]);
387
+ const layers = (0, _react.useDeferredValue)(mode === _editModes.ViewMode ? [...memoizedLayers].reverse() : memoizedLayers); // draw scatterplot on top of polygons when in ViewMode
380
388
 
381
- // const layers = useDeferredValue(
382
- // mode === ViewMode ? memoizedLayers.reverse() : memoizedLayers,
383
- // ); // draw scatterplot on top of polygons when in ViewMode
384
- const layers = (0, _react.useDeferredValue)([...memoizedLayers, hoverLayer].filter(Boolean));
385
389
  (0, _react.useEffect)(() => {
386
390
  var _features$features;
387
391
  if (!(features !== null && features !== void 0 && (_features$features = features.features) !== null && _features$features !== void 0 && _features$features.length)) {
@@ -398,22 +402,22 @@ function Scatterplot(_ref) {
398
402
  });
399
403
  }, [settings.selectedObsm, dispatch, features.features]);
400
404
  function onLayerClick(info) {
405
+ var _info$layer, _info$layer2;
401
406
  if (mode !== _editModes.ViewMode) return;
402
- if (!info.object) {
407
+ if (info.index === undefined || info.index === null) {
403
408
  // clicked empty space
404
409
  setSelectedFeatureIndexes([]);
405
410
  return;
406
411
  }
407
- if (info.layer.id === 'cherita-layer-draw') {
412
+ if (((_info$layer = info.layer) === null || _info$layer === void 0 ? void 0 : _info$layer.id) === 'cherita-layer-draw') {
408
413
  // clicked a drawn polygon
409
414
  setSelectedFeatureIndexes([info.index]);
410
- } else if (info.layer.id === 'cherita-layer-scatterplot' && pointInteractionEnabled) {
415
+ } else if (((_info$layer2 = info.layer) === null || _info$layer2 === void 0 ? void 0 : _info$layer2.id) === 'cherita-layer-scatterplot' && pointInteractionEnabled) {
411
416
  // clicked a scatterplot point
412
417
  clickedInsideRef.current = true;
413
- const originalIndex = getOriginalIndex(info.index);
414
418
  dispatch({
415
419
  type: 'set.selectedObsIndex',
416
- index: originalIndex
420
+ index: info.index
417
421
  });
418
422
  // in collapsed view, open offcanvas
419
423
  if (pointInteractionEnabled && showSearchBtn) {
@@ -438,25 +442,26 @@ function Scatterplot(_ref) {
438
442
  object,
439
443
  index
440
444
  } = _ref5;
441
- if (!object || (object === null || object === void 0 ? void 0 : object.type) === 'Feature') return;
445
+ if ((object === null || object === void 0 ? void 0 : object.type) === 'Feature') return;
446
+ if (index < 0 || index === null) return;
442
447
  const text = [];
443
448
  if (settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS && selectedObs && !_lodash.default.includes(settings.labelObs, selectedObs.name)) {
444
- var _data$values;
445
- text.push(getLabel(selectedObs, (_data$values = data.values) === null || _data$values === void 0 ? void 0 : _data$values[getOriginalIndex(index)]));
449
+ var _data$values2;
450
+ text.push(getLabel(selectedObs, (_data$values2 = data.values) === null || _data$values2 === void 0 ? void 0 : _data$values2[index]));
446
451
  }
447
452
  if (settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR && settings.selectedVar) {
448
- var _data$values2;
449
- text.push(getLabel(settings.selectedVar, (_data$values2 = data.values) === null || _data$values2 === void 0 ? void 0 : _data$values2[getOriginalIndex(index)], true));
453
+ var _data$values3;
454
+ text.push(getLabel(settings.selectedVar, (_data$values3 = data.values) === null || _data$values3 === void 0 ? void 0 : _data$values3[index], true));
450
455
  }
451
456
  if (settings.labelObs.length) {
452
457
  text.push(..._lodash.default.map(labelObsData.data, (v, k) => {
453
458
  if (!v) return;
454
459
  const labelObs = settings.data.obs[k];
455
- return getLabel(labelObs, v[getOriginalIndex(index)]);
460
+ return getLabel(labelObs, v[index]);
456
461
  }));
457
462
  }
458
463
  if (!text.length) return;
459
- const grayOut = sortedObsIndices && !sortedObsIndices.has(index);
464
+ const grayOut = obsIndices && !obsIndices.has(index);
460
465
  return {
461
466
  text: text.length ? _lodash.default.compact(text).join('\n') : null,
462
467
  className: grayOut ? 'tooltip-grayout' : 'deck-tooltip',
@@ -474,7 +479,7 @@ function Scatterplot(_ref) {
474
479
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_PlotAlert.PlotAlert, {
475
480
  variant: "info",
476
481
  heading: "Scatterplot unavailable for this dataset",
477
- plotType: plotType,
482
+ plotType: _constants.PLOT_TYPES.SCATTERPLOT,
478
483
  setPlotType: setPlotType,
479
484
  children: "This dataset does not include any embeddings, so a scatterplot cannot be displayed. Please choose a different plot type to explore the data."
480
485
  });
@@ -498,22 +503,14 @@ function Scatterplot(_ref) {
498
503
  setIsRendering(false);
499
504
  },
500
505
  useDevicePixels: false,
501
- onHover: _ref6 => {
506
+ getCursor: _ref6 => {
502
507
  let {
503
- object,
504
- index
508
+ isDragging,
509
+ isHovering
505
510
  } = _ref6;
506
- const active = pointInteractionEnabled && !!object;
507
- setHoveredIndex(active ? index : null);
508
- setIsHoveringPoint(active);
509
- },
510
- getCursor: _ref7 => {
511
- let {
512
- isDragging
513
- } = _ref7;
514
511
  if (mode !== _editModes.ViewMode) return 'crosshair';
515
512
  if (isDragging) return 'grabbing';
516
- if (isHoveringPoint) return 'pointer';
513
+ if (isHovering && pointInteractionEnabled) return 'pointer';
517
514
  return 'grab';
518
515
  },
519
516
  ref: deckRef
@@ -532,7 +529,8 @@ function Scatterplot(_ref) {
532
529
  })),
533
530
  setShowCategories: setShowCategories,
534
531
  setShowSearch: setShowSearch,
535
- isFullscreen: isFullscreen
532
+ isFullscreen: isFullscreen,
533
+ isSearchObs: isSearchObs
536
534
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
537
535
  className: "cherita-spatial-footer",
538
536
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
@@ -549,7 +547,7 @@ function Scatterplot(_ref) {
549
547
  })]
550
548
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Toolbox.Toolbox, {
551
549
  mode: settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR ? (_settings$selectedVar = settings.selectedVar) === null || _settings$selectedVar === void 0 ? void 0 : _settings$selectedVar.name : settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS ? selectedObs === null || selectedObs === void 0 ? void 0 : selectedObs.name : null,
552
- obsLength: parseInt((_data$positions2 = data.positions) === null || _data$positions2 === void 0 ? void 0 : _data$positions2.length),
550
+ obsLength: parseInt((_data$positions6 = data.positions) === null || _data$positions6 === void 0 ? void 0 : _data$positions6.length),
553
551
  slicedLength: parseInt(slicedLength),
554
552
  setHasObsm: setHasObsm
555
553
  })]
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ScatterplotLayer = void 0;
7
+ var _core = require("@luma.gl/core");
8
+ var _deck = require("deck.gl");
9
+ var _constants = require("../../constants/constants");
10
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
13
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
14
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
15
+ class ScatterplotLayer extends _deck.ScatterplotLayer {
16
+ getShaders() {
17
+ const shaders = super.getShaders();
18
+ return _objectSpread(_objectSpread({}, shaders), {}, {
19
+ inject: {
20
+ 'vs:#decl': "\n in float value;\n in float indexEnabled;\n in float instanceIndex;\n out float vNormValue;\n out float vSelected;\n out float vEnabled;\n uniform float highlightMultiplier;\n uniform bool isCategorical;\n uniform float valueMin;\n uniform float valueMax;\n uniform bool pointInteractionEnabled;\n uniform float selectedIndex;\n ",
21
+ // add z-axis position based on sortValue, zMin, and zMax
22
+ // to draw higher sortValue points above lower sortValue points
23
+ // and picked points at the front
24
+ 'vs:DECKGL_FILTER_GL_POSITION': "\n float normValue = clamp((value - valueMin) / max(valueMax - valueMin, 1e-6), 0.0, 1.0);\n if (isVertexPicked(geometry.pickingColor)) {\n position.z = -position.w;\n }\n else if (indexEnabled < 0.5) {\n position.z = position.w;\n }\n else if (isCategorical) {\n if (value == -1.0) {\n position.z = position.w; // draw invalid at back\n }\n else { position.z = 0.0; }\n }\n else {\n position.z = mix(position.w, -position.w, normValue);\n }\n ",
25
+ // increase point size for hovered points
26
+ 'vs:DECKGL_FILTER_SIZE': "\n if (pointInteractionEnabled) {\n size *= 4.0;\n if(instanceIndex == selectedIndex) {\n size *= 3.0;\n }\n }\n if (isVertexPicked(geometry.pickingColor)) {\n size *= highlightMultiplier;\n }\n if (indexEnabled < 0.5) {\n size *= 0.3;\n }\n ",
27
+ // Pass colorValue to fragment shader
28
+ 'vs:DECKGL_FILTER_COLOR': "\n float normValue = clamp((value - valueMin) / max(valueMax - valueMin, 1e-6), 0.0, 1.0);\n vNormValue = normValue;\n vEnabled = indexEnabled;\n vSelected = float(instanceIndex == selectedIndex);\n ",
29
+ 'fs:#decl': "\n in float vNormValue;\n in float vEnabled;\n in float vSelected;\n uniform sampler2D colorTexture;\n uniform bool useTexture;\n uniform float colormapSize;\n uniform vec4 gray;\n uniform float grayMix;\n ",
30
+ // Sample color from texture in fragment shader
31
+ 'fs:DECKGL_FILTER_COLOR': "\n if (useTexture) {\n // Remap to texel centers so sampling matches JS interpolation\n float u = (vNormValue * (colormapSize - 1.0) + 0.5) / colormapSize;\n color = texture(colorTexture, vec2(u, 0.5));\n }\n if (vEnabled < 0.5) {\n color.rgb = mix(color.rgb, gray.rgb / 255.0, grayMix);\n color.a = gray.a / 255.0;\n }\n if(vSelected > 0.5) {\n color = picking_uHighlightColor;\n }\n "
32
+ }
33
+ });
34
+ }
35
+ initializeState() {
36
+ super.initializeState();
37
+ this.getAttributeManager().addInstanced({
38
+ value: {
39
+ size: 1,
40
+ accessor: 'getValues',
41
+ defaultValue: 0.0
42
+ },
43
+ indexEnabled: {
44
+ size: 1,
45
+ accessor: 'getEnabled',
46
+ defaultValue: 1.0
47
+ },
48
+ instanceIndex: {
49
+ size: 1,
50
+ accessor: (_, _ref) => {
51
+ let {
52
+ index
53
+ } = _ref;
54
+ return index;
55
+ },
56
+ defaultValue: 0
57
+ }
58
+ });
59
+ }
60
+ updateState(params) {
61
+ super.updateState(params);
62
+ const {
63
+ props,
64
+ oldProps,
65
+ changeFlags
66
+ } = params;
67
+ if (props.colormap !== oldProps.colormap || changeFlags.extensionsChanged) {
68
+ this._updateColorTexture(props.colormap);
69
+ }
70
+ }
71
+ _updateColorTexture(colormap) {
72
+ const {
73
+ gl
74
+ } = this.context;
75
+
76
+ // colormap is an array of hex strings
77
+ const colors = colormap.flatMap(hex => {
78
+ const r = parseInt(hex.slice(1, 3), 16);
79
+ const g = parseInt(hex.slice(3, 5), 16);
80
+ const b = parseInt(hex.slice(5, 7), 16);
81
+ const a = parseInt(hex.slice(7, 9) || 'ff', 16);
82
+ return [r, g, b, a];
83
+ });
84
+ if (this.state.colorTexture) {
85
+ this.state.colorTexture.delete();
86
+ }
87
+ const texture = new _core.Texture2D(gl, {
88
+ data: new Uint8Array(colors),
89
+ width: colormap === null || colormap === void 0 ? void 0 : colormap.length,
90
+ height: 1,
91
+ format: gl.RGBA,
92
+ type: gl.UNSIGNED_BYTE,
93
+ parameters: {
94
+ [gl.TEXTURE_MIN_FILTER]: gl.LINEAR,
95
+ [gl.TEXTURE_MAG_FILTER]: gl.LINEAR,
96
+ [gl.TEXTURE_WRAP_S]: gl.CLAMP_TO_EDGE,
97
+ [gl.TEXTURE_WRAP_T]: gl.CLAMP_TO_EDGE
98
+ }
99
+ });
100
+ this.setState({
101
+ colorTexture: texture
102
+ });
103
+ }
104
+ draw(params) {
105
+ var _this$props$colormap$, _this$props$colormap;
106
+ const {
107
+ highlightMultiplier = 1.0,
108
+ isCategorical = false,
109
+ valueMin = 0,
110
+ valueMax = 0,
111
+ pointInteractionEnabled = false,
112
+ selectedIndex = -1,
113
+ gray = [..._constants.GRAY, 255 * _constants.GRAY_ALPHA],
114
+ grayMix = _constants.GRAY_MIX
115
+ } = this.props;
116
+ const {
117
+ colorTexture
118
+ } = this.state;
119
+ const model = this.state.model;
120
+ if (!model) {
121
+ return;
122
+ }
123
+ model.setUniforms({
124
+ highlightMultiplier,
125
+ colorTexture: colorTexture || null,
126
+ useTexture: !!colorTexture,
127
+ colormapSize: (_this$props$colormap$ = (_this$props$colormap = this.props.colormap) === null || _this$props$colormap === void 0 ? void 0 : _this$props$colormap.length) !== null && _this$props$colormap$ !== void 0 ? _this$props$colormap$ : 1,
128
+ isCategorical,
129
+ valueMin: isNaN(valueMin) ? 0 : valueMin,
130
+ valueMax: isNaN(valueMax) ? 0 : valueMax,
131
+ pointInteractionEnabled,
132
+ selectedIndex,
133
+ gray,
134
+ grayMix
135
+ });
136
+ super.draw(params);
137
+ }
138
+ finalizeState(context) {
139
+ var _this$state$colorText;
140
+ super.finalizeState(context);
141
+ (_this$state$colorText = this.state.colorTexture) === null || _this$state$colorText === void 0 || _this$state$colorText.delete();
142
+ }
143
+ }
144
+ exports.ScatterplotLayer = ScatterplotLayer;
@@ -14,6 +14,7 @@ var _Button = _interopRequireDefault(require("react-bootstrap/Button"));
14
14
  var _ButtonGroup = _interopRequireDefault(require("react-bootstrap/ButtonGroup"));
15
15
  var _Dropdown = _interopRequireDefault(require("react-bootstrap/Dropdown"));
16
16
  var _ScatterplotControls = require("./ScatterplotControls");
17
+ var _DatasetContext = require("../../context/DatasetContext");
17
18
  var _SettingsContext = require("../../context/SettingsContext");
18
19
  var _usePlotVisibility = _interopRequireDefault(require("../../utils/usePlotVisibility"));
19
20
  var _OffCanvas = require("../offcanvas/OffCanvas");
@@ -32,8 +33,10 @@ function SpatialControls(_ref) {
32
33
  decreaseZoom,
33
34
  setShowCategories,
34
35
  setShowSearch,
35
- isFullscreen
36
+ isFullscreen,
37
+ isSearchObs = false
36
38
  } = _ref;
39
+ const dataset = (0, _DatasetContext.useDataset)();
37
40
  const settings = (0, _SettingsContext.useSettings)();
38
41
  const dispatch = (0, _SettingsContext.useSettingsDispatch)();
39
42
  const [showControls, setShowControls] = (0, _react.useState)(false);
@@ -113,7 +116,7 @@ function SpatialControls(_ref) {
113
116
  placement: "right",
114
117
  overlay: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactBootstrap.Tooltip, {
115
118
  id: "tooltip-obs",
116
- children: "Browse categories"
119
+ children: "Explore categories"
117
120
  }),
118
121
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
119
122
  size: isCompact && 'sm',
@@ -124,9 +127,9 @@ function SpatialControls(_ref) {
124
127
  })
125
128
  }), showSearchBtn && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactBootstrap.OverlayTrigger, {
126
129
  placement: "right",
127
- overlay: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactBootstrap.Tooltip, {
130
+ overlay: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactBootstrap.Tooltip, {
128
131
  id: "tooltip-vars",
129
- children: "Search features"
132
+ children: ["Search", ' ', isSearchObs ? dataset.obsLabel.plural : dataset.varLabel.plural]
130
133
  }),
131
134
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
132
135
  size: isCompact && 'sm',