@haniffalab/cherita-react 1.4.0 → 1.4.1-dev.2025-06-30.e26168b5

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 (39) hide show
  1. package/dist/cjs/components/full-page/FullPage.js +10 -42
  2. package/dist/cjs/components/full-page/PlotTypeSelector.js +4 -4
  3. package/dist/cjs/components/obs-list/ObsItem.js +27 -13
  4. package/dist/cjs/components/obs-list/ObsList.js +6 -2
  5. package/dist/cjs/components/obsm-list/ObsmList.js +30 -6
  6. package/dist/cjs/components/offcanvas/index.js +9 -4
  7. package/dist/cjs/components/scatterplot/Scatterplot.js +1 -1
  8. package/dist/cjs/components/search-bar/SearchBar.js +10 -1
  9. package/dist/cjs/components/search-bar/SearchResults.js +2 -2
  10. package/dist/cjs/components/var-list/VarItem.js +26 -25
  11. package/dist/cjs/components/var-list/VarList.js +3 -5
  12. package/dist/cjs/components/violin/Violin.js +15 -5
  13. package/dist/cjs/constants/constants.js +5 -2
  14. package/dist/cjs/context/DatasetContext.js +1 -1
  15. package/dist/cjs/context/SettingsContext.js +19 -2
  16. package/dist/cjs/utils/VirtualizedList.js +2 -2
  17. package/dist/css/cherita.css +85 -37
  18. package/dist/css/cherita.css.map +1 -1
  19. package/dist/esm/components/full-page/FullPage.js +11 -43
  20. package/dist/esm/components/full-page/PlotTypeSelector.js +4 -4
  21. package/dist/esm/components/obs-list/ObsItem.js +27 -13
  22. package/dist/esm/components/obs-list/ObsList.js +6 -2
  23. package/dist/esm/components/obsm-list/ObsmList.js +29 -6
  24. package/dist/esm/components/offcanvas/index.js +9 -4
  25. package/dist/esm/components/scatterplot/Scatterplot.js +1 -1
  26. package/dist/esm/components/search-bar/SearchBar.js +10 -1
  27. package/dist/esm/components/search-bar/SearchResults.js +2 -2
  28. package/dist/esm/components/var-list/VarItem.js +26 -25
  29. package/dist/esm/components/var-list/VarList.js +3 -5
  30. package/dist/esm/components/violin/Violin.js +15 -5
  31. package/dist/esm/constants/constants.js +4 -1
  32. package/dist/esm/context/DatasetContext.js +1 -1
  33. package/dist/esm/context/SettingsContext.js +19 -2
  34. package/dist/esm/utils/VirtualizedList.js +2 -2
  35. package/package.json +4 -3
  36. package/scss/cherita.scss +23 -0
  37. package/scss/components/accordions.scss +11 -1
  38. package/scss/components/layouts.scss +68 -32
  39. package/scss/components/lists.scss +8 -1
@@ -38,11 +38,6 @@ function FullPage(_ref) {
38
38
  defaultPlotType = _constants.PLOT_TYPES.SCATTERPLOT
39
39
  } = _ref,
40
40
  props = _objectWithoutProperties(_ref, _excluded);
41
- const appRef = (0, _react.useRef)();
42
- const [appDimensions, setAppDimensions] = (0, _react.useState)({
43
- width: 0,
44
- height: 0
45
- });
46
41
  const [showObs, setShowObs] = (0, _react.useState)(false);
47
42
  const [showObsm, setShowObsm] = (0, _react.useState)(false);
48
43
  const [showVars, setShowVars] = (0, _react.useState)(false);
@@ -58,27 +53,6 @@ function FullPage(_ref) {
58
53
  const XlBreakpoint = (0, _material.useMediaQuery)(_constants.BREAKPOINTS.XL);
59
54
  const showObsBtn = LgBreakpoint;
60
55
  const showVarsBtn = XlBreakpoint;
61
- (0, _react.useEffect)(() => {
62
- const updateDimensions = () => {
63
- if (appRef.current) {
64
- // Get the distance from the top of the page to the target element
65
- const rect = appRef.current.getBoundingClientRect();
66
- const distanceFromTop = rect.top + window.scrollY;
67
-
68
- // Calculate the available height for the Cherita app
69
- const availableHeight = window.innerHeight - distanceFromTop;
70
-
71
- // Update the dimensions to fit the viewport minus the navbar height
72
- setAppDimensions({
73
- width: appRef.current.offsetWidth,
74
- height: availableHeight
75
- });
76
- }
77
- };
78
- window.addEventListener("resize", updateDimensions);
79
- updateDimensions();
80
- return () => window.removeEventListener("resize", updateDimensions);
81
- }, []);
82
56
  const {
83
57
  plotControls,
84
58
  varMode,
@@ -138,29 +112,21 @@ function FullPage(_ref) {
138
112
  }
139
113
  }, [plotType, showObsBtn, showVarsBtn]);
140
114
  return /*#__PURE__*/_react.default.createElement("div", {
141
- ref: appRef,
142
- className: "cherita-app",
143
- style: {
144
- height: appDimensions.height
145
- }
115
+ className: "cherita-app"
146
116
  }, /*#__PURE__*/_react.default.createElement(_DatasetContext.DatasetProvider, props, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Container, {
147
117
  fluid: true,
148
- className: "cherita-app-container",
149
- style: {
150
- height: appDimensions.height
151
- }
118
+ className: "cherita-app-container"
152
119
  }, /*#__PURE__*/_react.default.createElement("div", {
153
120
  className: "cherita-app-obs modern-scrollbars border-end h-100"
154
121
  }, /*#__PURE__*/_react.default.createElement(_ObsList.ObsColsList, _extends({}, props, {
155
122
  showSelectedAsActive: showSelectedAsActive,
156
- showHistograms: varMode === _constants.SELECTION_MODES.SINGLE
123
+ showHistograms: varMode === _constants.SELECTION_MODES.SINGLE,
124
+ showColor: varMode === _constants.SELECTION_MODES.SINGLE
157
125
  }))), /*#__PURE__*/_react.default.createElement("div", {
158
126
  className: "cherita-app-canvas"
159
127
  }, plot), /*#__PURE__*/_react.default.createElement("div", {
160
128
  className: "cherita-app-sidebar p-3"
161
- }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card.Body, {
162
- className: "d-flex flex-column p-0"
163
- }, /*#__PURE__*/_react.default.createElement("div", {
129
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card.Body, null, /*#__PURE__*/_react.default.createElement("div", {
164
130
  className: "sidebar-plotselector"
165
131
  }, /*#__PURE__*/_react.default.createElement(_PlotTypeSelector.PlotTypeSelector, {
166
132
  currentType: plotType,
@@ -172,13 +138,15 @@ function FullPage(_ref) {
172
138
  setPlotType: setpseudospatialPlotType,
173
139
  setShowControls: setShowPseudospatialControls
174
140
  })) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null), /*#__PURE__*/_react.default.createElement("div", {
175
- className: "sidebar-features modern-scrollbars"
141
+ className: "sidebar-features"
176
142
  }, /*#__PURE__*/_react.default.createElement(_SearchBar.SearchBar, {
177
143
  searchDiseases: searchDiseases,
178
144
  searchVar: true
179
- }), /*#__PURE__*/_react.default.createElement(_VarList.VarNamesList, {
145
+ }), /*#__PURE__*/_react.default.createElement("div", {
146
+ className: "sidebar-features-list"
147
+ }, /*#__PURE__*/_react.default.createElement(_VarList.VarNamesList, {
180
148
  mode: varMode
181
- })))))), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal, {
149
+ }))))))), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal, {
182
150
  show: showModal,
183
151
  onHide: () => setShowModal(false),
184
152
  centered: true
@@ -13,11 +13,11 @@ var _violin = _interopRequireDefault(require("../../../assets/images/plots/violi
13
13
  var _constants = require("../../constants/constants");
14
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
15
  const iconMap = {
16
- [_constants.PLOT_TYPES.DOTPLOT]: _dotplot.default,
17
- [_constants.PLOT_TYPES.MATRIXPLOT]: _matrixplot.default,
18
- [_constants.PLOT_TYPES.VIOLINPLOT]: _violin.default,
19
16
  [_constants.PLOT_TYPES.SCATTERPLOT]: _scatterplot.default,
20
- [_constants.PLOT_TYPES.HEATMAP]: _heatmap.default
17
+ [_constants.PLOT_TYPES.MATRIXPLOT]: _matrixplot.default,
18
+ [_constants.PLOT_TYPES.HEATMAP]: _heatmap.default,
19
+ [_constants.PLOT_TYPES.DOTPLOT]: _dotplot.default,
20
+ [_constants.PLOT_TYPES.VIOLINPLOT]: _violin.default
21
21
  };
22
22
  const plotTypes = Object.entries(iconMap).map(_ref => {
23
23
  let [type, icon] = _ref;
@@ -173,19 +173,20 @@ function CategoricalItem(_ref2) {
173
173
  key: value,
174
174
  className: "obs-item"
175
175
  }, /*#__PURE__*/_react.default.createElement("div", {
176
- className: "d-flex align-items-center"
176
+ className: "d-flex align-items-center flex-wrap"
177
177
  }, /*#__PURE__*/_react.default.createElement("div", {
178
- className: "flex-grow-1"
178
+ className: "flex-grow-1 me-auto mw-100"
179
179
  }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Form.Check, {
180
180
  className: "obs-value-list-check",
181
181
  type: "switch",
182
+ title: label,
182
183
  label: label,
183
184
  checked: !isOmitted,
184
185
  onChange: () => onChange(value)
185
186
  })), /*#__PURE__*/_react.default.createElement("div", {
186
- className: "d-flex align-items-center"
187
- }, /*#__PURE__*/_react.default.createElement("div", {
188
- className: "pl-1 m-0 flex-grow-1"
187
+ className: "d-flex align-items-center ms-auto"
188
+ }, (!!histogramData.data || histogramData.isPending) && /*#__PURE__*/_react.default.createElement("div", {
189
+ className: "pl-1 m-0"
189
190
  }, /*#__PURE__*/_react.default.createElement(_Histogram.Histogram, {
190
191
  data: histogramData.data,
191
192
  isPending: histogramData.isPending,
@@ -200,7 +201,7 @@ function CategoricalItem(_ref2) {
200
201
  className: "d-flex align-items-center"
201
202
  }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Badge, {
202
203
  className: "value-count-badge"
203
- }, " ", isSliced && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, (0, _string.formatNumerical)(parseInt(filteredStats.value_counts)), " ", "out of", " "), (0, _string.formatNumerical)(parseInt(stats.value_counts), _string.FORMATS.EXPONENTIAL)), /*#__PURE__*/_react.default.createElement("div", {
204
+ }, " ", isSliced && parseInt(filteredStats.value_counts) !== parseInt(stats.value_counts) && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, (0, _string.formatNumerical)(parseInt(filteredStats.value_counts)), " ", "out of", " "), (0, _string.formatNumerical)(parseInt(stats.value_counts), _string.FORMATS.EXPONENTIAL)), /*#__PURE__*/_react.default.createElement("div", {
204
205
  className: "value-pct-gauge-container"
205
206
  }, isSliced ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_xCharts.Gauge, {
206
207
  className: "pct-gauge filtered-pct-gauge",
@@ -280,6 +281,7 @@ function CategoricalObs(_ref3) {
280
281
  enabled: showHistograms
281
282
  });
282
283
  const filteredObsData = useFilteredObsData(obs);
284
+ const enabledHistograms = (0, _react.useMemo)(() => showHistograms && settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR, [settings.colorEncoding, showHistograms]);
283
285
  const getDataAtIndex = (0, _react.useCallback)(index => {
284
286
  var _obsHistograms$fetche;
285
287
  return {
@@ -291,7 +293,7 @@ function CategoricalObs(_ref3) {
291
293
  },
292
294
  isOmitted: _lodash.default.includes(obs.omit, obs.codes[obs.values[index]]),
293
295
  label: obs.values[index],
294
- histogramData: showHistograms && settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR ? {
296
+ histogramData: enabledHistograms ? {
295
297
  data: (_obsHistograms$fetche = obsHistograms.fetchedData) === null || _obsHistograms$fetche === void 0 ? void 0 : _obsHistograms$fetche[obs.values[index]],
296
298
  isPending: obsHistograms.isPending,
297
299
  altColor: isSliced
@@ -306,7 +308,7 @@ function CategoricalObs(_ref3) {
306
308
  isSliced: isSliced,
307
309
  colors: obs.colors
308
310
  };
309
- }, [obs.values, obs.codes, obs.value_counts, obs.omit, totalCounts, showHistograms, obs.colors, settings.colorEncoding, obsHistograms.fetchedData, obsHistograms.isPending, isSliced, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.value_counts, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.pct]);
311
+ }, [obs.values, obs.codes, obs.value_counts, obs.omit, obs.colors, totalCounts, enabledHistograms, obsHistograms.fetchedData, obsHistograms.isPending, isSliced, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.value_counts, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.pct]);
310
312
  showColor &= settings.colorEncoding === _constants.COLOR_ENCODINGS.OBS;
311
313
  return /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, {
312
314
  variant: "flush",
@@ -325,7 +327,11 @@ function CategoricalObs(_ref3) {
325
327
  max: max,
326
328
  onChange: toggleObs,
327
329
  showColor: showColor,
328
- estimateSize: 42
330
+ estimateSize: i =>
331
+ // rough attempt to determine size based on label length
332
+ // estimate size of 68 pixels if label is long (>=25 chars if enabledHistograms, >=30 if showColor, >=35 otherwise), else 42
333
+ // TODO: consider isSliced as count badge will be longer ?
334
+ obs.values[i].length >= (enabledHistograms ? 25 : showColor ? 30 : 35) ? 68 : 42
329
335
  }));
330
336
  }
331
337
  function ObsContinuousStats(_ref4) {
@@ -409,6 +415,10 @@ function ContinuousObs(_ref5) {
409
415
  enabled: showHistograms
410
416
  });
411
417
  const filteredObsData = useFilteredObsData(obs);
418
+ const enabledHistograms = (0, _react.useMemo)(() => showHistograms && settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR, [settings.colorEncoding, showHistograms]);
419
+ const getLabel = (0, _react.useCallback)(index => {
420
+ return isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges);
421
+ }, [obs.bins.binEdges, obs.codes, obs.values]);
412
422
  const getDataAtIndex = (0, _react.useCallback)(index => {
413
423
  var _obsHistograms$fetche2;
414
424
  return {
@@ -419,8 +429,8 @@ function ContinuousObs(_ref5) {
419
429
  pct: obs.value_counts[obs.values[index]] / totalCounts * 100
420
430
  },
421
431
  isOmitted: _lodash.default.includes(obs.omit, obs.codes[obs.values[index]]),
422
- label: isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges),
423
- histogramData: showHistograms && settings.colorEncoding === _constants.COLOR_ENCODINGS.VAR ? {
432
+ label: getLabel(index),
433
+ histogramData: enabledHistograms ? {
424
434
  data: (_obsHistograms$fetche2 = obsHistograms.fetchedData) === null || _obsHistograms$fetche2 === void 0 ? void 0 : _obsHistograms$fetche2[obs.values[index]],
425
435
  isPending: obsHistograms.isPending,
426
436
  altColor: isSliced
@@ -434,7 +444,7 @@ function ContinuousObs(_ref5) {
434
444
  },
435
445
  isSliced: isSliced
436
446
  };
437
- }, [filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.pct, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.value_counts, isSliced, obs.bins.binEdges, obs.codes, obs.omit, obs.value_counts, obs.values, obsHistograms.fetchedData, obsHistograms.isPending, settings.colorEncoding, showHistograms, totalCounts]);
447
+ }, [enabledHistograms, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.pct, filteredObsData === null || filteredObsData === void 0 ? void 0 : filteredObsData.value_counts, getLabel, isSliced, obs.codes, obs.omit, obs.value_counts, obs.values, obsHistograms.fetchedData, obsHistograms.isPending, totalCounts]);
438
448
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, {
439
449
  variant: "flush",
440
450
  className: "cherita-list"
@@ -452,7 +462,11 @@ function ContinuousObs(_ref5) {
452
462
  max: max,
453
463
  onChange: toggleObs,
454
464
  showColor: false,
455
- estimateSize: 42
465
+ estimateSize: i =>
466
+ // rough attempt to determine size based on label length
467
+ // estimate size of 68 pixels if label is long (>=20 chars if enabledHistograms, >=30 otherwise), else 42
468
+ // TODO: consider isSliced as count badge will be longer ?
469
+ getLabel(i).length >= (enabledHistograms ? 20 : 30) ? 68 : 42
456
470
  })), /*#__PURE__*/_react.default.createElement(ObsContinuousStats, {
457
471
  obs: obs
458
472
  }));
@@ -49,7 +49,7 @@ const ObsAccordionToggle = _ref => {
49
49
  }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
50
50
  icon: isCurrentEventKey ? _freeSolidSvgIcons.faChevronDown : _freeSolidSvgIcons.faChevronRight
51
51
  })), /*#__PURE__*/_react.default.createElement("span", {
52
- className: "obs-accordion-header-title"
52
+ className: "obs-accordion-header-span"
53
53
  }, children));
54
54
  };
55
55
  function ObsColsList(_ref2) {
@@ -232,7 +232,11 @@ function ObsColsList(_ref2) {
232
232
  }, /*#__PURE__*/_react.default.createElement(ObsAccordionToggle, {
233
233
  eventKey: item.name,
234
234
  handleAccordionToggle: handleAccordionToggle
235
- }, /*#__PURE__*/_react.default.createElement("div", null, item.name), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("span", {
235
+ }, /*#__PURE__*/_react.default.createElement("div", {
236
+ className: "accordion-header-title"
237
+ }, item.name), /*#__PURE__*/_react.default.createElement("div", {
238
+ className: "accordion-header-toolbar"
239
+ }, /*#__PURE__*/_react.default.createElement("span", {
236
240
  className: "mx-1 cursor-pointer ".concat(inLabelObs ? "active-icon" : "text-muted opacity-50"),
237
241
  onClick: event => {
238
242
  event.stopPropagation();
@@ -5,11 +5,14 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ObsmKeysList = ObsmKeysList;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
+ var _lodash = _interopRequireDefault(require("lodash"));
8
9
  var _reactBootstrap = require("react-bootstrap");
10
+ var _constants = require("../../constants/constants");
9
11
  var _DatasetContext = require("../../context/DatasetContext");
10
12
  var _SettingsContext = require("../../context/SettingsContext");
11
13
  var _requests = require("../../utils/requests");
12
14
  var _Skeleton = require("../../utils/Skeleton");
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
17
  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; }
15
18
  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; }
@@ -43,13 +46,34 @@ function ObsmKeysList() {
43
46
  (0, _react.useEffect)(() => {
44
47
  if (!isPending && !serverError && fetchedData) {
45
48
  setObsmKeysList(fetchedData);
49
+
50
+ // Set default obsm if in keys list and not selected
51
+ if (!settings.selectedObsm && !!fetchedData.length) {
52
+ // Follow DEFAULT_OBSM_KEYS order
53
+ _lodash.default.each(_constants.DEFAULT_OBSM_KEYS, k => {
54
+ const defaultObsm = _lodash.default.find(fetchedData, item => item.toLowerCase() === k);
55
+ if (defaultObsm) {
56
+ dispatch({
57
+ type: "select.obsm",
58
+ obsm: defaultObsm
59
+ });
60
+ return false; // break
61
+ }
62
+ });
63
+ }
64
+ if (settings.selectedObsm) {
65
+ // If selected obsm is not in keys list, reset to null
66
+ if (!_lodash.default.includes(fetchedData, settings.selectedObsm)) {
67
+ dispatch({
68
+ type: "select.obsm",
69
+ obsm: null
70
+ });
71
+ } else {
72
+ setActive(settings.selectedObsm);
73
+ }
74
+ }
46
75
  }
47
- }, [fetchedData, isPending, serverError]);
48
- (0, _react.useEffect)(() => {
49
- if (settings.selectedObsm) {
50
- setActive(settings.selectedObsm);
51
- }
52
- }, [settings.selectedObsm]);
76
+ }, [dispatch, fetchedData, isPending, serverError, settings.selectedObsm]);
53
77
  const obsmList = obsmKeysList.map(item => {
54
78
  return /*#__PURE__*/_react.default.createElement(_reactBootstrap.Dropdown.Item, {
55
79
  key: item,
@@ -55,14 +55,19 @@ function OffcanvasVars(_ref3) {
55
55
  } = _ref3;
56
56
  return /*#__PURE__*/_react.default.createElement(_Offcanvas.default, {
57
57
  show: show,
58
- onHide: handleClose
58
+ onHide: handleClose,
59
+ className: "offcanvas-vars"
59
60
  }, /*#__PURE__*/_react.default.createElement(_Offcanvas.default.Header, {
60
61
  closeButton: true
61
- }, /*#__PURE__*/_react.default.createElement(_Offcanvas.default.Title, null, "Features")), /*#__PURE__*/_react.default.createElement(_Offcanvas.default.Body, null, /*#__PURE__*/_react.default.createElement(_SearchBar.SearchBar, {
62
+ }, /*#__PURE__*/_react.default.createElement(_Offcanvas.default.Title, null, "Features")), /*#__PURE__*/_react.default.createElement(_Offcanvas.default.Body, null, /*#__PURE__*/_react.default.createElement("div", {
63
+ className: "sidebar-features"
64
+ }, /*#__PURE__*/_react.default.createElement(_SearchBar.SearchBar, {
62
65
  searchDiseases: true
63
- }), /*#__PURE__*/_react.default.createElement(_VarList.VarNamesList, {
66
+ }), /*#__PURE__*/_react.default.createElement("div", {
67
+ className: "sidebar-features-list"
68
+ }, /*#__PURE__*/_react.default.createElement(_VarList.VarNamesList, {
64
69
  mode: mode
65
- })));
70
+ })))));
66
71
  }
67
72
  function OffcanvasControls(_ref4) {
68
73
  let {
@@ -154,7 +154,7 @@ function Scatterplot(_ref) {
154
154
  });
155
155
  });
156
156
  }
157
- }, [settings.selectedObsm, getRadiusScale, obsmData.data, obsmData.isPending, obsmData.serverError, data.positions]);
157
+ }, [getRadiusScale, obsmData.data, obsmData.isPending, obsmData.serverError, data.positions]);
158
158
  const getBounds = (0, _react.useCallback)(() => {
159
159
  var _deckRef$current3, _deckRef$current4;
160
160
  const {
@@ -8,6 +8,7 @@ exports.SearchModal = SearchModal;
8
8
  var _react = _interopRequireWildcard(require("react"));
9
9
  var _Close = _interopRequireDefault(require("@mui/icons-material/Close"));
10
10
  var _Search = _interopRequireDefault(require("@mui/icons-material/Search"));
11
+ var _lodash = _interopRequireDefault(require("lodash"));
11
12
  var _reactBootstrap = require("react-bootstrap");
12
13
  var _Col = _interopRequireDefault(require("react-bootstrap/Col"));
13
14
  var _Container = _interopRequireDefault(require("react-bootstrap/Container"));
@@ -24,7 +25,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
24
25
  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; }
25
26
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
26
27
  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); }
27
- function onVarSelect(dispatch, item) {
28
+ const select = (dispatch, item) => {
28
29
  dispatch({
29
30
  type: "select.var",
30
31
  var: item
@@ -37,6 +38,14 @@ function onVarSelect(dispatch, item) {
37
38
  type: "set.colorEncoding",
38
39
  value: _constants.COLOR_ENCODINGS.VAR
39
40
  });
41
+ };
42
+ const debounceSelect = _lodash.default.debounce(select, 2000);
43
+ function onVarSelect(dispatch, item) {
44
+ dispatch({
45
+ type: "add.var",
46
+ var: item
47
+ });
48
+ debounceSelect(dispatch, item);
40
49
  }
41
50
  function addVarSet(dispatch, _ref) {
42
51
  let {
@@ -98,7 +98,7 @@ function VarSearchResults(_ref) {
98
98
  count: deferredData.length,
99
99
  ItemComponent: ItemComponent,
100
100
  overscan: 500,
101
- estimateSize: 42,
101
+ estimateSize: () => 42,
102
102
  maxHeight: "70vh"
103
103
  }) : /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, {
104
104
  key: "empty",
@@ -169,7 +169,7 @@ function DiseasesSearchResults(_ref2) {
169
169
  count: deferredData.length,
170
170
  ItemComponent: ItemComponent,
171
171
  overscan: 250,
172
- estimateSize: 32,
172
+ estimateSize: () => 32,
173
173
  maxHeight: "70vh"
174
174
  }) : /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, {
175
175
  key: "empty",
@@ -123,14 +123,17 @@ function SelectionItem(_ref3) {
123
123
  });
124
124
  const hasDiseaseInfo = !isPending && !serverError && !!(fetchedData !== null && fetchedData !== void 0 && fetchedData.length);
125
125
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
126
- className: "d-flex justify-content-between ".concat(hasDiseaseInfo ? "cursor-pointer" : ""),
126
+ className: hasDiseaseInfo ? "cursor-pointer" : "",
127
127
  onClick: () => {
128
128
  setOpenInfo(o => !o);
129
129
  }
130
130
  }, /*#__PURE__*/_react.default.createElement("div", {
131
- className: "d-flex justify-content-between align-items-center w-100"
132
- }, /*#__PURE__*/_react.default.createElement("div", null, item.name), /*#__PURE__*/_react.default.createElement("div", {
133
- className: "d-flex align-items-center gap-1"
131
+ className: "d-flex align-items-center"
132
+ }, /*#__PURE__*/_react.default.createElement("div", {
133
+ className: "var-item-name",
134
+ title: item.name
135
+ }, item.name), /*#__PURE__*/_react.default.createElement("div", {
136
+ className: "ms-auto d-flex align-items-center gap-1"
134
137
  }, hasDiseaseInfo && /*#__PURE__*/_react.default.createElement(_iconsMaterial.MoreVert, null), /*#__PURE__*/_react.default.createElement(VarHistogram, {
135
138
  item: item
136
139
  }), showSetColorEncoding && /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
@@ -169,6 +172,24 @@ function SelectionItem(_ref3) {
169
172
  data: fetchedData
170
173
  }))));
171
174
  }
175
+ const select = (dispatch, mode, item) => {
176
+ if (mode === _constants.SELECTION_MODES.SINGLE) {
177
+ dispatch({
178
+ type: "select.var",
179
+ var: item
180
+ });
181
+ } else if (mode === _constants.SELECTION_MODES.MULTIPLE) {
182
+ dispatch({
183
+ type: "select.multivar",
184
+ var: item
185
+ });
186
+ }
187
+ dispatch({
188
+ type: "set.colorEncoding",
189
+ value: _constants.COLOR_ENCODINGS.VAR
190
+ });
191
+ };
192
+ const debounceSelect = _lodash.default.debounce(select, 200);
172
193
  function VarItem(_ref4) {
173
194
  let {
174
195
  item,
@@ -177,27 +198,7 @@ function VarItem(_ref4) {
177
198
  } = _ref4;
178
199
  const settings = (0, _SettingsContext.useSettings)();
179
200
  const dispatch = (0, _SettingsContext.useSettingsDispatch)();
180
- const selectVar = () => {
181
- if (mode === _constants.SELECTION_MODES.SINGLE) {
182
- dispatch({
183
- type: "select.var",
184
- var: item
185
- });
186
- dispatch({
187
- type: "set.colorEncoding",
188
- value: _constants.COLOR_ENCODINGS.VAR
189
- });
190
- } else if (mode === _constants.SELECTION_MODES.MULTIPLE) {
191
- dispatch({
192
- type: "select.multivar",
193
- var: item
194
- });
195
- dispatch({
196
- type: "set.colorEncoding",
197
- value: _constants.COLOR_ENCODINGS.VAR
198
- });
199
- }
200
- };
201
+ const selectVar = () => debounceSelect(dispatch, mode, item);
201
202
  const removeVar = () => {
202
203
  dispatch({
203
204
  type: "remove.var",
@@ -142,9 +142,7 @@ function VarNamesList(_ref) {
142
142
  };
143
143
  const isPending = varMeans.isPending && settings.varSort.var.sort === _constants.VAR_SORT.MATRIX;
144
144
  return /*#__PURE__*/_react.default.createElement("div", {
145
- className: "position-relative"
146
- }, /*#__PURE__*/_react.default.createElement("div", {
147
- className: "overflow-auto mt-3"
145
+ className: "mt-3 d-flex flex-column h-100"
148
146
  }, /*#__PURE__*/_react.default.createElement("div", {
149
147
  className: "d-flex justify-content-between mb-2"
150
148
  }, /*#__PURE__*/_react.default.createElement("h5", null, _lodash.default.capitalize(displayName)), /*#__PURE__*/_react.default.createElement(_ButtonGroup.default, {
@@ -175,9 +173,9 @@ function VarNamesList(_ref) {
175
173
  }), "Clear"))), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, !varList.length ? /*#__PURE__*/_react.default.createElement(_reactBootstrap.Alert, {
176
174
  variant: "light"
177
175
  }, "Search for a feature.") : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_VarListToolbar.VarListToolbar, null), /*#__PURE__*/_react.default.createElement("div", {
178
- className: "position-relative"
176
+ className: "overflow-auto flex-grow-1 modern-scrollbars"
179
177
  }, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingSpinner, null), /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, {
180
178
  variant: "flush",
181
179
  className: "cherita-list"
182
- }, varList))))));
180
+ }, varList)))));
183
181
  }
@@ -143,8 +143,15 @@ function Violin(_ref) {
143
143
  if (!serverError) {
144
144
  if (hasSelections) {
145
145
  return /*#__PURE__*/_react.default.createElement("div", {
146
- className: "cherita-plot cherita-violin position-relative"
147
- }, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingSpinner, null), /*#__PURE__*/_react.default.createElement(_reactPlotly.default, {
146
+ className: "cherita-plot cherita-violin"
147
+ }, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingSpinner, null), /*#__PURE__*/_react.default.createElement("div", {
148
+ className: "d-flex flex-column h-100"
149
+ }, /*#__PURE__*/_react.default.createElement("div", {
150
+ className: "flex-grow-1 position-relative",
151
+ style: {
152
+ minHeight: "0"
153
+ }
154
+ }, /*#__PURE__*/_react.default.createElement(_reactPlotly.default, {
148
155
  data: data,
149
156
  layout: layout,
150
157
  useResizeHandler: true,
@@ -156,14 +163,17 @@ function Violin(_ref) {
156
163
  displaylogo: false,
157
164
  modeBarButtons: modeBarButtons
158
165
  }
159
- }), (fetchedData === null || fetchedData === void 0 ? void 0 : fetchedData.resampled) && /*#__PURE__*/_react.default.createElement(_reactBootstrap.Alert, {
160
- variant: "warning"
166
+ })), (fetchedData === null || fetchedData === void 0 ? void 0 : fetchedData.resampled) && /*#__PURE__*/_react.default.createElement("div", {
167
+ className: "flex-shrink-0"
168
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Alert, {
169
+ variant: "warning",
170
+ className: "mb-1"
161
171
  }, /*#__PURE__*/_react.default.createElement("b", null, "Warning:"), " For performance reasons this plot was generated with resampled data. It will not be exactly the same as one produced with the entire dataset. \xA0", /*#__PURE__*/_react.default.createElement(_reactBootstrap.OverlayTrigger, {
162
172
  placement: "top",
163
173
  overlay: /*#__PURE__*/_react.default.createElement(_reactBootstrap.Tooltip, null, "Resampled to 100K values following a Monte Carlo style approach to help ensure resampled data is a good representation of the original dataset's distribution.")
164
174
  }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
165
175
  icon: _freeSolidSvgIcons.faCircleInfo
166
- }))));
176
+ }))))));
167
177
  }
168
178
  return /*#__PURE__*/_react.default.createElement("div", {
169
179
  className: "cherita-violin"
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.VIOLIN_MODES = exports.VIOLINPLOT_SCALES = exports.VAR_SORT_ORDER = exports.VAR_SORT = exports.UNSELECTED_POLYGON_FILLCOLOR = exports.SELECTION_MODES = exports.SELECTED_POLYGON_FILLCOLOR = exports.PSEUDOSPATIAL_PLOT_TYPES = exports.PSEUDOSPATIAL_CATEGORICAL_MODES = exports.PLOT_TYPES = exports.PLOTLY_MODEBAR_BUTTONS = exports.OBS_TYPES = exports.MATRIXPLOT_SCALES = exports.LOCAL_STORAGE_KEY = exports.DOTPLOT_SCALES = exports.DEFAULT_OBS_GROUP = exports.COLOR_ENCODINGS = exports.BREAKPOINTS = void 0;
6
+ exports.VIOLIN_MODES = exports.VIOLINPLOT_SCALES = exports.VAR_SORT_ORDER = exports.VAR_SORT = exports.UNSELECTED_POLYGON_FILLCOLOR = exports.SELECTION_MODES = exports.SELECTED_POLYGON_FILLCOLOR = exports.PSEUDOSPATIAL_PLOT_TYPES = exports.PSEUDOSPATIAL_CATEGORICAL_MODES = exports.PLOT_TYPES = exports.PLOTLY_MODEBAR_BUTTONS = exports.OBS_TYPES = exports.MATRIXPLOT_SCALES = exports.LOCAL_STORAGE_KEY = exports.DOTPLOT_SCALES = exports.DEFAULT_OBS_GROUP = exports.DEFAULT_OBSM_KEYS = exports.COLOR_ENCODINGS = exports.BREAKPOINTS = void 0;
7
7
  const LOCAL_STORAGE_KEY = exports.LOCAL_STORAGE_KEY = "CHERITA";
8
8
  const PLOT_TYPES = exports.PLOT_TYPES = {
9
9
  SCATTERPLOT: "scatterplot",
@@ -103,4 +103,7 @@ const PLOTLY_MODEBAR_BUTTONS = exports.PLOTLY_MODEBAR_BUTTONS = ["toImage", "zoo
103
103
  const BREAKPOINTS = exports.BREAKPOINTS = {
104
104
  LG: "(max-width: 991.98px)",
105
105
  XL: "(max-width: 1199.98px)"
106
- };
106
+ };
107
+
108
+ // In order of priority
109
+ const DEFAULT_OBSM_KEYS = exports.DEFAULT_OBSM_KEYS = ["x_umap", "x_tsne", "x_scvi", "x_pca"];
@@ -60,7 +60,7 @@ const persistOptions = {
60
60
  return false;
61
61
  }
62
62
  },
63
- buster: "1.4.0" || "0.0.0"
63
+ buster: "1.4.1-dev.2025-06-30.e26168b5" || "0.0.0"
64
64
  // @TODO: add maxAge and api version numbers as buster
65
65
  };
66
66
  const initialDataset = {
@@ -10,6 +10,7 @@ exports.useSettingsDispatch = useSettingsDispatch;
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _lodash = _interopRequireDefault(require("lodash"));
12
12
  var _constants = require("../constants/constants");
13
+ const _excluded = ["buster", "timestamp"];
13
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
15
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
16
  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; }
@@ -17,6 +18,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
17
18
  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; }
18
19
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
19
20
  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); }
21
+ function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
22
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
20
23
  const SettingsContext = exports.SettingsContext = /*#__PURE__*/(0, _react.createContext)(null);
21
24
  const SettingsDispatchContext = exports.SettingsDispatchContext = /*#__PURE__*/(0, _react.createContext)(null);
22
25
 
@@ -128,7 +131,18 @@ function SettingsProvider(_ref2) {
128
131
  const DATASET_STORAGE_KEY = "".concat(_constants.LOCAL_STORAGE_KEY, "-").concat(dataset_url);
129
132
  // Use localStorage directly instead of useLocalStorage due to unnecessary re-renders
130
133
  // https://github.com/uidotdev/usehooks/issues/157
131
- const localSettings = JSON.parse(localStorage.getItem(DATASET_STORAGE_KEY)) || {};
134
+ let _ref3 = JSON.parse(localStorage.getItem(DATASET_STORAGE_KEY)) || {},
135
+ {
136
+ buster,
137
+ timestamp
138
+ } = _ref3,
139
+ localSettings = _objectWithoutProperties(_ref3, _excluded);
140
+
141
+ // If the buster is not set or does not match the current package version,
142
+ // reset localSettings to avoid stale data
143
+ if (!buster || buster !== "1.4.1-dev.2025-06-30.e26168b5") {
144
+ localSettings = {};
145
+ }
132
146
  const [settings, dispatch] = (0, _react.useReducer)(settingsReducer, {
133
147
  canOverrideSettings,
134
148
  defaultSettings,
@@ -137,7 +151,10 @@ function SettingsProvider(_ref2) {
137
151
  (0, _react.useEffect)(() => {
138
152
  if (canOverrideSettings) {
139
153
  try {
140
- localStorage.setItem(DATASET_STORAGE_KEY, JSON.stringify(settings));
154
+ localStorage.setItem(DATASET_STORAGE_KEY, JSON.stringify(_objectSpread({
155
+ buster: "1.4.1-dev.2025-06-30.e26168b5" || "0.0.0",
156
+ timestamp: Date.now()
157
+ }, settings)));
141
158
  } catch (err) {
142
159
  if (err.code === 22 || err.code === 1014 || err.name === "QuotaExceededError" || err.name === "NS_ERROR_DOM_QUOTA_REACHED") {
143
160
  console.err("Browser storage quota exceeded");
@@ -17,7 +17,7 @@ function VirtualizedList(_ref) {
17
17
  getDataAtIndex,
18
18
  count,
19
19
  ItemComponent,
20
- estimateSize = 45,
20
+ estimateSize = () => 45,
21
21
  overscan = 25,
22
22
  maxHeight = "65vh"
23
23
  } = _ref,
@@ -26,7 +26,7 @@ function VirtualizedList(_ref) {
26
26
  const itemVirtualizer = (0, _reactVirtual.useVirtualizer)({
27
27
  count: count,
28
28
  getScrollElement: () => parentNode,
29
- estimateSize: () => estimateSize,
29
+ estimateSize: i => estimateSize(i),
30
30
  overscan: overscan
31
31
  });
32
32
  const refCallback = (0, _react.useCallback)(node => {