@vitessce/statistical-plots 3.3.11 → 3.4.0

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,gEAwMC"}
@@ -0,0 +1,173 @@
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
+ }, [cellSetSelection, additionalCellSets, cellSetColor]);
48
+ const svgRef = useRef();
49
+ // Get the max characters in an axis label for autsizing the bottom margin.
50
+ const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
51
+ // eslint-disable-next-line no-param-reassign
52
+ acc = acc === undefined || val[OBS_KEY].length > acc ? val[OBS_KEY].length : acc;
53
+ return acc;
54
+ }, 0), [data]);
55
+ useEffect(() => {
56
+ const domElement = svgRef.current;
57
+ const unitSuffix = yUnits ? ` (${yUnits})` : '';
58
+ const yTitle = `${capitalize(featureName)}${unitSuffix}`;
59
+ const xTitle = `${capitalize(obsType)}`;
60
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
61
+ // so the perpendicular distance to the bottom of the labels is proportional to the
62
+ // square root of the length of the labels along the imaginary hypotenuse.
63
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
64
+ const autoMarginBottom = marginBottom
65
+ || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
66
+ const foregroundColor = (theme === 'dark' ? 'lightgray' : 'black');
67
+ data.sort((a, b) => ascending(a[FEATURE_KEY], b[FEATURE_KEY]));
68
+ const svg = select(domElement);
69
+ svg.selectAll('g').remove();
70
+ svg
71
+ .attr('width', width)
72
+ .attr('height', height);
73
+ const g = svg
74
+ .append('g')
75
+ .attr('width', width)
76
+ .attr('height', height);
77
+ const innerWidth = width - marginLeft;
78
+ const innerHeight = height - autoMarginBottom;
79
+ const xScale = scaleBand()
80
+ .range([marginLeft, width - marginRight])
81
+ .domain(data.map(d => d[OBS_KEY]))
82
+ .padding(0.1);
83
+ // For the y domain, use the yMin prop
84
+ // to support a use case such as 'Aspect Ratio',
85
+ // where the domain minimum should be 1 rather than 0.
86
+ const yScale = scaleLinear()
87
+ .domain([yMin, yMax])
88
+ .range([innerHeight, marginTop]);
89
+ // Bar areas
90
+ g
91
+ .selectAll('bar')
92
+ .data(data)
93
+ .enter()
94
+ .append('rect')
95
+ .attr('x', d => xScale(d[OBS_KEY]))
96
+ .attr('y', d => yScale(d[FEATURE_KEY]))
97
+ .attr('width', xScale.bandwidth())
98
+ .attr('height', d => innerHeight - yScale(d[FEATURE_KEY]))
99
+ .style('fill', (d) => {
100
+ if (d[OBS_KEY] === cellHighlight)
101
+ return 'orange';
102
+ if (setsSave.has(d[OBS_KEY])) {
103
+ const { color } = setsSave.get(d[OBS_KEY]);
104
+ return rgbToHex(color);
105
+ }
106
+ return foregroundColor;
107
+ })
108
+ .style('cursor', 'pointer')
109
+ // eslint-disable-next-line no-unused-vars
110
+ .on('click', (event, d) => {
111
+ onBarSelect(d[OBS_KEY]);
112
+ })
113
+ // eslint-disable-next-line no-unused-vars
114
+ .on('mouseover', (event, d) => {
115
+ onBarHighlight(d[OBS_KEY]);
116
+ })
117
+ .on('mouseout', () => {
118
+ onBarHighlight(null);
119
+ });
120
+ const axis = axisLeft(yScale);
121
+ axis.tickFormat(d => `${Math.round(d * 10000000)} µm`);
122
+ // Y-axis ticks
123
+ g
124
+ .append('g')
125
+ .attr('transform', `translate(${marginLeft},0)`)
126
+ .call(axis)
127
+ .selectAll('text')
128
+ .style('font-size', '11px');
129
+ // X-axis ticks
130
+ g
131
+ .append('g')
132
+ .attr('transform', `translate(0,${innerHeight})`)
133
+ .style('font-size', '14px')
134
+ .call(axisBottom(xScale))
135
+ .selectAll('text')
136
+ .style('font-size', '11px')
137
+ .attr('dx', '-6px')
138
+ .attr('dy', '6px')
139
+ .attr('transform', 'rotate(-45)')
140
+ .style('text-anchor', 'end');
141
+ // Y-axis title
142
+ g
143
+ .append('text')
144
+ .attr('text-anchor', 'middle')
145
+ .attr('x', -innerHeight / 2)
146
+ .attr('y', 15)
147
+ .attr('transform', 'rotate(-90)')
148
+ .text(yTitle)
149
+ .style('font-size', '12px')
150
+ .style('fill', foregroundColor);
151
+ // X-axis title
152
+ g
153
+ .append('text')
154
+ .attr('text-anchor', 'middle')
155
+ .attr('x', marginLeft + innerWidth / 2)
156
+ .attr('y', height - 10)
157
+ .text(xTitle)
158
+ .style('font-size', '12px')
159
+ .style('fill', foregroundColor);
160
+ }, [width, height, data, marginLeft, marginBottom, colors,
161
+ jitter, theme, yMin, marginTop, marginRight, featureType,
162
+ featureValueType, yUnits, obsType,
163
+ maxCharactersForLabel, yMax, featureName, onBarSelect, onBarHighlight,
164
+ cellHighlight, setsSave,
165
+ ]);
166
+ return (_jsx("svg", { ref: svgRef, style: {
167
+ top: 0,
168
+ left: 0,
169
+ width: `${width}px`,
170
+ height: `${height}px`,
171
+ position: 'relative',
172
+ } }));
173
+ }
@@ -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.11",
3
+ "version": "3.4.0",
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.11",
29
- "@vitessce/vit-s": "3.3.11",
30
- "@vitessce/constants-internal": "3.3.11",
31
- "@vitessce/utils": "3.3.11",
32
- "@vitessce/vega": "3.3.11"
28
+ "d3-format": "^3.1.0",
29
+ "@vitessce/sets-utils": "3.4.0",
30
+ "@vitessce/constants-internal": "3.4.0",
31
+ "@vitessce/utils": "3.4.0",
32
+ "@vitessce/vega": "3.4.0",
33
+ "@vitessce/vit-s": "3.4.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "react": "^18.0.0",
@@ -0,0 +1,225 @@
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
+ }, [cellSetSelection, additionalCellSets, cellSetColor]);
77
+
78
+ const svgRef = useRef();
79
+ // Get the max characters in an axis label for autsizing the bottom margin.
80
+ const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
81
+ // eslint-disable-next-line no-param-reassign
82
+ acc = acc === undefined || val[OBS_KEY].length > acc ? val[OBS_KEY].length : acc;
83
+ return acc;
84
+ }, 0), [data]);
85
+
86
+ useEffect(() => {
87
+ const domElement = svgRef.current;
88
+
89
+ const unitSuffix = yUnits ? ` (${yUnits})` : '';
90
+ const yTitle = `${capitalize(featureName)}${unitSuffix}`;
91
+
92
+ const xTitle = `${capitalize(obsType)}`;
93
+
94
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
95
+ // so the perpendicular distance to the bottom of the labels is proportional to the
96
+ // square root of the length of the labels along the imaginary hypotenuse.
97
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
98
+ const autoMarginBottom = marginBottom
99
+ || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
100
+
101
+ const foregroundColor = (theme === 'dark' ? 'lightgray' : 'black');
102
+
103
+ data.sort((a, b) => ascending(a[FEATURE_KEY], b[FEATURE_KEY]));
104
+
105
+ const svg = select(domElement);
106
+ svg.selectAll('g').remove();
107
+ svg
108
+ .attr('width', width)
109
+ .attr('height', height);
110
+
111
+ const g = svg
112
+ .append('g')
113
+ .attr('width', width)
114
+ .attr('height', height);
115
+
116
+ const innerWidth = width - marginLeft;
117
+ const innerHeight = height - autoMarginBottom;
118
+
119
+ const xScale = scaleBand()
120
+ .range([marginLeft, width - marginRight])
121
+ .domain(data.map(d => d[OBS_KEY]))
122
+ .padding(0.1);
123
+
124
+ // For the y domain, use the yMin prop
125
+ // to support a use case such as 'Aspect Ratio',
126
+ // where the domain minimum should be 1 rather than 0.
127
+ const yScale = scaleLinear()
128
+ .domain([yMin, yMax])
129
+ .range([innerHeight, marginTop]);
130
+
131
+ // Bar areas
132
+ g
133
+ .selectAll('bar')
134
+ .data(data)
135
+ .enter()
136
+ .append('rect')
137
+ .attr('x', d => xScale(d[OBS_KEY]))
138
+ .attr('y', d => yScale(d[FEATURE_KEY]))
139
+ .attr('width', xScale.bandwidth())
140
+ .attr('height', d => innerHeight - yScale(d[FEATURE_KEY]))
141
+ .style('fill', (d) => {
142
+ if (d[OBS_KEY] === cellHighlight) return 'orange';
143
+ if (setsSave.has(d[OBS_KEY])) {
144
+ const { color } = setsSave.get(d[OBS_KEY]);
145
+ return rgbToHex(color);
146
+ }
147
+ return foregroundColor;
148
+ })
149
+ .style('cursor', 'pointer')
150
+ // eslint-disable-next-line no-unused-vars
151
+ .on('click', (event, d) => {
152
+ onBarSelect(d[OBS_KEY]);
153
+ })
154
+ // eslint-disable-next-line no-unused-vars
155
+ .on('mouseover', (event, d) => {
156
+ onBarHighlight(d[OBS_KEY]);
157
+ })
158
+ .on('mouseout', () => {
159
+ onBarHighlight(null);
160
+ });
161
+
162
+
163
+ const axis = axisLeft(yScale);
164
+ axis.tickFormat(d => `${Math.round(d * 10000000)} µm`);
165
+ // Y-axis ticks
166
+ g
167
+ .append('g')
168
+ .attr('transform', `translate(${marginLeft},0)`)
169
+ .call(axis)
170
+ .selectAll('text')
171
+ .style('font-size', '11px');
172
+
173
+ // X-axis ticks
174
+ g
175
+ .append('g')
176
+ .attr('transform', `translate(0,${innerHeight})`)
177
+ .style('font-size', '14px')
178
+ .call(axisBottom(xScale))
179
+ .selectAll('text')
180
+ .style('font-size', '11px')
181
+ .attr('dx', '-6px')
182
+ .attr('dy', '6px')
183
+ .attr('transform', 'rotate(-45)')
184
+ .style('text-anchor', 'end');
185
+
186
+ // Y-axis title
187
+ g
188
+ .append('text')
189
+ .attr('text-anchor', 'middle')
190
+ .attr('x', -innerHeight / 2)
191
+ .attr('y', 15)
192
+ .attr('transform', 'rotate(-90)')
193
+ .text(yTitle)
194
+ .style('font-size', '12px')
195
+ .style('fill', foregroundColor);
196
+
197
+ // X-axis title
198
+ g
199
+ .append('text')
200
+ .attr('text-anchor', 'middle')
201
+ .attr('x', marginLeft + innerWidth / 2)
202
+ .attr('y', height - 10)
203
+ .text(xTitle)
204
+ .style('font-size', '12px')
205
+ .style('fill', foregroundColor);
206
+ }, [width, height, data, marginLeft, marginBottom, colors,
207
+ jitter, theme, yMin, marginTop, marginRight, featureType,
208
+ featureValueType, yUnits, obsType,
209
+ maxCharactersForLabel, yMax, featureName, onBarSelect, onBarHighlight,
210
+ cellHighlight, setsSave,
211
+ ]);
212
+
213
+ return (
214
+ <svg
215
+ ref={svgRef}
216
+ style={{
217
+ top: 0,
218
+ left: 0,
219
+ width: `${width}px`,
220
+ height: `${height}px`,
221
+ position: 'relative',
222
+ }}
223
+ />
224
+ );
225
+ }
@@ -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
+ }