@haniffalab/cherita-react 1.0.0 → 1.1.0-dev.2025-04-01.1c91bb8b

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 (106) hide show
  1. package/LICENSE +1 -1
  2. package/dist/{components → cjs/components}/full-page/FullPage.js +32 -22
  3. package/dist/{components → cjs/components}/full-page/FullPagePseudospatial.js +7 -6
  4. package/dist/{components → cjs/components}/obs-list/ObsItem.js +88 -77
  5. package/dist/{components → cjs/components}/obs-list/ObsList.js +133 -50
  6. package/dist/cjs/components/obs-list/ObsToolbar.js +24 -0
  7. package/dist/{components → cjs/components}/obsm-list/ObsmList.js +8 -5
  8. package/dist/{components → cjs/components}/offcanvas/index.js +24 -20
  9. package/dist/{components → cjs/components}/pseudospatial/Pseudospatial.js +10 -9
  10. package/dist/{components → cjs/components}/pseudospatial/PseudospatialToolbar.js +4 -3
  11. package/dist/{components → cjs/components}/scatterplot/Scatterplot.js +33 -24
  12. package/dist/{components → cjs/components}/scatterplot/SpatialControls.js +43 -42
  13. package/dist/cjs/components/scatterplot/Toolbox.js +62 -0
  14. package/dist/{components → cjs/components}/search-bar/SearchBar.js +24 -7
  15. package/dist/{components → cjs/components}/search-bar/SearchResults.js +13 -17
  16. package/dist/{components → cjs/components}/var-list/VarItem.js +38 -29
  17. package/dist/{components → cjs/components}/var-list/VarList.js +59 -31
  18. package/dist/{components → cjs/components}/var-list/VarListToolbar.js +18 -14
  19. package/dist/{components → cjs/components}/var-list/VarSet.js +24 -20
  20. package/dist/{components → cjs/components}/violin/Violin.js +4 -3
  21. package/dist/{constants → cjs/constants}/constants.js +6 -2
  22. package/dist/{context → cjs/context}/DatasetContext.js +12 -11
  23. package/dist/{context → cjs/context}/FilterContext.js +4 -3
  24. package/dist/{context → cjs/context}/ZarrDataContext.js +4 -3
  25. package/dist/{helpers → cjs/helpers}/color-helper.js +12 -11
  26. package/dist/{helpers → cjs/helpers}/map-helper.js +8 -7
  27. package/dist/{helpers → cjs/helpers}/zarr-helper.js +30 -38
  28. package/dist/{utils → cjs/utils}/Filter.js +1 -1
  29. package/dist/{utils → cjs/utils}/Histogram.js +12 -8
  30. package/dist/{utils → cjs/utils}/ImageViewer.js +6 -5
  31. package/dist/{utils → cjs/utils}/Legend.js +8 -7
  32. package/dist/{utils → cjs/utils}/LoadingIndicators.js +5 -4
  33. package/dist/cjs/utils/Skeleton.js +19 -0
  34. package/dist/{utils → cjs/utils}/VirtualizedList.js +10 -9
  35. package/dist/{utils → cjs/utils}/requests.js +37 -39
  36. package/dist/{utils → cjs/utils}/string.js +9 -4
  37. package/dist/{utils → cjs/utils}/zarrData.js +12 -4
  38. package/dist/css/cherita.css +147 -152
  39. package/dist/css/cherita.css.map +1 -1
  40. package/dist/esm/components/dotplot/Dotplot.js +135 -0
  41. package/dist/esm/components/dotplot/DotplotControls.js +148 -0
  42. package/dist/esm/components/full-page/FullPage.js +143 -0
  43. package/dist/esm/components/full-page/FullPagePseudospatial.js +151 -0
  44. package/dist/esm/components/heatmap/Heatmap.js +105 -0
  45. package/dist/esm/components/heatmap/HeatmapControls.js +23 -0
  46. package/dist/esm/components/matrixplot/Matrixplot.js +107 -0
  47. package/dist/esm/components/matrixplot/MatrixplotControls.js +38 -0
  48. package/dist/esm/components/obs-list/ObsItem.js +484 -0
  49. package/dist/esm/components/obs-list/ObsList.js +338 -0
  50. package/dist/esm/components/obs-list/ObsToolbar.js +17 -0
  51. package/dist/esm/components/obsm-list/ObsmList.js +75 -0
  52. package/dist/esm/components/offcanvas/index.js +67 -0
  53. package/dist/esm/components/pseudospatial/Pseudospatial.js +228 -0
  54. package/dist/esm/components/pseudospatial/PseudospatialToolbar.js +123 -0
  55. package/dist/esm/components/scatterplot/Scatterplot.js +394 -0
  56. package/dist/esm/components/scatterplot/ScatterplotControls.js +71 -0
  57. package/dist/esm/components/scatterplot/SpatialControls.js +140 -0
  58. package/dist/esm/components/scatterplot/Toolbox.js +55 -0
  59. package/dist/esm/components/search-bar/SearchBar.js +90 -0
  60. package/dist/esm/components/search-bar/SearchResults.js +139 -0
  61. package/dist/esm/components/var-list/VarItem.js +254 -0
  62. package/dist/esm/components/var-list/VarList.js +291 -0
  63. package/dist/esm/components/var-list/VarListToolbar.js +87 -0
  64. package/dist/esm/components/var-list/VarSet.js +194 -0
  65. package/dist/esm/components/violin/Violin.js +141 -0
  66. package/dist/esm/components/violin/ViolinControls.js +24 -0
  67. package/dist/esm/constants/colorscales.js +22 -0
  68. package/dist/esm/constants/constants.js +88 -0
  69. package/dist/esm/context/DatasetContext.js +571 -0
  70. package/dist/esm/context/FilterContext.js +48 -0
  71. package/dist/esm/context/ZarrDataContext.js +26 -0
  72. package/dist/esm/helpers/color-helper.js +66 -0
  73. package/dist/esm/helpers/map-helper.js +53 -0
  74. package/dist/esm/helpers/zarr-helper.js +111 -0
  75. package/dist/esm/index.js +22 -0
  76. package/dist/esm/utils/Filter.js +147 -0
  77. package/dist/esm/utils/Histogram.js +44 -0
  78. package/dist/esm/utils/ImageViewer.js +27 -0
  79. package/dist/esm/utils/Legend.js +58 -0
  80. package/dist/esm/utils/LoadingIndicators.js +22 -0
  81. package/dist/esm/utils/Skeleton.js +12 -0
  82. package/dist/esm/utils/VirtualizedList.js +55 -0
  83. package/dist/esm/utils/errors.js +47 -0
  84. package/dist/esm/utils/requests.js +102 -0
  85. package/dist/esm/utils/search.js +39 -0
  86. package/dist/esm/utils/string.js +59 -0
  87. package/dist/esm/utils/zarrData.js +102 -0
  88. package/package.json +22 -9
  89. package/scss/cherita.scss +19 -50
  90. package/scss/components/accordions.scss +32 -0
  91. package/scss/components/layouts.scss +2 -1
  92. package/scss/components/lists.scss +14 -0
  93. package/dist/components/obs-list/ObsToolbar.js +0 -64
  94. package/dist/components/scatterplot/Toolbox.js +0 -31
  95. /package/dist/{components → cjs/components}/dotplot/Dotplot.js +0 -0
  96. /package/dist/{components → cjs/components}/dotplot/DotplotControls.js +0 -0
  97. /package/dist/{components → cjs/components}/heatmap/Heatmap.js +0 -0
  98. /package/dist/{components → cjs/components}/heatmap/HeatmapControls.js +0 -0
  99. /package/dist/{components → cjs/components}/matrixplot/Matrixplot.js +0 -0
  100. /package/dist/{components → cjs/components}/matrixplot/MatrixplotControls.js +0 -0
  101. /package/dist/{components → cjs/components}/scatterplot/ScatterplotControls.js +0 -0
  102. /package/dist/{components → cjs/components}/violin/ViolinControls.js +0 -0
  103. /package/dist/{constants → cjs/constants}/colorscales.js +0 -0
  104. /package/dist/{index.js → cjs/index.js} +0 -0
  105. /package/dist/{utils → cjs/utils}/errors.js +0 -0
  106. /package/dist/{utils → cjs/utils}/search.js +0 -0
@@ -0,0 +1,484 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } 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 } from "react-bootstrap";
6
+ import { COLOR_ENCODINGS, OBS_TYPES } from "../../constants/constants";
7
+ import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
8
+ import { useFilteredData } from "../../context/FilterContext";
9
+ import { useColor } from "../../helpers/color-helper";
10
+ import { Histogram } from "../../utils/Histogram";
11
+ import { LoadingLinear } from "../../utils/LoadingIndicators";
12
+ import { useFetch } from "../../utils/requests";
13
+ import { formatNumerical, FORMATS } from "../../utils/string";
14
+ import { VirtualizedList } from "../../utils/VirtualizedList";
15
+ import { useObsData } from "../../utils/zarrData";
16
+ import { ObsToolbar } from "./ObsToolbar";
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
+ className: "cherita-list"
300
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, {
301
+ className: "cherita-list-item-unstyled"
302
+ }, /*#__PURE__*/React.createElement(ObsToolbar, {
303
+ item: obs,
304
+ onToggleAllObs: toggleAll
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(ListGroup, {
334
+ variant: "flush",
335
+ className: "cherita-list"
336
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, {
337
+ className: "obs-item"
338
+ }, /*#__PURE__*/React.createElement("h5", {
339
+ className: "mb-2"
340
+ }, "Statistics"), /*#__PURE__*/React.createElement("div", {
341
+ className: "row"
342
+ }, /*#__PURE__*/React.createElement("div", {
343
+ className: "col-md-7"
344
+ }, /*#__PURE__*/React.createElement("p", {
345
+ className: "mb-1 small"
346
+ }, "Distribution of continuous values"), isPending && /*#__PURE__*/React.createElement(LoadingLinear, null), !isPending && !serverError && /*#__PURE__*/React.createElement("div", {
347
+ className: "obs-distribution"
348
+ }, /*#__PURE__*/React.createElement(SparkLineChart, {
349
+ data: fetchedData.kde_values[1],
350
+ showHighlight: true,
351
+ showTooltip: true,
352
+ margin: {
353
+ top: 10,
354
+ right: 20,
355
+ bottom: 10,
356
+ left: 20
357
+ },
358
+ xAxis: {
359
+ data: fetchedData.kde_values[0],
360
+ valueFormatter: v => `${formatNumerical(v, FORMATS.EXPONENTIAL)}`
361
+ },
362
+ valueFormatter: v => `${formatNumerical(v, FORMATS.EXPONENTIAL)}`,
363
+ slotProps: {
364
+ popper: {
365
+ className: "feature-histogram-tooltip"
366
+ }
367
+ }
368
+ }))), /*#__PURE__*/React.createElement("div", {
369
+ className: "col-md-5 d-flex flex-column text-end"
370
+ }, /*#__PURE__*/React.createElement("div", {
371
+ className: "d-flex justify-content-between"
372
+ }, /*#__PURE__*/React.createElement("span", null, "Min"), " ", /*#__PURE__*/React.createElement("span", null, formatNumerical(obs.min, FORMATS.EXPONENTIAL))), /*#__PURE__*/React.createElement("div", {
373
+ className: "d-flex justify-content-between"
374
+ }, /*#__PURE__*/React.createElement("span", null, "Max"), " ", /*#__PURE__*/React.createElement("span", null, formatNumerical(obs.max, FORMATS.EXPONENTIAL))), /*#__PURE__*/React.createElement("div", {
375
+ className: "d-flex justify-content-between"
376
+ }, /*#__PURE__*/React.createElement("span", null, "Mean"), " ", /*#__PURE__*/React.createElement("span", null, formatNumerical(obs.mean, FORMATS.EXPONENTIAL))), /*#__PURE__*/React.createElement("div", {
377
+ className: "d-flex justify-content-between"
378
+ }, /*#__PURE__*/React.createElement("span", null, "Median"), " ", /*#__PURE__*/React.createElement("span", null, formatNumerical(obs.median, FORMATS.EXPONENTIAL)))))));
379
+ }
380
+ export function ContinuousObs(_ref4) {
381
+ let {
382
+ obs,
383
+ updateObs,
384
+ toggleAll,
385
+ toggleObs,
386
+ toggleLabel,
387
+ toggleSlice,
388
+ toggleColor
389
+ } = _ref4;
390
+ const ENDPOINT = "obs/bins";
391
+ const dataset = useDataset();
392
+ const {
393
+ isSliced
394
+ } = useFilteredData();
395
+ const dispatch = useDatasetDispatch();
396
+ const binnedObs = binContinuous(obs, _.min([N_BINS, obs.n_unique]));
397
+ const params = {
398
+ url: dataset.url,
399
+ obsCol: binnedObs.name,
400
+ thresholds: binnedObs.bins.thresholds,
401
+ nBins: binnedObs.bins.nBins
402
+ };
403
+ const {
404
+ fetchedData,
405
+ isPending,
406
+ serverError
407
+ } = useFetch(ENDPOINT, params, {
408
+ refetchOnMount: false
409
+ });
410
+ const filteredObsData = useFilteredObsData(obs);
411
+ const updatedObs = fetchedData && _.isMatch(obs, fetchedData);
412
+ useEffect(() => {
413
+ // Update ObsList obsCols with bin data
414
+ // after update -> re-render -> obs will already be updated
415
+ if (!isPending && !serverError && !_.isMatch(obs, fetchedData)) {
416
+ updateObs({
417
+ ...binnedObs,
418
+ ...fetchedData,
419
+ codesMap: _.invert(fetchedData.codes)
420
+ });
421
+ }
422
+ }, [binnedObs, fetchedData, isPending, obs, serverError, updateObs]);
423
+ useEffect(() => {
424
+ if (updatedObs && dataset.selectedObs?.name === obs.name) {
425
+ const selectedObsData = _.omit(dataset.selectedObs, ["omit"]);
426
+ const obsData = _.omit(obs, ["omit"]);
427
+ if (!_.isEqual(selectedObsData, obsData)) {
428
+ // outdated selectedObs
429
+ dispatch({
430
+ type: "select.obs",
431
+ obs: obs
432
+ });
433
+ } else if (!_.isEqual(dataset.selectedObs.omit, obs.omit)) {
434
+ updateObs({
435
+ ...obs,
436
+ omit: dataset.selectedObs.omit
437
+ });
438
+ }
439
+ }
440
+ }, [dataset.selectedObs, dispatch, obs, obs.name, updateObs, updatedObs]);
441
+ const totalCounts = _.sum(_.values(obs?.value_counts));
442
+ const min = _.min(_.values(obs?.codes));
443
+ const max = _.max(_.values(obs?.codes));
444
+ const getDataAtIndex = index => {
445
+ return {
446
+ value: obs.values[index],
447
+ code: obs.codes[obs.values[index]],
448
+ stats: {
449
+ value_counts: obs.value_counts[obs.values[index]],
450
+ pct: obs.value_counts[obs.values[index]] / totalCounts * 100
451
+ },
452
+ isOmitted: _.includes(obs.omit, obs.codes[obs.values[index]]),
453
+ label: isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges),
454
+ filteredStats: {
455
+ value_counts: filteredObsData?.value_counts[obs.values[index]] || 0,
456
+ pct: filteredObsData?.pct[obs.values[index]] || 0
457
+ },
458
+ isSliced: isSliced
459
+ };
460
+ };
461
+ 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, {
462
+ variant: "flush",
463
+ className: "cherita-list"
464
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, {
465
+ className: "cherita-list-item-unstyled"
466
+ }, /*#__PURE__*/React.createElement(ObsToolbar, {
467
+ item: obs,
468
+ onToggleAllObs: toggleAll,
469
+ onToggleLabel: toggleLabel,
470
+ onToggleSlice: toggleSlice,
471
+ onToggleColor: toggleColor
472
+ })), /*#__PURE__*/React.createElement(VirtualizedList, {
473
+ getDataAtIndex: getDataAtIndex,
474
+ count: obs.values.length,
475
+ ItemComponent: CategoricalItem,
476
+ totalCounts: totalCounts,
477
+ min: min,
478
+ max: max,
479
+ onChange: toggleObs,
480
+ showColor: false
481
+ })), /*#__PURE__*/React.createElement(ObsContinuousStats, {
482
+ obs: obs
483
+ })));
484
+ }