@vitessce/statistical-plots 3.5.9 → 3.5.10
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-ad0dcbe4.js} +1 -1
- package/dist/{index-dc733355.js → index-b8398176.js} +33528 -15439
- package/dist/index.js +6 -5
- package/dist/{jpeg-a83077be.js → jpeg-81bd1053.js} +1 -1
- package/dist/{lerc-1edd075a.js → lerc-b15c3a4c.js} +1 -1
- package/dist/{lzw-9572eac3.js → lzw-503cb795.js} +1 -1
- package/dist/{packbits-cce11fbc.js → packbits-40cbad40.js} +1 -1
- package/dist/{raw-f7587aff.js → raw-9b8d9daf.js} +1 -1
- package/dist/{webimage-8d38cd8b.js → webimage-bbc59b4a.js} +1 -1
- package/dist-tsc/CellSetCompositionBarPlot.js +3 -3
- 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/CellSetExpressionPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/CellSetExpressionPlotSubscriber.js +5 -2
- package/dist-tsc/DotPlot.d.ts.map +1 -1
- package/dist-tsc/DotPlot.js +54 -5
- package/dist-tsc/DotPlotSubscriber.d.ts.map +1 -1
- package/dist-tsc/DotPlotSubscriber.js +1 -1
- 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 +5 -3
- 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/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 +3 -3
- package/src/CellSetCompositionBarPlotSubscriber.js +1 -1
- package/src/CellSetExpressionPlot.js +33 -10
- package/src/CellSetExpressionPlotSubscriber.js +10 -3
- package/src/DotPlot.js +77 -9
- package/src/DotPlotSubscriber.js +3 -1
- package/src/FeatureStatsTable.js +116 -0
- package/src/FeatureStatsTableSubscriber.js +133 -0
- package/src/Treemap.js +21 -3
- package/src/TreemapSubscriber.js +6 -4
- package/src/VolcanoPlot.js +16 -64
- package/src/VolcanoPlotSubscriber.js +6 -1
- package/src/index.js +1 -0
- package/src/utils.js +82 -1
@@ -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
@@ -173,12 +173,12 @@ export function TreemapSubscriber(props) {
|
|
173
173
|
});
|
174
174
|
});
|
175
175
|
|
176
|
-
const sampleSetSizes = treeToSetSizesBySetNames(
|
176
|
+
const sampleSetSizes = hasSampleSetSelection ? treeToSetSizesBySetNames(
|
177
177
|
mergedSampleSets, sampleSetSelection, sampleSetSelection, sampleSetColor, theme,
|
178
|
-
);
|
178
|
+
) : null;
|
179
179
|
|
180
180
|
sampleSetKeys.forEach((sampleSetKey) => {
|
181
|
-
const sampleSetSize = sampleSetSizes
|
181
|
+
const sampleSetSize = sampleSetSizes?.find(d => isEqual(d.setNamePath, sampleSetKey))?.size;
|
182
182
|
sampleResult.set(sampleSetKey, sampleSetSize || 0);
|
183
183
|
});
|
184
184
|
|
@@ -193,7 +193,9 @@ export function TreemapSubscriber(props) {
|
|
193
193
|
|
194
194
|
const cellSet = cellIdToSetMap?.get(obsId);
|
195
195
|
const sampleId = sampleEdges?.get(obsId);
|
196
|
-
const sampleSet = sampleId
|
196
|
+
const sampleSet = sampleId && hasSampleSetSelection
|
197
|
+
? sampleIdToSetMap?.get(sampleId)
|
198
|
+
: null;
|
197
199
|
|
198
200
|
if (hasSampleSetSelection && !sampleSet) {
|
199
201
|
// Skip this sample if it is not in the selected sample set.
|
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/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';
|
package/src/utils.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
/* eslint-disable camelcase */
|
1
2
|
import { useMemo } from 'react';
|
2
3
|
import { isEqual } from 'lodash-es';
|
3
4
|
import { colorArrayToString } from '@vitessce/sets-utils';
|
4
5
|
import { getDefaultColor } from '@vitessce/utils';
|
5
6
|
|
6
|
-
|
7
7
|
function createOrdinalScale(domainArr, rangeArr) {
|
8
8
|
return (queryVal) => {
|
9
9
|
const i = domainArr.findIndex(domainVal => isEqual(domainVal, queryVal));
|
@@ -45,3 +45,84 @@ export function useRawSetPaths(columnNameMapping, setPaths) {
|
|
45
45
|
return newSetPath;
|
46
46
|
}), [columnNameMapping, setPaths]);
|
47
47
|
}
|
48
|
+
|
49
|
+
// Data transformation hook function that is used both here
|
50
|
+
// and in the FeatureStatsTable view.
|
51
|
+
export function useFilteredVolcanoData(props) {
|
52
|
+
const {
|
53
|
+
data,
|
54
|
+
obsSetsColumnNameMappingReversed,
|
55
|
+
sampleSetsColumnNameMappingReversed,
|
56
|
+
featurePointFoldChangeThreshold,
|
57
|
+
featurePointSignificanceThreshold,
|
58
|
+
sampleSetSelection,
|
59
|
+
} = props;
|
60
|
+
|
61
|
+
|
62
|
+
const computedData = useMemo(() => data.map((d) => {
|
63
|
+
const { metadata } = d;
|
64
|
+
|
65
|
+
const coordinationValues = metadata.coordination_values;
|
66
|
+
|
67
|
+
const rawObsSetPath = coordinationValues.obsSetFilter
|
68
|
+
? coordinationValues.obsSetFilter[0]
|
69
|
+
: coordinationValues.obsSetSelection[0];
|
70
|
+
const obsSetPath = [...rawObsSetPath];
|
71
|
+
obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
|
72
|
+
|
73
|
+
// Swap the foldchange direction if backwards with
|
74
|
+
// respect to the current sampleSetSelection pair.
|
75
|
+
// TODO: move this swapping into the computedData useMemo?
|
76
|
+
let shouldSwapFoldChangeDirection = false;
|
77
|
+
if (
|
78
|
+
coordinationValues.sampleSetFilter
|
79
|
+
&& coordinationValues.sampleSetFilter.length === 2
|
80
|
+
) {
|
81
|
+
const rawSampleSetPathA = coordinationValues.sampleSetFilter[0];
|
82
|
+
const sampleSetPathA = [...rawSampleSetPathA];
|
83
|
+
sampleSetPathA[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathA[0]];
|
84
|
+
|
85
|
+
const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
|
86
|
+
const sampleSetPathB = [...rawSampleSetPathB];
|
87
|
+
sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
|
88
|
+
|
89
|
+
if (
|
90
|
+
isEqual(sampleSetPathA, sampleSetSelection[1])
|
91
|
+
&& isEqual(sampleSetPathB, sampleSetSelection[0])
|
92
|
+
) {
|
93
|
+
shouldSwapFoldChangeDirection = true;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
return ({
|
98
|
+
...d,
|
99
|
+
df: {
|
100
|
+
...d.df,
|
101
|
+
minusLog10p: d.df.featureSignificance.map(v => -Math.log10(v)),
|
102
|
+
logFoldChange: d.df.featureFoldChange.map(v => (
|
103
|
+
Math.log2(v) * (shouldSwapFoldChangeDirection ? -1 : 1)
|
104
|
+
)),
|
105
|
+
},
|
106
|
+
});
|
107
|
+
}), [
|
108
|
+
data, obsSetsColumnNameMappingReversed, sampleSetsColumnNameMappingReversed,
|
109
|
+
sampleSetSelection,
|
110
|
+
]);
|
111
|
+
|
112
|
+
const filteredData = useMemo(() => computedData.map(obj => ({
|
113
|
+
...obj,
|
114
|
+
// Instead of an object of one array per column,
|
115
|
+
// this is now an array of one object per row.
|
116
|
+
df: obj.df.featureId.map((featureId, i) => ({
|
117
|
+
featureId,
|
118
|
+
logFoldChange: obj.df.logFoldChange[i],
|
119
|
+
featureSignificance: obj.df.featureSignificance[i],
|
120
|
+
minusLog10p: obj.df.minusLog10p[i],
|
121
|
+
})).filter(d => (
|
122
|
+
(Math.abs(d.logFoldChange) >= (featurePointFoldChangeThreshold ?? 1.0))
|
123
|
+
&& (d.featureSignificance <= (featurePointSignificanceThreshold ?? 0.05))
|
124
|
+
)),
|
125
|
+
})), [computedData, featurePointFoldChangeThreshold, featurePointSignificanceThreshold]);
|
126
|
+
|
127
|
+
return [computedData, filteredData];
|
128
|
+
}
|