@vitessce/statistical-plots 3.3.4 → 3.3.5
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/dist/index.js +16714 -12197
- package/dist-tsc/CellSetExpressionPlot.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlot.js +214 -219
- package/dist-tsc/CellSetExpressionPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlotSubscriber.js +3 -3
- package/dist-tsc/ExpressionHistogram.d.ts.map +1 -1
- package/dist-tsc/ExpressionHistogram.js +7 -4
- package/dist-tsc/ExpressionHistogramSubscriber.d.ts.map +1 -1
- package/dist-tsc/ExpressionHistogramSubscriber.js +3 -4
- package/package.json +11 -6
- package/src/CellSetExpressionPlot.js +265 -233
- package/src/CellSetExpressionPlotSubscriber.js +8 -2
- package/src/ExpressionHistogram.js +11 -3
- package/src/ExpressionHistogramSubscriber.js +6 -4
@@ -1,9 +1,65 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import {
|
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 {
|
8
|
+
bin,
|
9
|
+
min,
|
10
|
+
max,
|
11
|
+
rollup as d3_rollup,
|
12
|
+
mean as d3_mean,
|
13
|
+
deviation as d3_deviation,
|
14
|
+
ascending as d3_ascending,
|
15
|
+
map as d3_map,
|
16
|
+
quantileSorted,
|
17
|
+
} from 'd3-array';
|
18
|
+
import { area as d3_area, curveBasis } from 'd3-shape';
|
19
|
+
import { select } from 'd3-selection';
|
4
20
|
import { colorArrayToString } from '@vitessce/sets-utils';
|
5
21
|
import { capitalize } from '@vitessce/utils';
|
6
22
|
|
23
|
+
const scaleBand = vega_scale('band');
|
24
|
+
|
25
|
+
const GROUP_KEY = 'set';
|
26
|
+
const VALUE_KEY = 'value';
|
27
|
+
|
28
|
+
// Reference: https://github.com/d3/d3-array/issues/180#issuecomment-851378012
|
29
|
+
function summarize(iterable, keepZeros) {
|
30
|
+
const values = d3_map(iterable, d => d[VALUE_KEY])
|
31
|
+
.filter(d => keepZeros || d !== 0.0)
|
32
|
+
.sort(d3_ascending);
|
33
|
+
const minVal = values[0];
|
34
|
+
const maxVal = values[values.length - 1];
|
35
|
+
const q1 = quantileSorted(values, 0.25);
|
36
|
+
const q2 = quantileSorted(values, 0.5);
|
37
|
+
const q3 = quantileSorted(values, 0.75);
|
38
|
+
const iqr = q3 - q1; // interquartile range
|
39
|
+
const r0 = Math.max(minVal, q1 - iqr * 1.5);
|
40
|
+
const r1 = Math.min(maxVal, q3 + iqr * 1.5);
|
41
|
+
let i = -1;
|
42
|
+
while (values[++i] < r0);
|
43
|
+
const w0 = values[i];
|
44
|
+
while (values[++i] <= r1);
|
45
|
+
const w1 = values[i - 1];
|
46
|
+
|
47
|
+
// Chauvenet
|
48
|
+
// Reference: https://en.wikipedia.org/wiki/Chauvenet%27s_criterion
|
49
|
+
const mean = d3_mean(values);
|
50
|
+
const stdv = d3_deviation(values);
|
51
|
+
const c0 = mean - 3 * stdv;
|
52
|
+
const c1 = mean + 3 * stdv;
|
53
|
+
|
54
|
+
return {
|
55
|
+
quartiles: [q1, q2, q3],
|
56
|
+
range: [r0, r1],
|
57
|
+
whiskers: [w0, w1],
|
58
|
+
chauvenetRange: [c0, c1],
|
59
|
+
nonOutliers: values.filter(v => c0 <= v && v <= c1),
|
60
|
+
};
|
61
|
+
}
|
62
|
+
|
7
63
|
/**
|
8
64
|
* Gene expression histogram displayed as a bar chart,
|
9
65
|
* implemented with the VegaPlot component.
|
@@ -27,252 +83,228 @@ import { capitalize } from '@vitessce/utils';
|
|
27
83
|
*/
|
28
84
|
export default function CellSetExpressionPlot(props) {
|
29
85
|
const {
|
30
|
-
|
86
|
+
yMin: yMinProp,
|
87
|
+
yUnits,
|
88
|
+
jitter,
|
31
89
|
colors,
|
32
90
|
data,
|
33
91
|
theme,
|
34
92
|
width,
|
35
93
|
height,
|
36
|
-
|
94
|
+
marginTop = 5,
|
95
|
+
marginRight = 5,
|
96
|
+
marginLeft = 50,
|
37
97
|
marginBottom,
|
38
98
|
obsType,
|
99
|
+
featureType,
|
39
100
|
featureValueType,
|
40
101
|
featureValueTransformName,
|
41
102
|
} = props;
|
103
|
+
|
104
|
+
const svgRef = useRef();
|
105
|
+
|
42
106
|
// Get the max characters in an axis label for autsizing the bottom margin.
|
43
|
-
const maxCharactersForLabel = data.reduce((acc, val) => {
|
107
|
+
const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
|
44
108
|
// eslint-disable-next-line no-param-reassign
|
45
|
-
acc = acc === undefined || val.
|
109
|
+
acc = acc === undefined || val[GROUP_KEY].length > acc ? val[GROUP_KEY].length : acc;
|
46
110
|
return acc;
|
47
|
-
}, 0);
|
48
|
-
// Use a square-root term because the angle of the labels is 45 degrees (see below)
|
49
|
-
// so the perpendicular distance to the bottom of the labels is proportional to the
|
50
|
-
// square root of the length of the labels along the imaginary hypotenuse.
|
51
|
-
// 30 is an estimate of the pixel size of a given character and seems to work well.
|
52
|
-
const autoMarginBottom = marginBottom
|
53
|
-
|| 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
|
54
|
-
// Manually set the color scale so that Vega-Lite does
|
55
|
-
// not choose the colors automatically.
|
56
|
-
const colorScale = {
|
57
|
-
domain: colors.map(d => d.name),
|
58
|
-
range: colors.map(d => colorArrayToString(d.color)),
|
59
|
-
};
|
111
|
+
}, 0), [data]);
|
60
112
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
{
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
update: {
|
246
|
-
y: { scale: 'yscale', field: 'q1' },
|
247
|
-
y2: { scale: 'yscale', field: 'q3' },
|
248
|
-
xc: { signal: 'bandWidth / 2' },
|
249
|
-
},
|
250
|
-
},
|
251
|
-
},
|
252
|
-
{
|
253
|
-
type: 'rect',
|
254
|
-
from: { data: 'summary' },
|
255
|
-
encode: {
|
256
|
-
enter: {
|
257
|
-
fill: { value: rectColor },
|
258
|
-
height: { value: 2 },
|
259
|
-
width: { value: 8 },
|
260
|
-
},
|
261
|
-
update: {
|
262
|
-
y: { scale: 'yscale', field: 'median' },
|
263
|
-
xc: { signal: 'bandWidth / 2' },
|
264
|
-
},
|
265
|
-
},
|
266
|
-
},
|
267
|
-
],
|
268
|
-
},
|
269
|
-
],
|
270
|
-
};
|
113
|
+
useEffect(() => {
|
114
|
+
const domElement = svgRef.current;
|
115
|
+
|
116
|
+
const transformPrefix = (featureValueTransformName && featureValueTransformName !== 'None')
|
117
|
+
? `${featureValueTransformName}-Transformed `
|
118
|
+
: '';
|
119
|
+
const unitSuffix = yUnits ? ` (${yUnits})` : '';
|
120
|
+
const yTitle = `${transformPrefix}${capitalize(featureValueType)}${unitSuffix}`;
|
121
|
+
|
122
|
+
const xTitle = `${capitalize(obsType)} Set`;
|
123
|
+
|
124
|
+
// Use a square-root term because the angle of the labels is 45 degrees (see below)
|
125
|
+
// so the perpendicular distance to the bottom of the labels is proportional to the
|
126
|
+
// square root of the length of the labels along the imaginary hypotenuse.
|
127
|
+
// 30 is an estimate of the pixel size of a given character and seems to work well.
|
128
|
+
const autoMarginBottom = marginBottom
|
129
|
+
|| 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
|
130
|
+
|
131
|
+
const rectColor = (theme === 'dark' ? 'white' : 'black');
|
132
|
+
|
133
|
+
const svg = select(domElement);
|
134
|
+
svg.selectAll('g').remove();
|
135
|
+
svg
|
136
|
+
.attr('width', width)
|
137
|
+
.attr('height', height);
|
138
|
+
|
139
|
+
const g = svg
|
140
|
+
.append('g')
|
141
|
+
.attr('width', width)
|
142
|
+
.attr('height', height);
|
143
|
+
|
144
|
+
const groupNames = colors.map(d => d.name);
|
145
|
+
|
146
|
+
// Manually set the color scale so that Vega-Lite does
|
147
|
+
// not choose the colors automatically.
|
148
|
+
const colorScale = {
|
149
|
+
domain: colors.map(d => d.name),
|
150
|
+
range: colors.map(d => colorArrayToString(d.color)),
|
151
|
+
};
|
152
|
+
|
153
|
+
// Remove outliers on a per-group basis.
|
154
|
+
const groupedSummaries = Array.from(
|
155
|
+
d3_rollup(data, groupData => summarize(groupData, true), d => d[GROUP_KEY]),
|
156
|
+
([key, value]) => ({ key, value }),
|
157
|
+
);
|
158
|
+
const groupedData = groupedSummaries
|
159
|
+
.map(({ key, value }) => ({ key, value: value.nonOutliers }));
|
160
|
+
const trimmedData = groupedData.map(kv => kv.value).flat();
|
161
|
+
|
162
|
+
const innerWidth = width - marginLeft;
|
163
|
+
const innerHeight = height - autoMarginBottom;
|
164
|
+
|
165
|
+
const xGroup = scaleBand()
|
166
|
+
.range([marginLeft, width - marginRight])
|
167
|
+
.domain(groupNames)
|
168
|
+
.padding(0.1);
|
169
|
+
|
170
|
+
const yMin = (yMinProp === null ? Math.min(0, min(trimmedData)) : yMinProp);
|
171
|
+
|
172
|
+
// For the y domain, use the yMin prop
|
173
|
+
// to support a use case such as 'Aspect Ratio',
|
174
|
+
// where the domain minimum should be 1 rather than 0.
|
175
|
+
const y = scaleLinear()
|
176
|
+
.domain([yMin, max(trimmedData)])
|
177
|
+
.range([innerHeight, marginTop]);
|
178
|
+
|
179
|
+
const histogram = bin()
|
180
|
+
.thresholds(y.ticks(16))
|
181
|
+
.domain(y.domain());
|
182
|
+
|
183
|
+
const groupBins = groupedData.map(kv => ({ key: kv.key, value: histogram(kv.value) }));
|
184
|
+
|
185
|
+
const groupBinsMax = max(groupBins.flatMap(d => d.value.map(v => v.length)));
|
186
|
+
|
187
|
+
const x = scaleLinear()
|
188
|
+
.domain([-groupBinsMax, groupBinsMax])
|
189
|
+
.range([0, xGroup.bandwidth()]);
|
190
|
+
|
191
|
+
const area = d3_area()
|
192
|
+
.x0(d => (jitter ? x(0) : x(-d.length)))
|
193
|
+
.x1(d => x(d.length))
|
194
|
+
.y(d => y(d.x0))
|
195
|
+
.curve(curveBasis);
|
196
|
+
|
197
|
+
// Violin areas
|
198
|
+
g
|
199
|
+
.selectAll('violin')
|
200
|
+
.data(groupBins)
|
201
|
+
.enter()
|
202
|
+
.append('g')
|
203
|
+
.attr('transform', d => `translate(${xGroup(d.key)},0)`)
|
204
|
+
.style('fill', d => colorScale.range[groupNames.indexOf(d.key)])
|
205
|
+
.append('path')
|
206
|
+
.datum(d => d.value)
|
207
|
+
.style('stroke', 'none')
|
208
|
+
.attr('d', d => area(d));
|
209
|
+
|
210
|
+
// Whiskers
|
211
|
+
const whiskerGroups = g.selectAll('whiskers')
|
212
|
+
.data(groupedSummaries)
|
213
|
+
.enter()
|
214
|
+
.append('g')
|
215
|
+
.attr('transform', d => `translate(${xGroup(d.key)},0)`);
|
216
|
+
whiskerGroups.append('line')
|
217
|
+
.datum(d => d.value)
|
218
|
+
.attr('stroke', rectColor)
|
219
|
+
.attr('x1', xGroup.bandwidth() / 2)
|
220
|
+
.attr('x2', xGroup.bandwidth() / 2)
|
221
|
+
.attr('y1', d => y(d.quartiles[0]))
|
222
|
+
.attr('y2', d => y(d.quartiles[2]))
|
223
|
+
.attr('stroke-width', 2);
|
224
|
+
whiskerGroups.append('line')
|
225
|
+
.datum(d => d.value)
|
226
|
+
.attr('stroke', rectColor)
|
227
|
+
.attr('x1', xGroup.bandwidth() / 2 - (jitter ? 0 : 4))
|
228
|
+
.attr('x2', xGroup.bandwidth() / 2 + 4)
|
229
|
+
.attr('y1', d => y(d.quartiles[1]))
|
230
|
+
.attr('y2', d => y(d.quartiles[1]))
|
231
|
+
.attr('stroke-width', 2);
|
232
|
+
|
233
|
+
// Jittered points
|
234
|
+
if (jitter) {
|
235
|
+
groupedData.forEach(({ key, value }) => {
|
236
|
+
const groupG = g.append('g');
|
237
|
+
groupG.selectAll('point')
|
238
|
+
.data(value)
|
239
|
+
.enter()
|
240
|
+
.append('circle')
|
241
|
+
.attr('transform', `translate(${xGroup(key)},0)`)
|
242
|
+
.style('stroke', 'none')
|
243
|
+
.style('fill', 'silver')
|
244
|
+
.style('opacity', '0.1')
|
245
|
+
.attr('cx', () => 5 + Math.random() * ((xGroup.bandwidth() / 2) - 10))
|
246
|
+
.attr('cy', d => y(d))
|
247
|
+
.attr('r', 2);
|
248
|
+
});
|
249
|
+
}
|
250
|
+
|
251
|
+
// Y-axis ticks
|
252
|
+
g
|
253
|
+
.append('g')
|
254
|
+
.attr('transform', `translate(${marginLeft},0)`)
|
255
|
+
.call(axisLeft(y))
|
256
|
+
.selectAll('text')
|
257
|
+
.style('font-size', '11px');
|
258
|
+
|
259
|
+
// X-axis ticks
|
260
|
+
g
|
261
|
+
.append('g')
|
262
|
+
.attr('transform', `translate(0,${innerHeight})`)
|
263
|
+
.style('font-size', '14px')
|
264
|
+
.call(axisBottom(xGroup))
|
265
|
+
.selectAll('text')
|
266
|
+
.style('font-size', '11px')
|
267
|
+
.attr('dx', '-6px')
|
268
|
+
.attr('dy', '6px')
|
269
|
+
.attr('transform', 'rotate(-45)')
|
270
|
+
.style('text-anchor', 'end');
|
271
|
+
|
272
|
+
// Y-axis title
|
273
|
+
g
|
274
|
+
.append('text')
|
275
|
+
.attr('text-anchor', 'middle')
|
276
|
+
.attr('x', -innerHeight / 2)
|
277
|
+
.attr('y', 15)
|
278
|
+
.attr('transform', 'rotate(-90)')
|
279
|
+
.text(yTitle)
|
280
|
+
.style('font-size', '12px')
|
281
|
+
.style('fill', 'white');
|
282
|
+
|
283
|
+
// X-axis title
|
284
|
+
g
|
285
|
+
.append('text')
|
286
|
+
.attr('text-anchor', 'middle')
|
287
|
+
.attr('x', marginLeft + innerWidth / 2)
|
288
|
+
.attr('y', height - 10)
|
289
|
+
.text(xTitle)
|
290
|
+
.style('font-size', '12px')
|
291
|
+
.style('fill', 'white');
|
292
|
+
}, [width, height, data, marginLeft, marginBottom, colors,
|
293
|
+
jitter, theme, yMinProp, marginTop, marginRight, featureType,
|
294
|
+
featureValueType, featureValueTransformName, yUnits, obsType,
|
295
|
+
maxCharactersForLabel,
|
296
|
+
]);
|
271
297
|
|
272
298
|
return (
|
273
|
-
<
|
274
|
-
|
275
|
-
|
299
|
+
<svg
|
300
|
+
ref={svgRef}
|
301
|
+
style={{
|
302
|
+
top: 0,
|
303
|
+
left: 0,
|
304
|
+
width: `${width}px`,
|
305
|
+
height: `${height}px`,
|
306
|
+
position: 'relative',
|
307
|
+
}}
|
276
308
|
/>
|
277
309
|
);
|
278
310
|
}
|
@@ -112,6 +112,9 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
112
112
|
downloadButtonVisible,
|
113
113
|
removeGridComponent,
|
114
114
|
theme,
|
115
|
+
jitter = false,
|
116
|
+
yMin = null,
|
117
|
+
yUnits = null,
|
115
118
|
} = props;
|
116
119
|
|
117
120
|
const classes = useStyles();
|
@@ -172,7 +175,7 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
172
175
|
obsSetsUrls,
|
173
176
|
]);
|
174
177
|
|
175
|
-
const [expressionArr, setArr
|
178
|
+
const [expressionArr, setArr] = useExpressionByCellSet(
|
176
179
|
expressionData, obsIndex, cellSets, additionalCellSets,
|
177
180
|
geneSelection, cellSetSelection, cellSetColor,
|
178
181
|
featureValueTransform, featureValueTransformCoefficient,
|
@@ -209,13 +212,16 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
209
212
|
<div ref={containerRef} className={classes.vegaContainer}>
|
210
213
|
{expressionArr ? (
|
211
214
|
<CellSetExpressionPlot
|
212
|
-
|
215
|
+
yMin={yMin}
|
216
|
+
yUnits={yUnits}
|
217
|
+
jitter={jitter}
|
213
218
|
colors={setArr}
|
214
219
|
data={expressionArr}
|
215
220
|
theme={theme}
|
216
221
|
width={width}
|
217
222
|
height={height}
|
218
223
|
obsType={obsType}
|
224
|
+
featureType={featureType}
|
219
225
|
featureValueType={featureValueType}
|
220
226
|
featureValueTransformName={selectedTransformName}
|
221
227
|
/>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react';
|
2
2
|
import { clamp, debounce } from 'lodash-es';
|
3
3
|
import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
|
4
|
+
import { capitalize, pluralize } from '@vitessce/utils';
|
4
5
|
|
5
6
|
/**
|
6
7
|
* We use debounce, so that onSelect is called only after the user has finished the selection.
|
@@ -30,6 +31,9 @@ import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
|
|
30
31
|
export default function ExpressionHistogram(props) {
|
31
32
|
const {
|
32
33
|
geneSelection,
|
34
|
+
obsType,
|
35
|
+
featureType,
|
36
|
+
featureValueType,
|
33
37
|
data,
|
34
38
|
theme,
|
35
39
|
width,
|
@@ -41,9 +45,13 @@ export default function ExpressionHistogram(props) {
|
|
41
45
|
|
42
46
|
const [selectedRanges, setSelectedRanges] = useState([]);
|
43
47
|
|
48
|
+
const isExpression = (
|
49
|
+
featureType === 'gene' && featureValueType === 'expression'
|
50
|
+
);
|
51
|
+
// eslint-disable-next-line no-nested-ternary
|
44
52
|
const xTitle = geneSelection && geneSelection.length >= 1
|
45
|
-
?
|
46
|
-
: 'Total
|
53
|
+
? (isExpression ? `Expression Value (${geneSelection[0]})` : `${geneSelection[0]}`)
|
54
|
+
: (isExpression ? 'Total Transcript Count' : 'Sum of Feature Values');
|
47
55
|
|
48
56
|
const spec = {
|
49
57
|
data: { values: data },
|
@@ -58,7 +66,7 @@ export default function ExpressionHistogram(props) {
|
|
58
66
|
y: {
|
59
67
|
type: 'quantitative',
|
60
68
|
aggregate: 'count',
|
61
|
-
title:
|
69
|
+
title: `Number of ${capitalize(pluralize(obsType, 2))}`,
|
62
70
|
},
|
63
71
|
color: { value: 'gray' },
|
64
72
|
opacity: {
|
@@ -86,8 +86,7 @@ export function ExpressionHistogramSubscriber(props) {
|
|
86
86
|
return obsIndex.map((cellId, cellIndex) => {
|
87
87
|
const value = expressionData[0][cellIndex];
|
88
88
|
// Create new cellColors map based on the selected gene.
|
89
|
-
const
|
90
|
-
const newItem = { value: normValue, gene: firstGeneSelected, cellId };
|
89
|
+
const newItem = { value, gene: firstGeneSelected, cellId };
|
91
90
|
return newItem;
|
92
91
|
});
|
93
92
|
}
|
@@ -96,7 +95,7 @@ export function ExpressionHistogramSubscriber(props) {
|
|
96
95
|
return obsIndex.map((cellId, cellIndex) => {
|
97
96
|
const values = obsFeatureMatrix.data
|
98
97
|
.subarray(cellIndex * numGenes, (cellIndex + 1) * numGenes);
|
99
|
-
const sumValue = sum(values)
|
98
|
+
const sumValue = sum(values);
|
100
99
|
const newItem = { value: sumValue, gene: null, cellId };
|
101
100
|
return newItem;
|
102
101
|
});
|
@@ -121,7 +120,7 @@ export function ExpressionHistogramSubscriber(props) {
|
|
121
120
|
|
122
121
|
return (
|
123
122
|
<TitleInfo
|
124
|
-
title={`
|
123
|
+
title={`Histogram${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`}
|
125
124
|
closeButtonVisible={closeButtonVisible}
|
126
125
|
downloadButtonVisible={downloadButtonVisible}
|
127
126
|
removeGridComponent={removeGridComponent}
|
@@ -132,6 +131,9 @@ export function ExpressionHistogramSubscriber(props) {
|
|
132
131
|
<div ref={containerRef} className={classes.vegaContainer}>
|
133
132
|
<ExpressionHistogram
|
134
133
|
geneSelection={geneSelection}
|
134
|
+
obsType={obsType}
|
135
|
+
featureType={featureType}
|
136
|
+
featureValueType={featureValueType}
|
135
137
|
onSelect={onSelect}
|
136
138
|
data={data}
|
137
139
|
theme={theme}
|