@haniffalab/cherita-react 1.0.0-dev.2025-03-13.73606a74 → 1.0.0-dev.2025-03-13.274a553c
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.
- package/dist/{components → cjs/components}/full-page/FullPage.js +6 -5
- package/dist/{components → cjs/components}/full-page/FullPagePseudospatial.js +6 -5
- package/dist/{components → cjs/components}/obs-list/ObsItem.js +50 -46
- package/dist/{components → cjs/components}/obs-list/ObsList.js +9 -7
- package/dist/{components → cjs/components}/obs-list/ObsToolbar.js +12 -11
- package/dist/{components → cjs/components}/offcanvas/index.js +24 -20
- package/dist/{components → cjs/components}/pseudospatial/Pseudospatial.js +9 -8
- package/dist/{components → cjs/components}/pseudospatial/PseudospatialToolbar.js +4 -3
- package/dist/{components → cjs/components}/scatterplot/Scatterplot.js +31 -22
- package/dist/{components → cjs/components}/scatterplot/SpatialControls.js +11 -10
- package/dist/{components → cjs/components}/scatterplot/Toolbox.js +6 -5
- package/dist/{components → cjs/components}/search-bar/SearchBar.js +6 -5
- package/dist/{components → cjs/components}/search-bar/SearchResults.js +13 -17
- package/dist/{components → cjs/components}/var-list/VarItem.js +32 -27
- package/dist/{components → cjs/components}/var-list/VarList.js +14 -10
- package/dist/{components → cjs/components}/var-list/VarListToolbar.js +4 -3
- package/dist/{components → cjs/components}/var-list/VarSet.js +21 -18
- package/dist/{components → cjs/components}/violin/Violin.js +4 -3
- package/dist/{context → cjs/context}/DatasetContext.js +11 -9
- package/dist/{context → cjs/context}/FilterContext.js +4 -3
- package/dist/{context → cjs/context}/ZarrDataContext.js +4 -3
- package/dist/{helpers → cjs/helpers}/color-helper.js +12 -11
- package/dist/{helpers → cjs/helpers}/map-helper.js +8 -7
- package/dist/{helpers → cjs/helpers}/zarr-helper.js +25 -15
- package/dist/{utils → cjs/utils}/Histogram.js +12 -8
- package/dist/{utils → cjs/utils}/ImageViewer.js +6 -5
- package/dist/{utils → cjs/utils}/Legend.js +8 -7
- package/dist/{utils → cjs/utils}/LoadingIndicators.js +5 -4
- package/dist/{utils → cjs/utils}/VirtualizedList.js +10 -9
- package/dist/{utils → cjs/utils}/requests.js +33 -21
- package/dist/{utils → cjs/utils}/string.js +9 -4
- package/dist/{utils → cjs/utils}/zarrData.js +12 -4
- package/dist/css/cherita.css +10 -0
- package/dist/css/cherita.css.map +1 -1
- package/dist/esm/components/dotplot/Dotplot.js +135 -0
- package/dist/esm/components/dotplot/DotplotControls.js +148 -0
- package/dist/esm/components/full-page/FullPage.js +134 -0
- package/dist/esm/components/full-page/FullPagePseudospatial.js +151 -0
- package/dist/esm/components/heatmap/Heatmap.js +105 -0
- package/dist/esm/components/heatmap/HeatmapControls.js +23 -0
- package/dist/esm/components/matrixplot/Matrixplot.js +107 -0
- package/dist/esm/components/matrixplot/MatrixplotControls.js +38 -0
- package/dist/esm/components/obs-list/ObsItem.js +477 -0
- package/dist/esm/components/obs-list/ObsList.js +256 -0
- package/dist/esm/components/obs-list/ObsToolbar.js +58 -0
- package/dist/esm/components/obsm-list/ObsmList.js +72 -0
- package/dist/esm/components/offcanvas/index.js +67 -0
- package/dist/esm/components/pseudospatial/Pseudospatial.js +228 -0
- package/dist/esm/components/pseudospatial/PseudospatialToolbar.js +123 -0
- package/dist/esm/components/scatterplot/Scatterplot.js +394 -0
- package/dist/esm/components/scatterplot/ScatterplotControls.js +71 -0
- package/dist/esm/components/scatterplot/SpatialControls.js +140 -0
- package/dist/esm/components/scatterplot/Toolbox.js +25 -0
- package/dist/esm/components/search-bar/SearchBar.js +74 -0
- package/dist/esm/components/search-bar/SearchResults.js +139 -0
- package/dist/esm/components/var-list/VarItem.js +250 -0
- package/dist/esm/components/var-list/VarList.js +267 -0
- package/dist/esm/components/var-list/VarListToolbar.js +84 -0
- package/dist/esm/components/var-list/VarSet.js +193 -0
- package/dist/esm/components/violin/Violin.js +141 -0
- package/dist/esm/components/violin/ViolinControls.js +24 -0
- package/dist/esm/constants/colorscales.js +22 -0
- package/dist/esm/constants/constants.js +84 -0
- package/dist/esm/context/DatasetContext.js +572 -0
- package/dist/esm/context/FilterContext.js +48 -0
- package/dist/esm/context/ZarrDataContext.js +26 -0
- package/dist/esm/helpers/color-helper.js +66 -0
- package/dist/esm/helpers/map-helper.js +53 -0
- package/dist/esm/helpers/zarr-helper.js +129 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/utils/Filter.js +147 -0
- package/dist/esm/utils/Histogram.js +44 -0
- package/dist/esm/utils/ImageViewer.js +27 -0
- package/dist/esm/utils/Legend.js +58 -0
- package/dist/esm/utils/LoadingIndicators.js +22 -0
- package/dist/esm/utils/VirtualizedList.js +55 -0
- package/dist/esm/utils/errors.js +47 -0
- package/dist/esm/utils/requests.js +116 -0
- package/dist/esm/utils/search.js +39 -0
- package/dist/esm/utils/string.js +59 -0
- package/dist/esm/utils/zarrData.js +102 -0
- package/package.json +16 -5
- package/scss/cherita.scss +10 -0
- package/scss/components/layouts.scss +1 -0
- /package/dist/{components → cjs/components}/dotplot/Dotplot.js +0 -0
- /package/dist/{components → cjs/components}/dotplot/DotplotControls.js +0 -0
- /package/dist/{components → cjs/components}/heatmap/Heatmap.js +0 -0
- /package/dist/{components → cjs/components}/heatmap/HeatmapControls.js +0 -0
- /package/dist/{components → cjs/components}/matrixplot/Matrixplot.js +0 -0
- /package/dist/{components → cjs/components}/matrixplot/MatrixplotControls.js +0 -0
- /package/dist/{components → cjs/components}/obsm-list/ObsmList.js +0 -0
- /package/dist/{components → cjs/components}/scatterplot/ScatterplotControls.js +0 -0
- /package/dist/{components → cjs/components}/violin/ViolinControls.js +0 -0
- /package/dist/{constants → cjs/constants}/colorscales.js +0 -0
- /package/dist/{constants → cjs/constants}/constants.js +0 -0
- /package/dist/{index.js → cjs/index.js} +0 -0
- /package/dist/{utils → cjs/utils}/Filter.js +0 -0
- /package/dist/{utils → cjs/utils}/errors.js +0 -0
- /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
|
+
};
|