@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,53 @@
1
+ import { WebMercatorViewport } from "@deck.gl/core";
2
+ import _ from "lodash";
3
+ export class MapHelper {
4
+ fitBounds(coords) {
5
+ let {
6
+ width = 400,
7
+ height = 600
8
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
9
+ width: 400,
10
+ height: 600
11
+ };
12
+ let view = new WebMercatorViewport({
13
+ width: width,
14
+ height: height,
15
+ longitude: 0,
16
+ latitude: 0,
17
+ zoom: 12
18
+ });
19
+ let latMin;
20
+ let latMax;
21
+ let lonMin;
22
+ let lonMax;
23
+ if (coords) {
24
+ latMin = _.minBy(coords, c => c[1])[1];
25
+ latMax = _.maxBy(coords, c => c[1])[1];
26
+ lonMin = _.minBy(coords, c => c[0])[0];
27
+ lonMax = _.maxBy(coords, c => c[0])[0];
28
+ } else {
29
+ latMin = 0;
30
+ latMax = 1;
31
+ lonMin = 0;
32
+ lonMax = 1;
33
+ }
34
+ const bounds = [[lonMin, latMin], [lonMax, latMax]];
35
+ const {
36
+ longitude,
37
+ latitude,
38
+ zoom
39
+ } = view.fitBounds(bounds, {
40
+ padding: {
41
+ top: height * 0.05,
42
+ bottom: height * 0.075,
43
+ left: width * 0.05,
44
+ right: width * 0.05
45
+ }
46
+ });
47
+ return {
48
+ longitude,
49
+ latitude,
50
+ zoom
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,111 @@
1
+ import { useCallback } from "react";
2
+ import { useQueries, useQuery } from "@tanstack/react-query";
3
+ import { ArrayNotFoundError, GroupNotFoundError, openArray } from "zarr";
4
+ export const GET_OPTIONS = {
5
+ concurrencyLimit: 10,
6
+ // max number of concurrent requests (default 10)
7
+ progressCallback: _ref => {
8
+ let {
9
+ progress,
10
+ queueSize
11
+ } = _ref;
12
+ console.debug(`${progress / queueSize * 100}% complete.`);
13
+ } // callback executed after each request
14
+ };
15
+ export class ZarrHelper {
16
+ async open(url, path) {
17
+ const z = await openArray({
18
+ store: url,
19
+ path: path,
20
+ mode: "r"
21
+ });
22
+ return z;
23
+ }
24
+ }
25
+ const fetchDataFromZarr = async (url, path, s, opts) => {
26
+ try {
27
+ const zarrHelper = new ZarrHelper();
28
+ const z = await zarrHelper.open(url, path);
29
+ const result = await z.get(s, opts);
30
+ return result.data;
31
+ } catch (error) {
32
+ if (error instanceof ArrayNotFoundError || error instanceof GroupNotFoundError) {
33
+ error.status = 404;
34
+ }
35
+ throw error;
36
+ }
37
+ };
38
+ export const useZarr = function (_ref2) {
39
+ let {
40
+ url,
41
+ path
42
+ } = _ref2;
43
+ let s = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
44
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : GET_OPTIONS;
45
+ let opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
46
+ const {
47
+ data = null,
48
+ isLoading: isPending = false,
49
+ error: serverError = null
50
+ } = useQuery({
51
+ queryKey: ["zarr", url, path, s],
52
+ queryFn: () => fetchDataFromZarr(url, path, s, options),
53
+ retry: (failureCount, _ref3) => {
54
+ let {
55
+ error
56
+ } = _ref3;
57
+ if ([400, 401, 403, 404, 422].includes(error?.status)) return false;
58
+ return failureCount < 3;
59
+ },
60
+ ...opts
61
+ });
62
+ return {
63
+ data,
64
+ isPending,
65
+ serverError
66
+ };
67
+ };
68
+ const aggregateData = (inputs, data) => {
69
+ const dataObject = {};
70
+ inputs.forEach((input, index) => {
71
+ const key = input.key;
72
+ dataObject[key] = data?.[index];
73
+ });
74
+ return dataObject;
75
+ };
76
+ export const useMultipleZarr = function (inputs) {
77
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : GET_OPTIONS;
78
+ let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
79
+ let agg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : aggregateData;
80
+ const combine = useCallback(results => {
81
+ return {
82
+ data: agg(inputs, results.map(result => result.data)),
83
+ isLoading: results.some(result => result.isLoading),
84
+ serverError: results.find(result => result.error)
85
+ };
86
+ }, [agg, inputs]);
87
+ const {
88
+ data = null,
89
+ isLoading: isPending = false,
90
+ serverError = null
91
+ } = useQueries({
92
+ queries: inputs.map(input => ({
93
+ queryKey: ["zarr", input.url, input.path, input.s],
94
+ queryFn: () => fetchDataFromZarr(input.url, input.path, input.s, options),
95
+ retry: (failureCount, _ref4) => {
96
+ let {
97
+ error
98
+ } = _ref4;
99
+ if ([400, 401, 403, 404, 422].includes(error?.status)) return false;
100
+ return failureCount < 3;
101
+ },
102
+ ...opts
103
+ })),
104
+ combine
105
+ });
106
+ return {
107
+ data,
108
+ isPending,
109
+ serverError
110
+ };
111
+ };
@@ -0,0 +1,22 @@
1
+ export { Dotplot } from "./components/dotplot/Dotplot";
2
+ export { DotplotControls } from "./components/dotplot/DotplotControls";
3
+ export { Heatmap } from "./components/heatmap/Heatmap";
4
+ export { HeatmapControls } from "./components/heatmap/HeatmapControls";
5
+ export { Matrixplot } from "./components/matrixplot/Matrixplot";
6
+ export { MatrixplotControls } from "./components/matrixplot/MatrixplotControls";
7
+ export { ObsColsList } from "./components/obs-list/ObsList";
8
+ export { ObsmKeysList } from "./components/obsm-list/ObsmList";
9
+ export { Pseudospatial } from "./components/pseudospatial/Pseudospatial";
10
+ export { Scatterplot } from "./components/scatterplot/Scatterplot";
11
+ export { ScatterplotControls } from "./components/scatterplot/ScatterplotControls";
12
+ export { SearchBar } from "./components/search-bar/SearchBar";
13
+ export { VarNamesList } from "./components/var-list/VarList";
14
+ export { Violin } from "./components/violin/Violin";
15
+ export { ViolinControls } from "./components/violin/ViolinControls";
16
+ export { FullPage, FullPagePlots, FullPageScatterplot } from "./components/full-page/FullPage";
17
+ export { FullPagePseudospatial } from "./components/full-page/FullPagePseudospatial";
18
+ export { OffcanvasControls, OffcanvasObs, OffcanvasObsm, OffcanvasVars } from "./components/offcanvas";
19
+ export { COLORSCALES } from "./constants/colorscales";
20
+ export { SELECTION_MODES, VIOLIN_MODES } from "./constants/constants";
21
+ export { DatasetProvider } from "./context/DatasetContext";
22
+ export { FilterProvider } from "./context/FilterContext";
@@ -0,0 +1,147 @@
1
+ import { useEffect, useCallback, useMemo } from "react";
2
+ import { booleanPointInPolygon, point } from "@turf/turf";
3
+ import _ from "lodash";
4
+ import { COLOR_ENCODINGS, OBS_TYPES } from "../constants/constants";
5
+ import { useDataset } from "../context/DatasetContext";
6
+ import { useFilteredDataDispatch } from "../context/FilterContext";
7
+ const EPSILON = 1e-6;
8
+ const isInBins = (v, binEdges, indices) => {
9
+ const lastEdge = _.last(binEdges);
10
+ const allButLastEdges = _.initial(binEdges);
11
+ // add epsilon to last edge to include the last value
12
+ const modifiedBinEdges = [...allButLastEdges, [lastEdge[0], lastEdge[1] + EPSILON]];
13
+ const binIndices = _.difference(_.range(binEdges.length), indices);
14
+ const ranges = _.at(modifiedBinEdges, binIndices);
15
+ return _.some(ranges, range => _.inRange(v, ...range));
16
+ };
17
+ const isInPolygons = (polygons, positions, index) => {
18
+ if (!polygons?.length || !positions?.length) {
19
+ return false;
20
+ }
21
+ return _.some(polygons, (_f, i) => {
22
+ return booleanPointInPolygon(point([positions[index][0], positions[index][1]]), polygons[i]);
23
+ });
24
+ };
25
+ const isInValues = (omit, value) => {
26
+ if (!omit?.length) {
27
+ return true;
28
+ }
29
+ return !_.includes(omit, value);
30
+ };
31
+ export const useFilter = data => {
32
+ const dataset = useDataset();
33
+ const filterDataDispatch = useFilteredDataDispatch();
34
+ const {
35
+ obsmData,
36
+ xData,
37
+ obsData,
38
+ isPending,
39
+ serverError
40
+ } = data;
41
+ const isCategorical = dataset.selectedObs?.type === OBS_TYPES.CATEGORICAL || dataset.selectedObs?.type === OBS_TYPES.BOOLEAN;
42
+ const isContinuous = dataset.selectedObs?.type === OBS_TYPES.CONTINUOUS;
43
+ const sliceByObs = dataset.colorEncoding === COLOR_ENCODINGS.OBS && !!dataset.selectedObs?.omit.length || dataset.sliceBy.obs;
44
+ const isInObsSlice = useCallback((index, values) => {
45
+ let inSlice = true;
46
+ if (values && sliceByObs) {
47
+ if (isCategorical) {
48
+ inSlice &= isInValues(dataset.selectedObs?.omit, values[index]);
49
+ } else if (isContinuous) {
50
+ if (isNaN(values[index])) {
51
+ inSlice &= isInValues(dataset.selectedObs?.omit, -1);
52
+ } else {
53
+ inSlice &= isInBins(values[index], dataset.selectedObs.bins.binEdges, _.without(dataset.selectedObs.omit, -1));
54
+ }
55
+ }
56
+ }
57
+ return inSlice;
58
+ }, [dataset.selectedObs?.bins?.binEdges, dataset.selectedObs?.omit, isCategorical, isContinuous, sliceByObs]);
59
+ const isInPolygonsSlice = useCallback((index, positions) => {
60
+ let inSlice = true;
61
+ if (dataset.sliceBy.polygons && positions) {
62
+ inSlice &= isInPolygons(dataset.polygons[dataset.selectedObsm], positions, index);
63
+ }
64
+ return inSlice;
65
+ }, [dataset.polygons, dataset.selectedObsm, dataset.sliceBy.polygons]);
66
+ const isInSlice = useCallback((index, values, positions) => {
67
+ return isInObsSlice(index, values) && isInPolygonsSlice(index, positions);
68
+ }, [isInObsSlice, isInPolygonsSlice]);
69
+ const {
70
+ filteredIndices,
71
+ valueMin,
72
+ valueMax,
73
+ slicedLength
74
+ } = useMemo(() => {
75
+ if (isPending || serverError) {
76
+ return {
77
+ filteredIndices: null,
78
+ valueMin: null,
79
+ valueMax: null,
80
+ slicedLength: null
81
+ };
82
+ }
83
+ if (dataset.colorEncoding === COLOR_ENCODINGS.VAR) {
84
+ const {
85
+ filtered,
86
+ filteredIndices
87
+ } = _.reduce(xData.data, (acc, v, i) => {
88
+ if (isInSlice(i, obsData.data, obsmData.data)) {
89
+ acc.filtered.push(v);
90
+ acc.filteredIndices.add(i);
91
+ }
92
+ return acc;
93
+ }, {
94
+ filtered: [],
95
+ filteredIndices: new Set()
96
+ });
97
+ return {
98
+ filteredIndices: filteredIndices,
99
+ valueMin: _.min(filtered),
100
+ valueMax: _.max(filtered),
101
+ slicedLength: filtered.length
102
+ };
103
+ } else if (dataset.colorEncoding === COLOR_ENCODINGS.OBS) {
104
+ const {
105
+ filtered,
106
+ filteredIndices
107
+ } = _.reduce(obsData.data, (acc, v, i) => {
108
+ if (isInSlice(i, obsData.data, obsmData.data)) {
109
+ acc.filtered.push(v);
110
+ acc.filteredIndices.add(i);
111
+ }
112
+ return acc;
113
+ }, {
114
+ filtered: [],
115
+ filteredIndices: new Set()
116
+ });
117
+ return {
118
+ filteredIndices: filteredIndices,
119
+ valueMin: _.min(isContinuous ? filtered : obsData.data),
120
+ valueMax: _.max(isContinuous ? filtered : obsData.data),
121
+ slicedLength: filtered.length
122
+ };
123
+ } else {
124
+ return {
125
+ filteredIndices: null,
126
+ valueMin: _.min(obsData.data),
127
+ valueMax: _.max(obsData.data),
128
+ slicedLength: obsData.data?.length
129
+ };
130
+ }
131
+ }, [dataset.colorEncoding, isContinuous, isInSlice, isPending, obsData.data, obsmData.data, serverError, xData.data]);
132
+ const isSliced = sliceByObs || dataset.sliceBy.polygons;
133
+ // const isSliced = dataset.sliceBy.obs || dataset.sliceBy.polygons;
134
+
135
+ useEffect(() => {
136
+ if (!isPending && !serverError) {
137
+ filterDataDispatch({
138
+ type: "set.obs.indices",
139
+ indices: isSliced ? filteredIndices : null,
140
+ valueMin: valueMin,
141
+ valueMax: valueMax,
142
+ slicedLength: slicedLength,
143
+ isSliced: isSliced
144
+ });
145
+ }
146
+ }, [dataset.sliceBy.obs, dataset.sliceBy.polygons, filterDataDispatch, filteredIndices, isPending, isSliced, serverError, slicedLength, valueMax, valueMin]);
147
+ };
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { SparkLineChart, mangoFusionPalette, blueberryTwilightPalette } from "@mui/x-charts";
3
+ import _ from "lodash";
4
+ import { LoadingLinear } from "./LoadingIndicators";
5
+ import { formatNumerical, FORMATS } from "./string";
6
+ export function Histogram(_ref) {
7
+ let {
8
+ data,
9
+ isPending,
10
+ altColor = false
11
+ } = _ref;
12
+ return /*#__PURE__*/React.createElement("div", {
13
+ className: "feature-histogram-container"
14
+ }, isPending ? /*#__PURE__*/React.createElement(LoadingLinear, null) : data ? /*#__PURE__*/React.createElement("div", {
15
+ className: "feature-histogram m-1"
16
+ }, /*#__PURE__*/React.createElement(SparkLineChart, {
17
+ plotType: "bar",
18
+ data: data.log10,
19
+ margin: {
20
+ top: 0,
21
+ right: 0,
22
+ bottom: 0,
23
+ left: 0
24
+ },
25
+ colors: altColor ? mangoFusionPalette : blueberryTwilightPalette,
26
+ showHighlight: true,
27
+ showTooltip: true,
28
+ valueFormatter: (v, _ref2) => {
29
+ let {
30
+ dataIndex
31
+ } = _ref2;
32
+ return `${formatNumerical(data.hist[dataIndex])}`;
33
+ },
34
+ xAxis: {
35
+ data: _.range(data.bin_edges?.length) || null,
36
+ valueFormatter: v => `Bin [${formatNumerical(data.bin_edges[v][0], FORMATS.EXPONENTIAL)}, ${formatNumerical(data.bin_edges[v][1], FORMATS.EXPONENTIAL)}${v === data.bin_edges.length - 1 ? "]" : ")"}`
37
+ },
38
+ slotProps: {
39
+ popper: {
40
+ className: "feature-histogram-tooltip"
41
+ }
42
+ }
43
+ })) : null);
44
+ }
@@ -0,0 +1,27 @@
1
+ import React, { useState } from "react";
2
+ import { Alert } from "react-bootstrap";
3
+ export const ImageViewer = _ref => {
4
+ let {
5
+ src,
6
+ alt,
7
+ className = "img-fluid"
8
+ } = _ref;
9
+ const [error, setError] = useState(false);
10
+ const handleError = () => {
11
+ console.error("Error loading image from src:", src);
12
+ setError(true);
13
+ };
14
+ if (!error) {
15
+ return /*#__PURE__*/React.createElement("img", {
16
+ src: src,
17
+ alt: alt,
18
+ className: className,
19
+ loading: "lazy",
20
+ onError: handleError
21
+ });
22
+ } else {
23
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Alert, {
24
+ variant: "danger"
25
+ }, "Failed to load image"));
26
+ }
27
+ };
@@ -0,0 +1,58 @@
1
+ import React, { useMemo } from "react";
2
+ import _ from "lodash";
3
+ import { formatNumerical, FORMATS } from "./string";
4
+ import { COLOR_ENCODINGS } from "../constants/constants";
5
+ import { useDataset } from "../context/DatasetContext";
6
+ import { rgbToHex, useColor } from "../helpers/color-helper";
7
+ export function Legend(_ref) {
8
+ let {
9
+ isCategorical = false,
10
+ min = 0,
11
+ max = 1,
12
+ colorscale = null,
13
+ addText = ""
14
+ } = _ref;
15
+ const dataset = useDataset();
16
+ const {
17
+ getColor
18
+ } = useColor();
19
+ const spanList = useMemo(() => {
20
+ return _.range(100).map(i => {
21
+ var color = rgbToHex(getColor({
22
+ value: i / 100,
23
+ categorical: isCategorical,
24
+ colorscale: colorscale
25
+ }));
26
+ return /*#__PURE__*/React.createElement("span", {
27
+ key: i,
28
+ className: "grad-step",
29
+ style: {
30
+ backgroundColor: color
31
+ }
32
+ });
33
+ });
34
+ }, [colorscale, getColor, isCategorical]);
35
+ if (dataset.colorEncoding && !isCategorical) {
36
+ return /*#__PURE__*/React.createElement("div", {
37
+ className: "cherita-legend"
38
+ }, /*#__PURE__*/React.createElement("div", {
39
+ className: "gradient"
40
+ }, /*#__PURE__*/React.createElement("p", {
41
+ className: "small m-0 p-0"
42
+ }, (dataset.colorEncoding === COLOR_ENCODINGS.VAR ? dataset.selectedVar?.name : dataset.selectedObs?.name) + addText), spanList, /*#__PURE__*/React.createElement("span", {
43
+ className: "domain-min"
44
+ }, formatNumerical(min, FORMATS.EXPONENTIAL)), /*#__PURE__*/React.createElement("span", {
45
+ className: "domain-med"
46
+ }, formatNumerical((min + max) * 0.5, FORMATS.EXPONENTIAL)), /*#__PURE__*/React.createElement("span", {
47
+ className: "domain-max"
48
+ }, formatNumerical(max, FORMATS.EXPONENTIAL))));
49
+ } else {
50
+ return /*#__PURE__*/React.createElement("div", {
51
+ className: "cherita-legend"
52
+ }, /*#__PURE__*/React.createElement("div", {
53
+ className: "gradient"
54
+ }, /*#__PURE__*/React.createElement("p", {
55
+ className: "small m-0 p-0"
56
+ }, dataset.colorEncoding === COLOR_ENCODINGS.OBS && dataset.selectedObs ? dataset.selectedObs.name : "")));
57
+ }
58
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { Box, CircularProgress, LinearProgress } from "@mui/material";
3
+ export const LoadingSpinner = _ref => {
4
+ let {
5
+ text = null,
6
+ disableShrink = false
7
+ } = _ref;
8
+ return /*#__PURE__*/React.createElement("div", {
9
+ className: "loading-spinner"
10
+ }, /*#__PURE__*/React.createElement(CircularProgress, {
11
+ disableShrink: disableShrink
12
+ }), text?.length && /*#__PURE__*/React.createElement("span", {
13
+ className: "visually-hidden"
14
+ }, text));
15
+ };
16
+ export const LoadingLinear = () => {
17
+ return /*#__PURE__*/React.createElement(Box, {
18
+ sx: {
19
+ width: "100%"
20
+ }
21
+ }, /*#__PURE__*/React.createElement(LinearProgress, null));
22
+ };
@@ -0,0 +1,12 @@
1
+ import { Button, Placeholder } from "react-bootstrap";
2
+ export const ObsmKeysListBtn = () => {
3
+ return /*#__PURE__*/React.createElement(Placeholder, {
4
+ as: Button,
5
+ animation: "glow"
6
+ }, /*#__PURE__*/React.createElement(Placeholder, {
7
+ xs: 6,
8
+ style: {
9
+ width: "40px"
10
+ }
11
+ }));
12
+ };
@@ -0,0 +1,55 @@
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
+ import React, { useCallback, useEffect, useState } from "react";
3
+ import { useVirtualizer } from "@tanstack/react-virtual";
4
+ export function VirtualizedList(_ref) {
5
+ let {
6
+ getDataAtIndex,
7
+ count,
8
+ ItemComponent,
9
+ estimateSize = 45,
10
+ overscan = 25,
11
+ maxHeight = "80vh",
12
+ ...props
13
+ } = _ref;
14
+ const [parentNode, setParentNode] = useState(null);
15
+ const itemVirtualizer = useVirtualizer({
16
+ count: count,
17
+ getScrollElement: () => parentNode,
18
+ estimateSize: () => estimateSize,
19
+ overscan: overscan
20
+ });
21
+ const refCallback = useCallback(node => {
22
+ setParentNode(node);
23
+ }, []);
24
+ const virtualItems = itemVirtualizer.getVirtualItems();
25
+ useEffect(() => {
26
+ itemVirtualizer.measure();
27
+ }, [itemVirtualizer, parentNode?.clientHeight, getDataAtIndex]);
28
+ return /*#__PURE__*/React.createElement("div", {
29
+ ref: refCallback,
30
+ style: {
31
+ overflowY: "auto",
32
+ maxHeight: maxHeight
33
+ },
34
+ className: "modern-scrollbars"
35
+ }, /*#__PURE__*/React.createElement("div", {
36
+ style: {
37
+ height: `${itemVirtualizer.getTotalSize()}px`,
38
+ width: "100%",
39
+ position: "relative",
40
+ willChange: "transform"
41
+ }
42
+ }, /*#__PURE__*/React.createElement("div", {
43
+ style: {
44
+ position: "absolute",
45
+ top: 0,
46
+ left: 0,
47
+ width: "100%",
48
+ transform: `translateY(${virtualItems[0]?.start ?? 0}px)`
49
+ }
50
+ }, virtualItems.map(virtualItem => /*#__PURE__*/React.createElement("div", {
51
+ key: virtualItem.key,
52
+ "data-index": virtualItem.index,
53
+ ref: itemVirtualizer.measureElement
54
+ }, /*#__PURE__*/React.createElement(ItemComponent, _extends({}, getDataAtIndex(virtualItem.index), props)))))));
55
+ }
@@ -0,0 +1,47 @@
1
+ export const parseError = err => {
2
+ if (err === DOMException.TIMEOUT_ERR) {
3
+ return {
4
+ message: "Timeout error",
5
+ name: err
6
+ };
7
+ }
8
+ switch (err?.name) {
9
+ case "TypeError":
10
+ return {
11
+ ...err,
12
+ message: "Failed to fetch data from server"
13
+ };
14
+ case "ReadZarrError":
15
+ return {
16
+ ...err,
17
+ message: "Failed to read AnnData-Zarr"
18
+ };
19
+ case "InvalidObs":
20
+ return {
21
+ ...err,
22
+ message: "Observation not found in dataset"
23
+ };
24
+ case "InvalidVar":
25
+ return {
26
+ ...err,
27
+ message: "Feature not found in dataset"
28
+ };
29
+ case "InvalidKey":
30
+ return {
31
+ ...err,
32
+ message: "Key not found in datset"
33
+ };
34
+ case "BadRequest":
35
+ return {
36
+ ...err,
37
+ message: "Invalid request to server"
38
+ };
39
+ case "InternalServerError":
40
+ return {
41
+ ...err,
42
+ message: "Server error"
43
+ };
44
+ default:
45
+ return err;
46
+ }
47
+ };