@vitessce/statistical-plots 3.3.12 → 3.4.1

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.
@@ -0,0 +1,2 @@
1
+ export default function FeatureBarPlot(props: any): JSX.Element;
2
+ //# sourceMappingURL=FeatureBarPlot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeatureBarPlot.d.ts","sourceRoot":"","sources":["../src/FeatureBarPlot.js"],"names":[],"mappings":"AAwBA,gEAyMC"}
@@ -0,0 +1,174 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable indent */
3
+ /* eslint-disable camelcase */
4
+ import React, { useMemo, useEffect, useRef } from 'react';
5
+ import { scaleLinear } from 'd3-scale';
6
+ import { scale as vega_scale } from 'vega-scale';
7
+ import { axisBottom, axisLeft } from 'd3-axis';
8
+ import { ascending } from 'd3-array';
9
+ import { select } from 'd3-selection';
10
+ import { capitalize } from '@vitessce/utils';
11
+ const scaleBand = vega_scale('band');
12
+ const OBS_KEY = 'obsId';
13
+ const FEATURE_KEY = 'value';
14
+ function componentToHex(c) {
15
+ const hex = c.toString(16);
16
+ return hex.length === 1 ? `0${hex}` : hex;
17
+ }
18
+ function rgbToHex(color) {
19
+ return `#${componentToHex(color[0])}${componentToHex(color[1])}${componentToHex(color[2])}`;
20
+ }
21
+ export default function FeatureBarPlot(props) {
22
+ const { yMin, yMax, yUnits, jitter, colors, data, theme, width, height, marginTop = 5, marginRight = 5, marginLeft = 80, marginBottom, obsType, cellHighlight, cellSetSelection, additionalCellSets, cellSetColor, featureType, featureValueType, featureName, onBarSelect, onBarHighlight, } = props;
23
+ // TODO: use a more descriptive name than setsSave.
24
+ const setsSave = useMemo(() => {
25
+ const result = new Map();
26
+ cellSetSelection?.forEach((obsSetPath) => {
27
+ // TODO: this does not use the full set path for comparison.
28
+ const selectedElement = obsSetPath[1];
29
+ // TODO: this is only considering the first set grouping in the tree.
30
+ // TODO: use sets-utils to traverse sets tree.
31
+ additionalCellSets?.tree?.[0]?.children?.forEach((child) => {
32
+ if (child.name === selectedElement) {
33
+ child.set.forEach(([obsId]) => {
34
+ const info = { name: '', id: '', color: [] };
35
+ info.name = selectedElement;
36
+ info.id = obsId;
37
+ cellSetColor.forEach((color) => {
38
+ if (color.path[1] === selectedElement) {
39
+ info.color = color.color;
40
+ }
41
+ });
42
+ result.set(info.id, info);
43
+ });
44
+ }
45
+ });
46
+ });
47
+ return result;
48
+ }, [cellSetSelection, additionalCellSets, cellSetColor]);
49
+ const svgRef = useRef();
50
+ // Get the max characters in an axis label for autsizing the bottom margin.
51
+ const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
52
+ // eslint-disable-next-line no-param-reassign
53
+ acc = acc === undefined || val[OBS_KEY].length > acc ? val[OBS_KEY].length : acc;
54
+ return acc;
55
+ }, 0), [data]);
56
+ useEffect(() => {
57
+ const domElement = svgRef.current;
58
+ const unitSuffix = yUnits ? ` (${yUnits})` : '';
59
+ const yTitle = `${capitalize(featureName)}${unitSuffix}`;
60
+ const xTitle = `${capitalize(obsType)}`;
61
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
62
+ // so the perpendicular distance to the bottom of the labels is proportional to the
63
+ // square root of the length of the labels along the imaginary hypotenuse.
64
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
65
+ const autoMarginBottom = marginBottom
66
+ || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
67
+ const foregroundColor = (theme === 'dark' ? 'lightgray' : 'black');
68
+ data.sort((a, b) => ascending(a[FEATURE_KEY], b[FEATURE_KEY]));
69
+ const svg = select(domElement);
70
+ svg.selectAll('g').remove();
71
+ svg
72
+ .attr('width', width)
73
+ .attr('height', height);
74
+ const g = svg
75
+ .append('g')
76
+ .attr('width', width)
77
+ .attr('height', height);
78
+ const innerWidth = width - marginLeft;
79
+ const innerHeight = height - autoMarginBottom;
80
+ const xScale = scaleBand()
81
+ .range([marginLeft, width - marginRight])
82
+ .domain(data.map(d => d[OBS_KEY]))
83
+ .padding(0.1);
84
+ // For the y domain, use the yMin prop
85
+ // to support a use case such as 'Aspect Ratio',
86
+ // where the domain minimum should be 1 rather than 0.
87
+ const yScale = scaleLinear()
88
+ .domain([yMin, yMax])
89
+ .range([innerHeight, marginTop]);
90
+ // Bar areas
91
+ g
92
+ .selectAll('bar')
93
+ .data(data)
94
+ .enter()
95
+ .append('rect')
96
+ .attr('x', d => xScale(d[OBS_KEY]))
97
+ .attr('y', d => yScale(d[FEATURE_KEY]))
98
+ .attr('width', xScale.bandwidth())
99
+ .attr('height', d => innerHeight - yScale(d[FEATURE_KEY]))
100
+ .style('fill', (d) => {
101
+ if (d[OBS_KEY] === cellHighlight)
102
+ return 'orange';
103
+ if (setsSave.has(d[OBS_KEY])) {
104
+ const { color } = setsSave.get(d[OBS_KEY]);
105
+ return rgbToHex(color);
106
+ }
107
+ return foregroundColor;
108
+ })
109
+ .style('cursor', 'pointer')
110
+ // eslint-disable-next-line no-unused-vars
111
+ .on('click', (event, d) => {
112
+ onBarSelect(d[OBS_KEY]);
113
+ })
114
+ // eslint-disable-next-line no-unused-vars
115
+ .on('mouseover', (event, d) => {
116
+ onBarHighlight(d[OBS_KEY]);
117
+ })
118
+ .on('mouseout', () => {
119
+ onBarHighlight(null);
120
+ });
121
+ const axis = axisLeft(yScale);
122
+ axis.tickFormat(d => `${Math.round(d * 10000000)} µm`);
123
+ // Y-axis ticks
124
+ g
125
+ .append('g')
126
+ .attr('transform', `translate(${marginLeft},0)`)
127
+ .call(axis)
128
+ .selectAll('text')
129
+ .style('font-size', '11px');
130
+ // X-axis ticks
131
+ g
132
+ .append('g')
133
+ .attr('transform', `translate(0,${innerHeight})`)
134
+ .style('font-size', '14px')
135
+ .call(axisBottom(xScale))
136
+ .selectAll('text')
137
+ .style('font-size', '11px')
138
+ .attr('dx', '-6px')
139
+ .attr('dy', '6px')
140
+ .attr('transform', 'rotate(-45)')
141
+ .style('text-anchor', 'end');
142
+ // Y-axis title
143
+ g
144
+ .append('text')
145
+ .attr('text-anchor', 'middle')
146
+ .attr('x', -innerHeight / 2)
147
+ .attr('y', 15)
148
+ .attr('transform', 'rotate(-90)')
149
+ .text(yTitle)
150
+ .style('font-size', '12px')
151
+ .style('fill', foregroundColor);
152
+ // X-axis title
153
+ g
154
+ .append('text')
155
+ .attr('text-anchor', 'middle')
156
+ .attr('x', marginLeft + innerWidth / 2)
157
+ .attr('y', height - 10)
158
+ .text(xTitle)
159
+ .style('font-size', '12px')
160
+ .style('fill', foregroundColor);
161
+ }, [width, height, data, marginLeft, marginBottom, colors,
162
+ jitter, theme, yMin, marginTop, marginRight, featureType,
163
+ featureValueType, yUnits, obsType,
164
+ maxCharactersForLabel, yMax, featureName, onBarSelect, onBarHighlight,
165
+ cellHighlight, setsSave,
166
+ ]);
167
+ return (_jsx("svg", { ref: svgRef, style: {
168
+ top: 0,
169
+ left: 0,
170
+ width: `${width}px`,
171
+ height: `${height}px`,
172
+ position: 'relative',
173
+ } }));
174
+ }
@@ -0,0 +1,2 @@
1
+ export function FeatureBarPlotSubscriber(props: any): JSX.Element;
2
+ //# sourceMappingURL=FeatureBarPlotSubscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeatureBarPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/FeatureBarPlotSubscriber.js"],"names":[],"mappings":"AAkBA,kEA0IC"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useMemo, useCallback } from 'react';
3
+ import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, } from '@vitessce/vit-s';
4
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
5
+ import { setObsSelection } from '@vitessce/sets-utils';
6
+ import FeatureBarPlot from './FeatureBarPlot.js';
7
+ import { useStyles } from './styles.js';
8
+ export function FeatureBarPlotSubscriber(props) {
9
+ const { coordinationScopes, removeGridComponent, theme, yMin = 0, yUnits = null, } = props;
10
+ const classes = useStyles();
11
+ const loaders = useLoaders();
12
+ // Get "props" from the coordination space.
13
+ const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, featureValueTransform, featureValueTransformCoefficient, obsHighlight: cellHighlight, additionalObsSets: additionalCellSets, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, }, { setObsSetSelection: setCellSetSelection, setObsHighlight: setCellHighlight, setObsSetColor: setCellSetColor, setObsColorEncoding: setCellColorEncoding, setAdditionalObsSets: setAdditionalCellSets, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_BAR_PLOT], coordinationScopes);
14
+ // console.log("BarPlot: " + cellHighlight)
15
+ const [width, height, containerRef] = useGridItemSize();
16
+ // Get data from loaders using the data hooks.
17
+ // eslint-disable-next-line no-unused-vars
18
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
19
+ // TODO: support multiple feature labels using featureLabelsType coordination values.
20
+ const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(loaders, dataset, false, {}, {}, { featureType });
21
+ const [{ obsIndex }, matrixIndicesStatus, matrixIndicesUrls,] = useObsFeatureMatrixIndices(loaders, dataset, false, { obsType, featureType, featureValueType });
22
+ const isReady = useReady([
23
+ featureSelectionStatus,
24
+ matrixIndicesStatus,
25
+ featureLabelsStatus,
26
+ ]);
27
+ const urls = useUrls([
28
+ featureLabelsUrls,
29
+ matrixIndicesUrls,
30
+ ]);
31
+ const onBarSelect = useCallback((obsId) => {
32
+ const obsIdsToSelect = [obsId];
33
+ setObsSelection(obsIdsToSelect, additionalCellSets, cellSetColor, setCellSetSelection, setAdditionalCellSets, setCellSetColor, setCellColorEncoding);
34
+ }, [additionalCellSets, cellSetColor, setCellColorEncoding,
35
+ setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
36
+ const onBarHighlight = useCallback((obsId) => {
37
+ setCellHighlight(obsId);
38
+ }, []);
39
+ const firstGeneSelected = geneSelection && geneSelection.length >= 1
40
+ ? (featureLabelsMap?.get(geneSelection[0]) || geneSelection[0])
41
+ : null;
42
+ const [expressionArr, expressionMax] = useMemo(() => {
43
+ if (firstGeneSelected && expressionData && obsIndex) {
44
+ let exprMax = -Infinity;
45
+ const cellIndices = {};
46
+ for (let i = 0; i < obsIndex.length; i += 1) {
47
+ cellIndices[obsIndex[i]] = i;
48
+ }
49
+ const exprValues = obsIndex.map((obsId, cellIndex) => {
50
+ const value = expressionData[0][cellIndex];
51
+ exprMax = Math.max(value, exprMax);
52
+ return { obsId, value, feature: firstGeneSelected };
53
+ });
54
+ return [exprValues, exprMax];
55
+ }
56
+ return [null, null];
57
+ }, [expressionData, obsIndex, geneSelection, theme,
58
+ featureValueTransform, featureValueTransformCoefficient,
59
+ firstGeneSelected,
60
+ ]);
61
+ return (_jsx(TitleInfo, { title: `Feature Values${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: expressionArr ? (_jsx(FeatureBarPlot, { yMin: yMin, yMax: expressionMax, yUnits: yUnits, data: expressionArr, theme: theme, width: width, height: height, obsType: obsType, cellHighlight: cellHighlight, cellSetSelection: cellSetSelection, additionalCellSets: additionalCellSets, cellSetColor: cellSetColor, featureType: featureType, featureValueType: featureValueType, featureName: firstGeneSelected, onBarSelect: onBarSelect, onBarHighlight: onBarHighlight })) : (_jsxs("span", { children: ["Select a ", featureType, "."] })) }) }));
62
+ }
@@ -1,6 +1,7 @@
1
1
  export { CellSetExpressionPlotSubscriber } from "./CellSetExpressionPlotSubscriber.js";
2
2
  export { CellSetSizesPlotSubscriber } from "./CellSetSizesPlotSubscriber.js";
3
3
  export { ExpressionHistogramSubscriber } from "./ExpressionHistogramSubscriber.js";
4
+ export { FeatureBarPlotSubscriber } from "./FeatureBarPlotSubscriber.js";
4
5
  export { default as CellSetSizesPlot } from "./CellSetSizesPlot.js";
5
6
  export { default as CellSetExpressionPlot } from "./CellSetExpressionPlot.js";
6
7
  export { default as ExpressionHistogram } from "./ExpressionHistogram.js";
package/dist-tsc/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { CellSetExpressionPlotSubscriber } from './CellSetExpressionPlotSubscriber.js';
2
2
  export { CellSetSizesPlotSubscriber } from './CellSetSizesPlotSubscriber.js';
3
3
  export { ExpressionHistogramSubscriber } from './ExpressionHistogramSubscriber.js';
4
+ export { FeatureBarPlotSubscriber } from './FeatureBarPlotSubscriber.js';
4
5
  export { default as CellSetSizesPlot } from './CellSetSizesPlot.js';
5
6
  export { default as CellSetExpressionPlot } from './CellSetExpressionPlot.js';
6
7
  export { default as ExpressionHistogram } from './ExpressionHistogram.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/statistical-plots",
3
- "version": "3.3.12",
3
+ "version": "3.4.1",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -25,11 +25,12 @@
25
25
  "vega-scale": "^6.0.0",
26
26
  "lodash-es": "^4.17.21",
27
27
  "react-aria": "^3.28.0",
28
- "@vitessce/sets-utils": "3.3.12",
29
- "@vitessce/constants-internal": "3.3.12",
30
- "@vitessce/vega": "3.3.12",
31
- "@vitessce/utils": "3.3.12",
32
- "@vitessce/vit-s": "3.3.12"
28
+ "d3-format": "^3.1.0",
29
+ "@vitessce/constants-internal": "3.4.1",
30
+ "@vitessce/sets-utils": "3.4.1",
31
+ "@vitessce/utils": "3.4.1",
32
+ "@vitessce/vega": "3.4.1",
33
+ "@vitessce/vit-s": "3.4.1"
33
34
  },
34
35
  "devDependencies": {
35
36
  "react": "^18.0.0",
@@ -0,0 +1,226 @@
1
+ /* eslint-disable indent */
2
+ /* eslint-disable camelcase */
3
+ import React, { useMemo, useEffect, useRef } from 'react';
4
+ import { scaleLinear } from 'd3-scale';
5
+ import { scale as vega_scale } from 'vega-scale';
6
+ import { axisBottom, axisLeft } from 'd3-axis';
7
+ import { ascending } from 'd3-array';
8
+ import { select } from 'd3-selection';
9
+ import { capitalize } from '@vitessce/utils';
10
+
11
+ const scaleBand = vega_scale('band');
12
+
13
+ const OBS_KEY = 'obsId';
14
+ const FEATURE_KEY = 'value';
15
+
16
+ function componentToHex(c) {
17
+ const hex = c.toString(16);
18
+ return hex.length === 1 ? `0${hex}` : hex;
19
+ }
20
+
21
+ function rgbToHex(color) {
22
+ return `#${componentToHex(color[0])}${componentToHex(color[1])}${componentToHex(color[2])}`;
23
+ }
24
+
25
+ export default function FeatureBarPlot(props) {
26
+ const {
27
+ yMin,
28
+ yMax,
29
+ yUnits,
30
+ jitter,
31
+ colors,
32
+ data,
33
+ theme,
34
+ width,
35
+ height,
36
+ marginTop = 5,
37
+ marginRight = 5,
38
+ marginLeft = 80,
39
+ marginBottom,
40
+ obsType,
41
+ cellHighlight,
42
+ cellSetSelection,
43
+ additionalCellSets,
44
+ cellSetColor,
45
+ featureType,
46
+ featureValueType,
47
+ featureName,
48
+ onBarSelect,
49
+ onBarHighlight,
50
+ } = props;
51
+
52
+ // TODO: use a more descriptive name than setsSave.
53
+ const setsSave = useMemo(() => {
54
+ const result = new Map();
55
+ cellSetSelection?.forEach((obsSetPath) => {
56
+ // TODO: this does not use the full set path for comparison.
57
+ const selectedElement = obsSetPath[1];
58
+ // TODO: this is only considering the first set grouping in the tree.
59
+ // TODO: use sets-utils to traverse sets tree.
60
+ additionalCellSets?.tree?.[0]?.children?.forEach((child) => {
61
+ if (child.name === selectedElement) {
62
+ child.set.forEach(([obsId]) => {
63
+ const info = { name: '', id: '', color: [] };
64
+ info.name = selectedElement;
65
+ info.id = obsId;
66
+ cellSetColor.forEach((color) => {
67
+ if (color.path[1] === selectedElement) {
68
+ info.color = color.color;
69
+ }
70
+ });
71
+ result.set(info.id, info);
72
+ });
73
+ }
74
+ });
75
+ });
76
+ return result;
77
+ }, [cellSetSelection, additionalCellSets, cellSetColor]);
78
+
79
+ const svgRef = useRef();
80
+ // Get the max characters in an axis label for autsizing the bottom margin.
81
+ const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
82
+ // eslint-disable-next-line no-param-reassign
83
+ acc = acc === undefined || val[OBS_KEY].length > acc ? val[OBS_KEY].length : acc;
84
+ return acc;
85
+ }, 0), [data]);
86
+
87
+ useEffect(() => {
88
+ const domElement = svgRef.current;
89
+
90
+ const unitSuffix = yUnits ? ` (${yUnits})` : '';
91
+ const yTitle = `${capitalize(featureName)}${unitSuffix}`;
92
+
93
+ const xTitle = `${capitalize(obsType)}`;
94
+
95
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
96
+ // so the perpendicular distance to the bottom of the labels is proportional to the
97
+ // square root of the length of the labels along the imaginary hypotenuse.
98
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
99
+ const autoMarginBottom = marginBottom
100
+ || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
101
+
102
+ const foregroundColor = (theme === 'dark' ? 'lightgray' : 'black');
103
+
104
+ data.sort((a, b) => ascending(a[FEATURE_KEY], b[FEATURE_KEY]));
105
+
106
+ const svg = select(domElement);
107
+ svg.selectAll('g').remove();
108
+ svg
109
+ .attr('width', width)
110
+ .attr('height', height);
111
+
112
+ const g = svg
113
+ .append('g')
114
+ .attr('width', width)
115
+ .attr('height', height);
116
+
117
+ const innerWidth = width - marginLeft;
118
+ const innerHeight = height - autoMarginBottom;
119
+
120
+ const xScale = scaleBand()
121
+ .range([marginLeft, width - marginRight])
122
+ .domain(data.map(d => d[OBS_KEY]))
123
+ .padding(0.1);
124
+
125
+ // For the y domain, use the yMin prop
126
+ // to support a use case such as 'Aspect Ratio',
127
+ // where the domain minimum should be 1 rather than 0.
128
+ const yScale = scaleLinear()
129
+ .domain([yMin, yMax])
130
+ .range([innerHeight, marginTop]);
131
+
132
+ // Bar areas
133
+ g
134
+ .selectAll('bar')
135
+ .data(data)
136
+ .enter()
137
+ .append('rect')
138
+ .attr('x', d => xScale(d[OBS_KEY]))
139
+ .attr('y', d => yScale(d[FEATURE_KEY]))
140
+ .attr('width', xScale.bandwidth())
141
+ .attr('height', d => innerHeight - yScale(d[FEATURE_KEY]))
142
+ .style('fill', (d) => {
143
+ if (d[OBS_KEY] === cellHighlight) return 'orange';
144
+ if (setsSave.has(d[OBS_KEY])) {
145
+ const { color } = setsSave.get(d[OBS_KEY]);
146
+ return rgbToHex(color);
147
+ }
148
+ return foregroundColor;
149
+ })
150
+ .style('cursor', 'pointer')
151
+ // eslint-disable-next-line no-unused-vars
152
+ .on('click', (event, d) => {
153
+ onBarSelect(d[OBS_KEY]);
154
+ })
155
+ // eslint-disable-next-line no-unused-vars
156
+ .on('mouseover', (event, d) => {
157
+ onBarHighlight(d[OBS_KEY]);
158
+ })
159
+ .on('mouseout', () => {
160
+ onBarHighlight(null);
161
+ });
162
+
163
+
164
+ const axis = axisLeft(yScale);
165
+ axis.tickFormat(d => `${Math.round(d * 10000000)} µm`);
166
+ // Y-axis ticks
167
+ g
168
+ .append('g')
169
+ .attr('transform', `translate(${marginLeft},0)`)
170
+ .call(axis)
171
+ .selectAll('text')
172
+ .style('font-size', '11px');
173
+
174
+ // X-axis ticks
175
+ g
176
+ .append('g')
177
+ .attr('transform', `translate(0,${innerHeight})`)
178
+ .style('font-size', '14px')
179
+ .call(axisBottom(xScale))
180
+ .selectAll('text')
181
+ .style('font-size', '11px')
182
+ .attr('dx', '-6px')
183
+ .attr('dy', '6px')
184
+ .attr('transform', 'rotate(-45)')
185
+ .style('text-anchor', 'end');
186
+
187
+ // Y-axis title
188
+ g
189
+ .append('text')
190
+ .attr('text-anchor', 'middle')
191
+ .attr('x', -innerHeight / 2)
192
+ .attr('y', 15)
193
+ .attr('transform', 'rotate(-90)')
194
+ .text(yTitle)
195
+ .style('font-size', '12px')
196
+ .style('fill', foregroundColor);
197
+
198
+ // X-axis title
199
+ g
200
+ .append('text')
201
+ .attr('text-anchor', 'middle')
202
+ .attr('x', marginLeft + innerWidth / 2)
203
+ .attr('y', height - 10)
204
+ .text(xTitle)
205
+ .style('font-size', '12px')
206
+ .style('fill', foregroundColor);
207
+ }, [width, height, data, marginLeft, marginBottom, colors,
208
+ jitter, theme, yMin, marginTop, marginRight, featureType,
209
+ featureValueType, yUnits, obsType,
210
+ maxCharactersForLabel, yMax, featureName, onBarSelect, onBarHighlight,
211
+ cellHighlight, setsSave,
212
+ ]);
213
+
214
+ return (
215
+ <svg
216
+ ref={svgRef}
217
+ style={{
218
+ top: 0,
219
+ left: 0,
220
+ width: `${width}px`,
221
+ height: `${height}px`,
222
+ position: 'relative',
223
+ }}
224
+ />
225
+ );
226
+ }
@@ -0,0 +1,157 @@
1
+ import React, { useMemo, useCallback } from 'react';
2
+ import {
3
+ TitleInfo,
4
+ useCoordination,
5
+ useLoaders,
6
+ useUrls,
7
+ useReady,
8
+ useGridItemSize,
9
+ useFeatureSelection,
10
+ useObsFeatureMatrixIndices,
11
+ useFeatureLabelsData,
12
+ } from '@vitessce/vit-s';
13
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
14
+ import { setObsSelection } from '@vitessce/sets-utils';
15
+ import FeatureBarPlot from './FeatureBarPlot.js';
16
+ import { useStyles } from './styles.js';
17
+
18
+
19
+ export function FeatureBarPlotSubscriber(props) {
20
+ const {
21
+ coordinationScopes,
22
+ removeGridComponent,
23
+ theme,
24
+ yMin = 0,
25
+ yUnits = null,
26
+ } = props;
27
+
28
+ const classes = useStyles();
29
+ const loaders = useLoaders();
30
+
31
+ // Get "props" from the coordination space.
32
+ const [{
33
+ dataset,
34
+ obsType,
35
+ featureType,
36
+ featureValueType,
37
+ featureSelection: geneSelection,
38
+ featureValueTransform,
39
+ featureValueTransformCoefficient,
40
+ obsHighlight: cellHighlight,
41
+ additionalObsSets: additionalCellSets,
42
+ obsSetSelection: cellSetSelection,
43
+ obsSetColor: cellSetColor,
44
+ }, {
45
+ setObsSetSelection: setCellSetSelection,
46
+ setObsHighlight: setCellHighlight,
47
+ setObsSetColor: setCellSetColor,
48
+ setObsColorEncoding: setCellColorEncoding,
49
+ setAdditionalObsSets: setAdditionalCellSets,
50
+ }] = useCoordination(
51
+ COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_BAR_PLOT],
52
+ coordinationScopes,
53
+ );
54
+ // console.log("BarPlot: " + cellHighlight)
55
+ const [width, height, containerRef] = useGridItemSize();
56
+
57
+ // Get data from loaders using the data hooks.
58
+ // eslint-disable-next-line no-unused-vars
59
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(
60
+ loaders, dataset, false, geneSelection,
61
+ { obsType, featureType, featureValueType },
62
+ );
63
+ // TODO: support multiple feature labels using featureLabelsType coordination values.
64
+ const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(
65
+ loaders, dataset, false, {}, {},
66
+ { featureType },
67
+ );
68
+ const [
69
+ { obsIndex }, matrixIndicesStatus, matrixIndicesUrls,
70
+ ] = useObsFeatureMatrixIndices(
71
+ loaders, dataset, false,
72
+ { obsType, featureType, featureValueType },
73
+ );
74
+ const isReady = useReady([
75
+ featureSelectionStatus,
76
+ matrixIndicesStatus,
77
+ featureLabelsStatus,
78
+ ]);
79
+ const urls = useUrls([
80
+ featureLabelsUrls,
81
+ matrixIndicesUrls,
82
+ ]);
83
+
84
+ const onBarSelect = useCallback((obsId) => {
85
+ const obsIdsToSelect = [obsId];
86
+ setObsSelection(
87
+ obsIdsToSelect, additionalCellSets, cellSetColor,
88
+ setCellSetSelection, setAdditionalCellSets, setCellSetColor,
89
+ setCellColorEncoding,
90
+ );
91
+ }, [additionalCellSets, cellSetColor, setCellColorEncoding,
92
+ setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
93
+
94
+ const onBarHighlight = useCallback((obsId) => {
95
+ setCellHighlight(obsId);
96
+ }, []);
97
+
98
+ const firstGeneSelected = geneSelection && geneSelection.length >= 1
99
+ ? (featureLabelsMap?.get(geneSelection[0]) || geneSelection[0])
100
+ : null;
101
+
102
+ const [expressionArr, expressionMax] = useMemo(() => {
103
+ if (firstGeneSelected && expressionData && obsIndex) {
104
+ let exprMax = -Infinity;
105
+ const cellIndices = {};
106
+ for (let i = 0; i < obsIndex.length; i += 1) {
107
+ cellIndices[obsIndex[i]] = i;
108
+ }
109
+ const exprValues = obsIndex.map((obsId, cellIndex) => {
110
+ const value = expressionData[0][cellIndex];
111
+ exprMax = Math.max(value, exprMax);
112
+ return { obsId, value, feature: firstGeneSelected };
113
+ });
114
+ return [exprValues, exprMax];
115
+ }
116
+ return [null, null];
117
+ }, [expressionData, obsIndex, geneSelection, theme,
118
+ featureValueTransform, featureValueTransformCoefficient,
119
+ firstGeneSelected,
120
+ ]);
121
+
122
+ return (
123
+ <TitleInfo
124
+ title={`Feature Values${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`}
125
+ removeGridComponent={removeGridComponent}
126
+ urls={urls}
127
+ theme={theme}
128
+ isReady={isReady}
129
+ >
130
+ <div ref={containerRef} className={classes.vegaContainer}>
131
+ {expressionArr ? (
132
+ <FeatureBarPlot
133
+ yMin={yMin}
134
+ yMax={expressionMax}
135
+ yUnits={yUnits}
136
+ data={expressionArr}
137
+ theme={theme}
138
+ width={width}
139
+ height={height}
140
+ obsType={obsType}
141
+ cellHighlight={cellHighlight}
142
+ cellSetSelection={cellSetSelection}
143
+ additionalCellSets={additionalCellSets}
144
+ cellSetColor={cellSetColor}
145
+ featureType={featureType}
146
+ featureValueType={featureValueType}
147
+ featureName={firstGeneSelected}
148
+ onBarSelect={onBarSelect}
149
+ onBarHighlight={onBarHighlight}
150
+ />
151
+ ) : (
152
+ <span>Select a {featureType}.</span>
153
+ )}
154
+ </div>
155
+ </TitleInfo>
156
+ );
157
+ }