@vitessce/statistical-plots 3.5.9 → 3.5.11
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/{deflate-19841f78.js → deflate-b8c6daae.js} +1 -1
- package/dist/{index-dc733355.js → index-134a71c6.js} +33739 -15506
- package/dist/index.js +6 -5
- package/dist/{jpeg-a83077be.js → jpeg-1b818d11.js} +1 -1
- package/dist/{lerc-1edd075a.js → lerc-f38cbdfc.js} +1 -1
- package/dist/{lzw-9572eac3.js → lzw-1120aba9.js} +1 -1
- package/dist/{packbits-cce11fbc.js → packbits-2d02b5e3.js} +1 -1
- package/dist/{raw-f7587aff.js → raw-0887baec.js} +1 -1
- package/dist/{webimage-8d38cd8b.js → webimage-533922a5.js} +1 -1
- package/dist-tsc/CellSetCompositionBarPlot.d.ts.map +1 -1
- package/dist-tsc/CellSetCompositionBarPlot.js +31 -10
- package/dist-tsc/CellSetCompositionBarPlotSubscriber.js +1 -1
- package/dist-tsc/CellSetExpressionPlot.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlot.js +26 -10
- package/dist-tsc/CellSetExpressionPlotOptions.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlotOptions.js +11 -4
- package/dist-tsc/CellSetExpressionPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlotSubscriber.js +46 -11
- package/dist-tsc/DotPlot.d.ts.map +1 -1
- package/dist-tsc/DotPlot.js +59 -6
- package/dist-tsc/DotPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/DotPlotSubscriber.js +1 -1
- package/dist-tsc/FeatureSetEnrichmentBarPlot.d.ts.map +1 -1
- package/dist-tsc/FeatureSetEnrichmentBarPlot.js +7 -6
- package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.js +5 -2
- package/dist-tsc/FeatureStatsTable.d.ts +2 -0
- package/dist-tsc/FeatureStatsTable.d.ts.map +1 -0
- package/dist-tsc/FeatureStatsTable.js +81 -0
- package/dist-tsc/FeatureStatsTableSubscriber.d.ts +2 -0
- package/dist-tsc/FeatureStatsTableSubscriber.d.ts.map +1 -0
- package/dist-tsc/FeatureStatsTableSubscriber.js +28 -0
- package/dist-tsc/Treemap.d.ts.map +1 -1
- package/dist-tsc/Treemap.js +17 -3
- package/dist-tsc/TreemapSubscriber.d.ts.map +1 -1
- package/dist-tsc/TreemapSubscriber.js +15 -9
- package/dist-tsc/VolcanoPlot.d.ts.map +1 -1
- package/dist-tsc/VolcanoPlot.js +15 -46
- package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/VolcanoPlotSubscriber.js +4 -2
- package/dist-tsc/expr-hooks.d.ts.map +1 -1
- package/dist-tsc/expr-hooks.test.js +2 -1
- package/dist-tsc/index.d.ts +1 -0
- package/dist-tsc/index.js +1 -0
- package/dist-tsc/utils.d.ts +1 -0
- package/dist-tsc/utils.d.ts.map +1 -1
- package/dist-tsc/utils.js +56 -0
- package/package.json +8 -7
- package/src/CellSetCompositionBarPlot.js +38 -12
- package/src/CellSetCompositionBarPlotSubscriber.js +1 -1
- package/src/CellSetExpressionPlot.js +33 -10
- package/src/CellSetExpressionPlotOptions.js +39 -2
- package/src/CellSetExpressionPlotSubscriber.js +56 -13
- package/src/DotPlot.js +81 -11
- package/src/DotPlotSubscriber.js +3 -1
- package/src/FeatureSetEnrichmentBarPlot.js +7 -6
- package/src/FeatureSetEnrichmentBarPlotSubscriber.js +5 -2
- package/src/FeatureStatsTable.js +116 -0
- package/src/FeatureStatsTableSubscriber.js +133 -0
- package/src/Treemap.js +21 -3
- package/src/TreemapSubscriber.js +26 -11
- package/src/VolcanoPlot.js +16 -64
- package/src/VolcanoPlotSubscriber.js +6 -1
- package/src/expr-hooks.js +0 -1
- package/src/expr-hooks.test.js +2 -1
- package/src/index.js +1 -0
- package/src/utils.js +82 -1
@@ -1,10 +1,13 @@
|
|
1
1
|
import React, { useCallback, useMemo } from 'react';
|
2
2
|
import { clamp, isEqual } from 'lodash-es';
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
4
|
+
import { extent } from 'd3-array';
|
4
5
|
import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
|
5
6
|
import { capitalize } from '@vitessce/utils';
|
6
7
|
import { getColorScale } from './utils.js';
|
7
8
|
|
9
|
+
const MAX_BAR_SIZE = 40;
|
10
|
+
|
8
11
|
/**
|
9
12
|
* Cell set composition results displayed using a bar chart.
|
10
13
|
*/
|
@@ -13,9 +16,9 @@ export default function CellSetCompositionBarPlot(props) {
|
|
13
16
|
data,
|
14
17
|
theme,
|
15
18
|
width,
|
16
|
-
height,
|
19
|
+
height: heightProp,
|
17
20
|
marginRight = 200,
|
18
|
-
marginBottom =
|
21
|
+
marginBottom = 60,
|
19
22
|
keyLength = 36,
|
20
23
|
obsType,
|
21
24
|
onBarSelect,
|
@@ -27,6 +30,13 @@ export default function CellSetCompositionBarPlot(props) {
|
|
27
30
|
sampleSetColor,
|
28
31
|
} = props;
|
29
32
|
|
33
|
+
const height = (
|
34
|
+
Array.isArray(obsSetSelection)
|
35
|
+
&& ((heightProp - marginBottom) / obsSetSelection.length >= MAX_BAR_SIZE)
|
36
|
+
)
|
37
|
+
? MAX_BAR_SIZE * obsSetSelection.length + marginBottom
|
38
|
+
: heightProp;
|
39
|
+
|
30
40
|
const [obsSetColorScale, sampleSetColorScale] = useMemo(() => [
|
31
41
|
getColorScale(obsSetSelection, obsSetColor, theme),
|
32
42
|
getColorScale(sampleSetSelection, sampleSetColor, theme),
|
@@ -39,21 +49,17 @@ export default function CellSetCompositionBarPlot(props) {
|
|
39
49
|
// Return in array-of-objects form that Vega-Lite likes.
|
40
50
|
|
41
51
|
const referenceCellType = metadata?.analysis_params?.reference_cell_type;
|
52
|
+
const covariateValue = metadata?.analysis_params?.covariate_value;
|
42
53
|
const coordinationValues = metadata?.coordination_values;
|
43
54
|
const obsSetColumnName = coordinationValues?.obsSetSelection?.[0]?.[0];
|
44
55
|
const obsSetGroupName = obsSetsColumnNameMappingReversed?.[obsSetColumnName];
|
45
56
|
|
46
57
|
const sampleSetColumnName = coordinationValues?.sampleSetFilter?.[0]?.[0];
|
47
58
|
const sampleSetGroupName = sampleSetsColumnNameMappingReversed?.[sampleSetColumnName];
|
48
|
-
|
49
|
-
// See https://github.com/keller-mark/compasce/issues/30 which should simplify this logic once implemented,
|
50
|
-
// so that we would no longer need to load/check the covariate column in the frontend.
|
51
|
-
const covariatePrefix = `${sampleSetColumnName}T.`;
|
52
|
-
const firstCovariateValue = df.covariate?.[0]?.substring(covariatePrefix.length);
|
53
|
-
const firstCovariateSetPath = [sampleSetGroupName, firstCovariateValue];
|
59
|
+
const covariateSetPath = [sampleSetGroupName, covariateValue];
|
54
60
|
|
55
61
|
let shouldSwapFoldChangeDirection = false;
|
56
|
-
if (isEqual(
|
62
|
+
if (isEqual(covariateSetPath, sampleSetSelection[0])) {
|
57
63
|
shouldSwapFoldChangeDirection = true;
|
58
64
|
}
|
59
65
|
|
@@ -95,13 +101,13 @@ export default function CellSetCompositionBarPlot(props) {
|
|
95
101
|
]);
|
96
102
|
|
97
103
|
// Get an array of keys for sorting purposes.
|
98
|
-
const keys = computedData
|
104
|
+
const keys = computedData?.map(d => d.keyName);
|
99
105
|
|
100
106
|
const colorScale = {
|
101
107
|
// Manually set the color scale so that Vega-Lite does
|
102
108
|
// not choose the colors automatically.
|
103
|
-
domain: computedData
|
104
|
-
range: computedData
|
109
|
+
domain: computedData?.map(d => d.key),
|
110
|
+
range: computedData?.map(d => d.color),
|
105
111
|
};
|
106
112
|
const captializedObsType = capitalize(obsType);
|
107
113
|
|
@@ -114,6 +120,24 @@ export default function CellSetCompositionBarPlot(props) {
|
|
114
120
|
range: [2.0, 0.5],
|
115
121
|
};
|
116
122
|
|
123
|
+
const xExtent = useMemo(() => {
|
124
|
+
if (computedData) {
|
125
|
+
const [min, max] = extent(computedData.map(d => d.logFoldChange));
|
126
|
+
const buffer = 1.05; // Ensure some extra space
|
127
|
+
const minAbs = Math.abs(min) * buffer;
|
128
|
+
const maxAbs = Math.abs(max) * buffer;
|
129
|
+
if (minAbs > maxAbs) {
|
130
|
+
return [-minAbs, minAbs];
|
131
|
+
}
|
132
|
+
return [-maxAbs, maxAbs];
|
133
|
+
}
|
134
|
+
return undefined;
|
135
|
+
}, [computedData]);
|
136
|
+
|
137
|
+
const xScale = {
|
138
|
+
domain: xExtent,
|
139
|
+
};
|
140
|
+
|
117
141
|
const spec = {
|
118
142
|
mark: { type: 'bar', stroke: 'black', cursor: 'pointer' },
|
119
143
|
params: [
|
@@ -149,6 +173,7 @@ export default function CellSetCompositionBarPlot(props) {
|
|
149
173
|
field: 'logFoldChange',
|
150
174
|
type: 'quantitative',
|
151
175
|
title: 'Log fold-change',
|
176
|
+
scale: xScale,
|
152
177
|
},
|
153
178
|
color: {
|
154
179
|
field: 'key',
|
@@ -165,6 +190,7 @@ export default function CellSetCompositionBarPlot(props) {
|
|
165
190
|
field: 'isReferenceSet',
|
166
191
|
type: 'nominal',
|
167
192
|
scale: strokeWidthScale,
|
193
|
+
legend: null,
|
168
194
|
},
|
169
195
|
tooltip: {
|
170
196
|
field: 'effectExpectedSample',
|
@@ -143,7 +143,7 @@ export function CellSetCompositionBarPlotSubscriber(props) {
|
|
143
143
|
onBarSelect={onBarSelect}
|
144
144
|
/>
|
145
145
|
) : (
|
146
|
-
<span>Select at least one {obsType} set.</span>
|
146
|
+
<span>Select at least one {obsType} set and a pair of {sampleType} sets.</span>
|
147
147
|
)}
|
148
148
|
</div>
|
149
149
|
</TitleInfo>
|
@@ -8,6 +8,7 @@ import { area as d3_area, curveBasis } from 'd3-shape';
|
|
8
8
|
import { select } from 'd3-selection';
|
9
9
|
import { colorArrayToString } from '@vitessce/sets-utils';
|
10
10
|
import { capitalize } from '@vitessce/utils';
|
11
|
+
import { getColorScale } from './utils.js';
|
11
12
|
|
12
13
|
const scaleBand = vega_scale('band');
|
13
14
|
|
@@ -35,9 +36,11 @@ const scaleBand = vega_scale('band');
|
|
35
36
|
export default function CellSetExpressionPlot(props) {
|
36
37
|
const {
|
37
38
|
yMin: yMinProp,
|
39
|
+
xAxisTitle = null,
|
38
40
|
yUnits,
|
39
41
|
jitter,
|
40
|
-
|
42
|
+
obsSetSelection,
|
43
|
+
obsSetColor,
|
41
44
|
sampleSetSelection,
|
42
45
|
sampleSetColor,
|
43
46
|
colors,
|
@@ -57,18 +60,22 @@ export default function CellSetExpressionPlot(props) {
|
|
57
60
|
|
58
61
|
const svgRef = useRef();
|
59
62
|
|
63
|
+
const obsSetColorScale = useMemo(() => getColorScale(
|
64
|
+
obsSetSelection, obsSetColor, theme,
|
65
|
+
), [obsSetSelection, obsSetColor, theme]);
|
66
|
+
|
60
67
|
// Get the max characters in an axis label for autsizing the bottom margin.
|
61
68
|
const maxCharactersForLabel = useMemo(() => {
|
62
|
-
if (!
|
69
|
+
if (!obsSetSelection) {
|
63
70
|
return 0;
|
64
71
|
}
|
65
|
-
const cellSetNames =
|
72
|
+
const cellSetNames = obsSetSelection.map(d => d.at(-1));
|
66
73
|
return cellSetNames.reduce((acc, name) => {
|
67
74
|
// eslint-disable-next-line no-param-reassign
|
68
75
|
acc = acc === undefined || name.length > acc ? name.length : acc;
|
69
76
|
return acc;
|
70
77
|
}, 0);
|
71
|
-
}, [
|
78
|
+
}, [obsSetSelection]);
|
72
79
|
|
73
80
|
const isStratified = (Array.isArray(sampleSetSelection) && sampleSetSelection.length === 2);
|
74
81
|
|
@@ -81,7 +88,7 @@ export default function CellSetExpressionPlot(props) {
|
|
81
88
|
const unitSuffix = yUnits ? ` (${yUnits})` : '';
|
82
89
|
const yTitle = `${transformPrefix}${capitalize(featureValueType)}${unitSuffix}`;
|
83
90
|
|
84
|
-
const xTitle = `${capitalize(obsType)} Set`;
|
91
|
+
const xTitle = xAxisTitle ?? `${capitalize(obsType)} Set`;
|
85
92
|
|
86
93
|
// Use a square-root term because the angle of the labels is 45 degrees (see below)
|
87
94
|
// so the perpendicular distance to the bottom of the labels is proportional to the
|
@@ -146,7 +153,7 @@ export default function CellSetExpressionPlot(props) {
|
|
146
153
|
|
147
154
|
const xGroup = scaleBand()
|
148
155
|
.range([marginLeft, width - marginRight])
|
149
|
-
.domain(
|
156
|
+
.domain(obsSetSelection)
|
150
157
|
.padding(0.1);
|
151
158
|
|
152
159
|
|
@@ -314,11 +321,12 @@ export default function CellSetExpressionPlot(props) {
|
|
314
321
|
.style('font-size', '11px');
|
315
322
|
|
316
323
|
// X-axis ticks
|
317
|
-
g
|
324
|
+
const xTickG = g
|
318
325
|
.append('g')
|
319
326
|
.attr('transform', `translate(0,${innerHeight})`)
|
320
|
-
.style('font-size', '14px')
|
321
|
-
|
327
|
+
.style('font-size', '14px');
|
328
|
+
|
329
|
+
xTickG.call(axisBottom(xGroup).tickFormat(d => d.at(-1)))
|
322
330
|
.selectAll('text')
|
323
331
|
.style('font-size', '11px')
|
324
332
|
.attr('dx', '-6px')
|
@@ -326,6 +334,20 @@ export default function CellSetExpressionPlot(props) {
|
|
326
334
|
.attr('transform', 'rotate(-45)')
|
327
335
|
.style('text-anchor', 'end');
|
328
336
|
|
337
|
+
if (isStratified) {
|
338
|
+
// Associate each X tick with a cell type color,
|
339
|
+
// since in the stratified case the violins are colored
|
340
|
+
// by sample set.
|
341
|
+
const tickWidth = xGroup.bandwidth();
|
342
|
+
xTickG.selectAll('.tick')
|
343
|
+
.append('rect')
|
344
|
+
.attr('x', -tickWidth / 2)
|
345
|
+
// .attr("y", -innerHeight)
|
346
|
+
.attr('width', tickWidth)
|
347
|
+
.attr('height', 4)
|
348
|
+
.style('fill', d => obsSetColorScale(d));
|
349
|
+
}
|
350
|
+
|
329
351
|
// Y-axis title
|
330
352
|
g
|
331
353
|
.append('text')
|
@@ -394,7 +416,8 @@ export default function CellSetExpressionPlot(props) {
|
|
394
416
|
}, [width, height, data, marginLeft, marginBottom, colors,
|
395
417
|
jitter, theme, yMinProp, marginTop, marginRight, featureType,
|
396
418
|
featureValueType, featureValueTransformName, yUnits, obsType,
|
397
|
-
maxCharactersForLabel, sampleSetSelection,
|
419
|
+
maxCharactersForLabel, sampleSetSelection, isStratified,
|
420
|
+
obsSetColorScale,
|
398
421
|
]);
|
399
422
|
|
400
423
|
return (
|
@@ -3,6 +3,10 @@ import { useId } from 'react-aria';
|
|
3
3
|
import { TableCell, TableRow, TextField, Slider } from '@material-ui/core';
|
4
4
|
import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
|
5
5
|
import { GLSL_COLORMAPS } from '@vitessce/gl';
|
6
|
+
import { capitalize } from '@vitessce/utils';
|
7
|
+
|
8
|
+
|
9
|
+
const FEATURE_AGGREGATION_STRATEGIES = ['first', 'last', 'sum', 'mean'];
|
6
10
|
|
7
11
|
export default function CellSetExpressionPlotOptions(props) {
|
8
12
|
const {
|
@@ -15,6 +19,8 @@ export default function CellSetExpressionPlotOptions(props) {
|
|
15
19
|
setFeatureValuePositivityThreshold,
|
16
20
|
featureValueColormap,
|
17
21
|
setFeatureValueColormap,
|
22
|
+
featureAggregationStrategy,
|
23
|
+
setFeatureAggregationStrategy,
|
18
24
|
} = props;
|
19
25
|
|
20
26
|
const cellSetExpressionPlotOptionsId = useId();
|
@@ -25,9 +31,13 @@ export default function CellSetExpressionPlotOptions(props) {
|
|
25
31
|
setFeatureValueColormap(event.target.value);
|
26
32
|
}
|
27
33
|
|
28
|
-
|
34
|
+
function handleTransformChange(event) {
|
29
35
|
setFeatureValueTransform(event.target.value === '' ? null : event.target.value);
|
30
|
-
}
|
36
|
+
}
|
37
|
+
|
38
|
+
function handleFeatureAggregationStrategyChange(event) {
|
39
|
+
setFeatureAggregationStrategy(event.target.value);
|
40
|
+
}
|
31
41
|
|
32
42
|
function handlePositivityThresholdChange(event, value) {
|
33
43
|
setFeatureValuePositivityThreshold(value);
|
@@ -119,6 +129,33 @@ export default function CellSetExpressionPlotOptions(props) {
|
|
119
129
|
/>
|
120
130
|
</TableCell>
|
121
131
|
</TableRow>
|
132
|
+
{setFeatureAggregationStrategy ? (
|
133
|
+
<TableRow>
|
134
|
+
<TableCell className={classes.labelCell} variant="head" scope="row">
|
135
|
+
<label
|
136
|
+
htmlFor={`feature-aggregation-strategy-${cellSetExpressionPlotOptionsId}`}
|
137
|
+
>
|
138
|
+
Feature Aggregation Strategy
|
139
|
+
</label>
|
140
|
+
</TableCell>
|
141
|
+
<TableCell className={classes.inputCell} variant="body">
|
142
|
+
<OptionSelect
|
143
|
+
className={classes.select}
|
144
|
+
value={featureAggregationStrategy ?? 'first'}
|
145
|
+
onChange={handleFeatureAggregationStrategyChange}
|
146
|
+
inputProps={{
|
147
|
+
id: `feature-aggregation-strategy-${cellSetExpressionPlotOptionsId}`,
|
148
|
+
}}
|
149
|
+
>
|
150
|
+
{FEATURE_AGGREGATION_STRATEGIES.map(opt => (
|
151
|
+
<option key={opt} value={opt}>
|
152
|
+
{capitalize(opt)}
|
153
|
+
</option>
|
154
|
+
))}
|
155
|
+
</OptionSelect>
|
156
|
+
</TableCell>
|
157
|
+
</TableRow>
|
158
|
+
) : null}
|
122
159
|
{setFeatureValuePositivityThreshold ? (
|
123
160
|
<TableRow key="transform-coefficient-option-row">
|
124
161
|
<TableCell className={classes.labelCell}>
|
@@ -26,6 +26,28 @@ import {
|
|
26
26
|
histogramStratifiedExpressionData,
|
27
27
|
} from './expr-hooks.js';
|
28
28
|
|
29
|
+
const DEFAULT_FEATURE_AGGREGATION_STRATEGY = 'first';
|
30
|
+
|
31
|
+
function featureSummary(geneSelection, featureAggregationStrategy) {
|
32
|
+
if (featureAggregationStrategy === 'first') {
|
33
|
+
return geneSelection?.[0];
|
34
|
+
} if (featureAggregationStrategy === 'last') {
|
35
|
+
return geneSelection?.at(-1);
|
36
|
+
} if (typeof featureAggregationStrategy === 'number') {
|
37
|
+
const i = featureAggregationStrategy;
|
38
|
+
return geneSelection?.[i];
|
39
|
+
} if (featureAggregationStrategy === 'sum') {
|
40
|
+
// TODO: make these .join()-ed labels more scalable,
|
41
|
+
// in particular, if more than 10 or so elements.
|
42
|
+
return geneSelection?.join(' + ');
|
43
|
+
} if (featureAggregationStrategy === 'mean') {
|
44
|
+
return `Mean of ${geneSelection?.join(', ')}`;
|
45
|
+
} if (featureAggregationStrategy === 'difference') {
|
46
|
+
return geneSelection?.join(' - ');
|
47
|
+
}
|
48
|
+
return '';
|
49
|
+
}
|
50
|
+
|
29
51
|
/**
|
30
52
|
* Get expression data for the cells
|
31
53
|
* in the selected cell sets.
|
@@ -50,7 +72,7 @@ function useExpressionByCellSet(
|
|
50
72
|
expressionData, obsIndex, cellSets, additionalCellSets,
|
51
73
|
geneSelection, cellSetSelection, cellSetColor,
|
52
74
|
featureValueTransform, featureValueTransformCoefficient,
|
53
|
-
theme, yMinProp,
|
75
|
+
theme, yMinProp, featureAggregationStrategy,
|
54
76
|
) {
|
55
77
|
const mergedCellSets = useMemo(
|
56
78
|
() => mergeObsSets(cellSets, additionalCellSets),
|
@@ -68,7 +90,7 @@ function useExpressionByCellSet(
|
|
68
90
|
);
|
69
91
|
if (stratifiedData) {
|
70
92
|
const aggregateData = aggregateStratifiedExpressionData(
|
71
|
-
stratifiedData, geneSelection,
|
93
|
+
stratifiedData, geneSelection, featureAggregationStrategy,
|
72
94
|
);
|
73
95
|
const summarizedData = summarizeStratifiedExpressionData(
|
74
96
|
aggregateData, true,
|
@@ -83,6 +105,7 @@ function useExpressionByCellSet(
|
|
83
105
|
mergedCellSets, cellSetSelection, cellSetColor,
|
84
106
|
featureValueTransform, featureValueTransformCoefficient,
|
85
107
|
yMinProp, sampleEdges, sampleSets, sampleSetSelection,
|
108
|
+
featureAggregationStrategy,
|
86
109
|
]);
|
87
110
|
|
88
111
|
// From the cell sets hierarchy and the list of selected cell sets,
|
@@ -114,6 +137,8 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
114
137
|
downloadButtonVisible,
|
115
138
|
removeGridComponent,
|
116
139
|
theme,
|
140
|
+
title,
|
141
|
+
xAxisTitle,
|
117
142
|
jitter = false,
|
118
143
|
yMin = null,
|
119
144
|
yUnits = null,
|
@@ -138,10 +163,12 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
138
163
|
sampleType,
|
139
164
|
sampleSetSelection,
|
140
165
|
sampleSetColor,
|
166
|
+
featureAggregationStrategy,
|
141
167
|
}, {
|
142
168
|
setFeatureValueTransform,
|
143
169
|
setFeatureValueTransformCoefficient,
|
144
170
|
setSampleSetColor,
|
171
|
+
setFeatureAggregationStrategy,
|
145
172
|
}] = useCoordination(
|
146
173
|
COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION],
|
147
174
|
coordinationScopes,
|
@@ -204,29 +231,41 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
204
231
|
sampleEdgesUrls,
|
205
232
|
]);
|
206
233
|
|
234
|
+
const featureAggregationStrategyToUse = featureAggregationStrategy
|
235
|
+
?? DEFAULT_FEATURE_AGGREGATION_STRATEGY;
|
236
|
+
|
207
237
|
const [histogramData, setArr, exprMax] = useExpressionByCellSet(
|
208
238
|
sampleEdges, sampleSets, sampleSetSelection,
|
209
239
|
expressionData, obsIndex, cellSets, additionalCellSets,
|
210
240
|
geneSelection, cellSetSelection, cellSetColor,
|
211
241
|
featureValueTransform, featureValueTransformCoefficient,
|
212
|
-
theme, yMin,
|
242
|
+
theme, yMin, featureAggregationStrategyToUse,
|
213
243
|
);
|
214
244
|
|
215
|
-
const
|
216
|
-
|
217
|
-
featureLabelsMap?.get(
|
218
|
-
|| featureLabelsMap?.get(cleanFeatureId(
|
219
|
-
||
|
220
|
-
)
|
221
|
-
|
245
|
+
const featureSuffix = useMemo(() => {
|
246
|
+
const cleanedGeneSelection = geneSelection?.map(geneName => (
|
247
|
+
featureLabelsMap?.get(geneName)
|
248
|
+
|| featureLabelsMap?.get(cleanFeatureId(geneName))
|
249
|
+
|| geneName
|
250
|
+
));
|
251
|
+
if (Array.isArray(cleanedGeneSelection)) {
|
252
|
+
return featureSummary(cleanedGeneSelection, featureAggregationStrategyToUse);
|
253
|
+
}
|
254
|
+
return null;
|
255
|
+
}, [geneSelection, featureAggregationStrategyToUse]);
|
256
|
+
|
257
|
+
|
222
258
|
const selectedTransformName = transformOptions.find(
|
223
259
|
o => o.value === featureValueTransform,
|
224
260
|
)?.name;
|
225
|
-
|
261
|
+
// Use empty string when firstGeneSelected is null
|
262
|
+
const titleSuffix = featureSuffix ? ` (${featureSuffix})` : '';
|
226
263
|
|
227
264
|
return (
|
228
265
|
<TitleInfo
|
229
|
-
title={
|
266
|
+
title={title ? `${title}${titleSuffix}`
|
267
|
+
: `Expression by ${capitalize(obsType)} Set${titleSuffix}`
|
268
|
+
}
|
230
269
|
closeButtonVisible={closeButtonVisible}
|
231
270
|
downloadButtonVisible={downloadButtonVisible}
|
232
271
|
removeGridComponent={removeGridComponent}
|
@@ -241,6 +280,8 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
241
280
|
featureValueTransformCoefficient={featureValueTransformCoefficient}
|
242
281
|
setFeatureValueTransformCoefficient={setFeatureValueTransformCoefficient}
|
243
282
|
transformOptions={transformOptions}
|
283
|
+
featureAggregationStrategy={featureAggregationStrategy}
|
284
|
+
setFeatureAggregationStrategy={setFeatureAggregationStrategy}
|
244
285
|
/>
|
245
286
|
)}
|
246
287
|
>
|
@@ -250,7 +291,8 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
250
291
|
yMin={yMin}
|
251
292
|
yUnits={yUnits}
|
252
293
|
jitter={jitter}
|
253
|
-
|
294
|
+
obsSetSelection={cellSetSelection}
|
295
|
+
obsSetColor={cellSetColor}
|
254
296
|
sampleSetSelection={sampleSetSelection}
|
255
297
|
sampleSetColor={sampleSetColor}
|
256
298
|
colors={setArr}
|
@@ -263,6 +305,7 @@ export function CellSetExpressionPlotSubscriber(props) {
|
|
263
305
|
featureType={featureType}
|
264
306
|
featureValueType={featureValueType}
|
265
307
|
featureValueTransformName={selectedTransformName}
|
308
|
+
xAxisTitle={xAxisTitle}
|
266
309
|
/>
|
267
310
|
) : (
|
268
311
|
<span>Select a {featureType}.</span>
|
package/src/DotPlot.js
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { useRef, useEffect, useCallback, useState, useMemo } from 'react';
|
2
2
|
import { clamp } from 'lodash-es';
|
3
3
|
import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
|
4
4
|
import { capitalize, pluralize as plur } from '@vitessce/utils';
|
5
|
+
import { select } from 'd3-selection';
|
6
|
+
import { getColorScale } from './utils.js';
|
5
7
|
|
6
8
|
/**
|
7
9
|
* Gene expression dot plot,
|
@@ -32,14 +34,19 @@ export default function DotPlot(props) {
|
|
32
34
|
marginRight,
|
33
35
|
marginBottom,
|
34
36
|
obsType,
|
37
|
+
sampleType,
|
35
38
|
keyLength = 36,
|
36
39
|
featureType,
|
37
40
|
featureValueType,
|
38
41
|
featureValueTransformName,
|
39
|
-
featureValueColormap
|
40
|
-
|
42
|
+
// TODO: re-enable featureValueColormap coordination
|
43
|
+
// featureValueColormap,
|
44
|
+
obsSetSelection,
|
45
|
+
obsSetColor,
|
41
46
|
} = props;
|
42
47
|
|
48
|
+
const vegaContainerRef = useRef();
|
49
|
+
|
43
50
|
// Add a property `keyGroup` and `keyFeature` which concatenates the key and the name,
|
44
51
|
// which is both unique and can easily be converted
|
45
52
|
// back to the name by taking a substring.
|
@@ -80,18 +87,17 @@ export default function DotPlot(props) {
|
|
80
87
|
|| 30 + Math.sqrt(maxCharactersForSampleSet / 2) * 30;
|
81
88
|
|
82
89
|
const plotWidth = transpose
|
83
|
-
? clamp(width - autoMarginForFeature - 180, 10, Infinity) / (
|
90
|
+
? clamp(width - autoMarginForFeature - 180, 10, Infinity) / (obsSetSelection?.length || 1)
|
84
91
|
: clamp(width - autoMarginForGroup - autoMarginForSampleSet - 200, 10, Infinity);
|
85
92
|
const plotHeight = transpose
|
86
93
|
? clamp((height - autoMarginForGroup - autoMarginForSampleSet - 50), 10, Infinity)
|
87
|
-
: clamp((height - autoMarginForFeature - 80), 10, Infinity) / (
|
94
|
+
: clamp((height - autoMarginForFeature - 80), 10, Infinity) / (obsSetSelection?.length || 1);
|
88
95
|
|
89
96
|
// Get an array of keys for sorting purposes.
|
90
97
|
const groupKeys = data.map(d => d.keyGroup);
|
91
98
|
const featureKeys = data.map(d => d.keyFeature);
|
92
99
|
const groupSecondaryKeys = data.map(d => d.keyGroupSecondary);
|
93
100
|
|
94
|
-
|
95
101
|
const meanTransform = (featureValueTransformName && featureValueTransformName !== 'None')
|
96
102
|
// Mean Log-Transformed Normalized Expression
|
97
103
|
? [`Mean ${featureValueTransformName}-transformed`, `normalized ${featureValueType}`, 'in set']
|
@@ -129,7 +135,8 @@ export default function DotPlot(props) {
|
|
129
135
|
type: 'quantitative',
|
130
136
|
title: meanTransform,
|
131
137
|
scale: {
|
132
|
-
scheme: featureValueColormap,
|
138
|
+
// scheme: featureValueColormap,
|
139
|
+
scheme: 'greys',
|
133
140
|
},
|
134
141
|
legend: {
|
135
142
|
direction: 'horizontal',
|
@@ -150,6 +157,11 @@ export default function DotPlot(props) {
|
|
150
157
|
legend: {
|
151
158
|
symbolFillColor: 'white',
|
152
159
|
},
|
160
|
+
scale: { domain: [0, 100] },
|
161
|
+
},
|
162
|
+
tooltip: {
|
163
|
+
field: 'pctPosInGroup',
|
164
|
+
type: 'quantitative',
|
153
165
|
},
|
154
166
|
},
|
155
167
|
width: plotWidth,
|
@@ -166,10 +178,68 @@ export default function DotPlot(props) {
|
|
166
178
|
},
|
167
179
|
};
|
168
180
|
|
181
|
+
const getTooltipText = useCallback(item => ({
|
182
|
+
[`${capitalize(featureType)}`]: item.datum.feature,
|
183
|
+
[`${capitalize(obsType)} Set`]: item.datum.group,
|
184
|
+
...(isStratified
|
185
|
+
? ({ [`${capitalize(sampleType)} Set`]: item.datum.secondaryGroup })
|
186
|
+
: {}
|
187
|
+
),
|
188
|
+
[`Percentage of ${plur(obsType, 2)} in set`]: item.datum.pctPosInGroup,
|
189
|
+
[meanTransform.join(' ')]: item.datum.meanExpInGroup,
|
190
|
+
}), [featureType, obsType, featureValueType, featureValueTransformName]);
|
191
|
+
|
192
|
+
const obsSetColorScale = useMemo(() => getColorScale(
|
193
|
+
obsSetSelection, obsSetColor, theme,
|
194
|
+
), [obsSetSelection, obsSetColor, theme]);
|
195
|
+
|
196
|
+
const [vegaRenderIncrement, setVegaRenderIncrement] = useState(0);
|
197
|
+
|
198
|
+
useEffect(() => {
|
199
|
+
// If the dot plot is stratified by both obsSet and sampleSet,
|
200
|
+
// then we want to add cell set colors.
|
201
|
+
// TODO: do we also want to add these color bars
|
202
|
+
// when only stratified by obsSet?
|
203
|
+
const domElement = vegaContainerRef.current;
|
204
|
+
|
205
|
+
// Here, we assume that the Vega SVG renderer is being used.
|
206
|
+
const svg = select(domElement)
|
207
|
+
.select('svg');
|
208
|
+
// We use the following CSS selector to identify all of
|
209
|
+
// the <line> elements that we are interested to modify.
|
210
|
+
const tickEls = svg.selectAll('g.root g.column_footer g.role-axis g.role-axis-domain line');
|
211
|
+
|
212
|
+
tickEls
|
213
|
+
.attr('stroke-width', 5)
|
214
|
+
.attr('dy', 2.5)
|
215
|
+
.attr('stroke', (d, i) => {
|
216
|
+
const obsSetPath = obsSetSelection?.[i];
|
217
|
+
return obsSetColorScale(obsSetPath);
|
218
|
+
});
|
219
|
+
}, [vegaContainerRef, vegaRenderIncrement, obsSetSelection, obsSetColorScale]);
|
220
|
+
|
221
|
+
|
222
|
+
// We want to increment the counter whenever we detect that VegaPlot
|
223
|
+
// has re-rendered.
|
224
|
+
const onNewView = useCallback(() => {
|
225
|
+
setVegaRenderIncrement(prev => prev + 1);
|
226
|
+
}, []);
|
227
|
+
|
228
|
+
// This is kind of hacky, since it is possible that the useEffect runs prior
|
229
|
+
// to the Vega rendering, but in practice it seems to work.
|
230
|
+
useEffect(() => {
|
231
|
+
setVegaRenderIncrement(prev => prev + 1);
|
232
|
+
}, [rawData]);
|
233
|
+
|
169
234
|
return (
|
170
|
-
<
|
171
|
-
|
172
|
-
|
173
|
-
|
235
|
+
<div ref={vegaContainerRef}>
|
236
|
+
<VegaPlot
|
237
|
+
data={data}
|
238
|
+
spec={spec}
|
239
|
+
onNewView={onNewView}
|
240
|
+
getTooltipText={getTooltipText}
|
241
|
+
renderer="svg"
|
242
|
+
/>
|
243
|
+
</div>
|
174
244
|
);
|
175
245
|
}
|
package/src/DotPlotSubscriber.js
CHANGED
@@ -160,11 +160,13 @@ export function DotPlotSubscriber(props) {
|
|
160
160
|
width={width}
|
161
161
|
height={height}
|
162
162
|
obsType={obsType}
|
163
|
+
sampleType={sampleType}
|
163
164
|
featureType={featureType}
|
164
165
|
featureValueType={featureValueType}
|
165
166
|
featureValueTransformName={selectedTransformName}
|
166
167
|
featureValueColormap={featureValueColormap}
|
167
|
-
|
168
|
+
obsSetSelection={cellSetSelection}
|
169
|
+
obsSetColor={cellSetColor}
|
168
170
|
/>
|
169
171
|
) : (
|
170
172
|
<span>Select at least one {featureType}.</span>
|
@@ -14,7 +14,7 @@ export default function FeatureSetEnrichmentBarPlot(props) {
|
|
14
14
|
theme,
|
15
15
|
width,
|
16
16
|
height,
|
17
|
-
marginRight =
|
17
|
+
marginRight = 300,
|
18
18
|
marginBottom = 120,
|
19
19
|
keyLength = 36,
|
20
20
|
featureType,
|
@@ -86,7 +86,7 @@ export default function FeatureSetEnrichmentBarPlot(props) {
|
|
86
86
|
return [...a, h];
|
87
87
|
}, []);
|
88
88
|
|
89
|
-
const MAX_ROWS =
|
89
|
+
const MAX_ROWS = 50;
|
90
90
|
result = result.slice(0, MAX_ROWS);
|
91
91
|
return result;
|
92
92
|
}
|
@@ -115,7 +115,7 @@ export default function FeatureSetEnrichmentBarPlot(props) {
|
|
115
115
|
select: {
|
116
116
|
type: 'point',
|
117
117
|
on: 'click[event.shiftKey === false]',
|
118
|
-
fields: ['name'],
|
118
|
+
fields: ['name', 'term'],
|
119
119
|
empty: 'none',
|
120
120
|
},
|
121
121
|
},
|
@@ -124,7 +124,7 @@ export default function FeatureSetEnrichmentBarPlot(props) {
|
|
124
124
|
select: {
|
125
125
|
type: 'point',
|
126
126
|
on: 'click[event.shiftKey]',
|
127
|
-
fields: ['name'],
|
127
|
+
fields: ['name', 'term'],
|
128
128
|
empty: 'none',
|
129
129
|
},
|
130
130
|
},
|
@@ -173,9 +173,10 @@ export default function FeatureSetEnrichmentBarPlot(props) {
|
|
173
173
|
|
174
174
|
const handleSignal = (name, value) => {
|
175
175
|
if (name === 'bar_select') {
|
176
|
-
onBarSelect(value.
|
176
|
+
onBarSelect(value.name?.[0], value.term?.[0]);
|
177
177
|
} else if (name === 'shift_bar_select') {
|
178
|
-
|
178
|
+
// Name and term may be arrays
|
179
|
+
onBarSelect(value.name, value.term, true);
|
179
180
|
}
|
180
181
|
};
|
181
182
|
|