@haniffalab/cherita-react 1.0.0-dev.2025-03-13.bda9e1a6 → 1.0.0-dev.2025-03-24.e68f9e22

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 (95) hide show
  1. package/dist/{components → cjs/components}/full-page/FullPage.js +6 -5
  2. package/dist/{components → cjs/components}/full-page/FullPagePseudospatial.js +6 -5
  3. package/dist/{components → cjs/components}/obs-list/ObsItem.js +50 -46
  4. package/dist/{components → cjs/components}/obs-list/ObsList.js +9 -7
  5. package/dist/{components → cjs/components}/obs-list/ObsToolbar.js +12 -11
  6. package/dist/{components → cjs/components}/offcanvas/index.js +24 -20
  7. package/dist/{components → cjs/components}/pseudospatial/Pseudospatial.js +9 -8
  8. package/dist/{components → cjs/components}/pseudospatial/PseudospatialToolbar.js +4 -3
  9. package/dist/{components → cjs/components}/scatterplot/Scatterplot.js +31 -22
  10. package/dist/{components → cjs/components}/scatterplot/SpatialControls.js +11 -10
  11. package/dist/{components → cjs/components}/scatterplot/Toolbox.js +6 -5
  12. package/dist/{components → cjs/components}/search-bar/SearchBar.js +6 -5
  13. package/dist/{components → cjs/components}/search-bar/SearchResults.js +11 -9
  14. package/dist/{components → cjs/components}/var-list/VarItem.js +32 -27
  15. package/dist/{components → cjs/components}/var-list/VarList.js +21 -12
  16. package/dist/{components → cjs/components}/var-list/VarListToolbar.js +4 -3
  17. package/dist/{components → cjs/components}/var-list/VarSet.js +21 -18
  18. package/dist/{components → cjs/components}/violin/Violin.js +4 -3
  19. package/dist/{context → cjs/context}/DatasetContext.js +11 -9
  20. package/dist/{context → cjs/context}/FilterContext.js +4 -3
  21. package/dist/{context → cjs/context}/ZarrDataContext.js +4 -3
  22. package/dist/{helpers → cjs/helpers}/color-helper.js +12 -11
  23. package/dist/{helpers → cjs/helpers}/map-helper.js +8 -7
  24. package/dist/{helpers → cjs/helpers}/zarr-helper.js +25 -15
  25. package/dist/{utils → cjs/utils}/Histogram.js +12 -8
  26. package/dist/{utils → cjs/utils}/ImageViewer.js +6 -5
  27. package/dist/{utils → cjs/utils}/Legend.js +8 -7
  28. package/dist/{utils → cjs/utils}/LoadingIndicators.js +5 -4
  29. package/dist/{utils → cjs/utils}/VirtualizedList.js +10 -9
  30. package/dist/{utils → cjs/utils}/requests.js +33 -21
  31. package/dist/{utils → cjs/utils}/string.js +9 -4
  32. package/dist/{utils → cjs/utils}/zarrData.js +12 -4
  33. package/dist/esm/components/dotplot/Dotplot.js +135 -0
  34. package/dist/esm/components/dotplot/DotplotControls.js +148 -0
  35. package/dist/esm/components/full-page/FullPage.js +134 -0
  36. package/dist/esm/components/full-page/FullPagePseudospatial.js +151 -0
  37. package/dist/esm/components/heatmap/Heatmap.js +105 -0
  38. package/dist/esm/components/heatmap/HeatmapControls.js +23 -0
  39. package/dist/esm/components/matrixplot/Matrixplot.js +107 -0
  40. package/dist/esm/components/matrixplot/MatrixplotControls.js +38 -0
  41. package/dist/esm/components/obs-list/ObsItem.js +477 -0
  42. package/dist/esm/components/obs-list/ObsList.js +256 -0
  43. package/dist/esm/components/obs-list/ObsToolbar.js +58 -0
  44. package/dist/esm/components/obsm-list/ObsmList.js +72 -0
  45. package/dist/esm/components/offcanvas/index.js +67 -0
  46. package/dist/esm/components/pseudospatial/Pseudospatial.js +228 -0
  47. package/dist/esm/components/pseudospatial/PseudospatialToolbar.js +123 -0
  48. package/dist/esm/components/scatterplot/Scatterplot.js +394 -0
  49. package/dist/esm/components/scatterplot/ScatterplotControls.js +71 -0
  50. package/dist/esm/components/scatterplot/SpatialControls.js +140 -0
  51. package/dist/esm/components/scatterplot/Toolbox.js +25 -0
  52. package/dist/esm/components/search-bar/SearchBar.js +74 -0
  53. package/dist/esm/components/search-bar/SearchResults.js +139 -0
  54. package/dist/esm/components/var-list/VarItem.js +250 -0
  55. package/dist/esm/components/var-list/VarList.js +272 -0
  56. package/dist/esm/components/var-list/VarListToolbar.js +84 -0
  57. package/dist/esm/components/var-list/VarSet.js +193 -0
  58. package/dist/esm/components/violin/Violin.js +141 -0
  59. package/dist/esm/components/violin/ViolinControls.js +24 -0
  60. package/dist/esm/constants/colorscales.js +22 -0
  61. package/dist/esm/constants/constants.js +84 -0
  62. package/dist/esm/context/DatasetContext.js +572 -0
  63. package/dist/esm/context/FilterContext.js +48 -0
  64. package/dist/esm/context/ZarrDataContext.js +26 -0
  65. package/dist/esm/helpers/color-helper.js +66 -0
  66. package/dist/esm/helpers/map-helper.js +53 -0
  67. package/dist/esm/helpers/zarr-helper.js +129 -0
  68. package/dist/esm/index.js +22 -0
  69. package/dist/esm/utils/Filter.js +147 -0
  70. package/dist/esm/utils/Histogram.js +44 -0
  71. package/dist/esm/utils/ImageViewer.js +27 -0
  72. package/dist/esm/utils/Legend.js +58 -0
  73. package/dist/esm/utils/LoadingIndicators.js +22 -0
  74. package/dist/esm/utils/VirtualizedList.js +55 -0
  75. package/dist/esm/utils/errors.js +47 -0
  76. package/dist/esm/utils/requests.js +116 -0
  77. package/dist/esm/utils/search.js +39 -0
  78. package/dist/esm/utils/string.js +59 -0
  79. package/dist/esm/utils/zarrData.js +102 -0
  80. package/package.json +16 -5
  81. /package/dist/{components → cjs/components}/dotplot/Dotplot.js +0 -0
  82. /package/dist/{components → cjs/components}/dotplot/DotplotControls.js +0 -0
  83. /package/dist/{components → cjs/components}/heatmap/Heatmap.js +0 -0
  84. /package/dist/{components → cjs/components}/heatmap/HeatmapControls.js +0 -0
  85. /package/dist/{components → cjs/components}/matrixplot/Matrixplot.js +0 -0
  86. /package/dist/{components → cjs/components}/matrixplot/MatrixplotControls.js +0 -0
  87. /package/dist/{components → cjs/components}/obsm-list/ObsmList.js +0 -0
  88. /package/dist/{components → cjs/components}/scatterplot/ScatterplotControls.js +0 -0
  89. /package/dist/{components → cjs/components}/violin/ViolinControls.js +0 -0
  90. /package/dist/{constants → cjs/constants}/colorscales.js +0 -0
  91. /package/dist/{constants → cjs/constants}/constants.js +0 -0
  92. /package/dist/{index.js → cjs/index.js} +0 -0
  93. /package/dist/{utils → cjs/utils}/Filter.js +0 -0
  94. /package/dist/{utils → cjs/utils}/errors.js +0 -0
  95. /package/dist/{utils → cjs/utils}/search.js +0 -0
@@ -0,0 +1,477 @@
1
+ import React, { useCallback, useEffect, useState, useMemo } from "react";
2
+ import { Tooltip } from "@mui/material";
3
+ import { Gauge, SparkLineChart } from "@mui/x-charts";
4
+ import _ from "lodash";
5
+ import { Badge, Form, ListGroup, Table } from "react-bootstrap";
6
+ import { ObsToolbar } from "./ObsToolbar";
7
+ import { COLOR_ENCODINGS, OBS_TYPES } from "../../constants/constants";
8
+ import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
9
+ import { useFilteredData } from "../../context/FilterContext";
10
+ import { useColor } from "../../helpers/color-helper";
11
+ import { Histogram } from "../../utils/Histogram";
12
+ import { LoadingLinear } from "../../utils/LoadingIndicators";
13
+ import { useFetch } from "../../utils/requests";
14
+ import { formatNumerical, FORMATS } from "../../utils/string";
15
+ import { VirtualizedList } from "../../utils/VirtualizedList";
16
+ import { useObsData } from "../../utils/zarrData";
17
+ const N_BINS = 5;
18
+ function binContinuous(data, nBins) {
19
+ const binSize = (data.max - data.min) * (1 / nBins);
20
+ const thresholds = _.range(nBins + 1).map(b => {
21
+ return data.min + binSize * b;
22
+ });
23
+ const binEdges = _.range(thresholds.length - 1).map(i => [thresholds[i], thresholds[i + 1]]);
24
+ const bins = {
25
+ nBins: nBins,
26
+ binSize: binSize,
27
+ thresholds: thresholds,
28
+ binEdges: binEdges
29
+ };
30
+ return {
31
+ ...data,
32
+ bins: bins
33
+ };
34
+ }
35
+ function getContinuousLabel(code, binEdges) {
36
+ return `[ ${formatNumerical(binEdges[code][0])}, ${formatNumerical(binEdges[code][1], FORMATS.EXPONENTIAL)}${code === binEdges.length - 1 ? " ]" : " )"}`;
37
+ }
38
+ const useObsHistogram = obs => {
39
+ const ENDPOINT = "obs/histograms";
40
+ const dataset = useDataset();
41
+ const {
42
+ obsIndices,
43
+ isSliced
44
+ } = useFilteredData();
45
+ const [params, setParams] = useState({
46
+ url: dataset.url,
47
+ obsCol: _.omit(obs, "omit"),
48
+ // avoid re-rendering when toggling unselected obs
49
+ varKey: dataset.selectedVar?.isSet ? {
50
+ name: dataset.selectedVar?.name,
51
+ indices: dataset.selectedVar?.vars.map(v => v.index)
52
+ } : dataset.selectedVar?.index,
53
+ obsIndices: isSliced ? [...(obsIndices || [])] : null
54
+ });
55
+ useEffect(() => {
56
+ setParams(p => {
57
+ return {
58
+ ...p,
59
+ obsCol: _.omit(obs, "omit"),
60
+ varKey: dataset.selectedVar?.isSet ? {
61
+ name: dataset.selectedVar?.name,
62
+ indices: dataset.selectedVar?.vars.map(v => v.index)
63
+ } : dataset.selectedVar?.index,
64
+ obsIndices: isSliced ? [...(obsIndices || [])] : null
65
+ };
66
+ });
67
+ }, [dataset.selectedVar?.index, dataset.selectedVar?.isSet, dataset.selectedVar?.name, dataset.selectedVar?.vars, obsIndices, isSliced, obs]);
68
+ return useFetch(ENDPOINT, params, {
69
+ enabled: !!dataset.selectedVar && dataset.colorEncoding === COLOR_ENCODINGS.VAR,
70
+ refetchOnMount: false
71
+ });
72
+ };
73
+ const getBinIndex = (v, binEdges) => {
74
+ const EPSILON = 1e-6;
75
+ const lastEdge = _.last(binEdges);
76
+ const allButLastEdges = _.initial(binEdges);
77
+ const modifiedBinEdges = [...allButLastEdges, [lastEdge[0], lastEdge[1] + EPSILON]];
78
+ return _.findIndex(modifiedBinEdges, range => _.inRange(v, ...range));
79
+ };
80
+ const useFilteredObsData = obs => {
81
+ const {
82
+ obsIndices
83
+ } = useFilteredData();
84
+ const obsData = useObsData(obs);
85
+ const isCategorical = obs.type === OBS_TYPES.CATEGORICAL || obs.type === OBS_TYPES.BOOLEAN;
86
+ const {
87
+ valueCounts,
88
+ pct
89
+ } = useMemo(() => {
90
+ const filteredObsValues = _.at(obsData.data, [...(obsIndices || [])]);
91
+ let valueCounts = {};
92
+ if (isCategorical) {
93
+ valueCounts = _.countBy(filteredObsValues);
94
+ } else {
95
+ valueCounts = _.countBy(filteredObsValues, v => {
96
+ return getBinIndex(v, obs.bins?.binEdges || [[null, null]]);
97
+ });
98
+ }
99
+ valueCounts = _.mapKeys(valueCounts, (_v, i) => {
100
+ return obs.codesMap[i];
101
+ });
102
+ const totalCounts = obsIndices?.size;
103
+ const pct = _.mapValues(valueCounts, v => v / totalCounts * 100);
104
+ return {
105
+ valueCounts,
106
+ pct
107
+ };
108
+ }, [isCategorical, obs.bins?.binEdges, obs.codesMap, obsData.data, obsIndices]);
109
+ return {
110
+ value_counts: valueCounts,
111
+ pct: pct
112
+ };
113
+ };
114
+ function CategoricalItem(_ref) {
115
+ let {
116
+ value,
117
+ label,
118
+ code,
119
+ stats = {
120
+ value_counts: null,
121
+ pct: null
122
+ },
123
+ isOmitted,
124
+ min,
125
+ max,
126
+ onChange,
127
+ histogramData = {
128
+ data: null,
129
+ isPending: false,
130
+ altColor: false
131
+ },
132
+ filteredStats = {
133
+ value_counts: null,
134
+ pct: null
135
+ },
136
+ isSliced,
137
+ showColor = true
138
+ } = _ref;
139
+ const {
140
+ getColor
141
+ } = useColor();
142
+ return /*#__PURE__*/React.createElement(ListGroup.Item, {
143
+ key: value,
144
+ className: "obs-item"
145
+ }, /*#__PURE__*/React.createElement("div", {
146
+ className: "d-flex align-items-center"
147
+ }, /*#__PURE__*/React.createElement("div", {
148
+ className: "flex-grow-1"
149
+ }, /*#__PURE__*/React.createElement(Form.Check, {
150
+ className: "obs-value-list-check",
151
+ type: "switch",
152
+ label: label,
153
+ checked: !isOmitted,
154
+ onChange: () => onChange(value)
155
+ })), /*#__PURE__*/React.createElement("div", {
156
+ className: "d-flex align-items-center"
157
+ }, /*#__PURE__*/React.createElement("div", {
158
+ className: "pl-1 m-0 flex-grow-1"
159
+ }, /*#__PURE__*/React.createElement(Histogram, {
160
+ data: histogramData.data,
161
+ isPending: histogramData.isPending,
162
+ altColor: histogramData.altColor
163
+ })), /*#__PURE__*/React.createElement("div", {
164
+ className: "pl-1 m-0"
165
+ }, /*#__PURE__*/React.createElement(Tooltip, {
166
+ title: isSliced ? /*#__PURE__*/React.createElement(React.Fragment, null, "Filtered:", " ", formatNumerical(filteredStats.pct, FORMATS.EXPONENTIAL), "%", /*#__PURE__*/React.createElement("br", null), "Total: ", formatNumerical(stats.pct, FORMATS.EXPONENTIAL), "%") : `${formatNumerical(stats.pct, FORMATS.EXPONENTIAL)}%`,
167
+ placement: "left",
168
+ arrow: true
169
+ }, /*#__PURE__*/React.createElement("div", {
170
+ className: "d-flex align-items-center"
171
+ }, /*#__PURE__*/React.createElement(Badge, {
172
+ className: "value-count-badge"
173
+ }, " ", isSliced && /*#__PURE__*/React.createElement(React.Fragment, null, formatNumerical(parseInt(filteredStats.value_counts)), " ", "out of", " "), formatNumerical(parseInt(stats.value_counts), FORMATS.EXPONENTIAL)), /*#__PURE__*/React.createElement("div", {
174
+ className: "value-pct-gauge-container"
175
+ }, isSliced ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Gauge, {
176
+ className: "pct-gauge filtered-pct-gauge",
177
+ value: filteredStats.pct,
178
+ text: null,
179
+ innerRadius: "50%",
180
+ outerRadius: "75%",
181
+ margin: {
182
+ top: 0,
183
+ right: 0,
184
+ bottom: 0,
185
+ left: 0
186
+ }
187
+ }), /*#__PURE__*/React.createElement(Gauge, {
188
+ className: "pct-gauge",
189
+ value: stats.pct,
190
+ text: null,
191
+ innerRadius: "75%",
192
+ margin: {
193
+ top: 0,
194
+ right: 0,
195
+ bottom: 0,
196
+ left: 0
197
+ }
198
+ })) : /*#__PURE__*/React.createElement(Gauge, {
199
+ value: stats.pct,
200
+ text: null,
201
+ innerRadius: "50%",
202
+ margin: {
203
+ top: 0,
204
+ right: 0,
205
+ bottom: 0,
206
+ left: 0
207
+ }
208
+ }))))), showColor ? /*#__PURE__*/React.createElement("div", {
209
+ className: "pl-1"
210
+ }, /*#__PURE__*/React.createElement("svg", {
211
+ xmlns: "http://www.w3.org/2000/svg",
212
+ width: 24,
213
+ height: 24,
214
+ fill: "currentColor",
215
+ viewBox: "0 0 10 10"
216
+ }, /*#__PURE__*/React.createElement("rect", {
217
+ x: "0",
218
+ y: "0",
219
+ width: "10",
220
+ height: "10",
221
+ fill: `rgb(${getColor({
222
+ value: (code - min) / (max - min),
223
+ categorical: true,
224
+ grayOut: isOmitted,
225
+ grayParams: {
226
+ alpha: 1
227
+ },
228
+ colorEncoding: "obs"
229
+ })})`
230
+ }))) : null)));
231
+ }
232
+ export function CategoricalObs(_ref2) {
233
+ let {
234
+ obs,
235
+ updateObs,
236
+ toggleAll,
237
+ toggleObs,
238
+ toggleLabel,
239
+ toggleSlice,
240
+ toggleColor,
241
+ showColor = true
242
+ } = _ref2;
243
+ const dataset = useDataset();
244
+ const {
245
+ isSliced
246
+ } = useFilteredData();
247
+ const dispatch = useDatasetDispatch();
248
+ const totalCounts = _.sum(_.values(obs.value_counts));
249
+ const min = _.min(_.values(obs.codes));
250
+ const max = _.max(_.values(obs.codes));
251
+ const obsHistograms = useObsHistogram(obs);
252
+ const filteredObsData = useFilteredObsData(obs);
253
+ useEffect(() => {
254
+ if (dataset.selectedObs?.name === obs.name) {
255
+ const selectedObsData = _.omit(dataset.selectedObs, ["omit"]);
256
+ const obsData = _.omit(obs, ["omit"]);
257
+ if (!_.isEqual(selectedObsData, obsData)) {
258
+ // outdated selectedObs
259
+ dispatch({
260
+ type: "select.obs",
261
+ obs: obs
262
+ });
263
+ } else if (!_.isEqual(dataset.selectedObs.omit, obs.omit)) {
264
+ updateObs({
265
+ ...obs,
266
+ omit: dataset.selectedObs.omit
267
+ });
268
+ }
269
+ }
270
+ }, [dataset.selectedObs, dispatch, obs, obs.name, updateObs]);
271
+ const getDataAtIndex = useCallback(index => {
272
+ return {
273
+ value: obs.values[index],
274
+ code: obs.codes[obs.values[index]],
275
+ stats: {
276
+ value_counts: obs.value_counts[obs.values[index]],
277
+ pct: obs.value_counts[obs.values[index]] / totalCounts * 100
278
+ },
279
+ isOmitted: _.includes(obs.omit, obs.codes[obs.values[index]]),
280
+ label: obs.values[index],
281
+ histogramData: dataset.colorEncoding === COLOR_ENCODINGS.VAR ? {
282
+ data: obsHistograms.fetchedData?.[obs.values[index]],
283
+ isPending: obsHistograms.isPending,
284
+ altColor: isSliced
285
+ } : {
286
+ data: null,
287
+ isPending: false
288
+ },
289
+ filteredStats: {
290
+ value_counts: filteredObsData?.value_counts[obs.values[index]] || 0,
291
+ pct: filteredObsData?.pct[obs.values[index]] || 0
292
+ },
293
+ isSliced: isSliced
294
+ };
295
+ }, [dataset.colorEncoding, filteredObsData?.pct, filteredObsData?.value_counts, isSliced, obs.codes, obs.omit, obs.value_counts, obs.values, obsHistograms.fetchedData, obsHistograms.isPending, totalCounts]);
296
+ showColor &= dataset.colorEncoding === COLOR_ENCODINGS.OBS;
297
+ return /*#__PURE__*/React.createElement(ListGroup, {
298
+ variant: "flush"
299
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, null, /*#__PURE__*/React.createElement(ObsToolbar, {
300
+ item: obs,
301
+ onToggleAllObs: toggleAll,
302
+ onToggleLabel: toggleLabel,
303
+ onToggleSlice: toggleSlice,
304
+ onToggleColor: toggleColor
305
+ })), /*#__PURE__*/React.createElement(VirtualizedList, {
306
+ getDataAtIndex: getDataAtIndex,
307
+ count: obs.values.length,
308
+ ItemComponent: CategoricalItem,
309
+ totalCounts: totalCounts,
310
+ min: min,
311
+ max: max,
312
+ onChange: toggleObs,
313
+ showColor: showColor
314
+ }));
315
+ }
316
+ function ObsContinuousStats(_ref3) {
317
+ let {
318
+ obs
319
+ } = _ref3;
320
+ const ENDPOINT = "obs/distribution";
321
+ const dataset = useDataset();
322
+ const params = {
323
+ url: dataset.url,
324
+ obsColname: obs.name
325
+ };
326
+ const {
327
+ fetchedData,
328
+ isPending,
329
+ serverError
330
+ } = useFetch(ENDPOINT, params);
331
+
332
+ // @TODO: fix width issue when min/max/etc values are too large
333
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
334
+ className: "d-flex justify-content-between mt-3 align-items-center"
335
+ }, /*#__PURE__*/React.createElement(Table, {
336
+ size: "sm",
337
+ className: "obs-continuous-stats",
338
+ striped: true
339
+ }, /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Min"), /*#__PURE__*/React.createElement("td", {
340
+ className: "text-end"
341
+ }, formatNumerical(obs.min, FORMATS.EXPONENTIAL))), /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Max"), /*#__PURE__*/React.createElement("td", {
342
+ className: "text-end"
343
+ }, formatNumerical(obs.max, FORMATS.EXPONENTIAL))))), /*#__PURE__*/React.createElement(Table, {
344
+ size: "sm",
345
+ className: "obs-continuous-stats",
346
+ striped: true
347
+ }, /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Mean"), /*#__PURE__*/React.createElement("td", {
348
+ className: "text-end"
349
+ }, formatNumerical(obs.mean, FORMATS.EXPONENTIAL))), /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Median"), /*#__PURE__*/React.createElement("td", {
350
+ className: "text-end"
351
+ }, formatNumerical(obs.median, FORMATS.EXPONENTIAL))))), isPending && /*#__PURE__*/React.createElement(LoadingLinear, null), !isPending && !serverError && /*#__PURE__*/React.createElement("div", {
352
+ className: "obs-distribution"
353
+ }, /*#__PURE__*/React.createElement(SparkLineChart, {
354
+ data: fetchedData.kde_values[1],
355
+ showHighlight: true,
356
+ showTooltip: true // throws Maximum update depth exceeded error. Documented here: https://github.com/mui/mui-x/issues/13450
357
+ ,
358
+ margin: {
359
+ top: 10,
360
+ right: 20,
361
+ bottom: 10,
362
+ left: 20
363
+ },
364
+ xAxis: {
365
+ data: fetchedData.kde_values[0],
366
+ valueFormatter: v => `${formatNumerical(v, FORMATS.EXPONENTIAL)}`
367
+ },
368
+ valueFormatter: v => `${formatNumerical(v, FORMATS.EXPONENTIAL)}`,
369
+ slotProps: {
370
+ popper: {
371
+ className: "feature-histogram-tooltip"
372
+ }
373
+ }
374
+ }))));
375
+ }
376
+ export function ContinuousObs(_ref4) {
377
+ let {
378
+ obs,
379
+ updateObs,
380
+ toggleAll,
381
+ toggleObs,
382
+ toggleLabel,
383
+ toggleSlice,
384
+ toggleColor
385
+ } = _ref4;
386
+ const ENDPOINT = "obs/bins";
387
+ const dataset = useDataset();
388
+ const {
389
+ isSliced
390
+ } = useFilteredData();
391
+ const dispatch = useDatasetDispatch();
392
+ const binnedObs = binContinuous(obs, _.min([N_BINS, obs.n_unique]));
393
+ const params = {
394
+ url: dataset.url,
395
+ obsCol: binnedObs.name,
396
+ thresholds: binnedObs.bins.thresholds,
397
+ nBins: binnedObs.bins.nBins
398
+ };
399
+ const {
400
+ fetchedData,
401
+ isPending,
402
+ serverError
403
+ } = useFetch(ENDPOINT, params, {
404
+ refetchOnMount: false
405
+ });
406
+ const filteredObsData = useFilteredObsData(obs);
407
+ const updatedObs = fetchedData && _.isMatch(obs, fetchedData);
408
+ useEffect(() => {
409
+ // Update ObsList obsCols with bin data
410
+ // after update -> re-render -> obs will already be updated
411
+ if (!isPending && !serverError && !_.isMatch(obs, fetchedData)) {
412
+ updateObs({
413
+ ...binnedObs,
414
+ ...fetchedData,
415
+ codesMap: _.invert(fetchedData.codes)
416
+ });
417
+ }
418
+ }, [binnedObs, fetchedData, isPending, obs, serverError, updateObs]);
419
+ useEffect(() => {
420
+ if (updatedObs && dataset.selectedObs?.name === obs.name) {
421
+ const selectedObsData = _.omit(dataset.selectedObs, ["omit"]);
422
+ const obsData = _.omit(obs, ["omit"]);
423
+ if (!_.isEqual(selectedObsData, obsData)) {
424
+ // outdated selectedObs
425
+ dispatch({
426
+ type: "select.obs",
427
+ obs: obs
428
+ });
429
+ } else if (!_.isEqual(dataset.selectedObs.omit, obs.omit)) {
430
+ updateObs({
431
+ ...obs,
432
+ omit: dataset.selectedObs.omit
433
+ });
434
+ }
435
+ }
436
+ }, [dataset.selectedObs, dispatch, obs, obs.name, updateObs, updatedObs]);
437
+ const totalCounts = _.sum(_.values(obs?.value_counts));
438
+ const min = _.min(_.values(obs?.codes));
439
+ const max = _.max(_.values(obs?.codes));
440
+ const getDataAtIndex = index => {
441
+ return {
442
+ value: obs.values[index],
443
+ code: obs.codes[obs.values[index]],
444
+ stats: {
445
+ value_counts: obs.value_counts[obs.values[index]],
446
+ pct: obs.value_counts[obs.values[index]] / totalCounts * 100
447
+ },
448
+ isOmitted: _.includes(obs.omit, obs.codes[obs.values[index]]),
449
+ label: isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges),
450
+ filteredStats: {
451
+ value_counts: filteredObsData?.value_counts[obs.values[index]] || 0,
452
+ pct: filteredObsData?.pct[obs.values[index]] || 0
453
+ },
454
+ isSliced: isSliced
455
+ };
456
+ };
457
+ return /*#__PURE__*/React.createElement(React.Fragment, null, isPending && /*#__PURE__*/React.createElement(LoadingLinear, null), !serverError && updatedObs && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ListGroup, {
458
+ variant: "flush"
459
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, null, /*#__PURE__*/React.createElement(ObsToolbar, {
460
+ item: obs,
461
+ onToggleAllObs: toggleAll,
462
+ onToggleLabel: toggleLabel,
463
+ onToggleSlice: toggleSlice,
464
+ onToggleColor: toggleColor
465
+ })), /*#__PURE__*/React.createElement(VirtualizedList, {
466
+ getDataAtIndex: getDataAtIndex,
467
+ count: obs.values.length,
468
+ ItemComponent: CategoricalItem,
469
+ totalCounts: totalCounts,
470
+ min: min,
471
+ max: max,
472
+ onChange: toggleObs,
473
+ showColor: false
474
+ })), /*#__PURE__*/React.createElement(ObsContinuousStats, {
475
+ obs: obs
476
+ })));
477
+ }