@vitessce/biomarker-select 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/index.js +993 -40
- package/dist-tsc/ComparativeHeadingSubscriber.d.ts.map +1 -1
- package/dist-tsc/ComparativeHeadingSubscriber.js +19 -5
- package/dist-tsc/SampleSetPairManagerSubscriber.d.ts +2 -0
- package/dist-tsc/SampleSetPairManagerSubscriber.d.ts.map +1 -0
- package/dist-tsc/SampleSetPairManagerSubscriber.js +76 -0
- package/dist-tsc/default-async-functions.d.ts +2 -2
- package/dist-tsc/default-async-functions.d.ts.map +1 -1
- package/dist-tsc/default-async-functions.js +31 -7
- package/dist-tsc/index.d.ts +1 -0
- package/dist-tsc/index.js +1 -0
- package/package.json +5 -4
- package/src/ComparativeHeadingSubscriber.js +35 -5
- package/src/SampleSetPairManagerSubscriber.js +146 -0
- package/src/default-async-functions.js +31 -7
- package/src/index.js +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComparativeHeadingSubscriber.d.ts","sourceRoot":"","sources":["../src/ComparativeHeadingSubscriber.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ComparativeHeadingSubscriber.d.ts","sourceRoot":"","sources":["../src/ComparativeHeadingSubscriber.js"],"names":[],"mappings":"AAsCA,sEA8EC"}
|
|
@@ -11,6 +11,13 @@ const useStyles = makeStyles(theme => ({
|
|
|
11
11
|
flexDirection: 'column',
|
|
12
12
|
justifyContent: 'center',
|
|
13
13
|
height: '100%',
|
|
14
|
+
'& div': {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
flexDirection: 'row',
|
|
17
|
+
},
|
|
18
|
+
'& div > button': {
|
|
19
|
+
height: 'auto',
|
|
20
|
+
},
|
|
14
21
|
},
|
|
15
22
|
headingContainer: {
|
|
16
23
|
backgroundColor: 'transparent',
|
|
@@ -28,10 +35,7 @@ const useStyles = makeStyles(theme => ({
|
|
|
28
35
|
}));
|
|
29
36
|
export function ComparativeHeadingSubscriber(props) {
|
|
30
37
|
const { coordinationScopes, } = props;
|
|
31
|
-
const [{ sampleSetSelection, featureSelection, }, { setSampleSetSelection,
|
|
32
|
-
// TODO: buttons for modification of featureSelection
|
|
33
|
-
// setFeatureSelection,
|
|
34
|
-
}] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.COMPARATIVE_HEADING], coordinationScopes);
|
|
38
|
+
const [{ sampleSetSelection, featureSelection, }, { setSampleSetSelection, setFeatureSelection, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.COMPARATIVE_HEADING], coordinationScopes);
|
|
35
39
|
const classes = useStyles();
|
|
36
40
|
const swapSampleSets = useCallback(() => {
|
|
37
41
|
if (sampleSetSelection?.length === 2) {
|
|
@@ -41,5 +45,15 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
41
45
|
]);
|
|
42
46
|
}
|
|
43
47
|
}, [sampleSetSelection, setSampleSetSelection]);
|
|
44
|
-
|
|
48
|
+
const clearSampleSetSelection = useCallback(() => {
|
|
49
|
+
if (sampleSetSelection?.length === 2) {
|
|
50
|
+
setSampleSetSelection(null);
|
|
51
|
+
}
|
|
52
|
+
}, [sampleSetSelection, setSampleSetSelection]);
|
|
53
|
+
const clearFeatureSelection = useCallback(() => {
|
|
54
|
+
if (Array.isArray(featureSelection) && featureSelection.length > 0) {
|
|
55
|
+
setFeatureSelection(null);
|
|
56
|
+
}
|
|
57
|
+
}, [featureSelection, setFeatureSelection]);
|
|
58
|
+
return (_jsxs("div", { className: classes.headingContainer, children: [sampleSetSelection && sampleSetSelection.length === 2 ? (_jsxs("div", { className: classes.headingSectionContainer, children: [_jsx("div", { style: { width: '45%' }, children: _jsx("h2", { className: classes.headingText, children: sampleSetSelection?.[0]?.at(-1) }) }), _jsx("div", { style: { width: '5%' }, children: _jsx("h2", { className: classes.headingText, style: { textAlign: 'right' }, children: "vs.\u00A0" }) }), _jsx("div", { style: { width: '50%' }, children: _jsx("h2", { className: classes.headingText, children: sampleSetSelection?.[1]?.at(-1) }) }), _jsx("div", { className: classes.buttonContainer, children: _jsxs("div", { children: [_jsx("button", { onClick: swapSampleSets, type: "button", children: "Swap" }), _jsx("button", { onClick: clearSampleSetSelection, type: "button", children: "Clear" })] }) })] })) : (_jsx("div", { className: classes.headingSectionContainer, children: _jsx("div", { children: _jsx("h2", { className: classes.headingText, children: "All samples" }) }) })), featureSelection && featureSelection.length > 0 ? (_jsxs("div", { className: classes.headingSectionContainer, children: [_jsx("div", { children: _jsx("h3", { className: classes.headingText, children: "Selected Biomarkers:\u00A0" }) }), featureSelection?.map((featureName, i) => (_jsx("div", { children: _jsxs("h3", { className: classes.headingText, children: [featureName, i < featureSelection.length - 1 ? (_jsx("span", { children: ",\u00A0" })) : null] }) }, featureName))), _jsx("div", { className: classes.buttonContainer, children: _jsx("div", { children: _jsx("button", { onClick: clearFeatureSelection, type: "button", children: "Clear" }) }) })] })) : null] }));
|
|
45
59
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SampleSetPairManagerSubscriber.d.ts","sourceRoot":"","sources":["../src/SampleSetPairManagerSubscriber.js"],"names":[],"mappings":"AA6BA,wEAoHC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo, useCallback } from 'react';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { makeStyles } from '@material-ui/core';
|
|
6
|
+
import { TitleInfo, useReady, useCoordination, useLoaders, useComparisonMetadata, useMatchingLoader, useColumnNameMapping, } from '@vitessce/vit-s';
|
|
7
|
+
import { ViewType, DataType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping } from '@vitessce/constants-internal';
|
|
8
|
+
const useStyles = makeStyles(() => ({
|
|
9
|
+
selectedPair: {
|
|
10
|
+
fontWeight: 'bold',
|
|
11
|
+
},
|
|
12
|
+
pairUl: {
|
|
13
|
+
paddingLeft: '15px',
|
|
14
|
+
marginTop: 0,
|
|
15
|
+
fontSize: '12px',
|
|
16
|
+
'& button': {
|
|
17
|
+
fontSize: '12px',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
export function SampleSetPairManagerSubscriber(props) {
|
|
22
|
+
const { coordinationScopes, removeGridComponent, theme, title = 'Sample Sets', closeButtonVisible, helpText = ViewHelpMapping.SAMPLE_SET_PAIR_MANAGER, } = props;
|
|
23
|
+
const classes = useStyles();
|
|
24
|
+
const loaders = useLoaders();
|
|
25
|
+
// Get "props" from the coordination space.
|
|
26
|
+
const [{ dataset, obsType, sampleType, sampleSetSelection, }, { setSampleSetSelection, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SAMPLE_SET_PAIR_MANAGER], coordinationScopes);
|
|
27
|
+
const sampleSetsLoader = useMatchingLoader(loaders, dataset, DataType.SAMPLE_SETS, { sampleType });
|
|
28
|
+
const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
|
|
29
|
+
const [{ comparisonMetadata }, cmpMetadataStatus] = useComparisonMetadata(loaders, dataset, false, {}, {}, { obsType, sampleType });
|
|
30
|
+
const isReady = useReady([
|
|
31
|
+
cmpMetadataStatus,
|
|
32
|
+
]);
|
|
33
|
+
const stratificationOptions = useMemo(() => {
|
|
34
|
+
/*
|
|
35
|
+
return array of objects like {
|
|
36
|
+
stratificationId: 'aki-vs-hr',
|
|
37
|
+
name: 'Acute kidney injury (AKI) vs. Healthy reference',
|
|
38
|
+
stratificationType: 'sampleSet',
|
|
39
|
+
sampleSets: [
|
|
40
|
+
['Disease Type', 'AKI'],
|
|
41
|
+
['Disease Type', 'Reference'],
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
*/
|
|
45
|
+
if (comparisonMetadata?.sample_group_pairs) {
|
|
46
|
+
return comparisonMetadata.sample_group_pairs.map((sampleGroupPair) => {
|
|
47
|
+
const [sampleGroupCol, sampleGroupValues] = sampleGroupPair;
|
|
48
|
+
const [sampleGroupCtrl, sampleGroupCase] = sampleGroupValues;
|
|
49
|
+
const groupName = sampleSetsColumnNameMappingReversed?.[sampleGroupCol];
|
|
50
|
+
return {
|
|
51
|
+
stratificationId: `${sampleGroupCol}_${sampleGroupCtrl}-vs-${sampleGroupCase}`,
|
|
52
|
+
name: `${groupName}: ${sampleGroupCtrl} vs. ${sampleGroupCase}`,
|
|
53
|
+
stratificationType: 'sampleSet',
|
|
54
|
+
sampleSets: [
|
|
55
|
+
// With sampleSets coming from the comparison_metadata,
|
|
56
|
+
// need to use loader options from obsSets and sampleSets to get mapping
|
|
57
|
+
// from column name to group name.
|
|
58
|
+
[groupName, sampleGroupCtrl],
|
|
59
|
+
[groupName, sampleGroupCase],
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}, [comparisonMetadata, sampleSetsColumnNameMappingReversed]);
|
|
66
|
+
const onSelectPair = useCallback((sampleSetPair) => {
|
|
67
|
+
setSampleSetSelection(sampleSetPair);
|
|
68
|
+
}, [sampleSetSelection, setSampleSetSelection]);
|
|
69
|
+
return (_jsx(TitleInfo, { title: title, closeButtonVisible: closeButtonVisible, removeGridComponent: removeGridComponent, isScroll: true, theme: theme, isReady: isReady, helpText: helpText, children: _jsx("ul", { className: classes.pairUl, children: stratificationOptions?.map((pairObj) => {
|
|
70
|
+
const isSelected = Array.isArray(sampleSetSelection)
|
|
71
|
+
&& sampleSetSelection.length === 2
|
|
72
|
+
&& (isEqual(pairObj.sampleSets, sampleSetSelection)
|
|
73
|
+
|| isEqual(pairObj.sampleSets, [sampleSetSelection?.[1], sampleSetSelection?.[0]]));
|
|
74
|
+
return (_jsxs("li", { className: clsx({ [classes.selectedPair]: isSelected }), children: [pairObj.name, "\u00A0", !isSelected ? (_jsx("button", { type: "button", onClick: () => onSelectPair(pairObj.sampleSets), children: "Select" })) : null] }, pairObj.stratificationId));
|
|
75
|
+
}) }) }));
|
|
76
|
+
}
|
|
@@ -13,13 +13,13 @@ export function autocompleteFeature({ queryClient }: {
|
|
|
13
13
|
* @satisfies {TransformFeatureFunc}
|
|
14
14
|
* @param {object} ctx
|
|
15
15
|
* @param {QueryClient} ctx.queryClient
|
|
16
|
-
* @param {KgNode}
|
|
16
|
+
* @param {KgNode} nodeOrig
|
|
17
17
|
* @param {TargetModalityType} targetModality
|
|
18
18
|
* @returns {Promise<KgNode[]>}
|
|
19
19
|
*/
|
|
20
20
|
export function transformFeature({ queryClient }: {
|
|
21
21
|
queryClient: QueryClient;
|
|
22
|
-
},
|
|
22
|
+
}, nodeOrig: KgNode, targetModality: TargetModalityType): Promise<KgNode[]>;
|
|
23
23
|
/**
|
|
24
24
|
* @satisfies {GetAlternativeTermsFunc}
|
|
25
25
|
* @param {object} ctx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-async-functions.d.ts","sourceRoot":"","sources":["../src/default-async-functions.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"default-async-functions.d.ts","sourceRoot":"","sources":["../src/default-async-functions.js"],"names":[],"mappings":"AAyHA;;;;;;;GAOG;AACH,qDALG;IAAyB,WAAW,EAA5B,WAAW;CACnB,WAAQ,MAAM,kBACN,IAAI,GAAG,kBAAkB,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiB7B;AAkBD;;;;;;;GAOG;AACH,kDALG;IAAyB,WAAW,EAA5B,WAAW;CACnB,YAAQ,MAAM,kBACN,kBAAkB,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoD7B;AAED;;;;;;GAMG;AACH,qDAJG;IAAyB,WAAW,EAA5B,WAAW;CACnB,SAAQ,MAAM,GACJ,OAAO,CAAC,MAAM,EAAE,CAAC,CA2B7B;AAED;;;;;;;GAOG;AACH,gDALG;IAAyB,WAAW,EAA5B,WAAW;CACnB,kBAAQ,MAAM,kBACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoBxC;iCAlRsD,uBAAuB;wCADsE,iBAAiB;4BAAjB,iBAAiB"}
|
|
@@ -44,14 +44,18 @@ function loadCellTypeNodes() {
|
|
|
44
44
|
* @returns {Promise<KgNode[]>}
|
|
45
45
|
*/
|
|
46
46
|
function loadPathwayNodes() {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const reactomeNodes = fetch(`${KG_BASE_URL}/Reactome_2022.nodes.csv`);
|
|
48
|
+
// TODO: load both GO and Reactome nodes, concat together.
|
|
49
|
+
// const goNodes = fetch(`${KG_BASE_URL}/GO_Biological_Process_2021.nodes.csv`);
|
|
50
|
+
return reactomeNodes.then(res => res.text())
|
|
49
51
|
.then((res) => {
|
|
50
52
|
const result = csvParse(res);
|
|
51
53
|
return result.map((/** @type {any} */ d) => ({
|
|
52
54
|
kgId: d.id,
|
|
53
|
-
label: d.pathway,
|
|
54
|
-
term: `
|
|
55
|
+
label: d.pathway, // For reactome
|
|
56
|
+
term: `REACTOME:${d.acc}`, // For reactome
|
|
57
|
+
// label: d.ontology_label, // For GO_BP
|
|
58
|
+
// term: d.acc, // For GO_BP
|
|
55
59
|
nodeType: 'pathway',
|
|
56
60
|
}));
|
|
57
61
|
});
|
|
@@ -124,7 +128,10 @@ export async function autocompleteFeature({ queryClient }, partial, targetModali
|
|
|
124
128
|
return results.map((/** @type {any} */ result) => result.item);
|
|
125
129
|
}
|
|
126
130
|
async function loadPathwayToGeneEdges() {
|
|
127
|
-
|
|
131
|
+
const reactomeEdges = fetch(`${KG_BASE_URL}/Reactome_2022.Reactome.Gene.edges.csv`);
|
|
132
|
+
// TODO: load both GO and Reactome edges, concat together.
|
|
133
|
+
// const goEdges = fetch(`${KG_BASE_URL}/GO_Biological_Process_2021.GO_BP.Gene.edges.csv`);
|
|
134
|
+
return reactomeEdges
|
|
128
135
|
.then(res => res.text())
|
|
129
136
|
.then((res) => {
|
|
130
137
|
const result = csvParse(res);
|
|
@@ -139,11 +146,14 @@ async function loadPathwayToGeneEdges() {
|
|
|
139
146
|
* @satisfies {TransformFeatureFunc}
|
|
140
147
|
* @param {object} ctx
|
|
141
148
|
* @param {QueryClient} ctx.queryClient
|
|
142
|
-
* @param {KgNode}
|
|
149
|
+
* @param {KgNode} nodeOrig
|
|
143
150
|
* @param {TargetModalityType} targetModality
|
|
144
151
|
* @returns {Promise<KgNode[]>}
|
|
145
152
|
*/
|
|
146
|
-
export async function transformFeature({ queryClient },
|
|
153
|
+
export async function transformFeature({ queryClient }, nodeOrig, targetModality) {
|
|
154
|
+
const node = {
|
|
155
|
+
...nodeOrig,
|
|
156
|
+
};
|
|
147
157
|
if (targetModality === node.nodeType) {
|
|
148
158
|
// For example, if the target modality is gene and the node is already a gene node.
|
|
149
159
|
return [node];
|
|
@@ -161,6 +171,20 @@ export async function transformFeature({ queryClient }, node, targetModality) {
|
|
|
161
171
|
staleTime: Infinity,
|
|
162
172
|
queryFn: loadPathwayToGeneEdges,
|
|
163
173
|
});
|
|
174
|
+
if (!node.kgId) {
|
|
175
|
+
const pathwayNodes = await queryClient.fetchQuery({
|
|
176
|
+
queryKey: ['pathwayNodes'],
|
|
177
|
+
staleTime: Infinity,
|
|
178
|
+
queryFn: loadPathwayNodes,
|
|
179
|
+
});
|
|
180
|
+
const foundId = pathwayNodes.find(n => n.term === node.term)?.kgId;
|
|
181
|
+
if (foundId) {
|
|
182
|
+
node.kgId = foundId;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.warn('Could not find matching pathway node based on term.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
164
188
|
// TODO: support matching using ontology term (rather than requiring kgId)?
|
|
165
189
|
const matchingEdges = pathwayGeneEdges.filter((/** @type {KgEdge} */ d) => d.source === node.kgId);
|
|
166
190
|
const matchingGeneIds = matchingEdges.map((/** @type {KgEdge} */ d) => d.target);
|
package/dist-tsc/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BiomarkerSelectSubscriber } from "./BiomarkerSelectSubscriber.js";
|
|
2
2
|
export { ComparativeHeadingSubscriber } from "./ComparativeHeadingSubscriber.js";
|
|
3
|
+
export { SampleSetPairManagerSubscriber } from "./SampleSetPairManagerSubscriber.js";
|
|
3
4
|
export { autocompleteFeature, transformFeature, getAlternativeTerms, getTermMapping } from "./default-async-functions.js";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-tsc/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
|
|
2
2
|
export { ComparativeHeadingSubscriber } from './ComparativeHeadingSubscriber.js';
|
|
3
|
+
export { SampleSetPairManagerSubscriber } from './SampleSetPairManagerSubscriber.js';
|
|
3
4
|
export { autocompleteFeature, transformFeature, getAlternativeTerms, getTermMapping, } from './default-async-functions.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/biomarker-select",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.11",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
"react-window": "^1.8.10",
|
|
25
25
|
"fuse.js": "^7.0.0",
|
|
26
26
|
"d3-dsv": "^1.1.1",
|
|
27
|
-
"
|
|
28
|
-
"@vitessce/
|
|
29
|
-
"@vitessce/
|
|
27
|
+
"clsx": "^1.1.1",
|
|
28
|
+
"@vitessce/constants-internal": "3.5.11",
|
|
29
|
+
"@vitessce/vit-s": "3.5.11",
|
|
30
|
+
"@vitessce/types": "3.5.11"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@testing-library/jest-dom": "^5.16.4",
|
|
@@ -13,6 +13,13 @@ const useStyles = makeStyles(theme => ({
|
|
|
13
13
|
flexDirection: 'column',
|
|
14
14
|
justifyContent: 'center',
|
|
15
15
|
height: '100%',
|
|
16
|
+
'& div': {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'row',
|
|
19
|
+
},
|
|
20
|
+
'& div > button': {
|
|
21
|
+
height: 'auto',
|
|
22
|
+
},
|
|
16
23
|
},
|
|
17
24
|
headingContainer: {
|
|
18
25
|
backgroundColor: 'transparent',
|
|
@@ -39,8 +46,7 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
39
46
|
featureSelection,
|
|
40
47
|
}, {
|
|
41
48
|
setSampleSetSelection,
|
|
42
|
-
|
|
43
|
-
// setFeatureSelection,
|
|
49
|
+
setFeatureSelection,
|
|
44
50
|
}] = useCoordination(
|
|
45
51
|
COMPONENT_COORDINATION_TYPES[ViewType.COMPARATIVE_HEADING],
|
|
46
52
|
coordinationScopes,
|
|
@@ -57,6 +63,18 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
57
63
|
}
|
|
58
64
|
}, [sampleSetSelection, setSampleSetSelection]);
|
|
59
65
|
|
|
66
|
+
const clearSampleSetSelection = useCallback(() => {
|
|
67
|
+
if (sampleSetSelection?.length === 2) {
|
|
68
|
+
setSampleSetSelection(null);
|
|
69
|
+
}
|
|
70
|
+
}, [sampleSetSelection, setSampleSetSelection]);
|
|
71
|
+
|
|
72
|
+
const clearFeatureSelection = useCallback(() => {
|
|
73
|
+
if (Array.isArray(featureSelection) && featureSelection.length > 0) {
|
|
74
|
+
setFeatureSelection(null);
|
|
75
|
+
}
|
|
76
|
+
}, [featureSelection, setFeatureSelection]);
|
|
77
|
+
|
|
60
78
|
return (
|
|
61
79
|
<div className={classes.headingContainer}>
|
|
62
80
|
{sampleSetSelection && sampleSetSelection.length === 2 ? (
|
|
@@ -65,21 +83,33 @@ export function ComparativeHeadingSubscriber(props) {
|
|
|
65
83
|
<div style={{ width: '5%' }}><h2 className={classes.headingText} style={{ textAlign: 'right' }}>vs. </h2></div>
|
|
66
84
|
<div style={{ width: '50%' }}><h2 className={classes.headingText}>{sampleSetSelection?.[1]?.at(-1)}</h2></div>
|
|
67
85
|
<div className={classes.buttonContainer}>
|
|
68
|
-
<
|
|
86
|
+
<div>
|
|
87
|
+
<button onClick={swapSampleSets} type="button">Swap</button>
|
|
88
|
+
<button onClick={clearSampleSetSelection} type="button">Clear</button>
|
|
89
|
+
</div>
|
|
69
90
|
</div>
|
|
70
91
|
</div>
|
|
71
|
-
) :
|
|
92
|
+
) : (
|
|
93
|
+
<div className={classes.headingSectionContainer}>
|
|
94
|
+
<div><h2 className={classes.headingText}>All samples</h2></div>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
72
97
|
{featureSelection && featureSelection.length > 0 ? (
|
|
73
98
|
<div className={classes.headingSectionContainer}>
|
|
74
99
|
<div><h3 className={classes.headingText}>Selected Biomarkers: </h3></div>
|
|
75
100
|
{featureSelection?.map((featureName, i) => (
|
|
76
|
-
<div>
|
|
101
|
+
<div key={featureName}>
|
|
77
102
|
<h3 className={classes.headingText}>
|
|
78
103
|
{featureName}
|
|
79
104
|
{i < featureSelection.length - 1 ? (<span>, </span>) : null}
|
|
80
105
|
</h3>
|
|
81
106
|
</div>
|
|
82
107
|
))}
|
|
108
|
+
<div className={classes.buttonContainer}>
|
|
109
|
+
<div>
|
|
110
|
+
<button onClick={clearFeatureSelection} type="button">Clear</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
83
113
|
</div>
|
|
84
114
|
) : null}
|
|
85
115
|
</div>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React, { useMemo, useCallback } from 'react';
|
|
2
|
+
import { isEqual } from 'lodash-es';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { makeStyles } from '@material-ui/core';
|
|
5
|
+
import {
|
|
6
|
+
TitleInfo,
|
|
7
|
+
useReady,
|
|
8
|
+
useCoordination,
|
|
9
|
+
useLoaders,
|
|
10
|
+
useComparisonMetadata,
|
|
11
|
+
useMatchingLoader,
|
|
12
|
+
useColumnNameMapping,
|
|
13
|
+
} from '@vitessce/vit-s';
|
|
14
|
+
import { ViewType, DataType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping } from '@vitessce/constants-internal';
|
|
15
|
+
|
|
16
|
+
const useStyles = makeStyles(() => ({
|
|
17
|
+
selectedPair: {
|
|
18
|
+
fontWeight: 'bold',
|
|
19
|
+
},
|
|
20
|
+
pairUl: {
|
|
21
|
+
paddingLeft: '15px',
|
|
22
|
+
marginTop: 0,
|
|
23
|
+
fontSize: '12px',
|
|
24
|
+
'& button': {
|
|
25
|
+
fontSize: '12px',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
export function SampleSetPairManagerSubscriber(props) {
|
|
31
|
+
const {
|
|
32
|
+
coordinationScopes,
|
|
33
|
+
removeGridComponent,
|
|
34
|
+
theme,
|
|
35
|
+
title = 'Sample Sets',
|
|
36
|
+
closeButtonVisible,
|
|
37
|
+
helpText = ViewHelpMapping.SAMPLE_SET_PAIR_MANAGER,
|
|
38
|
+
} = props;
|
|
39
|
+
|
|
40
|
+
const classes = useStyles();
|
|
41
|
+
const loaders = useLoaders();
|
|
42
|
+
|
|
43
|
+
// Get "props" from the coordination space.
|
|
44
|
+
const [{
|
|
45
|
+
dataset,
|
|
46
|
+
obsType,
|
|
47
|
+
sampleType,
|
|
48
|
+
sampleSetSelection,
|
|
49
|
+
}, {
|
|
50
|
+
setSampleSetSelection,
|
|
51
|
+
}] = useCoordination(
|
|
52
|
+
COMPONENT_COORDINATION_TYPES[ViewType.SAMPLE_SET_PAIR_MANAGER],
|
|
53
|
+
coordinationScopes,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const sampleSetsLoader = useMatchingLoader(
|
|
57
|
+
loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
|
|
58
|
+
);
|
|
59
|
+
const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
|
|
60
|
+
|
|
61
|
+
const [{ comparisonMetadata }, cmpMetadataStatus] = useComparisonMetadata(
|
|
62
|
+
loaders, dataset, false, {}, {}, { obsType, sampleType },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const isReady = useReady([
|
|
66
|
+
cmpMetadataStatus,
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const stratificationOptions = useMemo(() => {
|
|
70
|
+
/*
|
|
71
|
+
return array of objects like {
|
|
72
|
+
stratificationId: 'aki-vs-hr',
|
|
73
|
+
name: 'Acute kidney injury (AKI) vs. Healthy reference',
|
|
74
|
+
stratificationType: 'sampleSet',
|
|
75
|
+
sampleSets: [
|
|
76
|
+
['Disease Type', 'AKI'],
|
|
77
|
+
['Disease Type', 'Reference'],
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
*/
|
|
81
|
+
if (comparisonMetadata?.sample_group_pairs) {
|
|
82
|
+
return comparisonMetadata.sample_group_pairs.map((sampleGroupPair) => {
|
|
83
|
+
const [sampleGroupCol, sampleGroupValues] = sampleGroupPair;
|
|
84
|
+
const [sampleGroupCtrl, sampleGroupCase] = sampleGroupValues;
|
|
85
|
+
const groupName = sampleSetsColumnNameMappingReversed?.[sampleGroupCol];
|
|
86
|
+
return {
|
|
87
|
+
stratificationId: `${sampleGroupCol}_${sampleGroupCtrl}-vs-${sampleGroupCase}`,
|
|
88
|
+
name: `${groupName}: ${sampleGroupCtrl} vs. ${sampleGroupCase}`,
|
|
89
|
+
stratificationType: 'sampleSet',
|
|
90
|
+
sampleSets: [
|
|
91
|
+
// With sampleSets coming from the comparison_metadata,
|
|
92
|
+
// need to use loader options from obsSets and sampleSets to get mapping
|
|
93
|
+
// from column name to group name.
|
|
94
|
+
[groupName, sampleGroupCtrl],
|
|
95
|
+
[groupName, sampleGroupCase],
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}, [comparisonMetadata, sampleSetsColumnNameMappingReversed]);
|
|
102
|
+
|
|
103
|
+
const onSelectPair = useCallback((sampleSetPair) => {
|
|
104
|
+
setSampleSetSelection(sampleSetPair);
|
|
105
|
+
}, [sampleSetSelection, setSampleSetSelection]);
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<TitleInfo
|
|
110
|
+
title={title}
|
|
111
|
+
closeButtonVisible={closeButtonVisible}
|
|
112
|
+
removeGridComponent={removeGridComponent}
|
|
113
|
+
isScroll
|
|
114
|
+
theme={theme}
|
|
115
|
+
isReady={isReady}
|
|
116
|
+
helpText={helpText}
|
|
117
|
+
>
|
|
118
|
+
<ul className={classes.pairUl}>
|
|
119
|
+
{stratificationOptions?.map((pairObj) => {
|
|
120
|
+
const isSelected = Array.isArray(sampleSetSelection)
|
|
121
|
+
&& sampleSetSelection.length === 2
|
|
122
|
+
&& (
|
|
123
|
+
isEqual(pairObj.sampleSets, sampleSetSelection)
|
|
124
|
+
|| isEqual(pairObj.sampleSets, [sampleSetSelection?.[1], sampleSetSelection?.[0]])
|
|
125
|
+
);
|
|
126
|
+
return (
|
|
127
|
+
<li
|
|
128
|
+
key={pairObj.stratificationId}
|
|
129
|
+
className={clsx({ [classes.selectedPair]: isSelected })}
|
|
130
|
+
>
|
|
131
|
+
{pairObj.name}
|
|
132
|
+
{!isSelected ? (
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
onClick={() => onSelectPair(pairObj.sampleSets)}
|
|
136
|
+
>
|
|
137
|
+
Select
|
|
138
|
+
</button>
|
|
139
|
+
) : null}
|
|
140
|
+
</li>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</ul>
|
|
144
|
+
</TitleInfo>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -49,14 +49,18 @@ function loadCellTypeNodes() {
|
|
|
49
49
|
* @returns {Promise<KgNode[]>}
|
|
50
50
|
*/
|
|
51
51
|
function loadPathwayNodes() {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const reactomeNodes = fetch(`${KG_BASE_URL}/Reactome_2022.nodes.csv`);
|
|
53
|
+
// TODO: load both GO and Reactome nodes, concat together.
|
|
54
|
+
// const goNodes = fetch(`${KG_BASE_URL}/GO_Biological_Process_2021.nodes.csv`);
|
|
55
|
+
return reactomeNodes.then(res => res.text())
|
|
54
56
|
.then((res) => {
|
|
55
57
|
const result = csvParse(res);
|
|
56
58
|
return result.map((/** @type {any} */ d) => ({
|
|
57
59
|
kgId: d.id,
|
|
58
|
-
label: d.pathway,
|
|
59
|
-
term: `
|
|
60
|
+
label: d.pathway, // For reactome
|
|
61
|
+
term: `REACTOME:${d.acc}`, // For reactome
|
|
62
|
+
// label: d.ontology_label, // For GO_BP
|
|
63
|
+
// term: d.acc, // For GO_BP
|
|
60
64
|
nodeType: 'pathway',
|
|
61
65
|
}));
|
|
62
66
|
});
|
|
@@ -141,7 +145,10 @@ export async function autocompleteFeature({ queryClient }, partial, targetModali
|
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
async function loadPathwayToGeneEdges() {
|
|
144
|
-
|
|
148
|
+
const reactomeEdges = fetch(`${KG_BASE_URL}/Reactome_2022.Reactome.Gene.edges.csv`);
|
|
149
|
+
// TODO: load both GO and Reactome edges, concat together.
|
|
150
|
+
// const goEdges = fetch(`${KG_BASE_URL}/GO_Biological_Process_2021.GO_BP.Gene.edges.csv`);
|
|
151
|
+
return reactomeEdges
|
|
145
152
|
.then(res => res.text())
|
|
146
153
|
.then((res) => {
|
|
147
154
|
const result = csvParse(res);
|
|
@@ -157,11 +164,14 @@ async function loadPathwayToGeneEdges() {
|
|
|
157
164
|
* @satisfies {TransformFeatureFunc}
|
|
158
165
|
* @param {object} ctx
|
|
159
166
|
* @param {QueryClient} ctx.queryClient
|
|
160
|
-
* @param {KgNode}
|
|
167
|
+
* @param {KgNode} nodeOrig
|
|
161
168
|
* @param {TargetModalityType} targetModality
|
|
162
169
|
* @returns {Promise<KgNode[]>}
|
|
163
170
|
*/
|
|
164
|
-
export async function transformFeature({ queryClient },
|
|
171
|
+
export async function transformFeature({ queryClient }, nodeOrig, targetModality) {
|
|
172
|
+
const node = {
|
|
173
|
+
...nodeOrig,
|
|
174
|
+
};
|
|
165
175
|
if (targetModality === node.nodeType) {
|
|
166
176
|
// For example, if the target modality is gene and the node is already a gene node.
|
|
167
177
|
return [node];
|
|
@@ -180,6 +190,20 @@ export async function transformFeature({ queryClient }, node, targetModality) {
|
|
|
180
190
|
queryFn: loadPathwayToGeneEdges,
|
|
181
191
|
});
|
|
182
192
|
|
|
193
|
+
if (!node.kgId) {
|
|
194
|
+
const pathwayNodes = await queryClient.fetchQuery({
|
|
195
|
+
queryKey: ['pathwayNodes'],
|
|
196
|
+
staleTime: Infinity,
|
|
197
|
+
queryFn: loadPathwayNodes,
|
|
198
|
+
});
|
|
199
|
+
const foundId = pathwayNodes.find(n => n.term === node.term)?.kgId;
|
|
200
|
+
if (foundId) {
|
|
201
|
+
node.kgId = foundId;
|
|
202
|
+
} else {
|
|
203
|
+
console.warn('Could not find matching pathway node based on term.');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
183
207
|
// TODO: support matching using ontology term (rather than requiring kgId)?
|
|
184
208
|
const matchingEdges = pathwayGeneEdges.filter((/** @type {KgEdge} */ d) => d.source === node.kgId);
|
|
185
209
|
const matchingGeneIds = matchingEdges.map((/** @type {KgEdge} */ d) => d.target);
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
|
|
2
2
|
export { ComparativeHeadingSubscriber } from './ComparativeHeadingSubscriber.js';
|
|
3
|
+
export { SampleSetPairManagerSubscriber } from './SampleSetPairManagerSubscriber.js';
|
|
3
4
|
export {
|
|
4
5
|
autocompleteFeature,
|
|
5
6
|
transformFeature,
|