@haniffalab/cherita-react 1.0.0-dev.2025-03-13.bda9e1a6 → 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 +11 -9
- 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/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/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,140 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { faPlus, faMinus, faCrosshairs, faHand, faDrawPolygon, faTrash, faSliders } from "@fortawesome/free-solid-svg-icons";
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import { JoinInner } from "@mui/icons-material";
|
|
5
|
+
import { DrawPolygonMode, DrawLineStringMode, DrawPolygonByDraggingMode, DrawRectangleMode, ViewMode, ModifyMode } from "@nebula.gl/edit-modes";
|
|
6
|
+
import Button from "react-bootstrap/Button";
|
|
7
|
+
import ButtonGroup from "react-bootstrap/ButtonGroup";
|
|
8
|
+
import Dropdown from "react-bootstrap/Dropdown";
|
|
9
|
+
import DropdownButton from "react-bootstrap/DropdownButton";
|
|
10
|
+
import { ScatterplotControls } from "./ScatterplotControls";
|
|
11
|
+
import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
|
|
12
|
+
import { OffcanvasControls } from "../offcanvas";
|
|
13
|
+
export function SpatialControls(_ref) {
|
|
14
|
+
let {
|
|
15
|
+
mode,
|
|
16
|
+
setMode,
|
|
17
|
+
features,
|
|
18
|
+
setFeatures,
|
|
19
|
+
selectedFeatureIndexes,
|
|
20
|
+
resetBounds,
|
|
21
|
+
increaseZoom,
|
|
22
|
+
decreaseZoom
|
|
23
|
+
} = _ref;
|
|
24
|
+
const dataset = useDataset();
|
|
25
|
+
const dispatch = useDatasetDispatch();
|
|
26
|
+
const [showControls, setShowControls] = useState(false);
|
|
27
|
+
const handleCloseControls = () => setShowControls(false);
|
|
28
|
+
const handleShowControls = () => setShowControls(true);
|
|
29
|
+
const onSelect = (eventKey, event) => {
|
|
30
|
+
switch (eventKey) {
|
|
31
|
+
case "DrawPolygonMode":
|
|
32
|
+
setMode(() => DrawPolygonMode);
|
|
33
|
+
break;
|
|
34
|
+
case "DrawLineStringMode":
|
|
35
|
+
setMode(() => DrawLineStringMode);
|
|
36
|
+
break;
|
|
37
|
+
case "DrawPolygonByDraggingMode":
|
|
38
|
+
setMode(() => DrawPolygonByDraggingMode);
|
|
39
|
+
break;
|
|
40
|
+
case "DrawRectangleMode":
|
|
41
|
+
setMode(() => DrawRectangleMode);
|
|
42
|
+
break;
|
|
43
|
+
case "ModifyMode":
|
|
44
|
+
setMode(() => ModifyMode);
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
setMode(() => ViewMode);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const deleteFeatures = (_eventKey, _event) => {
|
|
51
|
+
setFeatures({
|
|
52
|
+
type: "FeatureCollection",
|
|
53
|
+
features: []
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const polygonControls = /*#__PURE__*/React.createElement("div", {
|
|
57
|
+
className: "mt-2"
|
|
58
|
+
}, /*#__PURE__*/React.createElement(ButtonGroup, {
|
|
59
|
+
vertical: true,
|
|
60
|
+
className: "w-100"
|
|
61
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
62
|
+
variant: dataset.sliceBy.polygons ? "primary" : "outline-primary",
|
|
63
|
+
title: "Filter data with polygons",
|
|
64
|
+
onClick: () => {
|
|
65
|
+
setMode(() => ViewMode);
|
|
66
|
+
dispatch({
|
|
67
|
+
type: "toggle.slice.polygons"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}, /*#__PURE__*/React.createElement(JoinInner, null)), /*#__PURE__*/React.createElement(Button, {
|
|
71
|
+
variant: "outline-primary",
|
|
72
|
+
title: "Delete selected polygons",
|
|
73
|
+
onClick: () => {
|
|
74
|
+
const newFeatures = features.features.filter((_f, i) => !selectedFeatureIndexes.includes(i));
|
|
75
|
+
setFeatures({
|
|
76
|
+
type: "FeatureCollection",
|
|
77
|
+
features: newFeatures
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
disabled: !selectedFeatureIndexes.length
|
|
81
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
82
|
+
icon: faTrash
|
|
83
|
+
}))));
|
|
84
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
85
|
+
className: "cherita-spatial-controls"
|
|
86
|
+
}, /*#__PURE__*/React.createElement(ButtonGroup, {
|
|
87
|
+
vertical: true,
|
|
88
|
+
className: "w-100"
|
|
89
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
90
|
+
onClick: increaseZoom,
|
|
91
|
+
title: "Increase zoom"
|
|
92
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
93
|
+
icon: faPlus
|
|
94
|
+
})), /*#__PURE__*/React.createElement(Button, {
|
|
95
|
+
onClick: decreaseZoom,
|
|
96
|
+
title: "Decrease zoom"
|
|
97
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
98
|
+
icon: faMinus
|
|
99
|
+
})), /*#__PURE__*/React.createElement(Button, {
|
|
100
|
+
onClick: resetBounds,
|
|
101
|
+
title: "Reset zoom and center"
|
|
102
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
103
|
+
icon: faCrosshairs
|
|
104
|
+
})), /*#__PURE__*/React.createElement(Button, {
|
|
105
|
+
onClick: () => setMode(() => ViewMode),
|
|
106
|
+
title: "Set dragging mode",
|
|
107
|
+
variant: mode === ViewMode ? "primary" : "outline-primary"
|
|
108
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
109
|
+
icon: faHand
|
|
110
|
+
})), /*#__PURE__*/React.createElement(DropdownButton, {
|
|
111
|
+
as: ButtonGroup,
|
|
112
|
+
title: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
113
|
+
icon: faDrawPolygon
|
|
114
|
+
})),
|
|
115
|
+
drop: "end",
|
|
116
|
+
id: "bg-vertical-dropdown-1",
|
|
117
|
+
className: "caret-off",
|
|
118
|
+
onSelect: onSelect
|
|
119
|
+
}, /*#__PURE__*/React.createElement(Dropdown.Header, null, "Selection tools"), /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
120
|
+
eventKey: "DrawPolygonMode"
|
|
121
|
+
}, "Draw a polygon"), /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
122
|
+
eventKey: "DrawPolygonByDraggingMode"
|
|
123
|
+
}, "Draw a Polygon by Dragging"), /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
124
|
+
eventKey: "ModifyMode"
|
|
125
|
+
}, "Modify polygons"), /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
126
|
+
eventKey: "ViewMode"
|
|
127
|
+
}, "Viewing mode"), /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
128
|
+
onClick: deleteFeatures
|
|
129
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
130
|
+
icon: faTrash
|
|
131
|
+
}), " Delete polygons")), /*#__PURE__*/React.createElement(Button, {
|
|
132
|
+
onClick: handleShowControls
|
|
133
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
134
|
+
icon: faSliders
|
|
135
|
+
}))), !!features?.features?.length && polygonControls, /*#__PURE__*/React.createElement(OffcanvasControls, {
|
|
136
|
+
show: showControls,
|
|
137
|
+
handleClose: handleCloseControls,
|
|
138
|
+
Controls: ScatterplotControls
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { faDroplet } from "@fortawesome/free-solid-svg-icons";
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import Button from "react-bootstrap/Button";
|
|
5
|
+
import ButtonGroup from "react-bootstrap/ButtonGroup";
|
|
6
|
+
import { formatNumerical } from "../../utils/string";
|
|
7
|
+
import { ObsmKeysList } from "../obsm-list/ObsmList";
|
|
8
|
+
export function Toolbox(_ref) {
|
|
9
|
+
let {
|
|
10
|
+
mode,
|
|
11
|
+
obsLength,
|
|
12
|
+
slicedLength
|
|
13
|
+
} = _ref;
|
|
14
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
15
|
+
className: "cherita-toolbox"
|
|
16
|
+
}, /*#__PURE__*/React.createElement(ButtonGroup, null, /*#__PURE__*/React.createElement(ObsmKeysList, null), /*#__PURE__*/React.createElement(Button, {
|
|
17
|
+
size: "sm"
|
|
18
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
19
|
+
icon: faDroplet
|
|
20
|
+
}), " ", mode), (mode || !isNaN(obsLength)) && (mode !== null && !isNaN(slicedLength) && slicedLength !== obsLength ? /*#__PURE__*/React.createElement(Button, {
|
|
21
|
+
size: "sm"
|
|
22
|
+
}, formatNumerical(slicedLength), " out of", " ", formatNumerical(obsLength), " cells") : /*#__PURE__*/React.createElement(Button, {
|
|
23
|
+
size: "sm"
|
|
24
|
+
}, formatNumerical(obsLength), " cells"))));
|
|
25
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from "react";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import { Form, FormGroup, Dropdown } from "react-bootstrap";
|
|
4
|
+
import { DiseasesSearchResults, VarSearchResults } from "./SearchResults";
|
|
5
|
+
function onVarSelect(dispatch, item) {
|
|
6
|
+
dispatch({
|
|
7
|
+
type: "select.var",
|
|
8
|
+
var: item
|
|
9
|
+
});
|
|
10
|
+
dispatch({
|
|
11
|
+
type: "select.multivar",
|
|
12
|
+
var: item
|
|
13
|
+
});
|
|
14
|
+
dispatch({
|
|
15
|
+
type: "set.colorEncoding",
|
|
16
|
+
value: "var"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function SearchBar(_ref) {
|
|
20
|
+
let {
|
|
21
|
+
searchVar = true,
|
|
22
|
+
searchDiseases = false,
|
|
23
|
+
handleSelect = onVarSelect
|
|
24
|
+
} = _ref;
|
|
25
|
+
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
26
|
+
const [text, setText] = useState("");
|
|
27
|
+
const inputRef = useRef(null);
|
|
28
|
+
const displayText = [...(searchVar ? ["features"] : []), ...(searchDiseases ? ["diseases"] : [])].join(" and ");
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (text.length > 0) {
|
|
31
|
+
setShowSuggestions(true);
|
|
32
|
+
}
|
|
33
|
+
}, [text]);
|
|
34
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Form, {
|
|
35
|
+
onSubmit: e => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
}
|
|
38
|
+
}, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Form.Control, {
|
|
39
|
+
ref: inputRef,
|
|
40
|
+
onFocus: () => {
|
|
41
|
+
setShowSuggestions(text.length > 0);
|
|
42
|
+
},
|
|
43
|
+
onBlur: () => {
|
|
44
|
+
_.delay(() => {
|
|
45
|
+
setShowSuggestions(false);
|
|
46
|
+
}, 150);
|
|
47
|
+
},
|
|
48
|
+
type: "text",
|
|
49
|
+
placeholder: "Search " + displayText,
|
|
50
|
+
value: text,
|
|
51
|
+
onChange: e => {
|
|
52
|
+
setText(e.target.value);
|
|
53
|
+
}
|
|
54
|
+
}), /*#__PURE__*/React.createElement(Dropdown, {
|
|
55
|
+
show: showSuggestions,
|
|
56
|
+
onMouseDown: e => {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
},
|
|
59
|
+
onSelect: () => {
|
|
60
|
+
inputRef.current.blur();
|
|
61
|
+
}
|
|
62
|
+
}, /*#__PURE__*/React.createElement(Dropdown.Menu, {
|
|
63
|
+
style: {
|
|
64
|
+
width: "90%"
|
|
65
|
+
}
|
|
66
|
+
}, searchVar && /*#__PURE__*/React.createElement(VarSearchResults, {
|
|
67
|
+
text: text,
|
|
68
|
+
setShowSuggestions: setShowSuggestions,
|
|
69
|
+
handleSelect: handleSelect
|
|
70
|
+
}), searchVar && searchDiseases && /*#__PURE__*/React.createElement(Dropdown.Divider, null), searchDiseases && /*#__PURE__*/React.createElement(DiseasesSearchResults, {
|
|
71
|
+
text: text,
|
|
72
|
+
setShowSuggestions: setShowSuggestions
|
|
73
|
+
}))))));
|
|
74
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo, useDeferredValue } from "react";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import { Dropdown } from "react-bootstrap";
|
|
4
|
+
import { useDatasetDispatch } from "../../context/DatasetContext";
|
|
5
|
+
import { useDiseaseSearch, useVarSearch } from "../../utils/search";
|
|
6
|
+
export function VarSearchResults(_ref) {
|
|
7
|
+
let {
|
|
8
|
+
text,
|
|
9
|
+
setShowSuggestions,
|
|
10
|
+
handleSelect
|
|
11
|
+
} = _ref;
|
|
12
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
13
|
+
const dispatch = useDatasetDispatch();
|
|
14
|
+
const {
|
|
15
|
+
setParams,
|
|
16
|
+
data: {
|
|
17
|
+
fetchedData = [],
|
|
18
|
+
isPending,
|
|
19
|
+
serverError
|
|
20
|
+
}
|
|
21
|
+
} = useVarSearch();
|
|
22
|
+
const deferredData = useDeferredValue(suggestions);
|
|
23
|
+
const isStale = deferredData !== fetchedData;
|
|
24
|
+
const updateParams = useMemo(() => {
|
|
25
|
+
const setData = text => {
|
|
26
|
+
if (text.length) {
|
|
27
|
+
setParams(p => {
|
|
28
|
+
return {
|
|
29
|
+
...p,
|
|
30
|
+
text: text
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
setSuggestions([]);
|
|
35
|
+
setShowSuggestions(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return _.debounce(setData, 300);
|
|
39
|
+
}, [setParams, setShowSuggestions]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
updateParams(text);
|
|
42
|
+
}, [text, updateParams]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!isPending && !serverError) {
|
|
45
|
+
setSuggestions(fetchedData);
|
|
46
|
+
setShowSuggestions(true);
|
|
47
|
+
}
|
|
48
|
+
}, [fetchedData, isPending, serverError, setShowSuggestions]);
|
|
49
|
+
const suggestionsList = useMemo(() => {
|
|
50
|
+
return deferredData?.map(item => {
|
|
51
|
+
return /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
52
|
+
key: item.name,
|
|
53
|
+
as: "button",
|
|
54
|
+
disabled: isStale,
|
|
55
|
+
onClick: () => {
|
|
56
|
+
handleSelect(dispatch, item);
|
|
57
|
+
_.delay(() => {
|
|
58
|
+
setShowSuggestions(false);
|
|
59
|
+
}, 150);
|
|
60
|
+
}
|
|
61
|
+
}, item.name);
|
|
62
|
+
});
|
|
63
|
+
}, [deferredData, dispatch, handleSelect, isStale, setShowSuggestions]);
|
|
64
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Dropdown.Header, null, "Features"), /*#__PURE__*/React.createElement("div", {
|
|
65
|
+
className: "search-results"
|
|
66
|
+
}, deferredData?.length ? suggestionsList : /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
67
|
+
key: "empty",
|
|
68
|
+
as: "button",
|
|
69
|
+
disabled: true
|
|
70
|
+
}, !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data")));
|
|
71
|
+
}
|
|
72
|
+
export function DiseasesSearchResults(_ref2) {
|
|
73
|
+
let {
|
|
74
|
+
text,
|
|
75
|
+
setShowSuggestions
|
|
76
|
+
} = _ref2;
|
|
77
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
78
|
+
const dispatch = useDatasetDispatch();
|
|
79
|
+
const {
|
|
80
|
+
setParams,
|
|
81
|
+
data: {
|
|
82
|
+
fetchedData = [],
|
|
83
|
+
isPending,
|
|
84
|
+
serverError
|
|
85
|
+
}
|
|
86
|
+
} = useDiseaseSearch();
|
|
87
|
+
const deferredData = useDeferredValue(suggestions);
|
|
88
|
+
const isStale = deferredData !== fetchedData;
|
|
89
|
+
const updateParams = useMemo(() => {
|
|
90
|
+
const setData = text => {
|
|
91
|
+
if (text.length) {
|
|
92
|
+
setParams(p => {
|
|
93
|
+
return {
|
|
94
|
+
...p,
|
|
95
|
+
text: text
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
setSuggestions([]);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return _.debounce(setData, 300);
|
|
103
|
+
}, [setParams]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
updateParams(text);
|
|
106
|
+
}, [text, updateParams]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!isPending && !serverError) {
|
|
109
|
+
setSuggestions(fetchedData);
|
|
110
|
+
setShowSuggestions(true);
|
|
111
|
+
}
|
|
112
|
+
}, [fetchedData, isPending, serverError, setShowSuggestions]);
|
|
113
|
+
const suggestionsList = useMemo(() => {
|
|
114
|
+
return deferredData?.map(item => {
|
|
115
|
+
return /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
116
|
+
key: item.id,
|
|
117
|
+
as: "button",
|
|
118
|
+
disabled: isStale,
|
|
119
|
+
onClick: () => {
|
|
120
|
+
dispatch({
|
|
121
|
+
type: "select.disease",
|
|
122
|
+
id: item.disease_id,
|
|
123
|
+
name: item.disease_name
|
|
124
|
+
});
|
|
125
|
+
_.delay(() => {
|
|
126
|
+
setShowSuggestions(false);
|
|
127
|
+
}, 150);
|
|
128
|
+
}
|
|
129
|
+
}, item.disease_name);
|
|
130
|
+
});
|
|
131
|
+
}, [deferredData, dispatch, isStale, setShowSuggestions]);
|
|
132
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Dropdown.Header, null, "Diseases"), /*#__PURE__*/React.createElement("div", {
|
|
133
|
+
className: "search-results"
|
|
134
|
+
}, deferredData?.length ? suggestionsList : /*#__PURE__*/React.createElement(Dropdown.Item, {
|
|
135
|
+
key: "empty",
|
|
136
|
+
as: "button",
|
|
137
|
+
disabled: true
|
|
138
|
+
}, !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data")));
|
|
139
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { faDroplet, faTrash } from "@fortawesome/free-solid-svg-icons";
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import { MoreVert } from "@mui/icons-material";
|
|
5
|
+
import _ from "lodash";
|
|
6
|
+
import { Button, Collapse, ListGroup, Table } from "react-bootstrap";
|
|
7
|
+
import { COLOR_ENCODINGS, SELECTION_MODES } from "../../constants/constants";
|
|
8
|
+
import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
|
|
9
|
+
import { useFilteredData } from "../../context/FilterContext";
|
|
10
|
+
import { Histogram } from "../../utils/Histogram";
|
|
11
|
+
import { useFetch, useDebouncedFetch } from "../../utils/requests";
|
|
12
|
+
import { VirtualizedList } from "../../utils/VirtualizedList";
|
|
13
|
+
function VarHistogram(_ref) {
|
|
14
|
+
let {
|
|
15
|
+
item
|
|
16
|
+
} = _ref;
|
|
17
|
+
const ENDPOINT = "var/histograms";
|
|
18
|
+
const dataset = useDataset();
|
|
19
|
+
const {
|
|
20
|
+
obsIndices
|
|
21
|
+
} = useFilteredData();
|
|
22
|
+
// @TODO: consider using Filter's isSliced; would trigger more re-renders/requests
|
|
23
|
+
// const { obsIndices, isSliced } = useFilteredData();
|
|
24
|
+
const isSliced = dataset.sliceBy.obs || dataset.sliceBy.polygons;
|
|
25
|
+
const [params, setParams] = useState({
|
|
26
|
+
url: dataset.url,
|
|
27
|
+
varKey: item.matrix_index,
|
|
28
|
+
obsIndices: isSliced ? [...(obsIndices || [])] : null
|
|
29
|
+
});
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setParams(p => {
|
|
32
|
+
return {
|
|
33
|
+
...p,
|
|
34
|
+
obsIndices: isSliced ? [...(obsIndices || [])] : null
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}, [obsIndices, isSliced]);
|
|
38
|
+
const {
|
|
39
|
+
fetchedData,
|
|
40
|
+
isPending,
|
|
41
|
+
serverError
|
|
42
|
+
} = useDebouncedFetch(ENDPOINT, params, {
|
|
43
|
+
refetchOnMount: false
|
|
44
|
+
});
|
|
45
|
+
return !serverError && /*#__PURE__*/React.createElement(Histogram, {
|
|
46
|
+
data: fetchedData,
|
|
47
|
+
isPending: isPending,
|
|
48
|
+
altColor: isSliced
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function VarDiseaseInfoItem(item) {
|
|
52
|
+
const dispatch = useDatasetDispatch();
|
|
53
|
+
return /*#__PURE__*/React.createElement(ListGroup.Item, {
|
|
54
|
+
key: item.disease_name,
|
|
55
|
+
className: "feature-disease-info"
|
|
56
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
|
|
57
|
+
type: "button",
|
|
58
|
+
className: "btn btn-link",
|
|
59
|
+
onClick: () => {
|
|
60
|
+
dispatch({
|
|
61
|
+
type: "select.disease",
|
|
62
|
+
id: item.disease_id,
|
|
63
|
+
name: item.disease_name
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}, item.disease_name), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(Table, {
|
|
67
|
+
striped: true
|
|
68
|
+
}, /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Confidence"), /*#__PURE__*/React.createElement("td", null, item.confidence || "unknown")), /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, "Organ", item.organs.length > 1 ? "s" : ""), /*#__PURE__*/React.createElement("td", null, item.organs.map(o => o.name).join(", "))), !_.isEmpty(item.metadata) && _.map(item.metadata, (value, key) => {
|
|
69
|
+
if (value !== null && value !== undefined) {
|
|
70
|
+
return /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, key), /*#__PURE__*/React.createElement("td", null, value));
|
|
71
|
+
}
|
|
72
|
+
})))));
|
|
73
|
+
}
|
|
74
|
+
function VarDiseaseInfo(_ref2) {
|
|
75
|
+
let {
|
|
76
|
+
data
|
|
77
|
+
} = _ref2;
|
|
78
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ListGroup, null, /*#__PURE__*/React.createElement(VirtualizedList, {
|
|
79
|
+
getDataAtIndex: index => data[index],
|
|
80
|
+
count: data.length,
|
|
81
|
+
estimateSize: 140,
|
|
82
|
+
maxHeight: "100%",
|
|
83
|
+
ItemComponent: VarDiseaseInfoItem
|
|
84
|
+
})));
|
|
85
|
+
}
|
|
86
|
+
export function SingleSelectionItem(_ref3) {
|
|
87
|
+
let {
|
|
88
|
+
item,
|
|
89
|
+
isActive,
|
|
90
|
+
selectVar,
|
|
91
|
+
removeVar,
|
|
92
|
+
isDiseaseGene = false,
|
|
93
|
+
showSetColorEncoding = true,
|
|
94
|
+
showRemove = true
|
|
95
|
+
} = _ref3;
|
|
96
|
+
const ENDPOINT = "disease/gene";
|
|
97
|
+
const [openInfo, setOpenInfo] = useState(false);
|
|
98
|
+
const dataset = useDataset();
|
|
99
|
+
const params = {
|
|
100
|
+
geneName: item.name,
|
|
101
|
+
diseaseDatasets: dataset.diseaseDatasets
|
|
102
|
+
};
|
|
103
|
+
const isNotInData = item.matrix_index === -1;
|
|
104
|
+
const {
|
|
105
|
+
fetchedData,
|
|
106
|
+
isPending,
|
|
107
|
+
serverError
|
|
108
|
+
} = useFetch(ENDPOINT, params, {
|
|
109
|
+
refetchOnMount: false,
|
|
110
|
+
enabled: !!dataset.diseaseDatasets.length
|
|
111
|
+
});
|
|
112
|
+
const hasDiseaseInfo = !isPending && !serverError && !!fetchedData.length;
|
|
113
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
114
|
+
className: `d-flex justify-content-between ${hasDiseaseInfo ? "cursor-pointer" : ""}`,
|
|
115
|
+
onClick: () => {
|
|
116
|
+
setOpenInfo(o => !o);
|
|
117
|
+
}
|
|
118
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
119
|
+
className: "d-flex justify-content-between align-items-center w-100"
|
|
120
|
+
}, /*#__PURE__*/React.createElement("div", null, item.name), /*#__PURE__*/React.createElement("div", {
|
|
121
|
+
className: "d-flex align-items-center gap-1"
|
|
122
|
+
}, hasDiseaseInfo && /*#__PURE__*/React.createElement(MoreVert, null), !isDiseaseGene && /*#__PURE__*/React.createElement(VarHistogram, {
|
|
123
|
+
item: item
|
|
124
|
+
}), showSetColorEncoding && /*#__PURE__*/React.createElement(Button, {
|
|
125
|
+
type: "button",
|
|
126
|
+
key: item.matrix_index,
|
|
127
|
+
variant: isActive ? "primary" : isNotInData ? "outline-secondary" : "outline-primary",
|
|
128
|
+
className: "m-0 p-0 px-1",
|
|
129
|
+
onClick: e => {
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
selectVar();
|
|
132
|
+
},
|
|
133
|
+
disabled: isNotInData,
|
|
134
|
+
title: isNotInData ? "Not present in data" : "Set as color encoding"
|
|
135
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
136
|
+
icon: faDroplet
|
|
137
|
+
})), (!isDiseaseGene || !showRemove) && /*#__PURE__*/React.createElement(Button, {
|
|
138
|
+
type: "button",
|
|
139
|
+
className: "m-0 p-0 px-1",
|
|
140
|
+
variant: "outline-secondary",
|
|
141
|
+
title: "Remove from list",
|
|
142
|
+
onClick: e => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
removeVar();
|
|
145
|
+
}
|
|
146
|
+
}, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
|
|
147
|
+
icon: faTrash
|
|
148
|
+
}))))), hasDiseaseInfo && /*#__PURE__*/React.createElement(Collapse, {
|
|
149
|
+
in: openInfo
|
|
150
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
151
|
+
className: "mt-2 var-disease-info-collapse"
|
|
152
|
+
}, /*#__PURE__*/React.createElement(VarDiseaseInfo, {
|
|
153
|
+
data: fetchedData
|
|
154
|
+
}))));
|
|
155
|
+
}
|
|
156
|
+
function MultipleSelectionItem(_ref4) {
|
|
157
|
+
let {
|
|
158
|
+
item,
|
|
159
|
+
isActive,
|
|
160
|
+
toggleVar
|
|
161
|
+
} = _ref4;
|
|
162
|
+
const isNotInData = item.matrix_index === -1;
|
|
163
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
164
|
+
className: "d-flex"
|
|
165
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
166
|
+
className: "flex-grow-1"
|
|
167
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
168
|
+
type: "button",
|
|
169
|
+
key: item.matrix_index,
|
|
170
|
+
variant: isActive ? "primary" : "outline-primary",
|
|
171
|
+
className: "m-0 p-0 px-1",
|
|
172
|
+
onClick: toggleVar,
|
|
173
|
+
disabled: isNotInData,
|
|
174
|
+
title: isNotInData ? "Not present in data" : item.name
|
|
175
|
+
}, item.name))));
|
|
176
|
+
}
|
|
177
|
+
export function VarItem(_ref5) {
|
|
178
|
+
let {
|
|
179
|
+
item,
|
|
180
|
+
active,
|
|
181
|
+
setVarButtons,
|
|
182
|
+
mode = SELECTION_MODES.SINGLE,
|
|
183
|
+
isDiseaseGene = false
|
|
184
|
+
} = _ref5;
|
|
185
|
+
const dataset = useDataset();
|
|
186
|
+
const dispatch = useDatasetDispatch();
|
|
187
|
+
const selectVar = () => {
|
|
188
|
+
if (mode === SELECTION_MODES.SINGLE) {
|
|
189
|
+
dispatch({
|
|
190
|
+
type: "select.var",
|
|
191
|
+
var: item
|
|
192
|
+
});
|
|
193
|
+
dispatch({
|
|
194
|
+
type: "set.colorEncoding",
|
|
195
|
+
value: "var"
|
|
196
|
+
});
|
|
197
|
+
} else if (mode === SELECTION_MODES.MULTIPLE) {
|
|
198
|
+
dispatch({
|
|
199
|
+
type: "select.multivar",
|
|
200
|
+
var: item
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const removeVar = () => {
|
|
205
|
+
setVarButtons(b => {
|
|
206
|
+
return b.filter(i => i.name !== item.name);
|
|
207
|
+
});
|
|
208
|
+
if (mode === SELECTION_MODES.SINGLE) {
|
|
209
|
+
if (active === item.matrix_index) {
|
|
210
|
+
dispatch({
|
|
211
|
+
type: "reset.var"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} else if (mode === SELECTION_MODES.MULTIPLE) {
|
|
215
|
+
if (active.includes(item.matrix_index)) {
|
|
216
|
+
dispatch({
|
|
217
|
+
type: "deselect.multivar",
|
|
218
|
+
var: item
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const toggleVar = () => {
|
|
224
|
+
if (active.includes(item.matrix_index)) {
|
|
225
|
+
dispatch({
|
|
226
|
+
type: "deselect.multivar",
|
|
227
|
+
var: item
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
selectVar(item);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
if (item && mode === SELECTION_MODES.SINGLE) {
|
|
234
|
+
return /*#__PURE__*/React.createElement(SingleSelectionItem, {
|
|
235
|
+
item: item,
|
|
236
|
+
isActive: dataset.colorEncoding === COLOR_ENCODINGS.VAR && active === item.matrix_index,
|
|
237
|
+
selectVar: selectVar,
|
|
238
|
+
removeVar: removeVar,
|
|
239
|
+
isDiseaseGene: isDiseaseGene
|
|
240
|
+
});
|
|
241
|
+
} else if (mode === SELECTION_MODES.MULTIPLE) {
|
|
242
|
+
return /*#__PURE__*/React.createElement(MultipleSelectionItem, {
|
|
243
|
+
item: item,
|
|
244
|
+
isActive: item.matrix_index !== -1 && _.includes(active, item.matrix_index),
|
|
245
|
+
toggleVar: toggleVar
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|