@haniffalab/cherita-react 1.3.0 → 1.4.0-dev.2025-06-09.09fbcbf5
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/assets/images/plots/dotplot.svg +152 -0
- package/dist/assets/images/plots/heatmap.svg +193 -0
- package/dist/assets/images/plots/matrixplot.svg +275 -0
- package/dist/assets/images/plots/scatterplot.svg +198 -0
- package/dist/assets/images/plots/violin.svg +50 -0
- package/dist/cjs/components/dotplot/Dotplot.js +35 -5
- package/dist/cjs/components/full-page/FullPage.js +109 -50
- package/dist/cjs/components/full-page/PlotTypeSelector.js +57 -0
- package/dist/cjs/components/heatmap/Heatmap.js +35 -5
- package/dist/cjs/components/matrixplot/Matrixplot.js +35 -5
- package/dist/cjs/components/obs-list/ObsItem.js +49 -22
- package/dist/cjs/components/obs-list/ObsList.js +9 -5
- package/dist/cjs/components/scatterplot/Scatterplot.js +115 -95
- package/dist/cjs/components/scatterplot/SpatialControls.js +3 -3
- package/dist/cjs/components/search-bar/SearchInfo.js +3 -35
- package/dist/cjs/components/toolbar/Toolbar.js +102 -0
- package/dist/cjs/components/var-list/VarList.js +11 -3
- package/dist/cjs/components/violin/Violin.js +37 -6
- package/dist/cjs/constants/constants.js +14 -2
- package/dist/cjs/context/DatasetContext.js +2 -1
- package/dist/cjs/context/SettingsContext.js +77 -46
- package/dist/cjs/helpers/map-helper.js +2 -1
- package/dist/cjs/index.js +15 -21
- package/dist/css/cherita.css +76 -23
- package/dist/css/cherita.css.map +1 -1
- package/dist/esm/components/dotplot/Dotplot.js +36 -6
- package/dist/esm/components/full-page/FullPage.js +111 -50
- package/dist/esm/components/full-page/PlotTypeSelector.js +50 -0
- package/dist/esm/components/heatmap/Heatmap.js +36 -6
- package/dist/esm/components/matrixplot/Matrixplot.js +36 -6
- package/dist/esm/components/obs-list/ObsItem.js +49 -22
- package/dist/esm/components/obs-list/ObsList.js +9 -5
- package/dist/esm/components/scatterplot/Scatterplot.js +115 -95
- package/dist/esm/components/scatterplot/SpatialControls.js +3 -3
- package/dist/esm/components/search-bar/SearchInfo.js +1 -33
- package/dist/esm/components/toolbar/Toolbar.js +91 -0
- package/dist/esm/components/var-list/VarList.js +10 -5
- package/dist/esm/components/violin/Violin.js +39 -8
- package/dist/esm/constants/constants.js +13 -1
- package/dist/esm/context/DatasetContext.js +2 -1
- package/dist/esm/context/SettingsContext.js +77 -46
- package/dist/esm/helpers/map-helper.js +2 -1
- package/dist/esm/index.js +4 -4
- package/package.json +6 -4
- package/scss/cherita.scss +0 -1
- package/scss/components/layouts.scss +69 -1
- package/scss/components/plotly.scss +19 -14
- package/dist/cjs/components/full-page/FullPagePseudospatial.js +0 -157
- package/dist/esm/components/full-page/FullPagePseudospatial.js +0 -149
|
@@ -15,6 +15,7 @@ import { Alert } from "react-bootstrap";
|
|
|
15
15
|
import { SpatialControls } from "./SpatialControls";
|
|
16
16
|
import { Toolbox } from "./Toolbox";
|
|
17
17
|
import { COLOR_ENCODINGS, OBS_TYPES, SELECTED_POLYGON_FILLCOLOR, UNSELECTED_POLYGON_FILLCOLOR } from "../../constants/constants";
|
|
18
|
+
import { useDataset } from "../../context/DatasetContext";
|
|
18
19
|
import { useFilteredData } from "../../context/FilterContext";
|
|
19
20
|
import { useSettings, useSettingsDispatch } from "../../context/SettingsContext";
|
|
20
21
|
import { useZarrData } from "../../context/ZarrDataContext";
|
|
@@ -34,13 +35,16 @@ const INITIAL_VIEW_STATE = {
|
|
|
34
35
|
bearing: 0
|
|
35
36
|
};
|
|
36
37
|
export function Scatterplot(_ref) {
|
|
37
|
-
var _settings$
|
|
38
|
+
var _settings$selectedObs2, _settings$selectedObs5, _settings$selectedObs8, _features$features2, _obsmData$serverError, _xData$serverError, _obsData$serverError, _labelObsData$serverE, _settings$selectedVar, _settings$selectedObs9, _data$positions;
|
|
38
39
|
let {
|
|
39
|
-
radius =
|
|
40
|
+
radius = null,
|
|
40
41
|
setShowObs,
|
|
41
42
|
setShowVars,
|
|
42
43
|
isFullscreen = false
|
|
43
44
|
} = _ref;
|
|
45
|
+
const {
|
|
46
|
+
useUnsColors
|
|
47
|
+
} = useDataset();
|
|
44
48
|
const settings = useSettings();
|
|
45
49
|
const {
|
|
46
50
|
obsIndices,
|
|
@@ -55,12 +59,13 @@ export function Scatterplot(_ref) {
|
|
|
55
59
|
const deckRef = useRef(null);
|
|
56
60
|
const [viewState, setViewState] = useState(INITIAL_VIEW_STATE);
|
|
57
61
|
const [isRendering, setIsRendering] = useState(true);
|
|
62
|
+
const [radiusScale, setRadiusScale] = useState(radius || 1);
|
|
63
|
+
const [isPending, setIsPending] = useState(false);
|
|
58
64
|
const [data, setData] = useState({
|
|
59
|
-
ids: [],
|
|
60
65
|
positions: [],
|
|
61
|
-
values: []
|
|
62
|
-
sliceValues: []
|
|
66
|
+
values: []
|
|
63
67
|
});
|
|
68
|
+
const [coordsError, setCoordsError] = useState(null);
|
|
64
69
|
|
|
65
70
|
// EditableGeoJsonLayer
|
|
66
71
|
const [mode, setMode] = useState(() => ViewMode);
|
|
@@ -77,24 +82,62 @@ export function Scatterplot(_ref) {
|
|
|
77
82
|
const labelObsData = useLabelObsData();
|
|
78
83
|
// @TODO: assert length of obsmData, xData, obsData is equal
|
|
79
84
|
|
|
85
|
+
const getRadiusScale = useCallback(bounds => {
|
|
86
|
+
if (!!radius) return radius;
|
|
87
|
+
// From 28 degrees ~= 30km -> 30m radius
|
|
88
|
+
const lonDim = bounds[1][0] - bounds[0][0];
|
|
89
|
+
const latDim = bounds[1][1] - bounds[0][1];
|
|
90
|
+
const minDim = Math.min(lonDim, latDim);
|
|
91
|
+
const rs = 0.01 / minDim * 111111;
|
|
92
|
+
return rs;
|
|
93
|
+
}, [radius]);
|
|
80
94
|
useEffect(() => {
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
if (obsmData.isPending || settings.colorEncoding === COLOR_ENCODINGS.VAR && xData.isPending || settings.colorEncoding === COLOR_ENCODINGS.OBS && obsData.isPending) {
|
|
96
|
+
setIsPending(true);
|
|
97
|
+
} else {
|
|
98
|
+
setIsPending(false);
|
|
84
99
|
setData(d => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
let values = d.values;
|
|
101
|
+
if (settings.colorEncoding === COLOR_ENCODINGS.VAR) {
|
|
102
|
+
values = !xData.serverError ? xData.data : values;
|
|
103
|
+
} else if (settings.colorEncoding === COLOR_ENCODINGS.OBS) {
|
|
104
|
+
values = !obsData.serverError ? obsData.data : values;
|
|
105
|
+
}
|
|
106
|
+
if (!obsmData.serverError && obsmData.data) {
|
|
107
|
+
if (obsmData.data[0].length !== 2) {
|
|
108
|
+
setCoordsError("Invalid coordinates. Expected 2D coordinates");
|
|
109
|
+
return {
|
|
110
|
+
positions: [],
|
|
111
|
+
values: []
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
setCoordsError(null);
|
|
115
|
+
return {
|
|
116
|
+
positions: obsmData.data,
|
|
117
|
+
values: values
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
positions: d.positions,
|
|
122
|
+
values: values
|
|
123
|
+
};
|
|
88
124
|
});
|
|
125
|
+
}
|
|
126
|
+
}, [obsData.data, obsData.isPending, obsData.serverError, obsmData.data, obsmData.isPending, obsmData.serverError, settings.colorEncoding, xData.data, xData.isPending, xData.serverError]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (data.positions && !!data.positions.length) {
|
|
129
|
+
var _deckRef$current, _deckRef$current2;
|
|
89
130
|
const mapHelper = new MapHelper();
|
|
90
131
|
const {
|
|
91
132
|
latitude,
|
|
92
133
|
longitude,
|
|
93
|
-
zoom
|
|
94
|
-
|
|
134
|
+
zoom,
|
|
135
|
+
bounds
|
|
136
|
+
} = mapHelper.fitBounds(data.positions, {
|
|
95
137
|
width: deckRef === null || deckRef === void 0 || (_deckRef$current = deckRef.current) === null || _deckRef$current === void 0 || (_deckRef$current = _deckRef$current.deck) === null || _deckRef$current === void 0 ? void 0 : _deckRef$current.width,
|
|
96
138
|
height: deckRef === null || deckRef === void 0 || (_deckRef$current2 = deckRef.current) === null || _deckRef$current2 === void 0 || (_deckRef$current2 = _deckRef$current2.deck) === null || _deckRef$current2 === void 0 ? void 0 : _deckRef$current2.height
|
|
97
139
|
});
|
|
140
|
+
setRadiusScale(getRadiusScale(bounds));
|
|
98
141
|
setViewState(v => {
|
|
99
142
|
return _objectSpread(_objectSpread({}, v), {}, {
|
|
100
143
|
longitude: longitude,
|
|
@@ -102,15 +145,8 @@ export function Scatterplot(_ref) {
|
|
|
102
145
|
zoom: zoom
|
|
103
146
|
});
|
|
104
147
|
});
|
|
105
|
-
} else if (!obsmData.isPending && obsmData.serverError) {
|
|
106
|
-
setIsRendering(true);
|
|
107
|
-
setData(d => {
|
|
108
|
-
return _objectSpread(_objectSpread({}, d), {}, {
|
|
109
|
-
positions: []
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
148
|
}
|
|
113
|
-
}, [settings.selectedObsm, obsmData.data, obsmData.isPending, obsmData.serverError]);
|
|
149
|
+
}, [settings.selectedObsm, getRadiusScale, obsmData.data, obsmData.isPending, obsmData.serverError, data.positions]);
|
|
114
150
|
const getBounds = useCallback(() => {
|
|
115
151
|
var _deckRef$current3, _deckRef$current4;
|
|
116
152
|
const {
|
|
@@ -127,65 +163,47 @@ export function Scatterplot(_ref) {
|
|
|
127
163
|
zoom
|
|
128
164
|
};
|
|
129
165
|
}, [data.positions]);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!obsData.isPending && !obsData.serverError) {
|
|
153
|
-
setData(d => {
|
|
154
|
-
return _objectSpread(_objectSpread({}, d), {}, {
|
|
155
|
-
values: obsData.data
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
} else if (!obsData.isPending && obsData.serverError) {
|
|
159
|
-
setData(d => {
|
|
160
|
-
return _objectSpread(_objectSpread({}, d), {}, {
|
|
161
|
-
values: []
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
} else if (settings.colorEncoding === COLOR_ENCODINGS.VAR && settings.sliceBy.obs) {
|
|
166
|
-
if (!obsData.isPending && !obsData.serverError) {
|
|
167
|
-
setData(d => {
|
|
168
|
-
return _objectSpread(_objectSpread({}, d), {}, {
|
|
169
|
-
sliceValues: obsData.data
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
} else if (!obsData.isPending && obsData.serverError) {
|
|
173
|
-
setData(d => {
|
|
174
|
-
return _objectSpread(_objectSpread({}, d), {}, {
|
|
175
|
-
sliceValues: []
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
}
|
|
166
|
+
|
|
167
|
+
// Make stable references for getOriginalIndex and sortedIndexMap
|
|
168
|
+
const identityGetOriginalIndex = useCallback(i => i, []);
|
|
169
|
+
const identitySortedIndexMap = useMemo(() => ({
|
|
170
|
+
get: key => key
|
|
171
|
+
}), []);
|
|
172
|
+
const {
|
|
173
|
+
sortedData,
|
|
174
|
+
getOriginalIndex,
|
|
175
|
+
sortedIndexMap
|
|
176
|
+
} = useMemo(() => {
|
|
177
|
+
var _settings$selectedObs;
|
|
178
|
+
if ((settings.colorEncoding === COLOR_ENCODINGS.VAR || settings.colorEncoding === COLOR_ENCODINGS.OBS && ((_settings$selectedObs = settings.selectedObs) === null || _settings$selectedObs === void 0 ? void 0 : _settings$selectedObs.type) === OBS_TYPES.CONTINUOUS) && data.positions && data.values && data.positions.length === data.values.length) {
|
|
179
|
+
const sortedIndices = _.map(data.values, (_v, i) => i).sort((a, b) => data.values[a] - data.values[b]);
|
|
180
|
+
const sortedIndexMap = new Map(_.map(sortedIndices, (originalIndex, sortedIndex) => [originalIndex, sortedIndex]));
|
|
181
|
+
return {
|
|
182
|
+
sortedData: _.mapValues(data, (v, _k) => {
|
|
183
|
+
return v ? _.at(v, sortedIndices) : v;
|
|
184
|
+
}),
|
|
185
|
+
getOriginalIndex: i => sortedIndices[i],
|
|
186
|
+
sortedIndexMap: sortedIndexMap
|
|
187
|
+
};
|
|
179
188
|
}
|
|
180
|
-
|
|
189
|
+
return {
|
|
190
|
+
sortedData: data,
|
|
191
|
+
getOriginalIndex: identityGetOriginalIndex,
|
|
192
|
+
// return original index
|
|
193
|
+
sortedIndexMap: identitySortedIndexMap // return original index
|
|
194
|
+
};
|
|
195
|
+
}, [data, identityGetOriginalIndex, identitySortedIndexMap, settings.colorEncoding, (_settings$selectedObs2 = settings.selectedObs) === null || _settings$selectedObs2 === void 0 ? void 0 : _settings$selectedObs2.type]);
|
|
196
|
+
const sortedObsIndices = useMemo(() => {
|
|
197
|
+
return obsIndices ? new Set(Array.from(obsIndices, i => sortedIndexMap.get(i))) : obsIndices;
|
|
198
|
+
}, [obsIndices, sortedIndexMap]);
|
|
181
199
|
const isCategorical = useMemo(() => {
|
|
182
200
|
if (settings.colorEncoding === COLOR_ENCODINGS.OBS) {
|
|
183
|
-
var _settings$
|
|
184
|
-
return ((_settings$
|
|
201
|
+
var _settings$selectedObs3, _settings$selectedObs4;
|
|
202
|
+
return ((_settings$selectedObs3 = settings.selectedObs) === null || _settings$selectedObs3 === void 0 ? void 0 : _settings$selectedObs3.type) === OBS_TYPES.CATEGORICAL || ((_settings$selectedObs4 = settings.selectedObs) === null || _settings$selectedObs4 === void 0 ? void 0 : _settings$selectedObs4.type) === OBS_TYPES.BOOLEAN;
|
|
185
203
|
} else {
|
|
186
204
|
return false;
|
|
187
205
|
}
|
|
188
|
-
}, [settings.colorEncoding, (_settings$
|
|
206
|
+
}, [settings.colorEncoding, (_settings$selectedObs5 = settings.selectedObs) === null || _settings$selectedObs5 === void 0 ? void 0 : _settings$selectedObs5.type]);
|
|
189
207
|
useEffect(() => {
|
|
190
208
|
dispatch({
|
|
191
209
|
type: "set.controls.valueRange",
|
|
@@ -200,31 +218,34 @@ export function Scatterplot(_ref) {
|
|
|
200
218
|
max: settings.controls.range[1] * (valueMax - valueMin) + valueMin
|
|
201
219
|
};
|
|
202
220
|
const getFillColor = useCallback((_d, _ref2) => {
|
|
221
|
+
var _settings$selectedObs6, _settings$selectedObs7;
|
|
203
222
|
let {
|
|
204
223
|
index
|
|
205
224
|
} = _ref2;
|
|
206
|
-
const grayOut =
|
|
207
|
-
return getColor({
|
|
208
|
-
value: (
|
|
225
|
+
const grayOut = isPending || sortedObsIndices && !sortedObsIndices.has(index);
|
|
226
|
+
return getColor(_objectSpread({
|
|
227
|
+
value: (sortedData.values[index] - min) / (max - min),
|
|
209
228
|
categorical: isCategorical,
|
|
210
229
|
grayOut: grayOut
|
|
211
|
-
})
|
|
212
|
-
|
|
230
|
+
}, useUnsColors && settings.colorEncoding === COLOR_ENCODINGS.OBS && (_settings$selectedObs6 = settings.selectedObs) !== null && _settings$selectedObs6 !== void 0 && _settings$selectedObs6.colors ? {
|
|
231
|
+
colorscale: (_settings$selectedObs7 = settings.selectedObs) === null || _settings$selectedObs7 === void 0 ? void 0 : _settings$selectedObs7.colors
|
|
232
|
+
} : {})) || [0, 0, 0, 100];
|
|
233
|
+
}, [isPending, sortedObsIndices, getColor, sortedData.values, min, max, isCategorical, useUnsColors, settings.colorEncoding, (_settings$selectedObs8 = settings.selectedObs) === null || _settings$selectedObs8 === void 0 ? void 0 : _settings$selectedObs8.colors]);
|
|
213
234
|
|
|
214
235
|
// @TODO: add support for pseudospatial hover to reflect in radius
|
|
215
236
|
const getRadius = useCallback((_d, _ref3) => {
|
|
216
237
|
let {
|
|
217
238
|
index
|
|
218
239
|
} = _ref3;
|
|
219
|
-
const grayOut =
|
|
240
|
+
const grayOut = sortedObsIndices && !sortedObsIndices.has(index);
|
|
220
241
|
return grayOut ? 1 : 3;
|
|
221
|
-
}, [
|
|
242
|
+
}, [sortedObsIndices]);
|
|
222
243
|
const memoizedLayers = useMemo(() => {
|
|
223
244
|
return [new ScatterplotLayer({
|
|
224
245
|
id: "cherita-layer-scatterplot",
|
|
225
246
|
pickable: true,
|
|
226
|
-
data:
|
|
227
|
-
radiusScale:
|
|
247
|
+
data: sortedData.positions,
|
|
248
|
+
radiusScale: radiusScale,
|
|
228
249
|
radiusMinPixels: 1,
|
|
229
250
|
getPosition: d => d,
|
|
230
251
|
getFillColor: getFillColor,
|
|
@@ -267,7 +288,7 @@ export function Scatterplot(_ref) {
|
|
|
267
288
|
}
|
|
268
289
|
}
|
|
269
290
|
})];
|
|
270
|
-
}, [
|
|
291
|
+
}, [sortedData.positions, features, getFillColor, getRadius, mode, radiusScale, selectedFeatureIndexes]);
|
|
271
292
|
const layers = useDeferredValue(mode === ViewMode ? memoizedLayers.reverse() : memoizedLayers); // draw scatterplot on top of polygons when in ViewMode
|
|
272
293
|
|
|
273
294
|
useEffect(() => {
|
|
@@ -310,21 +331,21 @@ export function Scatterplot(_ref) {
|
|
|
310
331
|
if (settings.colorEncoding === COLOR_ENCODINGS.OBS && settings.selectedObs && !_.some(settings.labelObs, {
|
|
311
332
|
name: settings.selectedObs.name
|
|
312
333
|
})) {
|
|
313
|
-
var
|
|
314
|
-
text.push(getLabel(settings.selectedObs, (
|
|
334
|
+
var _data$values;
|
|
335
|
+
text.push(getLabel(settings.selectedObs, (_data$values = data.values) === null || _data$values === void 0 ? void 0 : _data$values[getOriginalIndex(index)]));
|
|
315
336
|
}
|
|
316
337
|
if (settings.colorEncoding === COLOR_ENCODINGS.VAR && settings.selectedVar) {
|
|
317
|
-
var
|
|
318
|
-
text.push(getLabel(settings.selectedVar, (
|
|
338
|
+
var _data$values2;
|
|
339
|
+
text.push(getLabel(settings.selectedVar, (_data$values2 = data.values) === null || _data$values2 === void 0 ? void 0 : _data$values2[getOriginalIndex(index)], true));
|
|
319
340
|
}
|
|
320
341
|
if (settings.labelObs.length) {
|
|
321
342
|
text.push(..._.map(labelObsData.data, (v, k) => {
|
|
322
343
|
const labelObs = _.find(settings.labelObs, o => o.name === k);
|
|
323
|
-
return getLabel(labelObs, v[index]);
|
|
344
|
+
return getLabel(labelObs, v[getOriginalIndex(index)]);
|
|
324
345
|
}));
|
|
325
346
|
}
|
|
326
347
|
if (!text.length) return;
|
|
327
|
-
const grayOut =
|
|
348
|
+
const grayOut = sortedObsIndices && !sortedObsIndices.has(index);
|
|
328
349
|
return {
|
|
329
350
|
text: text.length ? _.compact(text).join("\n") : null,
|
|
330
351
|
className: grayOut ? "tooltip-grayout" : "deck-tooltip",
|
|
@@ -337,8 +358,7 @@ export function Scatterplot(_ref) {
|
|
|
337
358
|
}
|
|
338
359
|
};
|
|
339
360
|
};
|
|
340
|
-
const
|
|
341
|
-
const error = settings.selectedObsm && ((_obsmData$serverError = obsmData.serverError) === null || _obsmData$serverError === void 0 ? void 0 : _obsmData$serverError.length) || settings.colorEncoding === COLOR_ENCODINGS.VAR && ((_xData$serverError = xData.serverError) === null || _xData$serverError === void 0 ? void 0 : _xData$serverError.length) || settings.colorEncoding === COLOR_ENCODINGS.OBS && ((_obsData$serverError = obsData.serverError) === null || _obsData$serverError === void 0 ? void 0 : _obsData$serverError.length) || settings.labelObs.lengh && ((_labelObsData$serverE = labelObsData.serverError) === null || _labelObsData$serverE === void 0 ? void 0 : _labelObsData$serverE.length);
|
|
361
|
+
const error = settings.selectedObsm && ((_obsmData$serverError = obsmData.serverError) === null || _obsmData$serverError === void 0 ? void 0 : _obsmData$serverError.length) || settings.colorEncoding === COLOR_ENCODINGS.VAR && ((_xData$serverError = xData.serverError) === null || _xData$serverError === void 0 ? void 0 : _xData$serverError.length) || settings.colorEncoding === COLOR_ENCODINGS.OBS && ((_obsData$serverError = obsData.serverError) === null || _obsData$serverError === void 0 ? void 0 : _obsData$serverError.length) || settings.labelObs.length && ((_labelObsData$serverE = labelObsData.serverError) === null || _labelObsData$serverE === void 0 ? void 0 : _labelObsData$serverE.length) || coordsError;
|
|
342
362
|
return /*#__PURE__*/React.createElement("div", {
|
|
343
363
|
className: "cherita-container-scatterplot"
|
|
344
364
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -385,13 +405,13 @@ export function Scatterplot(_ref) {
|
|
|
385
405
|
className: "cherita-spatial-footer"
|
|
386
406
|
}, /*#__PURE__*/React.createElement("div", {
|
|
387
407
|
className: "cherita-toolbox-footer"
|
|
388
|
-
}, error && !
|
|
408
|
+
}, !!error && !isRendering && /*#__PURE__*/React.createElement(Alert, {
|
|
389
409
|
variant: "danger"
|
|
390
410
|
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
391
411
|
icon: faTriangleExclamation
|
|
392
412
|
}), "\xA0Error loading data"), /*#__PURE__*/React.createElement(Toolbox, {
|
|
393
|
-
mode: settings.colorEncoding === COLOR_ENCODINGS.VAR ? (_settings$selectedVar = settings.selectedVar) === null || _settings$selectedVar === void 0 ? void 0 : _settings$selectedVar.name : settings.colorEncoding === COLOR_ENCODINGS.OBS ? (_settings$
|
|
394
|
-
obsLength: parseInt((
|
|
413
|
+
mode: settings.colorEncoding === COLOR_ENCODINGS.VAR ? (_settings$selectedVar = settings.selectedVar) === null || _settings$selectedVar === void 0 ? void 0 : _settings$selectedVar.name : settings.colorEncoding === COLOR_ENCODINGS.OBS ? (_settings$selectedObs9 = settings.selectedObs) === null || _settings$selectedObs9 === void 0 ? void 0 : _settings$selectedObs9.name : null,
|
|
414
|
+
obsLength: parseInt((_data$positions = data.positions) === null || _data$positions === void 0 ? void 0 : _data$positions.length),
|
|
395
415
|
slicedLength: parseInt(slicedLength)
|
|
396
416
|
})), /*#__PURE__*/React.createElement(Legend, {
|
|
397
417
|
isCategorical: isCategorical,
|
|
@@ -8,9 +8,9 @@ import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
|
|
8
8
|
import Button from "react-bootstrap/Button";
|
|
9
9
|
import ButtonGroup from "react-bootstrap/ButtonGroup";
|
|
10
10
|
import Dropdown from "react-bootstrap/Dropdown";
|
|
11
|
-
import { useDataset } from "../../context/DatasetContext";
|
|
12
11
|
import { OffcanvasControls } from "../offcanvas";
|
|
13
12
|
import { ScatterplotControls } from "./ScatterplotControls";
|
|
13
|
+
import { BREAKPOINTS } from "../../constants/constants";
|
|
14
14
|
import { useSettings, useSettingsDispatch } from "../../context/SettingsContext";
|
|
15
15
|
export function SpatialControls(_ref) {
|
|
16
16
|
var _features$features;
|
|
@@ -32,8 +32,8 @@ export function SpatialControls(_ref) {
|
|
|
32
32
|
const [showControls, setShowControls] = useState(false);
|
|
33
33
|
const handleCloseControls = () => setShowControls(false);
|
|
34
34
|
const handleShowControls = () => setShowControls(true);
|
|
35
|
-
const LgBreakpoint = useMediaQuery(
|
|
36
|
-
const XlBreakpoint = useMediaQuery(
|
|
35
|
+
const LgBreakpoint = useMediaQuery(BREAKPOINTS.LG);
|
|
36
|
+
const XlBreakpoint = useMediaQuery(BREAKPOINTS.XL);
|
|
37
37
|
const showObsBtn = isFullscreen ? LgBreakpoint : true;
|
|
38
38
|
const showVarsBtn = isFullscreen ? XlBreakpoint : true;
|
|
39
39
|
const onSelect = (eventKey, event) => {
|
|
@@ -13,6 +13,7 @@ import { useDataset } from "../../context/DatasetContext";
|
|
|
13
13
|
import { useSettings, useSettingsDispatch } from "../../context/SettingsContext";
|
|
14
14
|
import { useFetch } from "../../utils/requests";
|
|
15
15
|
import { VarDiseaseInfo } from "../var-list/VarItem";
|
|
16
|
+
import { sortMeans, useVarMean } from "../var-list/VarList";
|
|
16
17
|
export function VarInfo(_ref) {
|
|
17
18
|
let {
|
|
18
19
|
varItem
|
|
@@ -43,39 +44,6 @@ export function VarInfo(_ref) {
|
|
|
43
44
|
data: fetchedData
|
|
44
45
|
})));
|
|
45
46
|
}
|
|
46
|
-
const useVarMean = function (varKeys) {
|
|
47
|
-
let enabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
48
|
-
const ENDPOINT = "matrix/mean";
|
|
49
|
-
const dataset = useDataset();
|
|
50
|
-
const [params, setParams] = useState({
|
|
51
|
-
url: dataset.url,
|
|
52
|
-
varKeys: _.map(varKeys, v => v.isSet ? {
|
|
53
|
-
name: v.name,
|
|
54
|
-
indices: v.vars.map(v => v.index)
|
|
55
|
-
} : v.index),
|
|
56
|
-
// obsIndices:
|
|
57
|
-
varNamesCol: dataset.varNamesCol
|
|
58
|
-
});
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
setParams(p => {
|
|
61
|
-
return _objectSpread(_objectSpread({}, p), {}, {
|
|
62
|
-
varKeys: _.map(varKeys, v => v.isSet ? {
|
|
63
|
-
name: v.name,
|
|
64
|
-
indices: v.vars.map(v => v.index)
|
|
65
|
-
} : v.index)
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}, [varKeys]);
|
|
69
|
-
return useFetch(ENDPOINT, params, {
|
|
70
|
-
enabled: enabled,
|
|
71
|
-
refetchOnMount: false
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// ensure nulls are lowest values
|
|
76
|
-
const sortMeans = (i, means) => {
|
|
77
|
-
return means[i.name] || _.min(_.values(means)) - 1;
|
|
78
|
-
};
|
|
79
47
|
export function DiseaseInfo(_ref2) {
|
|
80
48
|
let {
|
|
81
49
|
disease,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { faList, faSearch, faSliders } from "@fortawesome/free-solid-svg-icons";
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import { Container, Nav, Navbar } from "react-bootstrap";
|
|
5
|
+
export const Toolbar = _ref => {
|
|
6
|
+
let {
|
|
7
|
+
showObsBtn = true,
|
|
8
|
+
showVarsBtn = true,
|
|
9
|
+
showCtrlsBtn = true,
|
|
10
|
+
setShowObs,
|
|
11
|
+
setShowVars,
|
|
12
|
+
setShowControls
|
|
13
|
+
} = _ref;
|
|
14
|
+
return /*#__PURE__*/React.createElement(Navbar, {
|
|
15
|
+
expand: "md",
|
|
16
|
+
bg: "primary",
|
|
17
|
+
variant: "dark",
|
|
18
|
+
className: "cherita-navbar"
|
|
19
|
+
}, /*#__PURE__*/React.createElement(Container, {
|
|
20
|
+
fluid: true
|
|
21
|
+
}, /*#__PURE__*/React.createElement(Navbar.Toggle, {
|
|
22
|
+
"aria-controls": "navbarScroll"
|
|
23
|
+
}), /*#__PURE__*/React.createElement(Navbar.Collapse, {
|
|
24
|
+
id: "navbarScroll"
|
|
25
|
+
}, /*#__PURE__*/React.createElement(Nav, {
|
|
26
|
+
navbarScroll: true
|
|
27
|
+
}, showObsBtn && /*#__PURE__*/React.createElement(Nav.Item, {
|
|
28
|
+
className: "me-2"
|
|
29
|
+
}, /*#__PURE__*/React.createElement(Nav.Link, {
|
|
30
|
+
onClick: () => setShowObs(true)
|
|
31
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
32
|
+
icon: faList,
|
|
33
|
+
className: "me-2"
|
|
34
|
+
}), "Explore Categories")), showVarsBtn && /*#__PURE__*/React.createElement(Nav.Item, {
|
|
35
|
+
className: "me-2"
|
|
36
|
+
}, /*#__PURE__*/React.createElement(Nav.Link, {
|
|
37
|
+
onClick: () => setShowVars(true)
|
|
38
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
39
|
+
icon: faSearch,
|
|
40
|
+
className: "me-2"
|
|
41
|
+
}), "Search Genes")), showCtrlsBtn && /*#__PURE__*/React.createElement(Nav.Item, {
|
|
42
|
+
className: "me-2"
|
|
43
|
+
}, /*#__PURE__*/React.createElement(Nav.Link, {
|
|
44
|
+
onClick: () => setShowControls(true)
|
|
45
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
46
|
+
icon: faSliders,
|
|
47
|
+
className: "me-2"
|
|
48
|
+
}), "Controls"))))));
|
|
49
|
+
};
|
|
50
|
+
export const ObsPlotlyToolbar = _ref2 => {
|
|
51
|
+
let {
|
|
52
|
+
onClick
|
|
53
|
+
} = _ref2;
|
|
54
|
+
return {
|
|
55
|
+
name: "Categories",
|
|
56
|
+
icon: {
|
|
57
|
+
width: 512,
|
|
58
|
+
height: 512,
|
|
59
|
+
path: faList.icon[4]
|
|
60
|
+
},
|
|
61
|
+
click: onClick
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export const VarPlotlyToolbar = _ref3 => {
|
|
65
|
+
let {
|
|
66
|
+
onClick
|
|
67
|
+
} = _ref3;
|
|
68
|
+
return {
|
|
69
|
+
name: "Features",
|
|
70
|
+
icon: {
|
|
71
|
+
width: 512,
|
|
72
|
+
height: 512,
|
|
73
|
+
path: faSearch.icon[4]
|
|
74
|
+
},
|
|
75
|
+
click: onClick
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export const ControlsPlotlyToolbar = _ref4 => {
|
|
79
|
+
let {
|
|
80
|
+
onClick
|
|
81
|
+
} = _ref4;
|
|
82
|
+
return {
|
|
83
|
+
name: "Controls",
|
|
84
|
+
icon: {
|
|
85
|
+
width: 512,
|
|
86
|
+
height: 512,
|
|
87
|
+
path: faSliders.icon[4]
|
|
88
|
+
},
|
|
89
|
+
click: onClick
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -14,20 +14,24 @@ import { VarListToolbar } from "./VarListToolbar";
|
|
|
14
14
|
import { VarSet } from "./VarSet";
|
|
15
15
|
import { SELECTION_MODES, VAR_SORT } from "../../constants/constants";
|
|
16
16
|
import { useDataset } from "../../context/DatasetContext";
|
|
17
|
+
import { useFilteredData } from "../../context/FilterContext";
|
|
17
18
|
import { useSettings, useSettingsDispatch } from "../../context/SettingsContext";
|
|
18
19
|
import { LoadingSpinner } from "../../utils/LoadingIndicators";
|
|
19
20
|
import { useFetch } from "../../utils/requests";
|
|
20
|
-
const useVarMean = function (varKeys) {
|
|
21
|
+
export const useVarMean = function (varKeys) {
|
|
21
22
|
let enabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
22
23
|
const ENDPOINT = "matrix/mean";
|
|
23
24
|
const dataset = useDataset();
|
|
25
|
+
const {
|
|
26
|
+
obsIndices
|
|
27
|
+
} = useFilteredData();
|
|
24
28
|
const [params, setParams] = useState({
|
|
25
29
|
url: dataset.url,
|
|
26
30
|
varKeys: _.map(varKeys, v => v.isSet ? {
|
|
27
31
|
name: v.name,
|
|
28
32
|
indices: v.vars.map(v => v.index)
|
|
29
33
|
} : v.index),
|
|
30
|
-
|
|
34
|
+
obsIndices: obsIndices,
|
|
31
35
|
varNamesCol: dataset.varNamesCol
|
|
32
36
|
});
|
|
33
37
|
useEffect(() => {
|
|
@@ -36,10 +40,11 @@ const useVarMean = function (varKeys) {
|
|
|
36
40
|
varKeys: _.map(varKeys, v => v.isSet ? {
|
|
37
41
|
name: v.name,
|
|
38
42
|
indices: v.vars.map(v => v.index)
|
|
39
|
-
} : v.index)
|
|
43
|
+
} : v.index),
|
|
44
|
+
obsIndices: obsIndices
|
|
40
45
|
});
|
|
41
46
|
});
|
|
42
|
-
}, [varKeys]);
|
|
47
|
+
}, [obsIndices, varKeys]);
|
|
43
48
|
return useFetch(ENDPOINT, params, {
|
|
44
49
|
enabled: enabled,
|
|
45
50
|
refetchOnMount: false
|
|
@@ -47,7 +52,7 @@ const useVarMean = function (varKeys) {
|
|
|
47
52
|
};
|
|
48
53
|
|
|
49
54
|
// ensure nulls are lowest values
|
|
50
|
-
const sortMeans = (i, means) => {
|
|
55
|
+
export const sortMeans = (i, means) => {
|
|
51
56
|
return means[i.name] || _.min(_.values(means)) - 1;
|
|
52
57
|
};
|
|
53
58
|
export function VarNamesList(_ref) {
|
|
@@ -7,18 +7,25 @@ import React, { useEffect, useState } from "react";
|
|
|
7
7
|
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
|
8
8
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
9
9
|
import _ from "lodash";
|
|
10
|
-
import { Alert, OverlayTrigger, Tooltip } from "react-bootstrap";
|
|
10
|
+
import { Alert, Button, OverlayTrigger, Tooltip } from "react-bootstrap";
|
|
11
11
|
import Plot from "react-plotly.js";
|
|
12
|
-
import { VIOLIN_MODES } from "../../constants/constants";
|
|
12
|
+
import { PLOTLY_MODEBAR_BUTTONS, VIOLIN_MODES } from "../../constants/constants";
|
|
13
13
|
import { useDataset } from "../../context/DatasetContext";
|
|
14
14
|
import { useFilteredData } from "../../context/FilterContext";
|
|
15
15
|
import { useSettings } from "../../context/SettingsContext";
|
|
16
16
|
import { LoadingSpinner } from "../../utils/LoadingIndicators";
|
|
17
17
|
import { useDebouncedFetch } from "../../utils/requests";
|
|
18
|
+
import { ControlsPlotlyToolbar, ObsPlotlyToolbar, VarPlotlyToolbar } from "../toolbar/Toolbar";
|
|
18
19
|
export function Violin(_ref) {
|
|
19
20
|
var _settings$selectedVar, _settings$selectedVar2, _settings$selectedVar3, _settings$selectedVar4, _settings$selectedObs, _settings$selectedObs2, _settings$selectedObs3;
|
|
20
21
|
let {
|
|
21
|
-
mode = VIOLIN_MODES.MULTIKEY
|
|
22
|
+
mode = VIOLIN_MODES.MULTIKEY,
|
|
23
|
+
showObsBtn = false,
|
|
24
|
+
showVarsBtn = false,
|
|
25
|
+
showCtrlsBtn = false,
|
|
26
|
+
setShowObs,
|
|
27
|
+
setShowVars,
|
|
28
|
+
setShowControls
|
|
22
29
|
} = _ref;
|
|
23
30
|
const ENDPOINT = "violin";
|
|
24
31
|
const dataset = useDataset();
|
|
@@ -117,17 +124,29 @@ export function Violin(_ref) {
|
|
|
117
124
|
setLayout(fetchedData.layout);
|
|
118
125
|
}
|
|
119
126
|
}, [fetchedData, hasSelections, isPending, serverError]);
|
|
127
|
+
const customModeBarButtons = _.compact([showObsBtn && ObsPlotlyToolbar({
|
|
128
|
+
onClick: setShowObs
|
|
129
|
+
}), showVarsBtn && VarPlotlyToolbar({
|
|
130
|
+
onClick: setShowVars
|
|
131
|
+
}), showCtrlsBtn && ControlsPlotlyToolbar({
|
|
132
|
+
onClick: setShowControls
|
|
133
|
+
})]);
|
|
134
|
+
const modeBarButtons = customModeBarButtons.length ? [customModeBarButtons, PLOTLY_MODEBAR_BUTTONS] : [PLOTLY_MODEBAR_BUTTONS];
|
|
120
135
|
if (!serverError) {
|
|
121
136
|
if (hasSelections) {
|
|
122
137
|
return /*#__PURE__*/React.createElement("div", {
|
|
123
|
-
className: "cherita-violin position-relative"
|
|
138
|
+
className: "cherita-plot cherita-violin position-relative"
|
|
124
139
|
}, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(Plot, {
|
|
125
140
|
data: data,
|
|
126
141
|
layout: layout,
|
|
127
142
|
useResizeHandler: true,
|
|
128
143
|
style: {
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
width: "100%",
|
|
145
|
+
height: "100%"
|
|
146
|
+
},
|
|
147
|
+
config: {
|
|
148
|
+
displaylogo: false,
|
|
149
|
+
modeBarButtons: modeBarButtons
|
|
131
150
|
}
|
|
132
151
|
}), (fetchedData === null || fetchedData === void 0 ? void 0 : fetchedData.resampled) && /*#__PURE__*/React.createElement(Alert, {
|
|
133
152
|
variant: "warning"
|
|
@@ -142,9 +161,21 @@ export function Violin(_ref) {
|
|
|
142
161
|
className: "cherita-violin"
|
|
143
162
|
}, mode === VIOLIN_MODES.MULTIKEY && /*#__PURE__*/React.createElement(Alert, {
|
|
144
163
|
variant: "light"
|
|
145
|
-
}, "Select
|
|
164
|
+
}, "Select", " ", showVarsBtn ? /*#__PURE__*/React.createElement(Button, {
|
|
165
|
+
variant: "link",
|
|
166
|
+
className: "border-0 p-0 align-baseline",
|
|
167
|
+
onClick: setShowVars
|
|
168
|
+
}, "features") : "features"), mode === VIOLIN_MODES.GROUPBY && /*#__PURE__*/React.createElement(Alert, {
|
|
146
169
|
variant: "light"
|
|
147
|
-
}, "Select
|
|
170
|
+
}, "Select", " ", showObsBtn ? /*#__PURE__*/React.createElement(Button, {
|
|
171
|
+
variant: "link",
|
|
172
|
+
className: "border-0 p-0 align-baseline",
|
|
173
|
+
onClick: setShowObs
|
|
174
|
+
}, "categories") : "categories", " ", "and a", " ", showVarsBtn ? /*#__PURE__*/React.createElement(Button, {
|
|
175
|
+
variant: "link",
|
|
176
|
+
className: "border-0 p-0 align-baseline",
|
|
177
|
+
onClick: setShowVars
|
|
178
|
+
}, "feature") : "feature"));
|
|
148
179
|
} else {
|
|
149
180
|
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Alert, {
|
|
150
181
|
variant: "danger"
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export const LOCAL_STORAGE_KEY = "CHERITA";
|
|
2
|
+
export const PLOT_TYPES = {
|
|
3
|
+
SCATTERPLOT: "scatterplot",
|
|
4
|
+
DOTPLOT: "dotplot",
|
|
5
|
+
HEATMAP: "heatmap",
|
|
6
|
+
MATRIXPLOT: "matrixplot",
|
|
7
|
+
VIOLINPLOT: "violinplot"
|
|
8
|
+
};
|
|
2
9
|
export const COLOR_ENCODINGS = {
|
|
3
10
|
VAR: "var",
|
|
4
11
|
OBS: "obs"
|
|
@@ -85,4 +92,9 @@ export const PSEUDOSPATIAL_CATEGORICAL_MODES = {
|
|
|
85
92
|
|
|
86
93
|
// `default` cols to be shown out of accordion, at top of obslist
|
|
87
94
|
// default values from cellxgene schema
|
|
88
|
-
export const DEFAULT_OBS_GROUP = ["assay", "cell_type", "development_stage", "disease", "donor_id", "organism", "self_reported_ethnicity", "sex", "suspension_type", "tissue", "tissue_type"];
|
|
95
|
+
export const DEFAULT_OBS_GROUP = ["assay", "cell_type", "development_stage", "disease", "donor_id", "organism", "self_reported_ethnicity", "sex", "suspension_type", "tissue", "tissue_type"];
|
|
96
|
+
export const PLOTLY_MODEBAR_BUTTONS = ["toImage", "zoom2d", "pan2d", "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d"];
|
|
97
|
+
export const BREAKPOINTS = {
|
|
98
|
+
LG: "(max-width: 991.98px)",
|
|
99
|
+
XL: "(max-width: 1199.98px)"
|
|
100
|
+
};
|