@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
@@ -119,8 +119,11 @@ export function FeatureSetEnrichmentBarPlotSubscriber(props) {
|
|
119
119
|
// Will not work since transformFeature currently:
|
120
120
|
// - matches based on kgId (rather than term)
|
121
121
|
// - only knows about Reactome pathways (not GO terms).
|
122
|
-
|
123
|
-
|
122
|
+
const targetsInPathway = await transformFeature(kgNode, targetFeatureType);
|
123
|
+
const featureIds = targetsInPathway
|
124
|
+
.filter((d, i) => i < 10) // TODO: do not limit the number of genes here
|
125
|
+
.map(d => d.label);
|
126
|
+
setFeatureSelection(featureIds);
|
124
127
|
}, [setFeatureSelection]);
|
125
128
|
|
126
129
|
// TODO: support the following options
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import React, { useMemo, useState, useCallback } from 'react';
|
2
|
+
import { DataGrid } from '@mui/x-data-grid';
|
3
|
+
import { capitalize } from '@vitessce/utils';
|
4
|
+
import { useFilteredVolcanoData } from './utils.js';
|
5
|
+
|
6
|
+
const ROW_ID_DELIMITER = '___';
|
7
|
+
const INITIAL_SORT_MODEL = [
|
8
|
+
// We initially set the sorting this way
|
9
|
+
{ field: 'logFoldChange', sort: 'desc' },
|
10
|
+
];
|
11
|
+
|
12
|
+
export default function FeatureStatsTable(props) {
|
13
|
+
const {
|
14
|
+
obsType,
|
15
|
+
featureType,
|
16
|
+
obsSetsColumnNameMappingReversed,
|
17
|
+
sampleSetsColumnNameMappingReversed,
|
18
|
+
sampleSetSelection,
|
19
|
+
data,
|
20
|
+
setFeatureSelection,
|
21
|
+
featurePointSignificanceThreshold,
|
22
|
+
featurePointFoldChangeThreshold,
|
23
|
+
} = props;
|
24
|
+
|
25
|
+
const [
|
26
|
+
// eslint-disable-next-line no-unused-vars
|
27
|
+
computedData,
|
28
|
+
filteredData,
|
29
|
+
] = useFilteredVolcanoData({
|
30
|
+
data,
|
31
|
+
obsSetsColumnNameMappingReversed,
|
32
|
+
sampleSetsColumnNameMappingReversed,
|
33
|
+
featurePointFoldChangeThreshold,
|
34
|
+
featurePointSignificanceThreshold,
|
35
|
+
sampleSetSelection,
|
36
|
+
});
|
37
|
+
|
38
|
+
// Reference: https://v4.mui.com/api/data-grid/data-grid/
|
39
|
+
const columns = useMemo(() => ([
|
40
|
+
{
|
41
|
+
field: 'featureId',
|
42
|
+
headerName: capitalize(featureType),
|
43
|
+
width: 200,
|
44
|
+
editable: false,
|
45
|
+
},
|
46
|
+
{
|
47
|
+
field: 'logFoldChange',
|
48
|
+
headerName: 'Log Fold-Change',
|
49
|
+
width: 200,
|
50
|
+
editable: false,
|
51
|
+
},
|
52
|
+
{
|
53
|
+
field: 'featureSignificance',
|
54
|
+
headerName: 'P-value',
|
55
|
+
width: 200,
|
56
|
+
editable: false,
|
57
|
+
},
|
58
|
+
{
|
59
|
+
field: 'obsSetName',
|
60
|
+
headerName: `${capitalize(obsType)} Set`,
|
61
|
+
width: 200,
|
62
|
+
editable: false,
|
63
|
+
},
|
64
|
+
]), [obsType, featureType]);
|
65
|
+
|
66
|
+
const rows = useMemo(() => {
|
67
|
+
let result = [];
|
68
|
+
if (filteredData) {
|
69
|
+
filteredData.forEach((comparisonObject) => {
|
70
|
+
const { df, metadata } = comparisonObject;
|
71
|
+
|
72
|
+
const coordinationValues = metadata.coordination_values;
|
73
|
+
|
74
|
+
const rawObsSetPath = coordinationValues.obsSetFilter
|
75
|
+
? coordinationValues.obsSetFilter[0]
|
76
|
+
: coordinationValues.obsSetSelection[0];
|
77
|
+
const obsSetPath = [...rawObsSetPath];
|
78
|
+
obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
|
79
|
+
const obsSetName = obsSetPath.at(-1);
|
80
|
+
|
81
|
+
result = result.concat(df.map(row => ({
|
82
|
+
...row,
|
83
|
+
id: `${row.featureId}${ROW_ID_DELIMITER}${obsSetName}`,
|
84
|
+
obsSetName,
|
85
|
+
})));
|
86
|
+
});
|
87
|
+
}
|
88
|
+
return result;
|
89
|
+
}, [filteredData, obsSetsColumnNameMappingReversed]);
|
90
|
+
|
91
|
+
const onSelectionModelChange = useCallback((rowIds) => {
|
92
|
+
const featureIds = rowIds.map(rowId => rowId.split(ROW_ID_DELIMITER)[0]);
|
93
|
+
setFeatureSelection(featureIds);
|
94
|
+
}, []);
|
95
|
+
|
96
|
+
const rowSelectionModel = useMemo(() => [], []);
|
97
|
+
|
98
|
+
const [sortModel, setSortModel] = useState(INITIAL_SORT_MODEL);
|
99
|
+
|
100
|
+
const getRowId = useCallback(row => row.id, []);
|
101
|
+
|
102
|
+
return (
|
103
|
+
<DataGrid
|
104
|
+
density="compact"
|
105
|
+
rows={rows}
|
106
|
+
columns={columns}
|
107
|
+
pageSize={10}
|
108
|
+
// checkboxSelection // TODO: uncomment to enable multiple-row selection
|
109
|
+
onSelectionModelChange={onSelectionModelChange}
|
110
|
+
rowSelectionModel={rowSelectionModel}
|
111
|
+
getRowId={getRowId}
|
112
|
+
sortModel={sortModel}
|
113
|
+
onSortModelChange={setSortModel}
|
114
|
+
/>
|
115
|
+
);
|
116
|
+
}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
2
|
+
import React from 'react';
|
3
|
+
import {
|
4
|
+
TitleInfo,
|
5
|
+
useCoordination,
|
6
|
+
useLoaders,
|
7
|
+
useReady,
|
8
|
+
useFeatureStatsData,
|
9
|
+
useMatchingLoader,
|
10
|
+
useColumnNameMapping,
|
11
|
+
} from '@vitessce/vit-s';
|
12
|
+
import {
|
13
|
+
ViewType,
|
14
|
+
COMPONENT_COORDINATION_TYPES,
|
15
|
+
ViewHelpMapping,
|
16
|
+
DataType,
|
17
|
+
} from '@vitessce/constants-internal';
|
18
|
+
import FeatureStatsTable from './FeatureStatsTable.js';
|
19
|
+
import { useRawSetPaths } from './utils.js';
|
20
|
+
|
21
|
+
export function FeatureStatsTableSubscriber(props) {
|
22
|
+
const {
|
23
|
+
title = 'Differential Expression Results',
|
24
|
+
coordinationScopes,
|
25
|
+
removeGridComponent,
|
26
|
+
theme,
|
27
|
+
helpText = ViewHelpMapping.FEATURE_STATS_TABLE,
|
28
|
+
} = props;
|
29
|
+
|
30
|
+
const loaders = useLoaders();
|
31
|
+
|
32
|
+
// Get "props" from the coordination space.
|
33
|
+
const [{
|
34
|
+
dataset,
|
35
|
+
obsType,
|
36
|
+
sampleType,
|
37
|
+
featureType,
|
38
|
+
featureValueType,
|
39
|
+
obsFilter: cellFilter,
|
40
|
+
obsHighlight: cellHighlight,
|
41
|
+
obsSetSelection,
|
42
|
+
obsSetColor,
|
43
|
+
obsColorEncoding: cellColorEncoding,
|
44
|
+
additionalObsSets: additionalCellSets,
|
45
|
+
featurePointSignificanceThreshold,
|
46
|
+
featurePointFoldChangeThreshold,
|
47
|
+
featureValueTransform,
|
48
|
+
featureValueTransformCoefficient,
|
49
|
+
gatingFeatureSelectionX,
|
50
|
+
gatingFeatureSelectionY,
|
51
|
+
featureSelection,
|
52
|
+
sampleSetSelection,
|
53
|
+
sampleSetColor,
|
54
|
+
}, {
|
55
|
+
setObsFilter: setCellFilter,
|
56
|
+
setObsSetSelection,
|
57
|
+
setObsHighlight: setCellHighlight,
|
58
|
+
setObsSetColor: setCellSetColor,
|
59
|
+
setObsColorEncoding: setCellColorEncoding,
|
60
|
+
setAdditionalObsSets: setAdditionalCellSets,
|
61
|
+
setFeaturePointSignificanceThreshold,
|
62
|
+
setFeaturePointFoldChangeThreshold,
|
63
|
+
setFeatureValueTransform,
|
64
|
+
setFeatureValueTransformCoefficient,
|
65
|
+
setGatingFeatureSelectionX,
|
66
|
+
setGatingFeatureSelectionY,
|
67
|
+
setFeatureSelection,
|
68
|
+
setSampleSetSelection,
|
69
|
+
setSampleSetColor,
|
70
|
+
}] = useCoordination(
|
71
|
+
COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_STATS_TABLE],
|
72
|
+
coordinationScopes,
|
73
|
+
);
|
74
|
+
|
75
|
+
const obsSetsLoader = useMatchingLoader(
|
76
|
+
loaders, dataset, DataType.OBS_SETS, { obsType },
|
77
|
+
);
|
78
|
+
const sampleSetsLoader = useMatchingLoader(
|
79
|
+
loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
|
80
|
+
);
|
81
|
+
const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
|
82
|
+
const obsSetsColumnNameMappingReversed = useColumnNameMapping(obsSetsLoader, true);
|
83
|
+
const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
|
84
|
+
const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
|
85
|
+
|
86
|
+
const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
|
87
|
+
const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
|
88
|
+
|
89
|
+
const [{ featureStats }, featureStatsStatus] = useFeatureStatsData(
|
90
|
+
loaders, dataset, false,
|
91
|
+
{ obsType, featureType, sampleType },
|
92
|
+
// These volcanoOptions are passed to FeatureStatsAnndataLoader.loadMulti():
|
93
|
+
{ sampleSetSelection: rawSampleSetSelection, obsSetSelection: rawObsSetSelection },
|
94
|
+
);
|
95
|
+
|
96
|
+
const isReady = useReady([
|
97
|
+
featureStatsStatus,
|
98
|
+
]);
|
99
|
+
|
100
|
+
return (
|
101
|
+
<TitleInfo
|
102
|
+
title={title}
|
103
|
+
removeGridComponent={removeGridComponent}
|
104
|
+
theme={theme}
|
105
|
+
isReady={isReady}
|
106
|
+
helpText={helpText}
|
107
|
+
withPadding={false}
|
108
|
+
>
|
109
|
+
{featureStats ? (
|
110
|
+
<FeatureStatsTable
|
111
|
+
theme={theme}
|
112
|
+
obsType={obsType}
|
113
|
+
featureType={featureType}
|
114
|
+
obsSetsColumnNameMapping={obsSetsColumnNameMapping}
|
115
|
+
obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
|
116
|
+
sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
|
117
|
+
sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
|
118
|
+
sampleSetSelection={sampleSetSelection}
|
119
|
+
obsSetSelection={obsSetSelection}
|
120
|
+
obsSetColor={obsSetColor}
|
121
|
+
sampleSetColor={sampleSetColor}
|
122
|
+
data={featureStats}
|
123
|
+
featureSelection={featureSelection}
|
124
|
+
setFeatureSelection={setFeatureSelection}
|
125
|
+
featurePointSignificanceThreshold={featurePointSignificanceThreshold}
|
126
|
+
featurePointFoldChangeThreshold={featurePointFoldChangeThreshold}
|
127
|
+
/>
|
128
|
+
) : (
|
129
|
+
<p style={{ padding: '12px' }}>Select at least one {obsType} set.</p>
|
130
|
+
)}
|
131
|
+
</TitleInfo>
|
132
|
+
);
|
133
|
+
}
|
package/src/Treemap.js
CHANGED
@@ -105,6 +105,10 @@ export default function Treemap(props) {
|
|
105
105
|
useEffect(() => {
|
106
106
|
const domElement = svgRef.current;
|
107
107
|
|
108
|
+
if (!width || !height) {
|
109
|
+
return;
|
110
|
+
}
|
111
|
+
|
108
112
|
const svg = select(domElement);
|
109
113
|
svg.selectAll('g').remove();
|
110
114
|
svg
|
@@ -113,7 +117,7 @@ export default function Treemap(props) {
|
|
113
117
|
.attr('viewBox', [0, 0, width, height])
|
114
118
|
.attr('style', 'font: 10px sans-serif');
|
115
119
|
|
116
|
-
if (!treemapLeaves || !obsSetSelection
|
120
|
+
if (!treemapLeaves || !obsSetSelection) {
|
117
121
|
return;
|
118
122
|
}
|
119
123
|
|
@@ -174,14 +178,28 @@ export default function Treemap(props) {
|
|
174
178
|
.append('use')
|
175
179
|
.attr('xlink:href', d => d.leafUid.href);
|
176
180
|
|
181
|
+
const hasSampleSetSelection = Array.isArray(sampleSetSelection);
|
182
|
+
|
177
183
|
// Append multiline text.
|
178
184
|
leaf.append('text')
|
179
185
|
.attr('clip-path', d => `url(${d.clipUid.href})`)
|
180
186
|
.selectAll('tspan')
|
181
187
|
.data(d => ([
|
182
188
|
// Each element in this array corresponds to a line of text.
|
183
|
-
|
184
|
-
|
189
|
+
...(
|
190
|
+
hasSampleSetSelection
|
191
|
+
? ([
|
192
|
+
d.data?.[0]?.at(-1),
|
193
|
+
d.parent?.data?.[0]?.at(-1),
|
194
|
+
]) : ([
|
195
|
+
// Only use the cell set name
|
196
|
+
// for the line of text
|
197
|
+
// (since no sample set selection)
|
198
|
+
hierarchyLevels[0] === 'obsSet'
|
199
|
+
? d.parent?.data?.[0].at(-1)
|
200
|
+
: d.data?.[0].at(-1),
|
201
|
+
])
|
202
|
+
),
|
185
203
|
`${d.data?.[1].toLocaleString()} ${plur(obsType, d.data?.[1])}`,
|
186
204
|
]))
|
187
205
|
.join('tspan')
|
package/src/TreemapSubscriber.js
CHANGED
@@ -141,9 +141,6 @@ export function TreemapSubscriber(props) {
|
|
141
141
|
[sampleSets],
|
142
142
|
);
|
143
143
|
|
144
|
-
const obsCount = obsIndex?.length || 0;
|
145
|
-
const sampleCount = sampleIndex?.length || 0;
|
146
|
-
|
147
144
|
// TODO: use obsFilter / sampleFilter to display
|
148
145
|
// _all_ cells/samples in gray / transparent in background,
|
149
146
|
// and use obsSetSelection/sampleSetSelection to display
|
@@ -173,16 +170,16 @@ export function TreemapSubscriber(props) {
|
|
173
170
|
});
|
174
171
|
});
|
175
172
|
|
176
|
-
const sampleSetSizes = treeToSetSizesBySetNames(
|
173
|
+
const sampleSetSizes = hasSampleSetSelection ? treeToSetSizesBySetNames(
|
177
174
|
mergedSampleSets, sampleSetSelection, sampleSetSelection, sampleSetColor, theme,
|
178
|
-
);
|
175
|
+
) : null;
|
179
176
|
|
180
177
|
sampleSetKeys.forEach((sampleSetKey) => {
|
181
|
-
const sampleSetSize = sampleSetSizes
|
178
|
+
const sampleSetSize = sampleSetSizes?.find(d => isEqual(d.setNamePath, sampleSetKey))?.size;
|
182
179
|
sampleResult.set(sampleSetKey, sampleSetSize || 0);
|
183
180
|
});
|
184
181
|
|
185
|
-
if (mergedObsSets && obsSetSelection) {
|
182
|
+
if (mergedObsSets && obsSetSelection && obsIndex) {
|
186
183
|
const sampleIdToSetMap = sampleSets && sampleSetSelection
|
187
184
|
? treeToSelectedSetMap(sampleSets, sampleSetSelection)
|
188
185
|
: null;
|
@@ -193,7 +190,9 @@ export function TreemapSubscriber(props) {
|
|
193
190
|
|
194
191
|
const cellSet = cellIdToSetMap?.get(obsId);
|
195
192
|
const sampleId = sampleEdges?.get(obsId);
|
196
|
-
const sampleSet = sampleId
|
193
|
+
const sampleSet = sampleId && hasSampleSetSelection
|
194
|
+
? sampleIdToSetMap?.get(sampleId)
|
195
|
+
: null;
|
197
196
|
|
198
197
|
if (hasSampleSetSelection && !sampleSet) {
|
199
198
|
// Skip this sample if it is not in the selected sample set.
|
@@ -211,10 +210,20 @@ export function TreemapSubscriber(props) {
|
|
211
210
|
];
|
212
211
|
}, [obsIndex, sampleEdges, sampleSets, obsSetColor,
|
213
212
|
sampleSetColor, mergedObsSets, obsSetSelection, mergedSampleSets,
|
214
|
-
sampleSetSelection,
|
213
|
+
sampleSetSelection, obsIndex,
|
215
214
|
// TODO: consider filtering-related coordination values
|
216
215
|
]);
|
217
216
|
|
217
|
+
const totalObsCount = obsIndex?.length || 0;
|
218
|
+
const totalSampleCount = sampleIndex?.length || 0;
|
219
|
+
|
220
|
+
const selectedObsCount = obsCounts.reduce((a, h) => a + h.value, 0);
|
221
|
+
const selectedSampleCount = sampleCounts.reduce((a, h) => a + h.value, 0);
|
222
|
+
|
223
|
+
const unselectedObsCount = totalObsCount - selectedObsCount;
|
224
|
+
const unselectedSampleCount = totalSampleCount - selectedSampleCount;
|
225
|
+
|
226
|
+
|
218
227
|
const onNodeClick = useCallback((obsSetPath) => {
|
219
228
|
setObsSetSelection([obsSetPath]);
|
220
229
|
}, [setObsSetSelection]);
|
@@ -222,12 +231,13 @@ export function TreemapSubscriber(props) {
|
|
222
231
|
return (
|
223
232
|
<TitleInfo
|
224
233
|
title={`Treemap of ${capitalize(plur(obsType, 2))}`}
|
225
|
-
info={`${commaNumber(
|
234
|
+
info={`${commaNumber(selectedObsCount)} ${plur(obsType, selectedObsCount)} from ${commaNumber(selectedSampleCount)} ${plur(sampleType, selectedSampleCount)}`}
|
226
235
|
removeGridComponent={removeGridComponent}
|
227
236
|
urls={urls}
|
228
237
|
theme={theme}
|
229
238
|
isReady={isReady}
|
230
239
|
helpText={helpText}
|
240
|
+
withPadding={false}
|
231
241
|
options={(
|
232
242
|
<TreemapOptions
|
233
243
|
obsType={obsType}
|
@@ -252,7 +262,7 @@ export function TreemapSubscriber(props) {
|
|
252
262
|
hierarchyLevels={hierarchyLevels || DEFAULT_HIERARCHY_LEVELS}
|
253
263
|
theme={theme}
|
254
264
|
width={width}
|
255
|
-
height={height}
|
265
|
+
height={Math.max(height * (selectedObsCount / totalObsCount), 40)}
|
256
266
|
obsType={obsType}
|
257
267
|
sampleType={sampleType}
|
258
268
|
obsSetColor={obsSetColor}
|
@@ -262,6 +272,11 @@ export function TreemapSubscriber(props) {
|
|
262
272
|
onNodeClick={onNodeClick}
|
263
273
|
/>
|
264
274
|
</div>
|
275
|
+
<div style={{ position: 'absolute', right: '2px', bottom: '2px', fontSize: '10px' }}>
|
276
|
+
{unselectedObsCount > 0 ? (
|
277
|
+
<span>{`${commaNumber(unselectedObsCount)} ${plur(obsType, unselectedObsCount)} from ${commaNumber(unselectedSampleCount)} ${plur(sampleType, unselectedSampleCount)} currently omitted`}</span>
|
278
|
+
) : null}
|
279
|
+
</div>
|
265
280
|
</TitleInfo>
|
266
281
|
);
|
267
282
|
}
|
package/src/VolcanoPlot.js
CHANGED
@@ -5,10 +5,9 @@ import { scaleLinear } from 'd3-scale';
|
|
5
5
|
import { axisBottom, axisLeft } from 'd3-axis';
|
6
6
|
import { extent as d3_extent } from 'd3-array';
|
7
7
|
import { select } from 'd3-selection';
|
8
|
-
import { isEqual } from 'lodash-es';
|
9
8
|
import { capitalize, getDefaultForegroundColor } from '@vitessce/utils';
|
10
9
|
import { colorArrayToString } from '@vitessce/sets-utils';
|
11
|
-
import { getColorScale } from './utils.js';
|
10
|
+
import { getColorScale, useFilteredVolcanoData } from './utils.js';
|
12
11
|
|
13
12
|
export default function VolcanoPlot(props) {
|
14
13
|
const {
|
@@ -17,8 +16,8 @@ export default function VolcanoPlot(props) {
|
|
17
16
|
height,
|
18
17
|
obsType,
|
19
18
|
featureType,
|
20
|
-
|
21
|
-
|
19
|
+
obsSetsColumnNameMappingReversed,
|
20
|
+
sampleSetsColumnNameMappingReversed,
|
22
21
|
sampleSetSelection,
|
23
22
|
obsSetSelection,
|
24
23
|
obsSetColor,
|
@@ -37,14 +36,14 @@ export default function VolcanoPlot(props) {
|
|
37
36
|
|
38
37
|
const svgRef = useRef();
|
39
38
|
|
40
|
-
const computedData =
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
})
|
39
|
+
const [computedData, filteredData] = useFilteredVolcanoData({
|
40
|
+
data,
|
41
|
+
obsSetsColumnNameMappingReversed,
|
42
|
+
sampleSetsColumnNameMappingReversed,
|
43
|
+
featurePointFoldChangeThreshold,
|
44
|
+
featurePointSignificanceThreshold,
|
45
|
+
sampleSetSelection,
|
46
|
+
});
|
48
47
|
|
49
48
|
const [xExtent, yExtent] = useMemo(() => {
|
50
49
|
if (!computedData) {
|
@@ -78,7 +77,7 @@ export default function VolcanoPlot(props) {
|
|
78
77
|
.attr('viewBox', [0, 0, width, height])
|
79
78
|
.attr('style', 'font: 10px sans-serif');
|
80
79
|
|
81
|
-
if (!
|
80
|
+
if (!filteredData || !xExtent || !yExtent) {
|
82
81
|
return;
|
83
82
|
}
|
84
83
|
|
@@ -134,19 +133,6 @@ export default function VolcanoPlot(props) {
|
|
134
133
|
.style('font-size', '12px')
|
135
134
|
.style('fill', fgColor);
|
136
135
|
|
137
|
-
// Get a mapping from column name to group name.
|
138
|
-
const obsSetsColumnNameMappingReversed = Object.fromEntries(
|
139
|
-
Object
|
140
|
-
.entries(obsSetsColumnNameMapping)
|
141
|
-
.map(([key, value]) => ([value, key])),
|
142
|
-
);
|
143
|
-
|
144
|
-
const sampleSetsColumnNameMappingReversed = Object.fromEntries(
|
145
|
-
Object
|
146
|
-
.entries(sampleSetsColumnNameMapping)
|
147
|
-
.map(([key, value]) => ([value, key])),
|
148
|
-
);
|
149
|
-
|
150
136
|
// Horizontal and vertical rules to indicate currently-selected thresholds
|
151
137
|
// Vertical lines
|
152
138
|
const ruleColor = 'silver';
|
@@ -188,7 +174,6 @@ export default function VolcanoPlot(props) {
|
|
188
174
|
: `${capitalize(obsType)} Set`
|
189
175
|
);
|
190
176
|
|
191
|
-
|
192
177
|
titleG
|
193
178
|
.append('text')
|
194
179
|
.attr('text-anchor', 'start')
|
@@ -211,10 +196,10 @@ export default function VolcanoPlot(props) {
|
|
211
196
|
const g = svg.append('g');
|
212
197
|
|
213
198
|
// Append a circle for each data point.
|
214
|
-
|
199
|
+
filteredData.forEach((comparisonObject) => {
|
215
200
|
const obsSetG = g.append('g');
|
216
201
|
|
217
|
-
const { df, metadata } = comparisonObject;
|
202
|
+
const { df: filteredDf, metadata } = comparisonObject;
|
218
203
|
const coordinationValues = metadata.coordination_values;
|
219
204
|
|
220
205
|
const rawObsSetPath = coordinationValues.obsSetFilter
|
@@ -223,40 +208,6 @@ export default function VolcanoPlot(props) {
|
|
223
208
|
const obsSetPath = [...rawObsSetPath];
|
224
209
|
obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
|
225
210
|
|
226
|
-
// Swap the foldchange direction if backwards with
|
227
|
-
// respect to the current sampleSetSelection pair.
|
228
|
-
// TODO: move this swapping into the computedData useMemo?
|
229
|
-
let shouldSwapFoldChangeDirection = false;
|
230
|
-
if (
|
231
|
-
coordinationValues.sampleSetFilter
|
232
|
-
&& coordinationValues.sampleSetFilter.length === 2
|
233
|
-
) {
|
234
|
-
const rawSampleSetPathA = coordinationValues.sampleSetFilter[0];
|
235
|
-
const sampleSetPathA = [...rawSampleSetPathA];
|
236
|
-
sampleSetPathA[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathA[0]];
|
237
|
-
|
238
|
-
const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
|
239
|
-
const sampleSetPathB = [...rawSampleSetPathB];
|
240
|
-
sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
|
241
|
-
|
242
|
-
if (
|
243
|
-
isEqual(sampleSetPathA, sampleSetSelection[1])
|
244
|
-
&& isEqual(sampleSetPathB, sampleSetSelection[0])
|
245
|
-
) {
|
246
|
-
shouldSwapFoldChangeDirection = true;
|
247
|
-
}
|
248
|
-
}
|
249
|
-
|
250
|
-
const filteredDf = df.featureId.map((featureId, i) => ({
|
251
|
-
featureId,
|
252
|
-
logFoldChange: df.logFoldChange[i] * (shouldSwapFoldChangeDirection ? -1 : 1),
|
253
|
-
featureSignificance: df.featureSignificance[i],
|
254
|
-
minusLog10p: df.minusLog10p[i],
|
255
|
-
})).filter(d => (
|
256
|
-
(Math.abs(d.logFoldChange) >= (featurePointFoldChangeThreshold ?? 1.0))
|
257
|
-
&& (d.featureSignificance <= (featurePointSignificanceThreshold ?? 0.05))
|
258
|
-
));
|
259
|
-
|
260
211
|
const color = obsSetColorScale(obsSetPath);
|
261
212
|
|
262
213
|
obsSetG.append('g')
|
@@ -293,12 +244,13 @@ export default function VolcanoPlot(props) {
|
|
293
244
|
.text(d => `${featureType}: ${d.featureId}\nin ${obsSetPath?.at(-1)}\nlog2 fold-change: ${d.logFoldChange}\np-value: ${d.featureSignificance}`);
|
294
245
|
});
|
295
246
|
}, [width, height, theme, sampleSetColor, sampleSetSelection,
|
296
|
-
obsSetSelection, obsSetColor, featureType,
|
247
|
+
obsSetSelection, obsSetColor, featureType, filteredData,
|
297
248
|
xExtent, yExtent, obsType,
|
298
249
|
marginLeft, marginBottom, marginTop, marginRight,
|
299
250
|
obsSetColorScale, sampleSetColorScale, onFeatureClick,
|
300
251
|
featurePointSignificanceThreshold, featurePointFoldChangeThreshold,
|
301
252
|
featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold,
|
253
|
+
obsSetsColumnNameMappingReversed,
|
302
254
|
]);
|
303
255
|
|
304
256
|
return (
|
@@ -23,6 +23,7 @@ import { useRawSetPaths } from './utils.js';
|
|
23
23
|
|
24
24
|
export function VolcanoPlotSubscriber(props) {
|
25
25
|
const {
|
26
|
+
title = 'Volcano Plot',
|
26
27
|
coordinationScopes,
|
27
28
|
removeGridComponent,
|
28
29
|
theme,
|
@@ -87,7 +88,9 @@ export function VolcanoPlotSubscriber(props) {
|
|
87
88
|
loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
|
88
89
|
);
|
89
90
|
const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
|
91
|
+
const obsSetsColumnNameMappingReversed = useColumnNameMapping(obsSetsLoader, true);
|
90
92
|
const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
|
93
|
+
const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
|
91
94
|
|
92
95
|
const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
|
93
96
|
const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
|
@@ -109,7 +112,7 @@ export function VolcanoPlotSubscriber(props) {
|
|
109
112
|
|
110
113
|
return (
|
111
114
|
<TitleInfo
|
112
|
-
title=
|
115
|
+
title={title}
|
113
116
|
removeGridComponent={removeGridComponent}
|
114
117
|
theme={theme}
|
115
118
|
isReady={isReady}
|
@@ -140,7 +143,9 @@ export function VolcanoPlotSubscriber(props) {
|
|
140
143
|
obsType={obsType}
|
141
144
|
featureType={featureType}
|
142
145
|
obsSetsColumnNameMapping={obsSetsColumnNameMapping}
|
146
|
+
obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
|
143
147
|
sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
|
148
|
+
sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
|
144
149
|
sampleSetSelection={sampleSetSelection}
|
145
150
|
obsSetSelection={obsSetSelection}
|
146
151
|
obsSetColor={obsSetColor}
|
package/src/expr-hooks.js
CHANGED
@@ -92,7 +92,6 @@ export function summarizeStratifiedExpressionData(
|
|
92
92
|
stratifiedResult, keepZeros,
|
93
93
|
) {
|
94
94
|
const summarizedResult = new InternMap([], JSON.stringify);
|
95
|
-
|
96
95
|
Array.from(stratifiedResult.entries()).forEach(([cellSetKey, firstLevelInternMap]) => {
|
97
96
|
summarizedResult.set(cellSetKey, new InternMap([], JSON.stringify));
|
98
97
|
Array.from(firstLevelInternMap.entries()).forEach(([sampleSetKey, secondLevelInternMap]) => {
|
package/src/expr-hooks.test.js
CHANGED
@@ -78,6 +78,7 @@ describe('Utility functions for processing expression data for statistical plots
|
|
78
78
|
];
|
79
79
|
const featureValueTransform = null;
|
80
80
|
const featureValueTransformCoefficient = 1;
|
81
|
+
const featureAggregationStrategy = 'first';
|
81
82
|
|
82
83
|
const [result] = stratifyExpressionData(
|
83
84
|
sampleEdges, sampleSets, sampleSetSelection,
|
@@ -86,7 +87,7 @@ describe('Utility functions for processing expression data for statistical plots
|
|
86
87
|
featureValueTransform, featureValueTransformCoefficient,
|
87
88
|
);
|
88
89
|
const aggregateData = aggregateStratifiedExpressionData(
|
89
|
-
result, geneSelection,
|
90
|
+
result, geneSelection, featureAggregationStrategy,
|
90
91
|
);
|
91
92
|
const summaryResult = summarizeStratifiedExpressionData(aggregateData, true);
|
92
93
|
|
package/src/index.js
CHANGED
@@ -7,6 +7,7 @@ export { TreemapSubscriber } from './TreemapSubscriber.js';
|
|
7
7
|
export { VolcanoPlotSubscriber } from './VolcanoPlotSubscriber.js';
|
8
8
|
export { CellSetCompositionBarPlotSubscriber } from './CellSetCompositionBarPlotSubscriber.js';
|
9
9
|
export { FeatureSetEnrichmentBarPlotSubscriber } from './FeatureSetEnrichmentBarPlotSubscriber.js';
|
10
|
+
export { FeatureStatsTableSubscriber } from './FeatureStatsTableSubscriber.js';
|
10
11
|
export { default as CellSetSizesPlot } from './CellSetSizesPlot.js';
|
11
12
|
export { default as CellSetExpressionPlot } from './CellSetExpressionPlot.js';
|
12
13
|
export { default as ExpressionHistogram } from './ExpressionHistogram.js';
|