@haniffalab/cherita-react 1.0.0-dev.2025-03-13.bda9e1a6 → 1.0.0-dev.2025-03-24.e68f9e22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/{components → cjs/components}/full-page/FullPage.js +6 -5
  2. package/dist/{components → cjs/components}/full-page/FullPagePseudospatial.js +6 -5
  3. package/dist/{components → cjs/components}/obs-list/ObsItem.js +50 -46
  4. package/dist/{components → cjs/components}/obs-list/ObsList.js +9 -7
  5. package/dist/{components → cjs/components}/obs-list/ObsToolbar.js +12 -11
  6. package/dist/{components → cjs/components}/offcanvas/index.js +24 -20
  7. package/dist/{components → cjs/components}/pseudospatial/Pseudospatial.js +9 -8
  8. package/dist/{components → cjs/components}/pseudospatial/PseudospatialToolbar.js +4 -3
  9. package/dist/{components → cjs/components}/scatterplot/Scatterplot.js +31 -22
  10. package/dist/{components → cjs/components}/scatterplot/SpatialControls.js +11 -10
  11. package/dist/{components → cjs/components}/scatterplot/Toolbox.js +6 -5
  12. package/dist/{components → cjs/components}/search-bar/SearchBar.js +6 -5
  13. package/dist/{components → cjs/components}/search-bar/SearchResults.js +11 -9
  14. package/dist/{components → cjs/components}/var-list/VarItem.js +32 -27
  15. package/dist/{components → cjs/components}/var-list/VarList.js +21 -12
  16. package/dist/{components → cjs/components}/var-list/VarListToolbar.js +4 -3
  17. package/dist/{components → cjs/components}/var-list/VarSet.js +21 -18
  18. package/dist/{components → cjs/components}/violin/Violin.js +4 -3
  19. package/dist/{context → cjs/context}/DatasetContext.js +11 -9
  20. package/dist/{context → cjs/context}/FilterContext.js +4 -3
  21. package/dist/{context → cjs/context}/ZarrDataContext.js +4 -3
  22. package/dist/{helpers → cjs/helpers}/color-helper.js +12 -11
  23. package/dist/{helpers → cjs/helpers}/map-helper.js +8 -7
  24. package/dist/{helpers → cjs/helpers}/zarr-helper.js +25 -15
  25. package/dist/{utils → cjs/utils}/Histogram.js +12 -8
  26. package/dist/{utils → cjs/utils}/ImageViewer.js +6 -5
  27. package/dist/{utils → cjs/utils}/Legend.js +8 -7
  28. package/dist/{utils → cjs/utils}/LoadingIndicators.js +5 -4
  29. package/dist/{utils → cjs/utils}/VirtualizedList.js +10 -9
  30. package/dist/{utils → cjs/utils}/requests.js +33 -21
  31. package/dist/{utils → cjs/utils}/string.js +9 -4
  32. package/dist/{utils → cjs/utils}/zarrData.js +12 -4
  33. package/dist/esm/components/dotplot/Dotplot.js +135 -0
  34. package/dist/esm/components/dotplot/DotplotControls.js +148 -0
  35. package/dist/esm/components/full-page/FullPage.js +134 -0
  36. package/dist/esm/components/full-page/FullPagePseudospatial.js +151 -0
  37. package/dist/esm/components/heatmap/Heatmap.js +105 -0
  38. package/dist/esm/components/heatmap/HeatmapControls.js +23 -0
  39. package/dist/esm/components/matrixplot/Matrixplot.js +107 -0
  40. package/dist/esm/components/matrixplot/MatrixplotControls.js +38 -0
  41. package/dist/esm/components/obs-list/ObsItem.js +477 -0
  42. package/dist/esm/components/obs-list/ObsList.js +256 -0
  43. package/dist/esm/components/obs-list/ObsToolbar.js +58 -0
  44. package/dist/esm/components/obsm-list/ObsmList.js +72 -0
  45. package/dist/esm/components/offcanvas/index.js +67 -0
  46. package/dist/esm/components/pseudospatial/Pseudospatial.js +228 -0
  47. package/dist/esm/components/pseudospatial/PseudospatialToolbar.js +123 -0
  48. package/dist/esm/components/scatterplot/Scatterplot.js +394 -0
  49. package/dist/esm/components/scatterplot/ScatterplotControls.js +71 -0
  50. package/dist/esm/components/scatterplot/SpatialControls.js +140 -0
  51. package/dist/esm/components/scatterplot/Toolbox.js +25 -0
  52. package/dist/esm/components/search-bar/SearchBar.js +74 -0
  53. package/dist/esm/components/search-bar/SearchResults.js +139 -0
  54. package/dist/esm/components/var-list/VarItem.js +250 -0
  55. package/dist/esm/components/var-list/VarList.js +272 -0
  56. package/dist/esm/components/var-list/VarListToolbar.js +84 -0
  57. package/dist/esm/components/var-list/VarSet.js +193 -0
  58. package/dist/esm/components/violin/Violin.js +141 -0
  59. package/dist/esm/components/violin/ViolinControls.js +24 -0
  60. package/dist/esm/constants/colorscales.js +22 -0
  61. package/dist/esm/constants/constants.js +84 -0
  62. package/dist/esm/context/DatasetContext.js +572 -0
  63. package/dist/esm/context/FilterContext.js +48 -0
  64. package/dist/esm/context/ZarrDataContext.js +26 -0
  65. package/dist/esm/helpers/color-helper.js +66 -0
  66. package/dist/esm/helpers/map-helper.js +53 -0
  67. package/dist/esm/helpers/zarr-helper.js +129 -0
  68. package/dist/esm/index.js +22 -0
  69. package/dist/esm/utils/Filter.js +147 -0
  70. package/dist/esm/utils/Histogram.js +44 -0
  71. package/dist/esm/utils/ImageViewer.js +27 -0
  72. package/dist/esm/utils/Legend.js +58 -0
  73. package/dist/esm/utils/LoadingIndicators.js +22 -0
  74. package/dist/esm/utils/VirtualizedList.js +55 -0
  75. package/dist/esm/utils/errors.js +47 -0
  76. package/dist/esm/utils/requests.js +116 -0
  77. package/dist/esm/utils/search.js +39 -0
  78. package/dist/esm/utils/string.js +59 -0
  79. package/dist/esm/utils/zarrData.js +102 -0
  80. package/package.json +16 -5
  81. /package/dist/{components → cjs/components}/dotplot/Dotplot.js +0 -0
  82. /package/dist/{components → cjs/components}/dotplot/DotplotControls.js +0 -0
  83. /package/dist/{components → cjs/components}/heatmap/Heatmap.js +0 -0
  84. /package/dist/{components → cjs/components}/heatmap/HeatmapControls.js +0 -0
  85. /package/dist/{components → cjs/components}/matrixplot/Matrixplot.js +0 -0
  86. /package/dist/{components → cjs/components}/matrixplot/MatrixplotControls.js +0 -0
  87. /package/dist/{components → cjs/components}/obsm-list/ObsmList.js +0 -0
  88. /package/dist/{components → cjs/components}/scatterplot/ScatterplotControls.js +0 -0
  89. /package/dist/{components → cjs/components}/violin/ViolinControls.js +0 -0
  90. /package/dist/{constants → cjs/constants}/colorscales.js +0 -0
  91. /package/dist/{constants → cjs/constants}/constants.js +0 -0
  92. /package/dist/{index.js → cjs/index.js} +0 -0
  93. /package/dist/{utils → cjs/utils}/Filter.js +0 -0
  94. /package/dist/{utils → cjs/utils}/errors.js +0 -0
  95. /package/dist/{utils → cjs/utils}/search.js +0 -0
@@ -0,0 +1,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
+ }