@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,48 @@
1
+ import React, { useReducer, createContext, useContext } from "react";
2
+ export const FilteredDataContext = /*#__PURE__*/createContext(null);
3
+ export const FilteredDataDispatchContext = /*#__PURE__*/createContext(null);
4
+ const initialFilterData = {
5
+ obsIndices: null,
6
+ valueMin: null,
7
+ valueMax: null,
8
+ slicedLength: null,
9
+ isSliced: false
10
+ };
11
+ export function FilterProvider(_ref) {
12
+ let {
13
+ children
14
+ } = _ref;
15
+ const [filteredData, dispatch] = useReducer(filterReducer, {
16
+ ...initialFilterData
17
+ });
18
+ return /*#__PURE__*/React.createElement(FilteredDataContext.Provider, {
19
+ value: filteredData
20
+ }, /*#__PURE__*/React.createElement(FilteredDataDispatchContext.Provider, {
21
+ value: dispatch
22
+ }, children));
23
+ }
24
+ export function useFilteredData() {
25
+ return useContext(FilteredDataContext);
26
+ }
27
+ export function useFilteredDataDispatch() {
28
+ return useContext(FilteredDataDispatchContext);
29
+ }
30
+ function filterReducer(filteredData, action) {
31
+ switch (action.type) {
32
+ case "set.obs.indices":
33
+ {
34
+ return {
35
+ ...filteredData,
36
+ obsIndices: action.indices,
37
+ valueMin: action.valueMin,
38
+ valueMax: action.valueMax,
39
+ slicedLength: action.slicedLength,
40
+ isSliced: action.isSliced
41
+ };
42
+ }
43
+ default:
44
+ {
45
+ throw Error("Unknown action: " + action.type);
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,26 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import { useFilter } from "../utils/Filter";
3
+ import { useObsData, useObsmData, useXData } from "../utils/zarrData";
4
+ const ZarrDataContext = /*#__PURE__*/createContext(null);
5
+ export function ZarrDataProvider(_ref) {
6
+ let {
7
+ children
8
+ } = _ref;
9
+ const obsmData = useObsmData();
10
+ const obsData = useObsData();
11
+ const xData = useXData();
12
+ const data = {
13
+ obsmData: obsmData,
14
+ obsData: obsData,
15
+ xData: xData,
16
+ isPending: obsmData.isPending || obsData.isPending || xData.isPending,
17
+ serverError: obsmData.serverError || obsData.serverError || xData.serverError
18
+ };
19
+ useFilter(data);
20
+ return /*#__PURE__*/React.createElement(ZarrDataContext.Provider, {
21
+ value: data
22
+ }, children);
23
+ }
24
+ export function useZarrData() {
25
+ return useContext(ZarrDataContext);
26
+ }
@@ -0,0 +1,66 @@
1
+ import { useCallback } from "react";
2
+ import { COLORSCALES } from "../constants/colorscales";
3
+ import { useDataset } from "../context/DatasetContext";
4
+ const GRAY = [214, 212, 212];
5
+ const parseHexColor = color => {
6
+ const r = parseInt(color?.substring(1, 3), 16);
7
+ const g = parseInt(color?.substring(3, 5), 16);
8
+ const b = parseInt(color?.substring(5, 7), 16);
9
+ return [r, g, b];
10
+ };
11
+ const interpolateColor = (color1, color2, factor) => {
12
+ const [r1, g1, b1] = parseHexColor(color1);
13
+ const [r2, g2, b2] = parseHexColor(color2);
14
+ const r = Math.round(r1 + factor * (r2 - r1));
15
+ const g = Math.round(g1 + factor * (g2 - g1));
16
+ const b = Math.round(b1 + factor * (b2 - b1));
17
+ return [r, g, b];
18
+ };
19
+ const computeColor = (colormap, value) => {
20
+ if (!colormap || isNaN(value)) {
21
+ return [0, 0, 0, 255];
22
+ } else if (value <= 0) {
23
+ return parseHexColor(colormap[0]);
24
+ } else if (value >= 1) {
25
+ return parseHexColor(colormap[colormap.length - 1]);
26
+ }
27
+ const index1 = Math.floor(value * (colormap.length - 1));
28
+ const index2 = Math.ceil(value * (colormap.length - 1));
29
+ const factor = value * (colormap.length - 1) % 1;
30
+ return interpolateColor(colormap[index1], colormap[index2], factor);
31
+ };
32
+ export const rgbToHex = color => {
33
+ const [r, g, b] = color || [0, 0, 0, 0];
34
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
35
+ };
36
+ export const useColor = () => {
37
+ const dataset = useDataset();
38
+ const getColor = useCallback(_ref => {
39
+ let {
40
+ value,
41
+ categorical = false,
42
+ grayOut = false,
43
+ grayParams: {
44
+ alpha = 0.75,
45
+ gray = 0.95
46
+ } = {},
47
+ colorEncoding = dataset.colorEncoding,
48
+ colorscale = null
49
+ } = _ref;
50
+ const colormap = colorscale || COLORSCALES[categorical ? "Accent" : dataset.controls.colorScale];
51
+ if (colorEncoding) {
52
+ if (grayOut) {
53
+ // Mix color with gray manually instead of chroma.mix to get better performance with deck.gl
54
+ const rgb = computeColor(colormap, value);
55
+ return [rgb[0] * (1 - gray) + GRAY[0] * gray, rgb[1] * (1 - gray) + GRAY[1] * gray, rgb[2] * (1 - gray) + GRAY[2] * gray, 255 * alpha];
56
+ } else {
57
+ return [...computeColor(colormap, value), 255];
58
+ }
59
+ } else {
60
+ return null;
61
+ }
62
+ }, [dataset.colorEncoding, dataset.controls.colorScale]);
63
+ return {
64
+ getColor
65
+ };
66
+ };
@@ -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,129 @@
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
+ enabled = true
48
+ } = opts;
49
+ const {
50
+ data = null,
51
+ isPending = false,
52
+ error: serverError = null
53
+ } = useQuery({
54
+ queryKey: ["zarr", url, path, s],
55
+ queryFn: () => {
56
+ if (enabled) {
57
+ return fetchDataFromZarr(url, path, s, options);
58
+ } else {
59
+ return;
60
+ }
61
+ },
62
+ retry: (failureCount, _ref3) => {
63
+ let {
64
+ error
65
+ } = _ref3;
66
+ if ([400, 401, 403, 404, 422].includes(error?.status)) return false;
67
+ return failureCount < 3;
68
+ },
69
+ ...opts
70
+ });
71
+ return {
72
+ data,
73
+ isPending,
74
+ serverError
75
+ };
76
+ };
77
+ const aggregateData = (inputs, data) => {
78
+ const dataObject = {};
79
+ inputs.forEach((input, index) => {
80
+ const key = input.key;
81
+ dataObject[key] = data?.[index];
82
+ });
83
+ return dataObject;
84
+ };
85
+ export const useMultipleZarr = function (inputs) {
86
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : GET_OPTIONS;
87
+ let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
88
+ let agg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : aggregateData;
89
+ const {
90
+ enabled = true
91
+ } = opts;
92
+ const combine = useCallback(results => {
93
+ return {
94
+ data: agg(inputs, results.map(result => result.data)),
95
+ isPending: results.some(result => result.isPending),
96
+ serverError: results.find(result => result.error)
97
+ };
98
+ }, [agg, inputs]);
99
+ const {
100
+ data = null,
101
+ isPending = false,
102
+ serverError = null
103
+ } = useQueries({
104
+ queries: inputs.map(input => ({
105
+ queryKey: ["zarr", input.url, input.path, input.s],
106
+ queryFn: () => {
107
+ if (enabled) {
108
+ return fetchDataFromZarr(input.url, input.path, input.s, options);
109
+ } else {
110
+ return;
111
+ }
112
+ },
113
+ retry: (failureCount, _ref4) => {
114
+ let {
115
+ error
116
+ } = _ref4;
117
+ if ([400, 401, 403, 404, 422].includes(error?.status)) return false;
118
+ return failureCount < 3;
119
+ },
120
+ ...opts
121
+ })),
122
+ combine
123
+ });
124
+ return {
125
+ data,
126
+ isPending,
127
+ serverError
128
+ };
129
+ };
@@ -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
+ };