@haniffalab/cherita-react 0.2.1 → 1.0.0-dev.2025-03-07.d348216e

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  [![build](https://github.com/haniffalab/cherita-react/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/haniffalab/cherita-react/actions/workflows/npm-publish.yml)
2
2
  [![build-dev](https://github.com/haniffalab/cherita-react/actions/workflows/npm-publish-dev.yml/badge.svg)](https://github.com/haniffalab/cherita-react/actions/workflows/npm-publish-dev.yml)
3
3
  [![npm](https://img.shields.io/npm/v/@haniffalab/cherita-react)](https://www.npmjs.com/package/@haniffalab/cherita-react)
4
+ [![tests](https://github.com/haniffalab/cherita-react/actions/workflows/tests.yml/badge.svg)](https://github.com/haniffalab/cherita-react/actions/workflows/tests.yml)
4
5
  [![codecov](https://codecov.io/gh/haniffalab/cherita-react/graph/badge.svg?token=8RLSQP1FFB)](https://codecov.io/gh/haniffalab/cherita-react)
5
6
 
6
- # Cherita
7
+ # Cherita React
8
+
9
+ [![demo](https://img.shields.io/badge/Demo-view-blue)](https://default-dot-haniffa-lab.nw.r.appspot.com/)
10
+ [![doi](https://zenodo.org/badge/DOI/10.5281/zenodo.14244809.svg)](https://doi.org/10.5281/zenodo.14244809)
11
+
12
+ React component library designed for data visualisation and analysis of single-cell and spatial multi-omics data. This library provides a set of reusable and customisable components that can be used to used to build study narratives.
7
13
 
8
14
  ## Development
9
15
 
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.FullPage = FullPage;
7
7
  exports.FullPagePlots = FullPagePlots;
8
- exports.FullPagePseudospatial = FullPagePseudospatial;
9
8
  exports.FullPageScatterplot = FullPageScatterplot;
10
9
  var _react = _interopRequireWildcard(require("react"));
11
10
  var _reactBootstrap = require("react-bootstrap");
@@ -16,7 +15,6 @@ var _Heatmap = require("../heatmap/Heatmap");
16
15
  var _Matrixplot = require("../matrixplot/Matrixplot");
17
16
  var _ObsList = require("../obs-list/ObsList");
18
17
  var _offcanvas = require("../offcanvas");
19
- var _Pseudospatial = require("../pseudospatial/Pseudospatial");
20
18
  var _Scatterplot = require("../scatterplot/Scatterplot");
21
19
  var _ScatterplotControls = require("../scatterplot/ScatterplotControls");
22
20
  var _SearchBar = require("../search-bar/SearchBar");
@@ -30,46 +28,46 @@ function FullPage({
30
28
  varMode = _constants.SELECTION_MODES.SINGLE,
31
29
  ...props
32
30
  }) {
33
- const targetRef = (0, _react.useRef)();
31
+ const appRef = (0, _react.useRef)();
32
+ const [appDimensions, setAppDimensions] = (0, _react.useState)({
33
+ width: 0,
34
+ height: 0
35
+ });
34
36
  const [showObs, setShowObs] = (0, _react.useState)(false);
35
37
  const [showObsm, setShowObsm] = (0, _react.useState)(false);
36
38
  const [showVars, setShowVars] = (0, _react.useState)(false);
37
39
  const [showControls, setShowControls] = (0, _react.useState)(false);
38
- const [dimensions, setDimensions] = (0, _react.useState)({
39
- width: 0,
40
- height: 0
41
- });
42
- (0, _react.useLayoutEffect)(() => {
43
- function updateDimensions() {
44
- if (targetRef.current) {
40
+ (0, _react.useEffect)(() => {
41
+ const updateDimensions = () => {
42
+ if (appRef.current) {
45
43
  // Get the distance from the top of the page to the target element
46
- const rect = targetRef.current.getBoundingClientRect();
44
+ const rect = appRef.current.getBoundingClientRect();
47
45
  const distanceFromTop = rect.top + window.scrollY;
48
46
 
49
47
  // Calculate the available height for the Cherita app
50
48
  const availableHeight = window.innerHeight - distanceFromTop;
51
49
 
52
50
  // Update the dimensions to fit the viewport minus the navbar height
53
- setDimensions({
54
- width: targetRef.current.offsetWidth,
51
+ setAppDimensions({
52
+ width: appRef.current.offsetWidth,
55
53
  height: availableHeight
56
54
  });
57
55
  }
58
- }
56
+ };
59
57
  window.addEventListener("resize", updateDimensions);
60
58
  updateDimensions(); // Initial update
61
59
  return () => window.removeEventListener("resize", updateDimensions);
62
60
  }, []);
63
61
  return /*#__PURE__*/_react.default.createElement("div", {
64
- ref: targetRef,
62
+ ref: appRef,
65
63
  className: "cherita-app",
66
64
  style: {
67
- height: dimensions.height
65
+ height: appDimensions.height
68
66
  }
69
67
  }, /*#__PURE__*/_react.default.createElement(_DatasetContext.DatasetProvider, props, /*#__PURE__*/_react.default.createElement("div", {
70
68
  className: "row g-0"
71
69
  }, /*#__PURE__*/_react.default.createElement("div", {
72
- className: "cherita-app-obs"
70
+ className: "cherita-app-obs modern-scrollbars border-end h-100"
73
71
  }, /*#__PURE__*/_react.default.createElement(_ObsList.ObsColsList, null)), /*#__PURE__*/_react.default.createElement("div", {
74
72
  className: "cherita-app-plot"
75
73
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -126,27 +124,6 @@ function FullPage({
126
124
  function FullPageScatterplot(props) {
127
125
  return /*#__PURE__*/_react.default.createElement(FullPage, props, /*#__PURE__*/_react.default.createElement(_Scatterplot.Scatterplot, null));
128
126
  }
129
- function FullPagePseudospatial(props) {
130
- return /*#__PURE__*/_react.default.createElement(FullPage, props, /*#__PURE__*/_react.default.createElement("div", {
131
- className: "container-fluid h-100"
132
- }, /*#__PURE__*/_react.default.createElement("div", {
133
- className: "row"
134
- }, /*#__PURE__*/_react.default.createElement("div", {
135
- className: "col-12 col-lg-7"
136
- }, /*#__PURE__*/_react.default.createElement(_Scatterplot.Scatterplot, null)), /*#__PURE__*/_react.default.createElement("div", {
137
- className: "col-12 col-lg-5"
138
- }, /*#__PURE__*/_react.default.createElement("div", {
139
- className: "container-fluid h-100 d-flex align-itemms-center justify-content-center"
140
- }, /*#__PURE__*/_react.default.createElement("div", {
141
- className: "row w-100 py-3"
142
- }, /*#__PURE__*/_react.default.createElement("div", {
143
- className: "col-12"
144
- }, /*#__PURE__*/_react.default.createElement("div", {
145
- className: "p-2"
146
- }, /*#__PURE__*/_react.default.createElement(_Pseudospatial.Pseudospatial, null))), /*#__PURE__*/_react.default.createElement("div", {
147
- className: "col-12"
148
- }, /*#__PURE__*/_react.default.createElement(_Pseudospatial.PseudospatialImage, null))))))));
149
- }
150
127
  function FullPagePlots(props) {
151
128
  return /*#__PURE__*/_react.default.createElement(FullPage, _extends({}, props, {
152
129
  varMode: _constants.SELECTION_MODES.MULTIPLE
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FullPage = FullPage;
7
+ exports.FullPagePseudospatial = FullPagePseudospatial;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _freeSolidSvgIcons = require("@fortawesome/free-solid-svg-icons");
10
+ var _reactFontawesome = require("@fortawesome/react-fontawesome");
11
+ var _reactBootstrap = require("react-bootstrap");
12
+ var _constants = require("../../constants/constants");
13
+ var _DatasetContext = require("../../context/DatasetContext");
14
+ var _ObsList = require("../obs-list/ObsList");
15
+ var _offcanvas = require("../offcanvas");
16
+ var _Pseudospatial = require("../pseudospatial/Pseudospatial");
17
+ var _PseudospatialToolbar = require("../pseudospatial/PseudospatialToolbar");
18
+ var _Scatterplot = require("../scatterplot/Scatterplot");
19
+ var _ScatterplotControls = require("../scatterplot/ScatterplotControls");
20
+ var _SearchBar = require("../search-bar/SearchBar");
21
+ var _VarList = require("../var-list/VarList");
22
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
23
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
24
+ function FullPage({
25
+ children,
26
+ varMode = _constants.SELECTION_MODES.SINGLE,
27
+ ...props
28
+ }) {
29
+ const appRef = (0, _react.useRef)();
30
+ const [appDimensions, setAppDimensions] = (0, _react.useState)({
31
+ width: 0,
32
+ height: 0
33
+ });
34
+ const [showObs, setShowObs] = (0, _react.useState)(false);
35
+ const [showObsm, setShowObsm] = (0, _react.useState)(false);
36
+ const [showVars, setShowVars] = (0, _react.useState)(false);
37
+ const [showControls, setShowControls] = (0, _react.useState)(false);
38
+ const [showPseudospatialControls, setShowPseudospatialControls] = (0, _react.useState)(false);
39
+ const [showModal, setShowModal] = (0, _react.useState)(false);
40
+ const [pseudospatialPlotType, setpseudospatialPlotType] = (0, _react.useState)(null);
41
+ (0, _react.useEffect)(() => {
42
+ const updateDimensions = () => {
43
+ if (appRef.current) {
44
+ // Get the distance from the top of the page to the target element
45
+ const rect = appRef.current.getBoundingClientRect();
46
+ const distanceFromTop = rect.top + window.scrollY;
47
+
48
+ // Calculate the available height for the Cherita app
49
+ const availableHeight = window.innerHeight - distanceFromTop;
50
+
51
+ // Update the dimensions to fit the viewport minus the navbar height
52
+ setAppDimensions({
53
+ width: appRef.current.offsetWidth,
54
+ height: availableHeight
55
+ });
56
+ }
57
+ };
58
+ window.addEventListener("resize", updateDimensions);
59
+ updateDimensions(); // Initial update
60
+ return () => window.removeEventListener("resize", updateDimensions);
61
+ }, []);
62
+ return /*#__PURE__*/_react.default.createElement("div", {
63
+ ref: appRef,
64
+ className: "cherita-app",
65
+ style: {
66
+ height: appDimensions.height
67
+ }
68
+ }, /*#__PURE__*/_react.default.createElement(_DatasetContext.DatasetProvider, props, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Container, {
69
+ fluid: true,
70
+ className: "d-flex g-0",
71
+ style: {
72
+ height: appDimensions.height
73
+ }
74
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Navbar, {
75
+ expand: "sm",
76
+ bg: "primary",
77
+ className: "cherita-navbar"
78
+ }, /*#__PURE__*/_react.default.createElement("div", {
79
+ className: "container-fluid"
80
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Navbar.Toggle, {
81
+ "aria-controls": "navbarScroll"
82
+ }), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Navbar.Collapse, {
83
+ id: "navbarScroll"
84
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav, {
85
+ className: "me-auto my-0",
86
+ navbarScroll: true
87
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Item, {
88
+ className: "d-block d-lg-none"
89
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Link, {
90
+ onClick: () => setShowObs(true)
91
+ }, "Observations")), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Item, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Link, {
92
+ onClick: () => setShowVars(true)
93
+ }, "Features"))), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav, {
94
+ className: "d-flex"
95
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Item, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Nav.Link, {
96
+ onClick: () => setShowControls(true)
97
+ }, "Controls")))))), /*#__PURE__*/_react.default.createElement("div", {
98
+ className: "cherita-app-obs modern-scrollbars border-end h-100"
99
+ }, /*#__PURE__*/_react.default.createElement(_ObsList.ObsColsList, null)), /*#__PURE__*/_react.default.createElement("div", {
100
+ className: "cherita-app-canvas flex-grow-1"
101
+ }, children), /*#__PURE__*/_react.default.createElement("div", {
102
+ className: "cherita-app-sidebar p-3"
103
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card.Header, {
104
+ className: "d-flex justify-content-evenly align-items-center"
105
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
106
+ variant: "link",
107
+ onClick: () => setShowModal(true)
108
+ }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
109
+ icon: _freeSolidSvgIcons.faArrowUpRightFromSquare
110
+ }))), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Card.Body, {
111
+ className: "d-flex flex-column p-0"
112
+ }, /*#__PURE__*/_react.default.createElement("div", {
113
+ className: "sidebar-pseudospatial"
114
+ }, /*#__PURE__*/_react.default.createElement(_Pseudospatial.Pseudospatial, {
115
+ className: "sidebar-pseudospatial",
116
+ plotType: pseudospatialPlotType,
117
+ setPlotType: setpseudospatialPlotType,
118
+ setShowControls: setShowPseudospatialControls
119
+ })), /*#__PURE__*/_react.default.createElement("div", {
120
+ className: "sidebar-features modern-scrollbars"
121
+ }, /*#__PURE__*/_react.default.createElement(_SearchBar.SearchBar, {
122
+ searchDiseases: true,
123
+ searchVar: true
124
+ }), /*#__PURE__*/_react.default.createElement(_VarList.VarNamesList, {
125
+ mode: varMode
126
+ })))))), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal, {
127
+ show: showModal,
128
+ onHide: () => setShowModal(false),
129
+ centered: true
130
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Header, {
131
+ closeButton: true
132
+ }), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Body, null, /*#__PURE__*/_react.default.createElement(_Pseudospatial.Pseudospatial, {
133
+ plotType: pseudospatialPlotType,
134
+ setPlotType: setpseudospatialPlotType,
135
+ setShowControls: setShowPseudospatialControls,
136
+ height: 500
137
+ }))), /*#__PURE__*/_react.default.createElement(_offcanvas.OffcanvasObs, {
138
+ show: showObs,
139
+ handleClose: () => setShowObs(false)
140
+ }), /*#__PURE__*/_react.default.createElement(_offcanvas.OffcanvasVars, {
141
+ show: showVars,
142
+ handleClose: () => setShowVars(false)
143
+ }), /*#__PURE__*/_react.default.createElement(_offcanvas.OffcanvasControls, {
144
+ show: showControls,
145
+ handleClose: () => setShowControls(false),
146
+ Controls: _ScatterplotControls.ScatterplotControls
147
+ }), /*#__PURE__*/_react.default.createElement(_offcanvas.OffcanvasControls, {
148
+ show: showPseudospatialControls,
149
+ handleClose: () => setShowPseudospatialControls(false),
150
+ Controls: _PseudospatialToolbar.PseudospatialToolbar,
151
+ plotType: pseudospatialPlotType
152
+ }), /*#__PURE__*/_react.default.createElement(_offcanvas.OffcanvasObsm, {
153
+ show: showObsm,
154
+ handleClose: () => setShowObsm(false)
155
+ }))));
156
+ }
157
+ function FullPagePseudospatial(props) {
158
+ return /*#__PURE__*/_react.default.createElement(FullPage, props, /*#__PURE__*/_react.default.createElement(_Scatterplot.Scatterplot, null));
159
+ }
@@ -20,6 +20,7 @@ var _LoadingIndicators = require("../../utils/LoadingIndicators");
20
20
  var _requests = require("../../utils/requests");
21
21
  var _string = require("../../utils/string");
22
22
  var _VirtualizedList = require("../../utils/VirtualizedList");
23
+ var _zarrData = require("../../utils/zarrData");
23
24
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
24
25
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
25
26
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -79,12 +80,55 @@ const useObsHistogram = obs => {
79
80
  refetchOnMount: false
80
81
  });
81
82
  };
83
+ const getBinIndex = (v, binEdges) => {
84
+ const EPSILON = 1e-6;
85
+ const lastEdge = _lodash.default.last(binEdges);
86
+ const allButLastEdges = _lodash.default.initial(binEdges);
87
+ const modifiedBinEdges = [...allButLastEdges, [lastEdge[0], lastEdge[1] + EPSILON]];
88
+ return _lodash.default.findIndex(modifiedBinEdges, range => _lodash.default.inRange(v, ...range));
89
+ };
90
+ const useFilteredObsData = obs => {
91
+ const {
92
+ obsIndices
93
+ } = (0, _FilterContext.useFilteredData)();
94
+ const obsData = (0, _zarrData.useObsData)(obs);
95
+ const isCategorical = obs.type === _constants.OBS_TYPES.CATEGORICAL || obs.type === _constants.OBS_TYPES.BOOLEAN;
96
+ const {
97
+ valueCounts,
98
+ pct
99
+ } = (0, _react.useMemo)(() => {
100
+ const filteredObsValues = _lodash.default.at(obsData.data, [...(obsIndices || [])]);
101
+ let valueCounts = {};
102
+ if (isCategorical) {
103
+ valueCounts = _lodash.default.countBy(filteredObsValues);
104
+ } else {
105
+ valueCounts = _lodash.default.countBy(filteredObsValues, v => {
106
+ return getBinIndex(v, obs.bins?.binEdges || [[null, null]]);
107
+ });
108
+ }
109
+ valueCounts = _lodash.default.mapKeys(valueCounts, (_v, i) => {
110
+ return obs.codesMap[i];
111
+ });
112
+ const totalCounts = obsIndices?.size;
113
+ const pct = _lodash.default.mapValues(valueCounts, v => v / totalCounts * 100);
114
+ return {
115
+ valueCounts,
116
+ pct
117
+ };
118
+ }, [isCategorical, obs.bins?.binEdges, obs.codesMap, obsData.data, obsIndices]);
119
+ return {
120
+ value_counts: valueCounts,
121
+ pct: pct
122
+ };
123
+ };
82
124
  function CategoricalItem({
83
125
  value,
84
126
  label,
85
127
  code,
86
- value_counts,
87
- pct,
128
+ stats = {
129
+ value_counts: null,
130
+ pct: null
131
+ },
88
132
  isOmitted,
89
133
  min,
90
134
  max,
@@ -94,6 +138,11 @@ function CategoricalItem({
94
138
  isPending: false,
95
139
  altColor: false
96
140
  },
141
+ filteredStats = {
142
+ value_counts: null,
143
+ pct: null
144
+ },
145
+ isSliced,
97
146
  showColor = true
98
147
  }) {
99
148
  const {
@@ -114,27 +163,49 @@ function CategoricalItem({
114
163
  onChange: () => onChange(value)
115
164
  })), /*#__PURE__*/_react.default.createElement("div", {
116
165
  className: "d-flex align-items-center"
166
+ }, /*#__PURE__*/_react.default.createElement("div", {
167
+ className: "pl-1 m-0 flex-grow-1"
117
168
  }, /*#__PURE__*/_react.default.createElement(_Histogram.Histogram, {
118
169
  data: histogramData.data,
119
170
  isPending: histogramData.isPending,
120
171
  altColor: histogramData.altColor
121
- }), /*#__PURE__*/_react.default.createElement("div", {
172
+ })), /*#__PURE__*/_react.default.createElement("div", {
122
173
  className: "pl-1 m-0"
123
174
  }, /*#__PURE__*/_react.default.createElement(_material.Tooltip, {
124
- title: `${(0, _string.formatNumerical)(pct, _string.FORMATS.EXPONENTIAL)}%`,
175
+ title: isSliced ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Filtered:", " ", (0, _string.formatNumerical)(filteredStats.pct, _string.FORMATS.EXPONENTIAL), "%", /*#__PURE__*/_react.default.createElement("br", null), "Total: ", (0, _string.formatNumerical)(stats.pct, _string.FORMATS.EXPONENTIAL), "%") : `${(0, _string.formatNumerical)(stats.pct, _string.FORMATS.EXPONENTIAL)}%`,
125
176
  placement: "left",
126
177
  arrow: true
127
178
  }, /*#__PURE__*/_react.default.createElement("div", {
128
179
  className: "d-flex align-items-center"
129
180
  }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Badge, {
130
- className: "value-count-badge",
131
- style: {
132
- fontWeight: "lighter"
133
- }
134
- }, (0, _string.formatNumerical)(parseInt(value_counts), _string.FORMATS.EXPONENTIAL)), /*#__PURE__*/_react.default.createElement("div", {
181
+ className: "value-count-badge"
182
+ }, " ", isSliced && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, (0, _string.formatNumerical)(parseInt(filteredStats.value_counts)), " ", "out of", " "), (0, _string.formatNumerical)(parseInt(stats.value_counts), _string.FORMATS.EXPONENTIAL)), /*#__PURE__*/_react.default.createElement("div", {
135
183
  className: "value-pct-gauge-container"
136
- }, /*#__PURE__*/_react.default.createElement(_xCharts.Gauge, {
137
- value: pct,
184
+ }, isSliced ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_xCharts.Gauge, {
185
+ className: "pct-gauge filtered-pct-gauge",
186
+ value: filteredStats.pct,
187
+ text: null,
188
+ innerRadius: "50%",
189
+ outerRadius: "75%",
190
+ margin: {
191
+ top: 0,
192
+ right: 0,
193
+ bottom: 0,
194
+ left: 0
195
+ }
196
+ }), /*#__PURE__*/_react.default.createElement(_xCharts.Gauge, {
197
+ className: "pct-gauge",
198
+ value: stats.pct,
199
+ text: null,
200
+ innerRadius: "75%",
201
+ margin: {
202
+ top: 0,
203
+ right: 0,
204
+ bottom: 0,
205
+ left: 0
206
+ }
207
+ })) : /*#__PURE__*/_react.default.createElement(_xCharts.Gauge, {
208
+ value: stats.pct,
138
209
  text: null,
139
210
  innerRadius: "50%",
140
211
  margin: {
@@ -186,6 +257,7 @@ function CategoricalObs({
186
257
  const min = _lodash.default.min(_lodash.default.values(obs.codes));
187
258
  const max = _lodash.default.max(_lodash.default.values(obs.codes));
188
259
  const obsHistograms = useObsHistogram(obs);
260
+ const filteredObsData = useFilteredObsData(obs);
189
261
  (0, _react.useEffect)(() => {
190
262
  if (dataset.selectedObs?.name === obs.name) {
191
263
  const selectedObsData = _lodash.default.omit(dataset.selectedObs, ["omit"]);
@@ -208,8 +280,10 @@ function CategoricalObs({
208
280
  return {
209
281
  value: obs.values[index],
210
282
  code: obs.codes[obs.values[index]],
211
- value_counts: obs.value_counts[obs.values[index]],
212
- pct: obs.value_counts[obs.values[index]] / totalCounts * 100,
283
+ stats: {
284
+ value_counts: obs.value_counts[obs.values[index]],
285
+ pct: obs.value_counts[obs.values[index]] / totalCounts * 100
286
+ },
213
287
  isOmitted: _lodash.default.includes(obs.omit, obs.codes[obs.values[index]]),
214
288
  label: obs.values[index],
215
289
  histogramData: dataset.colorEncoding === _constants.COLOR_ENCODINGS.VAR ? {
@@ -219,11 +293,18 @@ function CategoricalObs({
219
293
  } : {
220
294
  data: null,
221
295
  isPending: false
222
- }
296
+ },
297
+ filteredStats: {
298
+ value_counts: filteredObsData?.value_counts[obs.values[index]] || 0,
299
+ pct: filteredObsData?.pct[obs.values[index]] || 0
300
+ },
301
+ isSliced: isSliced
223
302
  };
224
- }, [dataset.colorEncoding, isSliced, obs.codes, obs.omit, obs.value_counts, obs.values, obsHistograms.fetchedData, obsHistograms.isPending, totalCounts]);
303
+ }, [dataset.colorEncoding, filteredObsData?.pct, filteredObsData?.value_counts, isSliced, obs.codes, obs.omit, obs.value_counts, obs.values, obsHistograms.fetchedData, obsHistograms.isPending, totalCounts]);
225
304
  showColor &= dataset.colorEncoding === _constants.COLOR_ENCODINGS.OBS;
226
- return /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, null, /*#__PURE__*/_react.default.createElement(_ObsToolbar.ObsToolbar, {
305
+ return /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, {
306
+ variant: "flush"
307
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, null, /*#__PURE__*/_react.default.createElement(_ObsToolbar.ObsToolbar, {
227
308
  item: obs,
228
309
  onToggleAllObs: toggleAll,
229
310
  onToggleLabel: toggleLabel,
@@ -310,6 +391,9 @@ function ContinuousObs({
310
391
  }) {
311
392
  const ENDPOINT = "obs/bins";
312
393
  const dataset = (0, _DatasetContext.useDataset)();
394
+ const {
395
+ isSliced
396
+ } = (0, _FilterContext.useFilteredData)();
313
397
  const dispatch = (0, _DatasetContext.useDatasetDispatch)();
314
398
  const binnedObs = binContinuous(obs, _lodash.default.min([N_BINS, obs.n_unique]));
315
399
  const params = {
@@ -325,6 +409,7 @@ function ContinuousObs({
325
409
  } = (0, _requests.useFetch)(ENDPOINT, params, {
326
410
  refetchOnMount: false
327
411
  });
412
+ const filteredObsData = useFilteredObsData(obs);
328
413
  const updatedObs = fetchedData && _lodash.default.isMatch(obs, fetchedData);
329
414
  (0, _react.useEffect)(() => {
330
415
  // Update ObsList obsCols with bin data
@@ -362,13 +447,22 @@ function ContinuousObs({
362
447
  return {
363
448
  value: obs.values[index],
364
449
  code: obs.codes[obs.values[index]],
365
- value_counts: obs.value_counts[obs.values[index]],
366
- pct: obs.value_counts[obs.values[index]] / totalCounts * 100,
450
+ stats: {
451
+ value_counts: obs.value_counts[obs.values[index]],
452
+ pct: obs.value_counts[obs.values[index]] / totalCounts * 100
453
+ },
367
454
  isOmitted: _lodash.default.includes(obs.omit, obs.codes[obs.values[index]]),
368
- label: isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges)
455
+ label: isNaN(obs.values[index]) ? "NaN" : getContinuousLabel(obs.codes[obs.values[index]], obs.bins.binEdges),
456
+ filteredStats: {
457
+ value_counts: filteredObsData?.value_counts[obs.values[index]] || 0,
458
+ pct: filteredObsData?.pct[obs.values[index]] || 0
459
+ },
460
+ isSliced: isSliced
369
461
  };
370
462
  };
371
- return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingLinear, null), !serverError && updatedObs && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, null, /*#__PURE__*/_react.default.createElement(_ObsToolbar.ObsToolbar, {
463
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingLinear, null), !serverError && updatedObs && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup, {
464
+ variant: "flush"
465
+ }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.ListGroup.Item, null, /*#__PURE__*/_react.default.createElement(_ObsToolbar.ObsToolbar, {
372
466
  item: obs,
373
467
  onToggleAllObs: toggleAll,
374
468
  onToggleLabel: toggleLabel,
@@ -5,8 +5,13 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ObsColsList = ObsColsList;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
+ var _freeSolidSvgIcons = require("@fortawesome/free-solid-svg-icons");
9
+ var _reactFontawesome = require("@fortawesome/react-fontawesome");
8
10
  var _lodash = _interopRequireDefault(require("lodash"));
9
11
  var _reactBootstrap = require("react-bootstrap");
12
+ var _Accordion = _interopRequireDefault(require("react-bootstrap/Accordion"));
13
+ var _AccordionButton = require("react-bootstrap/AccordionButton");
14
+ var _AccordionContext = _interopRequireDefault(require("react-bootstrap/AccordionContext"));
10
15
  var _ObsItem = require("./ObsItem");
11
16
  var _constants = require("../../constants/constants");
12
17
  var _DatasetContext = require("../../context/DatasetContext");
@@ -173,17 +178,56 @@ function ObsColsList({
173
178
  });
174
179
  }
175
180
  };
176
- const obsList = _lodash.default.map(obsCols, item => {
181
+ function ObsAccordionToggle({
182
+ children,
183
+ eventKey
184
+ }) {
185
+ const {
186
+ activeEventKey
187
+ } = (0, _react.useContext)(_AccordionContext.default);
188
+ const decoratedOnClick = (0, _AccordionButton.useAccordionButton)(eventKey, () => {
189
+ handleAccordionToggle(eventKey);
190
+ });
191
+ const isCurrentEventKey = Array.isArray(activeEventKey) ? activeEventKey.includes(eventKey) : activeEventKey === eventKey;
192
+ return /*#__PURE__*/_react.default.createElement("div", {
193
+ className: `obs-accordion-header ${isCurrentEventKey ? "active" : ""}`,
194
+ onClick: decoratedOnClick
195
+ }, /*#__PURE__*/_react.default.createElement("span", {
196
+ className: "obs-accordion-header-chevron"
197
+ }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
198
+ icon: isCurrentEventKey ? _freeSolidSvgIcons.faChevronDown : _freeSolidSvgIcons.faChevronRight
199
+ })), /*#__PURE__*/_react.default.createElement("span", {
200
+ className: "obs-accordion-header-title"
201
+ }, children));
202
+ }
203
+ const obsList = _lodash.default.map(obsCols, (item, index) => {
177
204
  if (item.type === _constants.OBS_TYPES.DISCRETE) {
178
205
  return null;
179
206
  }
180
- return /*#__PURE__*/_react.default.createElement(_reactBootstrap.Accordion.Item, {
181
- key: item.name,
182
- eventKey: item.name,
183
- className: active === item.name && dataset.colorEncoding === _constants.COLOR_ENCODINGS.OBS && "cherita-accordion-active"
184
- }, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Accordion.Header, {
185
- onClick: () => handleAccordionToggle(item.name)
186
- }, item.name), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Accordion.Body, null, expandedItems[item.name] && (item.type === _constants.OBS_TYPES.CATEGORICAL || item.type === _constants.OBS_TYPES.BOOLEAN ? /*#__PURE__*/_react.default.createElement(_ObsItem.CategoricalObs, {
207
+ const inLabelObs = _lodash.default.some(dataset.labelObs, i => i.name === item.name);
208
+ const inSliceObs = dataset.sliceBy.obs && dataset.selectedObs?.name === item.name;
209
+ const isColorEncoding = dataset.colorEncoding === _constants.COLOR_ENCODINGS.OBS && dataset.selectedObs?.name === item.name;
210
+ return /*#__PURE__*/_react.default.createElement("div", {
211
+ key: item.name
212
+ }, /*#__PURE__*/_react.default.createElement(ObsAccordionToggle, {
213
+ eventKey: item.name
214
+ }, /*#__PURE__*/_react.default.createElement("div", null, item.name), /*#__PURE__*/_react.default.createElement("div", null, inLabelObs && /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
215
+ className: "mx-1",
216
+ icon: _freeSolidSvgIcons.faListOl,
217
+ title: "In tooltip"
218
+ }), inSliceObs && /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
219
+ className: "mx-1",
220
+ icon: _freeSolidSvgIcons.faScissors,
221
+ title: "Filter applied"
222
+ }), isColorEncoding && /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
223
+ className: "mx-1",
224
+ icon: _freeSolidSvgIcons.faDroplet,
225
+ title: "Is color encoding"
226
+ }))), /*#__PURE__*/_react.default.createElement(_Accordion.default.Collapse, {
227
+ eventKey: item.name
228
+ }, /*#__PURE__*/_react.default.createElement("div", {
229
+ className: "obs-accordion-body"
230
+ }, expandedItems[item.name] && (item.type === _constants.OBS_TYPES.CATEGORICAL || item.type === _constants.OBS_TYPES.BOOLEAN ? /*#__PURE__*/_react.default.createElement(_ObsItem.CategoricalObs, {
187
231
  key: item.name,
188
232
  obs: item,
189
233
  updateObs: updateObs,
@@ -202,18 +246,15 @@ function ObsColsList({
202
246
  toggleLabel: () => toggleLabel(item),
203
247
  toggleSlice: () => toggleSlice(item),
204
248
  toggleColor: () => toggleColor(item)
205
- }))));
249
+ })))));
206
250
  });
207
251
  if (!serverError) {
208
- return /*#__PURE__*/_react.default.createElement("div", {
209
- className: "position-relative h-100"
210
- }, /*#__PURE__*/_react.default.createElement("div", {
211
- className: "list-group overflow-auto h-100"
212
- }, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingSpinner, null), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Accordion, {
252
+ return /*#__PURE__*/_react.default.createElement("div", null, isPending && /*#__PURE__*/_react.default.createElement(_LoadingIndicators.LoadingSpinner, null), /*#__PURE__*/_react.default.createElement(_Accordion.default, {
213
253
  flush: true,
214
254
  defaultActiveKey: [active],
215
- alwaysOpen: true
216
- }, obsList)));
255
+ alwaysOpen: true,
256
+ className: "obs-accordion"
257
+ }, obsList));
217
258
  } else {
218
259
  return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Alert, {
219
260
  variant: "danger"
@@ -29,7 +29,7 @@ function ObsToolbar({
29
29
  const inSliceObs = dataset.sliceBy.obs && dataset.selectedObs?.name === item.name;
30
30
  const isColorEncoding = dataset.colorEncoding === _constants.COLOR_ENCODINGS.OBS && dataset.selectedObs?.name === item.name;
31
31
  return /*#__PURE__*/_react.default.createElement("div", {
32
- className: "d-flex"
32
+ className: "d-flex align-items-center"
33
33
  }, /*#__PURE__*/_react.default.createElement("div", {
34
34
  className: "flex-grow-1"
35
35
  }, showToggleAllObs && /*#__PURE__*/_react.default.createElement(_reactBootstrap.Form.Check, {
@@ -45,14 +45,14 @@ function ObsToolbar({
45
45
  onClick: onToggleLabel,
46
46
  title: "Add to tooltip"
47
47
  }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
48
- icon: _freeSolidSvgIcons.faFont
48
+ icon: _freeSolidSvgIcons.faListOl
49
49
  })), showSlice && /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
50
50
  variant: inSliceObs ? "primary" : "outline-primary",
51
51
  size: "sm",
52
52
  onClick: onToggleSlice,
53
- title: "Slice to selected"
53
+ title: "Filter to selected"
54
54
  }, /*#__PURE__*/_react.default.createElement(_reactFontawesome.FontAwesomeIcon, {
55
- icon: _freeSolidSvgIcons.faEye
55
+ icon: _freeSolidSvgIcons.faScissors
56
56
  })), showColor && /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
57
57
  variant: isColorEncoding ? "primary" : "outline-primary",
58
58
  size: "sm",