@haniffalab/cherita-react 1.1.1 → 1.2.0-dev.2025-04-09.382ade7b

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.
@@ -9,14 +9,11 @@ import { Alert } from "react-bootstrap";
9
9
  import Accordion from "react-bootstrap/Accordion";
10
10
  import { useAccordionButton } from "react-bootstrap/AccordionButton";
11
11
  import AccordionContext from "react-bootstrap/AccordionContext";
12
+ import { CategoricalObs, ContinuousObs } from "./ObsItem";
12
13
  import { COLOR_ENCODINGS, DEFAULT_OBS_GROUP, OBS_TYPES } from "../../constants/constants";
13
14
  import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
14
15
  import { LoadingSpinner } from "../../utils/LoadingIndicators";
15
16
  import { useFetch } from "../../utils/requests";
16
- import { CategoricalObs, ContinuousObs } from "./ObsItem";
17
-
18
- // @TODO: integrate ObsAccordionToggle with ObsColsList active, expandedItems, etc. to avoid duplication
19
-
20
17
  const ObsAccordionToggle = _ref => {
21
18
  let {
22
19
  children,
@@ -26,10 +23,10 @@ const ObsAccordionToggle = _ref => {
26
23
  const {
27
24
  activeEventKey
28
25
  } = useContext(AccordionContext);
26
+ const isCurrentEventKey = (activeEventKey || []).includes(eventKey);
29
27
  const decoratedOnClick = useAccordionButton(eventKey, () => {
30
- handleAccordionToggle(eventKey);
28
+ handleAccordionToggle(eventKey, isCurrentEventKey);
31
29
  });
32
- const isCurrentEventKey = Array.isArray(activeEventKey) ? activeEventKey.includes(eventKey) : activeEventKey === eventKey;
33
30
  return /*#__PURE__*/React.createElement("div", {
34
31
  className: `obs-accordion-header ${isCurrentEventKey ? "active" : ""}`,
35
32
  onClick: decoratedOnClick
@@ -51,8 +48,7 @@ export function ObsColsList(_ref2) {
51
48
  const dispatch = useDatasetDispatch();
52
49
  const [enableGroups, setEnableGroups] = useState(enableObsGroups);
53
50
  const [obsCols, setObsCols] = useState(null);
54
- const [active, setActive] = useState(dataset.selectedObs?.name);
55
- const [expandedItems, setExpandedItems] = useState({});
51
+ const [active, setActive] = useState([...[dataset.selectedObs?.name]]);
56
52
  const [params, setParams] = useState({
57
53
  url: dataset.url
58
54
  });
@@ -105,26 +101,10 @@ export function ObsColsList(_ref2) {
105
101
  }), "name"));
106
102
  }
107
103
  }, [fetchedData, isPending, obsGroups, serverError, enableGroups]);
108
- useEffect(() => {
109
- if (obsCols && Object.keys(expandedItems).length === 0) {
110
- const initialExpanded = Object.keys(obsCols).reduce((acc, key) => {
111
- acc[key] = false;
112
- return acc;
113
- }, {});
114
- if (active && obsCols[active]) {
115
- initialExpanded[active] = true;
116
- }
117
- setExpandedItems(initialExpanded);
118
- }
119
- }, [obsCols, expandedItems, active]);
120
-
121
- // @TODO: fix re-rendering performance issue
122
104
  useEffect(() => {
123
105
  if (obsCols) {
124
- if (obsCols[dataset.selectedObs?.name]) {
125
- setActive(dataset.selectedObs?.name);
126
- } else {
127
- setActive(null);
106
+ if (!obsCols[dataset.selectedObs?.name]) {
107
+ setActive([]);
128
108
  dispatch({
129
109
  type: "select.obs",
130
110
  obs: null
@@ -132,25 +112,12 @@ export function ObsColsList(_ref2) {
132
112
  }
133
113
  }
134
114
  }, [dataset.selectedObs, dispatch, obsCols]);
135
- const updateObs = updatedObs => {
136
- setObsCols(o => {
137
- return {
138
- ...o,
139
- [updatedObs.name]: updatedObs
140
- };
141
- });
142
- };
143
- const handleAccordionToggle = itemName => {
144
- _.delay(
145
- // to avoid contents of accordion disappearing while closing
146
- () => {
147
- setExpandedItems(prev => {
148
- return {
149
- ...prev,
150
- [itemName]: !prev[itemName]
151
- };
152
- });
153
- }, expandedItems[itemName] ? 250 : 0);
115
+ const handleAccordionToggle = (itemName, isCurrentEventKey) => {
116
+ if (isCurrentEventKey) {
117
+ _.delay(() => setActive(prev => _.without(prev, itemName)), 250);
118
+ } else {
119
+ setActive(prev => [...prev, itemName]);
120
+ }
154
121
  };
155
122
  const toggleAll = item => {
156
123
  const omit = item.omit.length ? [] : _.map(item.values, v => item.codes[v]);
@@ -163,7 +130,7 @@ export function ObsColsList(_ref2) {
163
130
  }
164
131
  };
165
132
  });
166
- if (active === item.name) {
133
+ if (dataset.selectedObs?.name === item.name) {
167
134
  dispatch({
168
135
  type: "select.obs",
169
136
  obs: {
@@ -223,7 +190,7 @@ export function ObsColsList(_ref2) {
223
190
  }
224
191
  };
225
192
  });
226
- if (active === item.name) {
193
+ if (dataset.selectedObs?.name === item.name) {
227
194
  dispatch({
228
195
  type: "select.obs",
229
196
  obs: {
@@ -274,20 +241,18 @@ export function ObsColsList(_ref2) {
274
241
  eventKey: item.name
275
242
  }, /*#__PURE__*/React.createElement("div", {
276
243
  className: "obs-accordion-body"
277
- }, expandedItems[item.name] && (item.type === OBS_TYPES.CATEGORICAL || item.type === OBS_TYPES.BOOLEAN ? /*#__PURE__*/React.createElement(CategoricalObs, {
244
+ }, active.includes(item.name) && (item.type === OBS_TYPES.CATEGORICAL || item.type === OBS_TYPES.BOOLEAN ? /*#__PURE__*/React.createElement(CategoricalObs, {
278
245
  key: item.name,
279
246
  obs: item,
280
- updateObs: updateObs,
281
247
  toggleAll: () => toggleAll(item),
282
248
  toggleObs: value => toggleObs(item, value),
283
249
  toggleLabel: () => toggleLabel(item),
284
250
  toggleSlice: () => toggleSlice(item),
285
251
  toggleColor: () => toggleColor(item),
286
- showColor: showColor
252
+ showColor: showColor && isColorEncoding
287
253
  }) : /*#__PURE__*/React.createElement(ContinuousObs, {
288
254
  key: item.name,
289
255
  obs: item,
290
- updateObs: updateObs,
291
256
  toggleAll: () => toggleAll(item),
292
257
  toggleObs: value => toggleObs(item, value),
293
258
  toggleLabel: () => toggleLabel(item),
@@ -314,11 +279,8 @@ export function ObsColsList(_ref2) {
314
279
  }, groupItems));
315
280
  }
316
281
  });
317
- const obsList = enableGroups ? /*#__PURE__*/React.createElement(Accordion, {
318
- className: "obs-group-accordion",
319
- flush: true,
320
- alwaysOpen: true
321
- }, groupList) : _.map(_.sortBy(obsCols, o => _.lowerCase(o.name)), item => obsItem(item));
282
+ const obsList = enableGroups ? /*#__PURE__*/React.createElement(React.Fragment, null, groupList) : _.map(_.sortBy(obsCols, o => _.lowerCase(o.name)), item => obsItem(item));
283
+ const defaultActiveGroup = enableGroups ? `group-${_.findKey(obsGroups, g => g.includes(active?.[0]))}` : null;
322
284
  if (!serverError) {
323
285
  return /*#__PURE__*/React.createElement("div", {
324
286
  className: "position-relative h-100"
@@ -326,7 +288,7 @@ export function ObsColsList(_ref2) {
326
288
  variant: "danger"
327
289
  }, "No observations found.") : /*#__PURE__*/React.createElement(Accordion, {
328
290
  flush: true,
329
- defaultActiveKey: [active],
291
+ defaultActiveKey: [...active, ...[defaultActiveGroup]],
330
292
  alwaysOpen: true,
331
293
  className: "obs-accordion"
332
294
  }, obsList));
@@ -51,11 +51,11 @@ function VarHistogram(_ref) {
51
51
  function VarDiseaseInfoItem(item) {
52
52
  const dispatch = useDatasetDispatch();
53
53
  return /*#__PURE__*/React.createElement(ListGroup.Item, {
54
- key: item.disease_name,
54
+ key: item.disease_id,
55
55
  className: "feature-disease-info"
56
- }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
56
+ }, /*#__PURE__*/React.createElement("button", {
57
57
  type: "button",
58
- className: "btn btn-link",
58
+ className: "btn btn-link disease-link",
59
59
  onClick: () => {
60
60
  dispatch({
61
61
  type: "select.disease",
@@ -63,25 +63,29 @@ function VarDiseaseInfoItem(item) {
63
63
  name: item.disease_name
64
64
  });
65
65
  }
66
- }, item.disease_name), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(Table, {
67
- striped: true
66
+ }, item.disease_name), /*#__PURE__*/React.createElement(Table, {
67
+ striped: true,
68
+ size: "sm",
69
+ responsive: true
68
70
  }, /*#__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
71
  if (value !== null && value !== undefined) {
70
- return /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, key), /*#__PURE__*/React.createElement("td", null, value));
72
+ return /*#__PURE__*/React.createElement("tr", {
73
+ key: key
74
+ }, /*#__PURE__*/React.createElement("td", null, _.upperFirst(key)), /*#__PURE__*/React.createElement("td", null, value));
71
75
  }
72
- })))));
76
+ }))));
73
77
  }
74
78
  function VarDiseaseInfo(_ref2) {
75
79
  let {
76
80
  data
77
81
  } = _ref2;
78
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ListGroup, null, /*#__PURE__*/React.createElement(VirtualizedList, {
82
+ return /*#__PURE__*/React.createElement(VirtualizedList, {
79
83
  getDataAtIndex: index => data[index],
80
84
  count: data.length,
81
85
  estimateSize: 140,
82
- maxHeight: "100%",
86
+ maxHeight: "40vh",
83
87
  ItemComponent: VarDiseaseInfoItem
84
- })));
88
+ });
85
89
  }
86
90
  export function SingleSelectionItem(_ref3) {
87
91
  let {
@@ -8,7 +8,7 @@ export function VirtualizedList(_ref) {
8
8
  ItemComponent,
9
9
  estimateSize = 45,
10
10
  overscan = 25,
11
- maxHeight = "80vh",
11
+ maxHeight = "65vh",
12
12
  ...props
13
13
  } = _ref;
14
14
  const [parentNode, setParentNode] = useState(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haniffalab/cherita-react",
3
- "version": "1.1.1",
3
+ "version": "1.2.0-dev.2025-04-09.382ade7b",
4
4
  "author": "Haniffa Lab",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -123,5 +123,6 @@
123
123
  "bugs": {
124
124
  "url": "https://github.com/haniffalab/cherita-react/issues"
125
125
  },
126
- "homepage": "https://github.com/haniffalab/cherita-react#readme"
127
- }
126
+ "homepage": "https://github.com/haniffalab/cherita-react#readme",
127
+ "prereleaseSha": "382ade7b92504de6a200b2f68f7942912a0271b8"
128
+ }
package/scss/cherita.scss CHANGED
@@ -249,15 +249,22 @@ $gauge-padding: 0.15rem;
249
249
  }
250
250
 
251
251
  .feature-disease-info {
252
- font-weight: lighter;
252
+ font-weight: 300;
253
+ padding-left: 0 !important;
253
254
  border-top: 1px solid var(list-group-border-color);
254
255
  border-right: 0;
255
256
  border-left: 0;
256
257
  border-bottom: 0;
257
258
  }
258
259
 
259
- .feature-disease-info table {
260
- margin: 0;
260
+ .disease-link {
261
+ padding: 0;
262
+ white-space: normal;
263
+ text-align: left;
264
+ width: 100%;
265
+ position: sticky;
266
+ top: 0;
267
+ background-color: var(--#{$prefix}body-bg) !important;
261
268
  }
262
269
 
263
270
  .cherita-app-plot {
@@ -316,11 +323,6 @@ $gauge-padding: 0.15rem;
316
323
  border-bottom: 1px solid #dee2e6; /* Adjust the color and width as needed */
317
324
  }
318
325
 
319
- .var-disease-info-collapse {
320
- max-height: 40vh;
321
- overflow-y: auto;
322
- }
323
-
324
326
  .cherita-scatterplot #deckgl-wrapper {
325
327
  z-index: 1 !important;
326
328
  }
@@ -30,3 +30,8 @@
30
30
  color: #0c63e4;
31
31
  }
32
32
  }
33
+
34
+ .obs-group-accordion-body {
35
+ @extend .accordion-flush;
36
+ padding: 0;
37
+ }
@@ -1,14 +1,26 @@
1
- .list-group-flush.cherita-list {
2
- .list-group-item.cherita-list-item-unstyled {
1
+ .list-group.cherita-list {
2
+ .virtualized-list-wrapper {
3
+ padding: 0 0.25rem 0.25rem 0.25rem;
4
+ }
5
+ .list-group-item.unstyled {
3
6
  background-color: transparent;
7
+ padding-left: 1rem;
4
8
  }
5
9
  .list-group-item {
6
10
  border: 0;
7
11
  padding: 0.375rem 0.75rem;
8
- margin-bottom: 0.215rem;
9
12
  line-height: 1.5;
10
13
  color: var(--#{$prefix}body-color);
11
14
  background-color: var(--#{$prefix}tertiary-bg);
12
15
  border-radius: var(--#{$prefix}border-radius);
13
16
  }
14
17
  }
18
+ .obs-statistics {
19
+ border: 0;
20
+ margin: 0 0.25rem 0.25rem 0.25rem;
21
+ padding: 0.375rem 0.75rem;
22
+ line-height: 1.5;
23
+ color: var(--#{$prefix}body-color);
24
+ background-color: var(--#{$prefix}tertiary-bg);
25
+ border-radius: var(--#{$prefix}border-radius);
26
+ }