@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,272 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Add } from "@mui/icons-material";
3
+ import _ from "lodash";
4
+ import { ListGroup, Button, Alert } from "react-bootstrap";
5
+ import { VarItem } from "./VarItem";
6
+ import { VarListToolbar } from "./VarListToolbar";
7
+ import { VarSet } from "./VarSet";
8
+ import { SELECTION_MODES, VAR_SORT } from "../../constants/constants";
9
+ import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
10
+ import { LoadingSpinner } from "../../utils/LoadingIndicators";
11
+ import { useFetch } from "../../utils/requests";
12
+ const useVarMean = function (varKeys) {
13
+ let enabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
14
+ const ENDPOINT = "matrix/mean";
15
+ const dataset = useDataset();
16
+ const [params, setParams] = useState({
17
+ url: dataset.url,
18
+ varKeys: _.map(varKeys, v => v.isSet ? {
19
+ name: v.name,
20
+ indices: v.vars.map(v => v.index)
21
+ } : v.index),
22
+ // obsIndices:
23
+ varNamesCol: dataset.varNamesCol
24
+ });
25
+ useEffect(() => {
26
+ setParams(p => {
27
+ return {
28
+ ...p,
29
+ varKeys: _.map(varKeys, v => v.isSet ? {
30
+ name: v.name,
31
+ indices: v.vars.map(v => v.index)
32
+ } : v.index)
33
+ };
34
+ });
35
+ }, [varKeys]);
36
+ return useFetch(ENDPOINT, params, {
37
+ enabled: enabled,
38
+ refetchOnMount: false
39
+ });
40
+ };
41
+
42
+ // ensure nulls are lowest values
43
+ const sortMeans = (i, means) => {
44
+ return means[i.name] || _.min(_.values(means)) - 1;
45
+ };
46
+
47
+ // @TODO: display where disease data comes from
48
+ // add to disease dataset metadata
49
+ function DiseaseVarList(_ref) {
50
+ let {
51
+ makeListItem
52
+ } = _ref;
53
+ const ENDPOINT = "disease/genes";
54
+ const dataset = useDataset();
55
+ const dispatch = useDatasetDispatch();
56
+ const [diseaseVars, setDiseaseVars] = useState([]);
57
+ const [sortedDiseaseVars, setSortedDiseaseVars] = useState([]);
58
+ const [params, setParams] = useState({
59
+ url: dataset.url,
60
+ col: dataset.varNamesCol,
61
+ diseaseId: dataset.selectedDisease?.id,
62
+ diseaseDatasets: dataset.diseaseDatasets
63
+ });
64
+ useEffect(() => {
65
+ setParams(p => {
66
+ return {
67
+ ...p,
68
+ diseaseId: dataset.selectedDisease?.id
69
+ };
70
+ });
71
+ }, [dataset.selectedDisease]);
72
+ const diseaseData = useFetch(ENDPOINT, params, {
73
+ enabled: !!params.diseaseId,
74
+ refetchOnMount: false
75
+ });
76
+ useEffect(() => {
77
+ if (!diseaseData.isPending && !diseaseData.serverError) {
78
+ setDiseaseVars(diseaseData.fetchedData);
79
+ }
80
+ }, [diseaseData.fetchedData, diseaseData.isPending, diseaseData.serverError]);
81
+ const varMeans = useVarMean(diseaseVars, diseaseVars && dataset.varSort.disease.sort === VAR_SORT.MATRIX);
82
+ useEffect(() => {
83
+ if (dataset.varSort.disease.sort === VAR_SORT.MATRIX) {
84
+ if (!varMeans.isPending && !varMeans.serverError) {
85
+ setSortedDiseaseVars(_.orderBy(diseaseVars, o => {
86
+ return sortMeans(o, varMeans.fetchedData);
87
+ }, dataset.varSort.disease.sortOrder));
88
+ }
89
+ } else if (dataset.varSort.disease.sort === VAR_SORT.NAME) {
90
+ setSortedDiseaseVars(_.orderBy(diseaseVars, "name", dataset.varSort.disease.sortOrder));
91
+ } else {
92
+ setSortedDiseaseVars(diseaseVars);
93
+ }
94
+ }, [dataset.varSort.disease.sort, dataset.varSort.disease.sortOrder, diseaseVars, varMeans.fetchedData, varMeans.isPending, varMeans.serverError]);
95
+ const diseaseVarList = _.map(sortedDiseaseVars, item => {
96
+ return makeListItem(item, true);
97
+ });
98
+ const isPending = diseaseData.isPending || varMeans.isPending && dataset.varSort.disease.sort === VAR_SORT.MATRIX;
99
+ return /*#__PURE__*/React.createElement(React.Fragment, null, dataset.selectedDisease && (!isPending && !diseaseVars?.length ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
100
+ className: "d-flex justify-content-between mt-3"
101
+ }, /*#__PURE__*/React.createElement("h5", null, "Disease genes")), /*#__PURE__*/React.createElement(Alert, {
102
+ variant: "light"
103
+ }, "No disease genes found.")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
104
+ className: "d-flex justify-content-between mt-3"
105
+ }, /*#__PURE__*/React.createElement("h5", null, "Disease genes"), /*#__PURE__*/React.createElement(Button, {
106
+ variant: "link",
107
+ onClick: () => {
108
+ dispatch({
109
+ type: "reset.disease"
110
+ });
111
+ }
112
+ }, "clear")), /*#__PURE__*/React.createElement("p", null, dataset.selectedDisease?.name), /*#__PURE__*/React.createElement(VarListToolbar, {
113
+ varType: "disease"
114
+ }), /*#__PURE__*/React.createElement("div", {
115
+ className: "position-relative"
116
+ }, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(ListGroup, null, diseaseVarList)))));
117
+ }
118
+ export function VarNamesList(_ref2) {
119
+ let {
120
+ mode = SELECTION_MODES.SINGLE,
121
+ displayName = "genes",
122
+ showDiseaseVarList = true
123
+ } = _ref2;
124
+ const dataset = useDataset();
125
+ const dispatch = useDatasetDispatch();
126
+ const [varButtons, setVarButtons] = useState(mode === SELECTION_MODES.SINGLE ? dataset.selectedVar ? _.unionWith([dataset.selectedVar], dataset.varSets, _.isEqual) : [...dataset.varSets] : [...dataset.selectedMultiVar, ...dataset.varSets]);
127
+ const [active, setActive] = useState(mode === SELECTION_MODES.SINGLE ? dataset.selectedVar?.matrix_index || dataset.selectedVar?.name : dataset.selectedMultiVar.map(i => i.matrix_index || i.name));
128
+ const [sortedVarButtons, setSortedVarButtons] = useState([]);
129
+ useEffect(() => {
130
+ if (mode === SELECTION_MODES.SINGLE) {
131
+ setVarButtons(v => {
132
+ if (dataset.selectedVar) {
133
+ return _.unionWith(v, [dataset.selectedVar], _.isEqual);
134
+ } else {
135
+ return v;
136
+ }
137
+ });
138
+ setActive(dataset.selectedVar?.matrix_index || dataset.selectedVar?.name);
139
+ }
140
+ }, [mode, dataset.selectedVar]);
141
+ useEffect(() => {
142
+ if (mode === SELECTION_MODES.MULTIPLE) {
143
+ setVarButtons(v => {
144
+ if (dataset.selectedMultiVar.length) {
145
+ return _.unionWith(v, dataset.selectedMultiVar, _.isEqual);
146
+ } else {
147
+ return v;
148
+ }
149
+ });
150
+ setActive(dataset.selectedMultiVar.map(i => i.matrix_index || i.name));
151
+ }
152
+ }, [mode, dataset.selectedMultiVar]);
153
+ useEffect(() => {
154
+ setVarButtons(v => {
155
+ const updated = _.compact(_.map(v, i => {
156
+ if (i.isSet) {
157
+ return dataset.varSets.find(s => s.name === i.name);
158
+ } else return i;
159
+ }));
160
+ const newSets = _.difference(dataset.varSets, updated);
161
+ return [...updated, ...newSets];
162
+ });
163
+ if (mode === SELECTION_MODES.SINGLE) {
164
+ if (dataset.selectedVar?.isSet) {
165
+ const selectedSet = dataset.varSets.find(s => s.name === dataset.selectedVar.name);
166
+ dispatch({
167
+ type: "select.var",
168
+ var: selectedSet
169
+ });
170
+ }
171
+ } else {
172
+ dispatch({
173
+ type: "update.multivar",
174
+ vars: dataset.varSets
175
+ });
176
+ }
177
+ }, [mode, dataset.varSets, dataset.selectedVar?.isSet, dataset.selectedVar?.name, dispatch]);
178
+ const varMeans = useVarMean(varButtons, dataset.varSort.var.sort === VAR_SORT.MATRIX);
179
+
180
+ // @TODO: deferr sortedVarButtons ?
181
+ useEffect(() => {
182
+ if (dataset.varSort.var.sort === VAR_SORT.MATRIX) {
183
+ if (!varMeans.isPending && !varMeans.serverError) {
184
+ setSortedVarButtons(_.orderBy(varButtons, o => {
185
+ return sortMeans(o, varMeans.fetchedData);
186
+ }, dataset.varSort.var.sortOrder));
187
+ }
188
+ } else if (dataset.varSort.var.sort === VAR_SORT.NAME) {
189
+ setSortedVarButtons(_.orderBy(varButtons, "name", dataset.varSort.var.sortOrder));
190
+ } else {
191
+ setSortedVarButtons(varButtons);
192
+ }
193
+ }, [dataset.varSort.var.sort, dataset.varSort.var.sortOrder, varButtons, varMeans.isPending, varMeans.serverError, varMeans.fetchedData]);
194
+ const makeListItem = function (item) {
195
+ let isDiseaseGene = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
196
+ return /*#__PURE__*/React.createElement(ListGroup.Item, {
197
+ key: item.matrix_index
198
+ }, /*#__PURE__*/React.createElement(VarItem, {
199
+ item: item,
200
+ active: active,
201
+ setVarButtons: setVarButtons,
202
+ mode: mode,
203
+ isDiseaseGene: isDiseaseGene
204
+ }));
205
+ };
206
+ const makeSetListItem = set => {
207
+ return /*#__PURE__*/React.createElement(ListGroup.Item, {
208
+ key: set.name
209
+ }, /*#__PURE__*/React.createElement(VarSet, {
210
+ set: set,
211
+ active: active,
212
+ mode: mode
213
+ }));
214
+ };
215
+ const varList = _.map(sortedVarButtons, item => {
216
+ if (item.isSet) {
217
+ return makeSetListItem(item);
218
+ } else {
219
+ return makeListItem(item);
220
+ }
221
+ });
222
+ const newSetName = () => {
223
+ let n = 1;
224
+ let setName = `Set ${n}`;
225
+ const setNameExists = name => {
226
+ return dataset.varSets.some(set => set.name === name);
227
+ };
228
+ while (setNameExists(setName)) {
229
+ n++;
230
+ setName = `Set ${n}`;
231
+ }
232
+ return setName;
233
+ };
234
+ const isPending = varMeans.isPending && dataset.varSort.var.sort === VAR_SORT.MATRIX;
235
+ return /*#__PURE__*/React.createElement("div", {
236
+ className: "position-relative"
237
+ }, /*#__PURE__*/React.createElement("div", {
238
+ className: "overflow-auto mt-3"
239
+ }, /*#__PURE__*/React.createElement("div", {
240
+ className: "d-flex justify-content-between"
241
+ }, /*#__PURE__*/React.createElement("h5", null, _.capitalize(displayName)), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Button, {
242
+ variant: "light",
243
+ className: "p-1",
244
+ onClick: () => {
245
+ dispatch({
246
+ type: "add.varSet",
247
+ varSet: {
248
+ name: newSetName(),
249
+ vars: [],
250
+ isSet: true
251
+ }
252
+ });
253
+ }
254
+ }, /*#__PURE__*/React.createElement(Add, null), "New set"), /*#__PURE__*/React.createElement(Button, {
255
+ variant: "link",
256
+ onClick: () => {
257
+ setVarButtons([]);
258
+ dispatch({
259
+ type: mode === SELECTION_MODES.SINGLE ? "reset.var" : "reset.multiVar"
260
+ });
261
+ dispatch({
262
+ type: "reset.varSets"
263
+ });
264
+ }
265
+ }, "clear"))), /*#__PURE__*/React.createElement(React.Fragment, null, !varList.length ? /*#__PURE__*/React.createElement(Alert, {
266
+ variant: "light"
267
+ }, "Search for a feature.") : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(VarListToolbar, null), /*#__PURE__*/React.createElement("div", {
268
+ className: "position-relative"
269
+ }, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(ListGroup, null, varList)))), /*#__PURE__*/React.createElement(React.Fragment, null, showDiseaseVarList && /*#__PURE__*/React.createElement(DiseaseVarList, {
270
+ makeListItem: makeListItem
271
+ }))));
272
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useState } from "react";
2
+ import { faArrowDownAZ, faArrowUpZA, faArrowDown19, faArrowUp91, faXmark } from "@fortawesome/free-solid-svg-icons";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import { ToggleButton, ToggleButtonGroup } from "@mui/material";
5
+ import { Navbar, InputGroup } from "react-bootstrap";
6
+ import { VAR_SORT, VAR_SORT_ORDER } from "../../constants/constants";
7
+ import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
8
+
9
+ // @TODO: set option for "var" and "disease"
10
+ export function VarListToolbar(_ref) {
11
+ let {
12
+ varType = "var"
13
+ } = _ref;
14
+ const dataset = useDataset();
15
+ const dispatch = useDatasetDispatch();
16
+ const [sort, setSort] = useState(dataset.varSort.var.sort);
17
+ const [nameSortOrder, setNameSortOrder] = useState(dataset.varSort.var.sortOrder);
18
+ const [matrixSortOrder, setMatrixSortOrder] = useState(dataset.varSort.var.sortOrder);
19
+ const handleSort = (sortValue, sortOrder, setSortOrder) => {
20
+ if (sort !== sortValue) {
21
+ setSort(sortValue);
22
+ dispatch({
23
+ type: "set.varSort",
24
+ var: varType,
25
+ sort: sortValue,
26
+ sortOrder: sortOrder
27
+ });
28
+ } else {
29
+ const newSortOrder = sortOrder === VAR_SORT_ORDER.ASC ? VAR_SORT_ORDER.DESC : VAR_SORT_ORDER.ASC;
30
+ setSortOrder(newSortOrder);
31
+ dispatch({
32
+ type: "set.varSort",
33
+ var: varType,
34
+ sort: sortValue,
35
+ sortOrder: newSortOrder
36
+ });
37
+ }
38
+ };
39
+ return /*#__PURE__*/React.createElement(Navbar, {
40
+ className: "var-list-toolbar"
41
+ }, /*#__PURE__*/React.createElement(InputGroup, null, /*#__PURE__*/React.createElement(InputGroup.Text, null, "Sort by:"), /*#__PURE__*/React.createElement(ToggleButtonGroup, {
42
+ "aria-label": "Sort feature by",
43
+ size: "small",
44
+ className: "mh-100"
45
+ }, /*#__PURE__*/React.createElement(ToggleButton, {
46
+ value: VAR_SORT.NAME,
47
+ "aria-label": "alphabetical",
48
+ title: "Sort alphabetically",
49
+ selected: sort === VAR_SORT.NAME,
50
+ onChange: () => {
51
+ handleSort(VAR_SORT.NAME, nameSortOrder, setNameSortOrder);
52
+ }
53
+ }, nameSortOrder === VAR_SORT_ORDER.ASC ? /*#__PURE__*/React.createElement(FontAwesomeIcon, {
54
+ icon: faArrowDownAZ
55
+ }) : /*#__PURE__*/React.createElement(FontAwesomeIcon, {
56
+ icon: faArrowUpZA
57
+ })), /*#__PURE__*/React.createElement(ToggleButton, {
58
+ value: VAR_SORT.MATRIX,
59
+ "aria-label": "matrix value",
60
+ title: "Sort by matrix value",
61
+ selected: sort === VAR_SORT.MATRIX,
62
+ onChange: () => {
63
+ handleSort(VAR_SORT.MATRIX, matrixSortOrder, setMatrixSortOrder);
64
+ }
65
+ }, matrixSortOrder === VAR_SORT_ORDER.ASC ? /*#__PURE__*/React.createElement(FontAwesomeIcon, {
66
+ icon: faArrowDown19
67
+ }) : /*#__PURE__*/React.createElement(FontAwesomeIcon, {
68
+ icon: faArrowUp91
69
+ })), /*#__PURE__*/React.createElement(ToggleButton, {
70
+ value: "none",
71
+ "aria-label": "none",
72
+ title: "No sorting",
73
+ onClick: () => {
74
+ setSort(VAR_SORT.NONE);
75
+ dispatch({
76
+ type: "set.varSort.sort",
77
+ var: varType,
78
+ sort: VAR_SORT.NONE
79
+ });
80
+ }
81
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
82
+ icon: faXmark
83
+ })))));
84
+ }
@@ -0,0 +1,193 @@
1
+ import React, { useState } from "react";
2
+ import { faDroplet, faTrash, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import { List } from "@mui/icons-material";
5
+ import _ from "lodash";
6
+ import { ListGroup, Button, Collapse, OverlayTrigger, Tooltip } from "react-bootstrap";
7
+ import { SingleSelectionItem } from "./VarItem";
8
+ import { COLOR_ENCODINGS, SELECTION_MODES } from "../../constants/constants";
9
+ import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
10
+ import { SearchBar } from "../search-bar/SearchBar";
11
+
12
+ // @TODO: add button to score genes and plot
13
+
14
+ const addVarToSet = (dispatch, set, v) => {
15
+ dispatch({
16
+ type: "add.varSet.var",
17
+ varSet: set,
18
+ var: v
19
+ });
20
+ };
21
+ function SingleSelectionSet(_ref) {
22
+ let {
23
+ set,
24
+ isActive,
25
+ selectSet,
26
+ removeSet,
27
+ removeSetVar,
28
+ showSearchBar = true
29
+ } = _ref;
30
+ const [openSet, setOpenSet] = useState(false);
31
+ const varList = set.vars.length ? _.map(set.vars, v => {
32
+ return /*#__PURE__*/React.createElement(ListGroup.Item, {
33
+ key: v.name
34
+ }, /*#__PURE__*/React.createElement(SingleSelectionItem, {
35
+ item: v,
36
+ showSetColorEncoding: false,
37
+ removeVar: () => removeSetVar(v)
38
+ }));
39
+ }) : /*#__PURE__*/React.createElement(ListGroup.Item, null, /*#__PURE__*/React.createElement("div", {
40
+ className: "text-muted"
41
+ }, "No features in this set"));
42
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
43
+ className: "d-flex justify-content-between cursor-pointer",
44
+ onClick: () => {
45
+ setOpenSet(o => !o);
46
+ }
47
+ }, /*#__PURE__*/React.createElement("div", {
48
+ className: "d-flex justify-content-between align-items-center w-100"
49
+ }, /*#__PURE__*/React.createElement("div", null, set.name), /*#__PURE__*/React.createElement("div", {
50
+ className: "d-flex align-items-center gap-1"
51
+ }, /*#__PURE__*/React.createElement(OverlayTrigger, {
52
+ placement: "top",
53
+ overlay: /*#__PURE__*/React.createElement(Tooltip, null, "This set represents the mean value of its features")
54
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
55
+ icon: faCircleInfo
56
+ })), /*#__PURE__*/React.createElement(List, null), /*#__PURE__*/React.createElement(Button, {
57
+ type: "button",
58
+ key: set.name,
59
+ variant: isActive ? "primary" : "outline-primary",
60
+ className: "m-0 p-0 px-1",
61
+ onClick: e => {
62
+ e.stopPropagation();
63
+ selectSet();
64
+ },
65
+ disabled: !set.vars.length,
66
+ title: "Set as color encoding"
67
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
68
+ icon: faDroplet
69
+ })), /*#__PURE__*/React.createElement(Button, {
70
+ type: "button",
71
+ className: "m-0 p-0 px-1",
72
+ variant: "outline-secondary",
73
+ title: "Remove from list",
74
+ onClick: e => {
75
+ e.stopPropagation();
76
+ removeSet();
77
+ }
78
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
79
+ icon: faTrash
80
+ }))))), /*#__PURE__*/React.createElement(Collapse, {
81
+ in: openSet
82
+ }, /*#__PURE__*/React.createElement("div", {
83
+ className: "mt-2"
84
+ }, showSearchBar &&
85
+ /*#__PURE__*/
86
+ // @TODO: fix how results are displayed, should be placed on top of parent components
87
+ React.createElement(SearchBar, {
88
+ handleSelect: (d, i) => addVarToSet(d, set, i)
89
+ }), /*#__PURE__*/React.createElement("div", {
90
+ className: "mx-2"
91
+ }, /*#__PURE__*/React.createElement(ListGroup, {
92
+ variant: "flush"
93
+ }, varList)))));
94
+ }
95
+ function MultipleSelectionSet(_ref2) {
96
+ let {
97
+ set,
98
+ isActive,
99
+ toggleSet
100
+ } = _ref2;
101
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
102
+ className: "d-flex"
103
+ }, /*#__PURE__*/React.createElement("div", {
104
+ className: "flex-grow-1"
105
+ }, /*#__PURE__*/React.createElement(Button, {
106
+ type: "button",
107
+ key: set.name,
108
+ variant: isActive ? "primary" : "outline-primary",
109
+ className: "m-0 p-0 px-1",
110
+ onClick: toggleSet,
111
+ title: set.name
112
+ }, set.name))));
113
+ }
114
+ export function VarSet(_ref3) {
115
+ let {
116
+ set,
117
+ active,
118
+ mode = SELECTION_MODES.SINGLE
119
+ } = _ref3;
120
+ const dataset = useDataset();
121
+ const dispatch = useDatasetDispatch();
122
+ const selectSet = () => {
123
+ if (mode === SELECTION_MODES.SINGLE) {
124
+ dispatch({
125
+ type: "select.var",
126
+ var: set
127
+ });
128
+ dispatch({
129
+ type: "set.colorEncoding",
130
+ value: "var"
131
+ });
132
+ } else if (mode === SELECTION_MODES.MULTIPLE) {
133
+ dispatch({
134
+ type: "select.multivar",
135
+ var: set
136
+ });
137
+ }
138
+ };
139
+ const removeSet = () => {
140
+ if (mode === SELECTION_MODES.SINGLE) {
141
+ if (active === set.name) {
142
+ dispatch({
143
+ type: "reset.var"
144
+ });
145
+ }
146
+ } else if (mode === SELECTION_MODES.MULTIPLE) {
147
+ if (active.includes(set.name)) {
148
+ dispatch({
149
+ type: "deselect.multivar",
150
+ var: set
151
+ });
152
+ }
153
+ }
154
+ dispatch({
155
+ type: "remove.varSet",
156
+ varSet: set
157
+ });
158
+ };
159
+ const removeSetVar = v => {
160
+ dispatch({
161
+ type: "remove.varSet.var",
162
+ varSet: set,
163
+ var: v
164
+ });
165
+ };
166
+ const toggleSet = () => {
167
+ if (active.includes(set.name)) {
168
+ dispatch({
169
+ type: "deselect.multivar",
170
+ var: set
171
+ });
172
+ } else {
173
+ selectSet();
174
+ }
175
+ };
176
+ if (set && mode === SELECTION_MODES.SINGLE) {
177
+ return /*#__PURE__*/React.createElement(SingleSelectionSet, {
178
+ set: set,
179
+ isActive: dataset.colorEncoding === COLOR_ENCODINGS.VAR && active === set.name,
180
+ selectSet: selectSet,
181
+ removeSet: removeSet,
182
+ removeSetVar: v => removeSetVar(v)
183
+ });
184
+ } else if (mode === SELECTION_MODES.MULTIPLE) {
185
+ return /*#__PURE__*/React.createElement(MultipleSelectionSet, {
186
+ set: set,
187
+ isActive: _.includes(active, set.name),
188
+ toggleSet: () => toggleSet(set)
189
+ });
190
+ } else {
191
+ return null;
192
+ }
193
+ }
@@ -0,0 +1,141 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import _ from "lodash";
5
+ import { Alert, OverlayTrigger, Tooltip } from "react-bootstrap";
6
+ import Plot from "react-plotly.js";
7
+ import { VIOLIN_MODES } from "../../constants/constants";
8
+ import { useDataset } from "../../context/DatasetContext";
9
+ import { useFilteredData } from "../../context/FilterContext";
10
+ import { LoadingSpinner } from "../../utils/LoadingIndicators";
11
+ import { useDebouncedFetch } from "../../utils/requests";
12
+ export function Violin(_ref) {
13
+ let {
14
+ mode = VIOLIN_MODES.MULTIKEY
15
+ } = _ref;
16
+ const ENDPOINT = "violin";
17
+ const dataset = useDataset();
18
+ const {
19
+ obsIndices,
20
+ isSliced
21
+ } = useFilteredData();
22
+ const [data, setData] = useState([]);
23
+ const [layout, setLayout] = useState({});
24
+ const [hasSelections, setHasSelections] = useState(false);
25
+ const [params, setParams] = useState({
26
+ url: dataset.url,
27
+ mode: mode,
28
+ scale: dataset.controls.scale.violinplot.value,
29
+ varNamesCol: dataset.varNamesCol,
30
+ ...{
31
+ [VIOLIN_MODES.MULTIKEY]: {
32
+ varKeys: dataset.selectedMultiVar.map(i => i.isSet ? {
33
+ name: i.name,
34
+ indices: i.vars.map(v => v.index)
35
+ } : i.index),
36
+ obsKeys: [] // @TODO: implement
37
+ },
38
+ [VIOLIN_MODES.GROUPBY]: {
39
+ varKey: dataset.selectedVar?.isSet ? {
40
+ name: dataset.selectedVar?.name,
41
+ indices: dataset.selectedVar?.vars.map(v => v.index)
42
+ } : dataset.selectedVar?.index,
43
+ obsCol: dataset.selectedObs,
44
+ obsValues: !dataset.selectedObs?.omit.length ? null : _.difference(_.values(dataset.selectedObs?.codes), dataset.selectedObs?.omit).map(c => dataset.selectedObs?.codesMap[c]),
45
+ obsIndices: isSliced ? [...(obsIndices || [])] : null
46
+ }
47
+ }[mode]
48
+ });
49
+ // @TODO: set default scale
50
+
51
+ useEffect(() => {
52
+ if (mode === VIOLIN_MODES.MULTIKEY) {
53
+ if (dataset.selectedMultiVar.length) {
54
+ setHasSelections(true);
55
+ } else {
56
+ setHasSelections(false);
57
+ }
58
+ setParams(p => {
59
+ return {
60
+ ...p,
61
+ url: dataset.url,
62
+ mode: mode,
63
+ varKeys: dataset.selectedMultiVar.map(i => i.isSet ? {
64
+ name: i.name,
65
+ indices: i.vars.map(v => v.index)
66
+ } : i.index),
67
+ scale: dataset.controls.scale.violinplot.value,
68
+ varNamesCol: dataset.varNamesCol
69
+ };
70
+ });
71
+ } else if (mode === VIOLIN_MODES.GROUPBY) {
72
+ if (dataset.selectedObs && dataset.selectedVar) {
73
+ setHasSelections(true);
74
+ } else {
75
+ setHasSelections(false);
76
+ }
77
+ setParams(p => {
78
+ return {
79
+ ...p,
80
+ url: dataset.url,
81
+ mode: mode,
82
+ varKey: dataset.selectedVar?.isSet ? {
83
+ name: dataset.selectedVar?.name,
84
+ indices: dataset.selectedVar?.vars.map(v => v.index)
85
+ } : dataset.selectedVar?.index,
86
+ obsCol: dataset.selectedObs,
87
+ obsValues: !dataset.selectedObs?.omit.length ? null : _.difference(_.values(dataset.selectedObs?.codes), dataset.selectedObs?.omit).map(c => dataset.selectedObs?.codesMap[c]),
88
+ obsIndices: isSliced ? [...(obsIndices || [])] : null,
89
+ scale: dataset.controls.scale.violinplot.value,
90
+ varNamesCol: dataset.varNamesCol
91
+ };
92
+ });
93
+ }
94
+ }, [dataset.controls.scale.violinplot.value, dataset.selectedMultiVar, dataset.selectedObs, dataset.selectedVar, dataset.url, dataset.varNamesCol, obsIndices, isSliced, mode]);
95
+ const {
96
+ fetchedData,
97
+ isPending,
98
+ serverError
99
+ } = useDebouncedFetch(ENDPOINT, params, 500, {
100
+ enabled: mode === VIOLIN_MODES.MULTIKEY && (!!params.varKeys.length || !!params.obsKeys.length) || mode === VIOLIN_MODES.GROUPBY && !!params.varKey && !!params.obsCol
101
+ });
102
+ useEffect(() => {
103
+ if (hasSelections && !isPending && !serverError) {
104
+ setData(fetchedData.data);
105
+ setLayout(fetchedData.layout);
106
+ }
107
+ }, [fetchedData, hasSelections, isPending, serverError]);
108
+ if (!serverError) {
109
+ if (hasSelections) {
110
+ return /*#__PURE__*/React.createElement("div", {
111
+ className: "cherita-violin position-relative"
112
+ }, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(Plot, {
113
+ data: data,
114
+ layout: layout,
115
+ useResizeHandler: true,
116
+ style: {
117
+ maxWidth: "100%",
118
+ maxHeight: "100%"
119
+ }
120
+ }), fetchedData?.resampled && /*#__PURE__*/React.createElement(Alert, {
121
+ variant: "warning"
122
+ }, /*#__PURE__*/React.createElement("b", null, "Warning:"), " For performance reasons this plot was generated with resampled data. It will not be exactly the same as one produced with the entire dataset. \xA0", /*#__PURE__*/React.createElement(OverlayTrigger, {
123
+ placement: "top",
124
+ overlay: /*#__PURE__*/React.createElement(Tooltip, null, "Resampled to 100K values following a Monte Carlo style approach to help ensure resampled data is a good representation of the original dataset's distribution.")
125
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
126
+ icon: faCircleInfo
127
+ }))));
128
+ }
129
+ return /*#__PURE__*/React.createElement("div", {
130
+ className: "cherita-violin"
131
+ }, mode === VIOLIN_MODES.MULTIKEY && /*#__PURE__*/React.createElement(Alert, {
132
+ variant: "light"
133
+ }, "Select features"), mode === VIOLIN_MODES.GROUPBY && /*#__PURE__*/React.createElement(Alert, {
134
+ variant: "light"
135
+ }, "Select categories and a feature"));
136
+ } else {
137
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Alert, {
138
+ variant: "danger"
139
+ }, serverError.message));
140
+ }
141
+ }