@vitessce/biomarker-select 3.9.2 → 3.9.4
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 +6339 -5505
- package/dist-tsc/BiomarkerSelectAlt.d.ts +2 -0
- package/dist-tsc/BiomarkerSelectAlt.d.ts.map +1 -0
- package/dist-tsc/BiomarkerSelectAlt.js +11 -0
- package/dist-tsc/BiomarkerSelectAltGeneAutocomplete.d.ts +2 -0
- package/dist-tsc/BiomarkerSelectAltGeneAutocomplete.d.ts.map +1 -0
- package/dist-tsc/BiomarkerSelectAltGeneAutocomplete.js +33 -0
- package/dist-tsc/BiomarkerSelectAltSampleGroups.d.ts +2 -0
- package/dist-tsc/BiomarkerSelectAltSampleGroups.d.ts.map +1 -0
- package/dist-tsc/BiomarkerSelectAltSampleGroups.js +92 -0
- package/dist-tsc/BiomarkerSelectAltSubscriber.d.ts +14 -0
- package/dist-tsc/BiomarkerSelectAltSubscriber.d.ts.map +1 -0
- package/dist-tsc/BiomarkerSelectAltSubscriber.js +112 -0
- package/dist-tsc/ComparativeHeadingSubscriber.js +1 -1
- package/dist-tsc/index.d.ts +1 -0
- package/dist-tsc/index.js +1 -0
- package/dist-tsc/select-specific.d.ts.map +1 -1
- package/dist-tsc/select-specific.js +1 -0
- package/package.json +6 -5
- package/src/BiomarkerSelectAlt.js +49 -0
- package/src/BiomarkerSelectAltGeneAutocomplete.js +103 -0
- package/src/BiomarkerSelectAltSampleGroups.js +153 -0
- package/src/BiomarkerSelectAltSubscriber.js +178 -0
- package/src/ComparativeHeadingSubscriber.js +4 -4
- package/src/index.js +1 -0
- package/src/select-specific.js +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { Grid, NativeSelect } from '@vitessce/styles';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function BiomarkerSelectAltSampleGroups(props) {
|
|
7
|
+
const {
|
|
8
|
+
setSampleSetFilter,
|
|
9
|
+
setSampleSetSelection,
|
|
10
|
+
|
|
11
|
+
stratifications,
|
|
12
|
+
} = props;
|
|
13
|
+
|
|
14
|
+
const [lhsSelectedGroup, setLhsSelectedGroup] = useState(null);
|
|
15
|
+
const [rhsSelectedGroup, setRhsSelectedGroup] = useState(null);
|
|
16
|
+
|
|
17
|
+
const sampleSetOptions = stratifications?.filter(s => s.stratificationType === 'sampleSet');
|
|
18
|
+
const hasSampleSetOptions = sampleSetOptions && sampleSetOptions.length > 0;
|
|
19
|
+
|
|
20
|
+
const lhsOptions = useMemo(() => {
|
|
21
|
+
if (sampleSetOptions) {
|
|
22
|
+
const result = [];
|
|
23
|
+
const withDuplicates = sampleSetOptions.map(o => o.sampleSets).flat();
|
|
24
|
+
withDuplicates.forEach((setPath) => {
|
|
25
|
+
if (!result.find(r => isEqual(r.path, setPath))) {
|
|
26
|
+
const rhsOptionsForLhs = [];
|
|
27
|
+
sampleSetOptions.forEach((o) => {
|
|
28
|
+
const pathPair = o.sampleSets;
|
|
29
|
+
if (isEqual(pathPair[0], setPath)) {
|
|
30
|
+
const otherPath = pathPair[1];
|
|
31
|
+
rhsOptionsForLhs.push({
|
|
32
|
+
name: `${otherPath[0]}: ${otherPath[1]}`,
|
|
33
|
+
path: otherPath,
|
|
34
|
+
sampleSets: o.sampleSets,
|
|
35
|
+
stratificationId: o.stratificationId,
|
|
36
|
+
});
|
|
37
|
+
} else if (isEqual(pathPair[1], setPath)) {
|
|
38
|
+
const otherPath = pathPair[0];
|
|
39
|
+
rhsOptionsForLhs.push({
|
|
40
|
+
name: `${otherPath[0]}: ${otherPath[1]}`,
|
|
41
|
+
path: otherPath,
|
|
42
|
+
// Reverse the order.
|
|
43
|
+
sampleSets: [o.sampleSets[1], o.sampleSets[0]],
|
|
44
|
+
stratificationId: o.stratificationId,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
result.push({
|
|
50
|
+
name: `${setPath[0]}: ${setPath[1]}`,
|
|
51
|
+
path: setPath,
|
|
52
|
+
rhsOptions: rhsOptionsForLhs,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
}, [sampleSetOptions]);
|
|
60
|
+
|
|
61
|
+
const rhsOptions = useMemo(() => {
|
|
62
|
+
if (sampleSetOptions && lhsOptions && lhsSelectedGroup) {
|
|
63
|
+
const lhsOption = lhsOptions.find(o => o.name === lhsSelectedGroup);
|
|
64
|
+
return lhsOption ? lhsOption.rhsOptions : [];
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}, [sampleSetOptions, lhsOptions, lhsSelectedGroup]);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
function handleFirstGroupChange(event) {
|
|
71
|
+
setLhsSelectedGroup(event.target.value);
|
|
72
|
+
|
|
73
|
+
// Update the sampleSetFilter and sampleSetSelection.
|
|
74
|
+
if (event.target.value === '__all__') {
|
|
75
|
+
setSampleSetFilter(null);
|
|
76
|
+
setSampleSetSelection(null);
|
|
77
|
+
} else {
|
|
78
|
+
const lhsOption = lhsOptions.find(o => o.name === event.target.value);
|
|
79
|
+
if (lhsOption) {
|
|
80
|
+
setSampleSetFilter([lhsOption.path]);
|
|
81
|
+
setSampleSetSelection([lhsOption.path]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleSecondGroupChange(event) {
|
|
87
|
+
setRhsSelectedGroup(event.target.value);
|
|
88
|
+
|
|
89
|
+
// Update the sampleSetFilter and sampleSetSelection.
|
|
90
|
+
if (event.target.value === '__none__') {
|
|
91
|
+
const lhsOption = lhsOptions.find(o => o.name === lhsSelectedGroup);
|
|
92
|
+
if (lhsOption) {
|
|
93
|
+
setSampleSetFilter([lhsOption.path]);
|
|
94
|
+
setSampleSetSelection([lhsOption.path]);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
const rhsOption = rhsOptions.find(o => o.name === event.target.value);
|
|
98
|
+
if (rhsOption) {
|
|
99
|
+
setSampleSetFilter(rhsOption.sampleSets);
|
|
100
|
+
setSampleSetSelection(rhsOption.sampleSets);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<>
|
|
107
|
+
<Grid container size={12}>
|
|
108
|
+
<Grid container size={4}>
|
|
109
|
+
<span style={{ lineHeight: '50px' }}>in: </span>
|
|
110
|
+
</Grid>
|
|
111
|
+
<Grid container size={8}>
|
|
112
|
+
<NativeSelect
|
|
113
|
+
onChange={handleFirstGroupChange}
|
|
114
|
+
value={lhsSelectedGroup ?? '__all__'}
|
|
115
|
+
variant="standard"
|
|
116
|
+
sx={{ width: '100%' }}
|
|
117
|
+
>
|
|
118
|
+
<option value="__all__">All</option>
|
|
119
|
+
{hasSampleSetOptions ? (
|
|
120
|
+
<>
|
|
121
|
+
{lhsOptions.map(o => (
|
|
122
|
+
<option value={o.name}>{o.name}</option>
|
|
123
|
+
))}
|
|
124
|
+
</>
|
|
125
|
+
) : null}
|
|
126
|
+
</NativeSelect>
|
|
127
|
+
</Grid>
|
|
128
|
+
</Grid>
|
|
129
|
+
<Grid container size={12}>
|
|
130
|
+
<Grid container size={4}>
|
|
131
|
+
<span style={{ lineHeight: '50px' }}>compare to: </span>
|
|
132
|
+
</Grid>
|
|
133
|
+
<Grid container size={8}>
|
|
134
|
+
<NativeSelect
|
|
135
|
+
onChange={handleSecondGroupChange}
|
|
136
|
+
value={rhsSelectedGroup ?? '__none__'}
|
|
137
|
+
variant="standard"
|
|
138
|
+
sx={{ width: '100%' }}
|
|
139
|
+
>
|
|
140
|
+
<option value="__none__">None</option>
|
|
141
|
+
{hasSampleSetOptions ? (
|
|
142
|
+
<>
|
|
143
|
+
{rhsOptions.map(o => (
|
|
144
|
+
<option value={o.name}>{o.name}</option>
|
|
145
|
+
))}
|
|
146
|
+
</>
|
|
147
|
+
) : null}
|
|
148
|
+
</NativeSelect>
|
|
149
|
+
</Grid>
|
|
150
|
+
</Grid>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
3
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
useAsyncFunction,
|
|
6
|
+
useViewConfigStoreApi,
|
|
7
|
+
useSetViewConfig,
|
|
8
|
+
useViewConfig,
|
|
9
|
+
useCoordination,
|
|
10
|
+
useLoaders,
|
|
11
|
+
useComparisonMetadata,
|
|
12
|
+
useMatchingLoader,
|
|
13
|
+
useColumnNameMapping,
|
|
14
|
+
useCoordinationScopes,
|
|
15
|
+
} from '@vitessce/vit-s';
|
|
16
|
+
import { AsyncFunctionType, ViewType, COMPONENT_COORDINATION_TYPES, DataType } from '@vitessce/constants-internal';
|
|
17
|
+
import { BiomarkerSelectAlt } from './BiomarkerSelectAlt.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Alternative biomarker select UI.
|
|
21
|
+
* @param {object} props
|
|
22
|
+
* @param {{ name: string, stratificationType: string, sampleSets: [string[], string[]]}[]} props.stratificationOptions
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
export function BiomarkerSelectAltSubscriber(props) {
|
|
26
|
+
const {
|
|
27
|
+
coordinationScopes: coordinationScopesRaw,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const loaders = useLoaders();
|
|
31
|
+
const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
|
|
32
|
+
|
|
33
|
+
const [{
|
|
34
|
+
dataset,
|
|
35
|
+
obsType,
|
|
36
|
+
sampleType,
|
|
37
|
+
sampleSetFilter,
|
|
38
|
+
sampleSetSelection,
|
|
39
|
+
featureSelection,
|
|
40
|
+
}, {
|
|
41
|
+
setSampleSetFilter,
|
|
42
|
+
setSampleSetSelection,
|
|
43
|
+
setFeatureSelection,
|
|
44
|
+
}] = useCoordination(
|
|
45
|
+
COMPONENT_COORDINATION_TYPES[ViewType.BIOMARKER_SELECT],
|
|
46
|
+
coordinationScopes,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const viewConfigStoreApi = useViewConfigStoreApi();
|
|
50
|
+
const viewConfig = useViewConfig();
|
|
51
|
+
const setViewConfig = useSetViewConfig(viewConfigStoreApi);
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// Need obsSets and sampleSets options to obtain mapping between
|
|
55
|
+
// group names and column names.
|
|
56
|
+
const obsSetsLoader = useMatchingLoader(
|
|
57
|
+
loaders, dataset, DataType.OBS_SETS, { obsType },
|
|
58
|
+
);
|
|
59
|
+
const sampleSetsLoader = useMatchingLoader(
|
|
60
|
+
loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
|
|
61
|
+
);
|
|
62
|
+
const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
|
|
63
|
+
|
|
64
|
+
const [mode] = useState(null);
|
|
65
|
+
const [currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection] = useState(null);
|
|
66
|
+
const [currentModalitySpecificSelection, setCurrentModalitySpecificSelection] = useState(null);
|
|
67
|
+
const [currentStratificationSelection, setCurrentStratificationSelection] = useState(null);
|
|
68
|
+
|
|
69
|
+
const autocompleteFeature = useAsyncFunction(AsyncFunctionType.AUTOCOMPLETE_FEATURE);
|
|
70
|
+
const transformFeature = useAsyncFunction(AsyncFunctionType.TRANSFORM_FEATURE);
|
|
71
|
+
|
|
72
|
+
const autocompleteNode = useCallback(async (inputValue) => {
|
|
73
|
+
const results = await autocompleteFeature(inputValue);
|
|
74
|
+
return results.map(item => ({
|
|
75
|
+
label: item.label,
|
|
76
|
+
data: item,
|
|
77
|
+
}));
|
|
78
|
+
}, [autocompleteFeature]);
|
|
79
|
+
const getEdges = useCallback(
|
|
80
|
+
async (node, targetModality) => transformFeature(node, targetModality),
|
|
81
|
+
[transformFeature],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const [{ comparisonMetadata }, cmpMetadataStatus, cmpMetadataUrls] = useComparisonMetadata(
|
|
85
|
+
loaders, dataset, false, {}, {}, { obsType, sampleType },
|
|
86
|
+
);
|
|
87
|
+
const stratificationOptions = useMemo(() => {
|
|
88
|
+
/*
|
|
89
|
+
return array of objects like {
|
|
90
|
+
stratificationId: 'aki-vs-hr',
|
|
91
|
+
name: 'Acute kidney injury (AKI) vs. Healthy reference',
|
|
92
|
+
stratificationType: 'sampleSet', // key changed from 'groupType'. value changed from 'clinical'
|
|
93
|
+
sampleSets: [
|
|
94
|
+
['Disease Type', 'AKI'],
|
|
95
|
+
['Disease Type', 'Reference'],
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
*/
|
|
99
|
+
if (comparisonMetadata?.sample_group_pairs) {
|
|
100
|
+
return comparisonMetadata.sample_group_pairs.map((sampleGroupPair) => {
|
|
101
|
+
const [sampleGroupCol, sampleGroupValues] = sampleGroupPair;
|
|
102
|
+
const [sampleGroupCtrl, sampleGroupCase] = sampleGroupValues;
|
|
103
|
+
const groupName = sampleSetsColumnNameMappingReversed?.[sampleGroupCol];
|
|
104
|
+
return {
|
|
105
|
+
stratificationId: `${sampleGroupCol}_${sampleGroupCtrl}-vs-${sampleGroupCase}`,
|
|
106
|
+
name: `${groupName}: ${sampleGroupCtrl} vs. ${sampleGroupCase}`,
|
|
107
|
+
stratificationType: 'sampleSet',
|
|
108
|
+
sampleSets: [
|
|
109
|
+
// With sampleSets coming from the comparison_metadata,
|
|
110
|
+
// need to use loader options from obsSets and sampleSets to get mapping
|
|
111
|
+
// from column name to group name.
|
|
112
|
+
[groupName, sampleGroupCtrl],
|
|
113
|
+
[groupName, sampleGroupCase],
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}, [comparisonMetadata, sampleSetsColumnNameMappingReversed]);
|
|
120
|
+
|
|
121
|
+
// TODO: Remove mode/step logic
|
|
122
|
+
// TODO: remove currentModalityAgnostic/SpecificSelection and use featureSelection + sampleSetSelection as the source of truth.
|
|
123
|
+
// Get rid of the local state within the children components and pass down the coordination values and setters directly
|
|
124
|
+
// so that everything stays in sync.
|
|
125
|
+
// E.g., if a user selects a gene from the volcano plot, the autocomplete input should reflect that selection
|
|
126
|
+
// and allow the user to view the corresponding gene info / gene card.
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
<BiomarkerSelectAlt
|
|
131
|
+
setFeatureSelection={setFeatureSelection}
|
|
132
|
+
setSampleSetFilter={setSampleSetFilter}
|
|
133
|
+
setSampleSetSelection={setSampleSetSelection}
|
|
134
|
+
currentModalityAgnosticSelection={currentModalityAgnosticSelection}
|
|
135
|
+
setCurrentModalityAgnosticSelection={setCurrentModalityAgnosticSelection}
|
|
136
|
+
setCurrentModalitySpecificSelection={setCurrentModalitySpecificSelection}
|
|
137
|
+
autocompleteNode={autocompleteNode}
|
|
138
|
+
stratifications={stratificationOptions}
|
|
139
|
+
|
|
140
|
+
onFinish={() => {
|
|
141
|
+
if (mode === 'exploratory') {
|
|
142
|
+
// mode is exploratory, configure accordingly.
|
|
143
|
+
|
|
144
|
+
// TODO
|
|
145
|
+
|
|
146
|
+
} else {
|
|
147
|
+
// mode is confirmatory, configure accordingly.
|
|
148
|
+
|
|
149
|
+
// TODO
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const newViewConfig = {
|
|
153
|
+
...viewConfig,
|
|
154
|
+
coordinationSpace: {
|
|
155
|
+
...viewConfig.coordinationSpace,
|
|
156
|
+
sampleSetFilter: {
|
|
157
|
+
...viewConfig.coordinationSpace.sampleSetFilter,
|
|
158
|
+
__comparison__: currentStratificationSelection?.sampleSets,
|
|
159
|
+
},
|
|
160
|
+
sampleSetSelection: {
|
|
161
|
+
...viewConfig.coordinationSpace.sampleSetSelection,
|
|
162
|
+
__comparison__: currentStratificationSelection?.sampleSets,
|
|
163
|
+
},
|
|
164
|
+
featureSelection: {
|
|
165
|
+
...viewConfig.coordinationSpace.featureSelection,
|
|
166
|
+
__comparison__: currentModalitySpecificSelection ? currentModalitySpecificSelection.map(d => d.label) : null,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// TODO: can the normal coordination value setters be used instead?
|
|
172
|
+
// (e.g., setFeatureSelection, setSampleSetSelection)
|
|
173
|
+
setViewConfig(newViewConfig);
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -82,9 +82,9 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
82
82
|
<div className={classes.headingContainer}>
|
|
83
83
|
{sampleSetSelection && sampleSetSelection.length === 2 ? (
|
|
84
84
|
<div className={classes.headingSectionContainer}>
|
|
85
|
-
<div style={{ width: '45%' }}><
|
|
86
|
-
<div style={{ width: '5%' }}><
|
|
87
|
-
<div style={{ width: '50%' }}><
|
|
85
|
+
<div style={{ width: '45%' }}><h3 className={classes.headingText}>{sampleSetSelection?.[0]?.at(-1)}</h3></div>
|
|
86
|
+
<div style={{ width: '5%' }}><h3 className={classes.headingText} style={{ textAlign: 'right' }}>vs. </h3></div>
|
|
87
|
+
<div style={{ width: '50%' }}><h3 className={classes.headingText}>{sampleSetSelection?.[1]?.at(-1)}</h3></div>
|
|
88
88
|
<div className={classes.buttonContainer}>
|
|
89
89
|
<div>
|
|
90
90
|
<button onClick={swapSampleSets} type="button">Swap</button>
|
|
@@ -94,7 +94,7 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
94
94
|
</div>
|
|
95
95
|
) : (
|
|
96
96
|
<div className={classes.headingSectionContainer}>
|
|
97
|
-
<div><
|
|
97
|
+
<div><h3 className={classes.headingText}>All samples</h3></div>
|
|
98
98
|
</div>
|
|
99
99
|
)}
|
|
100
100
|
{featureSelection && featureSelection.length > 0 ? (
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
|
|
2
|
+
export { BiomarkerSelectAltSubscriber } from './BiomarkerSelectAltSubscriber.js';
|
|
2
3
|
export { ComparativeHeadingSubscriber } from './ComparativeHeadingSubscriber.js';
|
|
3
4
|
export { SampleSetPairManagerSubscriber } from './SampleSetPairManagerSubscriber.js';
|
|
4
5
|
export {
|
package/src/select-specific.js
CHANGED
|
@@ -83,6 +83,7 @@ export function SelectSpecific(props) {
|
|
|
83
83
|
const newSelection = Array.from(newRowSelectionModel.ids)
|
|
84
84
|
.map(kgId => data.find(d => d.target.kgId === kgId)?.target)
|
|
85
85
|
.filter(Boolean);
|
|
86
|
+
// console.log('New specific selection:', newSelection);
|
|
86
87
|
setCurrentModalitySpecificSelection(newSelection);
|
|
87
88
|
}
|
|
88
89
|
|