@vitessce/biomarker-select 3.5.0
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/LICENSE +21 -0
- package/dist/index.js +32224 -0
- package/dist-tsc/BiomarkerSelectSubscriber.d.ts +14 -0
- package/dist-tsc/BiomarkerSelectSubscriber.d.ts.map +1 -0
- package/dist-tsc/BiomarkerSelectSubscriber.js +53 -0
- package/dist-tsc/cart.d.ts +2 -0
- package/dist-tsc/cart.d.ts.map +1 -0
- package/dist-tsc/cart.js +27 -0
- package/dist-tsc/confirmatory-stepper.d.ts +2 -0
- package/dist-tsc/confirmatory-stepper.d.ts.map +1 -0
- package/dist-tsc/confirmatory-stepper.js +65 -0
- package/dist-tsc/default-async-functions.d.ts +26 -0
- package/dist-tsc/default-async-functions.d.ts.map +1 -0
- package/dist-tsc/default-async-functions.js +168 -0
- package/dist-tsc/index.d.ts +3 -0
- package/dist-tsc/index.d.ts.map +1 -0
- package/dist-tsc/index.js +2 -0
- package/dist-tsc/missing-types.d.ts +3 -0
- package/dist-tsc/missing-types.d.ts.map +1 -0
- package/dist-tsc/missing-types.js +1 -0
- package/dist-tsc/scmd-ui.d.ts +2 -0
- package/dist-tsc/scmd-ui.d.ts.map +1 -0
- package/dist-tsc/scmd-ui.js +15 -0
- package/dist-tsc/select-agnostic.d.ts +2 -0
- package/dist-tsc/select-agnostic.d.ts.map +1 -0
- package/dist-tsc/select-agnostic.js +66 -0
- package/dist-tsc/select-specific.d.ts +2 -0
- package/dist-tsc/select-specific.d.ts.map +1 -0
- package/dist-tsc/select-specific.js +61 -0
- package/dist-tsc/select-stratification.d.ts +2 -0
- package/dist-tsc/select-stratification.d.ts.map +1 -0
- package/dist-tsc/select-stratification.js +18 -0
- package/dist-tsc/styles.d.ts +2 -0
- package/dist-tsc/styles.d.ts.map +1 -0
- package/dist-tsc/styles.js +32 -0
- package/package.json +52 -0
- package/src/BiomarkerSelectSubscriber.js +107 -0
- package/src/cart.js +95 -0
- package/src/confirmatory-stepper.js +209 -0
- package/src/default-async-functions.js +187 -0
- package/src/index.js +5 -0
- package/src/missing-types.ts +2 -0
- package/src/scmd-ui.js +75 -0
- package/src/select-agnostic.js +172 -0
- package/src/select-specific.js +111 -0
- package/src/select-stratification.js +90 -0
- package/src/styles.js +33 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {object} props
|
|
4
|
+
* @param {{ name: string, stratificationType: string, sampleSets: [string[], string[]]}[]} props.stratificationOptions
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function BiomarkerSelectSubscriber(props: {
|
|
8
|
+
stratificationOptions: {
|
|
9
|
+
name: string;
|
|
10
|
+
stratificationType: string;
|
|
11
|
+
sampleSets: [string[], string[]];
|
|
12
|
+
}[];
|
|
13
|
+
}): JSX.Element;
|
|
14
|
+
//# sourceMappingURL=BiomarkerSelectSubscriber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BiomarkerSelectSubscriber.d.ts","sourceRoot":"","sources":["../src/BiomarkerSelectSubscriber.js"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,iDAHG;IAA+F,qBAAqB,EAA5G;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;KAAC,EAAE;CACvF,eAwFF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
/* eslint-disable no-unused-vars */
|
|
4
|
+
import React, { useState, useCallback } from 'react';
|
|
5
|
+
import { useAsyncFunction, useViewConfigStoreApi, useSetViewConfig, useViewConfig, useCoordination, } from '@vitessce/vit-s';
|
|
6
|
+
import { AsyncFunctionType, ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
|
|
7
|
+
import { ScmdUi } from './scmd-ui.js';
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {object} props
|
|
11
|
+
* @param {{ name: string, stratificationType: string, sampleSets: [string[], string[]]}[]} props.stratificationOptions
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
export function BiomarkerSelectSubscriber(props) {
|
|
15
|
+
const { coordinationScopes, stratificationOptions, } = props;
|
|
16
|
+
const [{ sampleSetSelection, featureSelection, }, { setSampleSetSelection, setFeatureSelection, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.BIOMARKER_SELECT], coordinationScopes);
|
|
17
|
+
const viewConfigStoreApi = useViewConfigStoreApi();
|
|
18
|
+
const viewConfig = useViewConfig();
|
|
19
|
+
const setViewConfig = useSetViewConfig(viewConfigStoreApi);
|
|
20
|
+
// TODO: make isSelecting a coordination type plugin.
|
|
21
|
+
// TODO: use store hooks from @vitessce/vit-s to update the view config based on the selections.
|
|
22
|
+
const [isSelecting, setIsSelecting] = useState(true);
|
|
23
|
+
const [mode, setMode] = useState(null);
|
|
24
|
+
const [step, setStep] = useState(null);
|
|
25
|
+
const [currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection] = useState(null);
|
|
26
|
+
const [currentModalitySpecificSelection, setCurrentModalitySpecificSelection] = useState(null);
|
|
27
|
+
const [currentStratificationSelection, setCurrentStratificationSelection] = useState(null);
|
|
28
|
+
const autocompleteFeature = useAsyncFunction(AsyncFunctionType.AUTOCOMPLETE_FEATURE);
|
|
29
|
+
const transformFeature = useAsyncFunction(AsyncFunctionType.TRANSFORM_FEATURE);
|
|
30
|
+
const autocompleteNode = useCallback(async (inputValue) => autocompleteFeature(inputValue), [autocompleteFeature]);
|
|
31
|
+
const getEdges = useCallback(async (node, targetModality) => transformFeature(node, targetModality), [transformFeature]);
|
|
32
|
+
return (_jsx(_Fragment, { children: isSelecting ? (_jsx(ScmdUi, { mode: mode, setMode: setMode, step: step, setStep: setStep, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, autocompleteNode: autocompleteNode, getEdges: getEdges, stratifications: stratificationOptions, onFinish: () => {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.log('Done selecting');
|
|
35
|
+
/*
|
|
36
|
+
console.log(viewConfig);
|
|
37
|
+
setViewConfig({
|
|
38
|
+
...viewConfig,
|
|
39
|
+
coordinationSpace: {
|
|
40
|
+
...viewConfig.coordinationSpace,
|
|
41
|
+
sampleSetFilter: {
|
|
42
|
+
...viewConfig.coordinationSpace.sampleSetFilter,
|
|
43
|
+
'case-control': currentStratificationSelection?.sampleSets,
|
|
44
|
+
},
|
|
45
|
+
sampleSetSelection: {
|
|
46
|
+
...viewConfig.coordinationSpace.sampleSetSelection,
|
|
47
|
+
'case-control': currentStratificationSelection?.sampleSets,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
*/
|
|
52
|
+
} })) : null }));
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../src/cart.js"],"names":[],"mappings":"AAMA,0DAwFC"}
|
package/dist-tsc/cart.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@material-ui/core';
|
|
4
|
+
import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons';
|
|
5
|
+
import { useStyles } from './styles.js';
|
|
6
|
+
export function ConfirmatoryCart(props) {
|
|
7
|
+
const { currentModalityAgnosticSelection,
|
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
|
9
|
+
setCurrentModalityAgnosticSelection, currentModalitySpecificSelection,
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
|
+
setCurrentModalitySpecificSelection, currentStratificationSelection,
|
|
12
|
+
// eslint-disable-next-line no-unused-vars
|
|
13
|
+
setCurrentStratificationSelection, } = props;
|
|
14
|
+
const classes = useStyles();
|
|
15
|
+
const [expandedAccordions, setExpandedAccordions] = React.useState([
|
|
16
|
+
'agnostic', 'specific', 'stratification',
|
|
17
|
+
]);
|
|
18
|
+
const handleAccordionChange = panel => (event, isExpanded) => {
|
|
19
|
+
if (isExpanded) {
|
|
20
|
+
setExpandedAccordions([...expandedAccordions, panel]);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
setExpandedAccordions(expandedAccordions.filter(item => item !== panel));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsx(Typography, { children: "Current selections" }), _jsxs(Accordion, { className: classes.accordion, expanded: expandedAccordions.includes('agnostic'), onChange: handleAccordionChange('agnostic'), children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), "aria-controls": "panel1bh-content", id: "panel1bh-header", children: _jsx(Typography, { children: "Modality-agnostic biomarkers" }) }), _jsx(AccordionDetails, { className: classes.accordionDetails, children: _jsx("ul", { className: classes.cartUl, children: currentModalityAgnosticSelection ? (currentModalityAgnosticSelection.map(item => (_jsxs("li", { children: [item.label, " (", item.nodeType, ")"] }, item.kgId)))) : null }) })] }), _jsxs(Accordion, { className: classes.accordion, expanded: expandedAccordions.includes('specific'), onChange: handleAccordionChange('specific'), children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), "aria-controls": "panel2bh-content", id: "panel2bh-header", children: _jsx(Typography, { children: "Modality-specific signature" }) }), _jsx(AccordionDetails, { className: classes.accordionDetails, children: _jsx("ul", { className: classes.cartUl, children: currentModalitySpecificSelection ? (currentModalitySpecificSelection.map(item => (_jsxs("li", { children: [item.label, " (", item.nodeType, ")"] }, item.kgId)))) : null }) })] }), _jsxs(Accordion, { className: classes.accordion, expanded: expandedAccordions.includes('stratification'), onChange: handleAccordionChange('stratification'), children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), "aria-controls": "panel3bh-content", id: "panel3bh-header", children: _jsx(Typography, { children: "Case-control stratification" }) }), _jsx(AccordionDetails, { className: classes.accordionDetails, children: _jsx("ul", { className: classes.cartUl, children: currentStratificationSelection ? (_jsx("li", { children: currentStratificationSelection.name })) : null }) })] })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirmatory-stepper.d.ts","sourceRoot":"","sources":["../src/confirmatory-stepper.js"],"names":[],"mappings":"AAcA,6DAkMC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import { Stepper, Step, StepLabel, Button, Grid, Box, Typography } from '@material-ui/core';
|
|
4
|
+
import { ConfirmatoryCart } from './cart.js';
|
|
5
|
+
import { SelectAgnostic } from './select-agnostic.js';
|
|
6
|
+
import { SelectSpecific } from './select-specific.js';
|
|
7
|
+
import { SelectStratification } from './select-stratification.js';
|
|
8
|
+
import { useStyles } from './styles.js';
|
|
9
|
+
const steps = [
|
|
10
|
+
'Select biomarkers (modality-agnostic)',
|
|
11
|
+
'Derive modality-specific biomarker signature',
|
|
12
|
+
'Define case-control stratification',
|
|
13
|
+
];
|
|
14
|
+
export function ConfirmatoryStepper(props) {
|
|
15
|
+
const { autocompleteNode, currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection, currentModalitySpecificSelection, setCurrentModalitySpecificSelection, currentStratificationSelection, setCurrentStratificationSelection, getEdges, stratifications, onFinish, } = props;
|
|
16
|
+
const classes = useStyles();
|
|
17
|
+
const [activeStep, setActiveStep] = React.useState(0);
|
|
18
|
+
const [skipped, setSkipped] = React.useState(new Set());
|
|
19
|
+
const isStepOptional = step => step === 2;
|
|
20
|
+
const isStepSkipped = step => skipped.has(step);
|
|
21
|
+
const handleNext = () => {
|
|
22
|
+
let newSkipped = skipped;
|
|
23
|
+
if (isStepSkipped(activeStep)) {
|
|
24
|
+
newSkipped = new Set(newSkipped.values());
|
|
25
|
+
newSkipped.delete(activeStep);
|
|
26
|
+
}
|
|
27
|
+
setActiveStep(prevActiveStep => prevActiveStep + 1);
|
|
28
|
+
setSkipped(newSkipped);
|
|
29
|
+
};
|
|
30
|
+
const handleBack = () => {
|
|
31
|
+
setActiveStep(prevActiveStep => prevActiveStep - 1);
|
|
32
|
+
};
|
|
33
|
+
const handleSkip = () => {
|
|
34
|
+
if (!isStepOptional(activeStep)) {
|
|
35
|
+
// You probably want to guard against something like this,
|
|
36
|
+
// it should never occur unless someone's actively trying to break something.
|
|
37
|
+
throw new Error("You can't skip a step that isn't optional.");
|
|
38
|
+
}
|
|
39
|
+
setActiveStep(prevActiveStep => prevActiveStep + 1);
|
|
40
|
+
setSkipped((prevSkipped) => {
|
|
41
|
+
const newSkipped = new Set(prevSkipped.values());
|
|
42
|
+
newSkipped.add(activeStep);
|
|
43
|
+
return newSkipped;
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const handleReset = () => {
|
|
47
|
+
setActiveStep(0);
|
|
48
|
+
};
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (activeStep === steps.length) {
|
|
51
|
+
onFinish();
|
|
52
|
+
}
|
|
53
|
+
}, [activeStep, steps]);
|
|
54
|
+
return (_jsxs(Grid, { item: true, container: true, xs: 12, children: [_jsx(Grid, { item: true, container: true, xs: 12, children: _jsx(Box, { className: classes.fullWidthBox, children: _jsx(Stepper, { activeStep: activeStep, children: steps.map((label, index) => {
|
|
55
|
+
const stepProps = {};
|
|
56
|
+
const labelProps = {};
|
|
57
|
+
if (isStepOptional(index)) {
|
|
58
|
+
labelProps.optional = (_jsx(Typography, { variant: "caption", children: "Optional" }));
|
|
59
|
+
}
|
|
60
|
+
if (isStepSkipped(index)) {
|
|
61
|
+
stepProps.completed = false;
|
|
62
|
+
}
|
|
63
|
+
return (_jsx(Step, { ...stepProps, children: _jsx(StepLabel, { ...labelProps, children: label }) }, label));
|
|
64
|
+
}) }) }) }), _jsxs(Grid, { item: true, container: true, xs: 12, style: { marginTop: '20px' }, alignItems: "flex-start", children: [_jsx(Grid, { item: true, container: true, xs: 8, children: activeStep === steps.length ? (_jsx(_Fragment, { children: _jsx(Typography, { style: { marginTop: '20px', marginBottom: '10px' }, children: "All steps completed - you're finished" }) })) : (_jsxs(_Fragment, { children: [activeStep === 0 ? (_jsx(SelectAgnostic, { autocompleteNode: autocompleteNode, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, getEdges: getEdges, stratifications: stratifications })) : null, activeStep === 1 ? (_jsx(SelectSpecific, { autocompleteNode: autocompleteNode, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, getEdges: getEdges, stratifications: stratifications })) : null, activeStep === 2 ? (_jsx(SelectStratification, { autocompleteNode: autocompleteNode, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, getEdges: getEdges, stratifications: stratifications })) : null] })) }), _jsx(Grid, { item: true, container: true, xs: 4, children: _jsx(ConfirmatoryCart, { currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, stratifications: stratifications }) }), _jsx(Box, { className: classes.fullWidthBox, children: activeStep === steps.length ? (_jsx(_Fragment, { children: _jsxs(Box, { style: { display: 'flex', flexDirection: 'row', paddingTop: '20px' }, children: [_jsx(Box, { style: { flex: '1 1 auto' } }), _jsx(Button, { onClick: handleReset, children: "Reset" })] }) })) : (_jsxs(Box, { style: { display: 'flex', flexDirection: 'row', paddingTop: '20px' }, children: [_jsx(Button, { color: "inherit", disabled: activeStep === 0, onClick: handleBack, style: { mr: 1 }, children: "Back" }), _jsx(Box, { style: { flex: '1 1 auto' } }), isStepOptional(activeStep) && (_jsx(Button, { color: "inherit", onClick: handleSkip, style: { marginRight: '10px' }, children: "Skip" })), _jsx(Button, { onClick: handleNext, children: activeStep === steps.length - 1 ? 'Finish' : 'Next' })] })) })] })] }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @satisfies {AutocompleteFeatureFunc}
|
|
3
|
+
* @param {object} ctx
|
|
4
|
+
* @param {QueryClient} ctx.queryClient
|
|
5
|
+
* @param {string} partial
|
|
6
|
+
* @param {null | TargetModalityType} targetModality
|
|
7
|
+
* @returns {Promise<KgNode[]>}
|
|
8
|
+
*/
|
|
9
|
+
export function autocompleteFeature({ queryClient }: {
|
|
10
|
+
queryClient: QueryClient;
|
|
11
|
+
}, partial: string, targetModality: null | TargetModalityType): Promise<KgNode[]>;
|
|
12
|
+
/**
|
|
13
|
+
* @satisfies {TransformFeatureFunc}
|
|
14
|
+
* @param {object} ctx
|
|
15
|
+
* @param {QueryClient} ctx.queryClient
|
|
16
|
+
* @param {KgNode} node
|
|
17
|
+
* @param {TargetModalityType} targetModality
|
|
18
|
+
* @returns {Promise<KgNode[]>}
|
|
19
|
+
*/
|
|
20
|
+
export function transformFeature({ queryClient }: {
|
|
21
|
+
queryClient: QueryClient;
|
|
22
|
+
}, node: KgNode, targetModality: TargetModalityType): Promise<KgNode[]>;
|
|
23
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
24
|
+
import type { TargetModalityType } from '@vitessce/types';
|
|
25
|
+
import type { KgNode } from '@vitessce/types';
|
|
26
|
+
//# sourceMappingURL=default-async-functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-async-functions.d.ts","sourceRoot":"","sources":["../src/default-async-functions.js"],"names":[],"mappings":"AA4GA;;;;;;;GAOG;AACH,qDALG;IAAyB,WAAW,EAA5B,WAAW;CACnB,WAAQ,MAAM,kBACN,IAAI,GAAG,kBAAkB,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiB7B;AAeD;;;;;;;GAOG;AACH,kDALG;IAAyB,WAAW,EAA5B,WAAW;CACnB,QAAQ,MAAM,kBACN,kBAAkB,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAkC7B;iCAlLsD,uBAAuB;wCADyB,iBAAiB;4BAAjB,iBAAiB"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
/* eslint-disable no-unused-vars */
|
|
4
|
+
import { csvParse } from 'd3-dsv';
|
|
5
|
+
// eslint-disable-next-line import/no-unresolved
|
|
6
|
+
import Fuse from 'fuse.js/basic';
|
|
7
|
+
/** @import { KgNode, KgEdge, TargetModalityType, AutocompleteFeatureFunc, TransformFeatureFunc } from '@vitessce/types' */
|
|
8
|
+
/** @import { QueryClient, QueryFunctionContext } from '@tanstack/react-query' */
|
|
9
|
+
const KG_BASE_URL = 'https://storage.googleapis.com/vitessce-demo-data/enrichr-kg-september-2023';
|
|
10
|
+
/**
|
|
11
|
+
* @returns {Promise<KgNode[]>}
|
|
12
|
+
*/
|
|
13
|
+
function loadGeneNodes() {
|
|
14
|
+
return fetch(`${KG_BASE_URL}/Gene.nodes.csv`)
|
|
15
|
+
.then(res => res.text())
|
|
16
|
+
.then((res) => {
|
|
17
|
+
const result = csvParse(res);
|
|
18
|
+
return result.map((/** @type {any} */ d) => ({
|
|
19
|
+
kgId: d.id,
|
|
20
|
+
label: d.label,
|
|
21
|
+
term: `hgnc.symbol:${d.label}`,
|
|
22
|
+
nodeType: 'gene',
|
|
23
|
+
}));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @returns {Promise<KgNode[]>}
|
|
28
|
+
*/
|
|
29
|
+
function loadCellTypeNodes() {
|
|
30
|
+
return fetch(`${KG_BASE_URL}/HuBMAP_ASCTplusB_augmented_2022.nodes.csv`)
|
|
31
|
+
.then(res => res.text())
|
|
32
|
+
.then((res) => {
|
|
33
|
+
const result = csvParse(res);
|
|
34
|
+
return result.map((/** @type {any} */ d) => ({
|
|
35
|
+
kgId: d.id,
|
|
36
|
+
label: d.label,
|
|
37
|
+
term: d.cell_id.length ? d.cell_id : null,
|
|
38
|
+
nodeType: 'cell-type',
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @returns {Promise<KgNode[]>}
|
|
44
|
+
*/
|
|
45
|
+
function loadPathwayNodes() {
|
|
46
|
+
return fetch(`${KG_BASE_URL}/Reactome_2022.nodes.csv`)
|
|
47
|
+
.then(res => res.text())
|
|
48
|
+
.then((res) => {
|
|
49
|
+
const result = csvParse(res);
|
|
50
|
+
return result.map((/** @type {any} */ d) => ({
|
|
51
|
+
kgId: d.id,
|
|
52
|
+
label: d.pathway,
|
|
53
|
+
term: `reactome:${d.acc}`,
|
|
54
|
+
nodeType: 'pathway',
|
|
55
|
+
}));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Parent app can pass in queryClient
|
|
59
|
+
// (to every async function) so that functions can call .fetchQuery
|
|
60
|
+
// Reference: https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientfetchquery
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param {QueryFunctionContext} ctx
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
async function loadFuseObject(ctx) {
|
|
67
|
+
const queryClient = /** @type {QueryClient} */ (ctx.meta?.queryClient);
|
|
68
|
+
const [geneNodes, cellTypeNodes, pathwayNodes] = await Promise.all([
|
|
69
|
+
queryClient.fetchQuery({
|
|
70
|
+
queryKey: ['geneNodes'],
|
|
71
|
+
staleTime: Infinity,
|
|
72
|
+
queryFn: loadGeneNodes,
|
|
73
|
+
}),
|
|
74
|
+
queryClient.fetchQuery({
|
|
75
|
+
queryKey: ['cellTypeNodes'],
|
|
76
|
+
staleTime: Infinity,
|
|
77
|
+
queryFn: loadCellTypeNodes,
|
|
78
|
+
}),
|
|
79
|
+
queryClient.fetchQuery({
|
|
80
|
+
queryKey: ['pathwayNodes'],
|
|
81
|
+
staleTime: Infinity,
|
|
82
|
+
queryFn: loadPathwayNodes,
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
const fuseOptions = {
|
|
86
|
+
keys: ['label', 'term'],
|
|
87
|
+
};
|
|
88
|
+
// For fuzzy searching using fuse.js
|
|
89
|
+
return new Fuse([
|
|
90
|
+
...geneNodes,
|
|
91
|
+
...cellTypeNodes,
|
|
92
|
+
...pathwayNodes,
|
|
93
|
+
], fuseOptions);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* @satisfies {AutocompleteFeatureFunc}
|
|
97
|
+
* @param {object} ctx
|
|
98
|
+
* @param {QueryClient} ctx.queryClient
|
|
99
|
+
* @param {string} partial
|
|
100
|
+
* @param {null | TargetModalityType} targetModality
|
|
101
|
+
* @returns {Promise<KgNode[]>}
|
|
102
|
+
*/
|
|
103
|
+
export async function autocompleteFeature({ queryClient }, partial, targetModality) {
|
|
104
|
+
if (partial.length < 1) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
// TODO: parameterize the fuse object (via queryKey) based on targetModality?
|
|
108
|
+
// Or just perform that filtering after the search?
|
|
109
|
+
const fuse = await queryClient.fetchQuery({
|
|
110
|
+
queryKey: ['autocompleteFeature', 'fuseObject'],
|
|
111
|
+
staleTime: Infinity,
|
|
112
|
+
queryFn: loadFuseObject,
|
|
113
|
+
meta: { queryClient },
|
|
114
|
+
});
|
|
115
|
+
const results = fuse.search(partial);
|
|
116
|
+
return results.map((/** @type {any} */ result) => result.item);
|
|
117
|
+
}
|
|
118
|
+
async function loadPathwayToGeneEdges() {
|
|
119
|
+
return fetch(`${KG_BASE_URL}/Reactome_2022.Reactome.Gene.edges.csv`)
|
|
120
|
+
.then(res => res.text())
|
|
121
|
+
.then((res) => {
|
|
122
|
+
const result = csvParse(res);
|
|
123
|
+
return result.map((/** @type {any} */ d) => ({
|
|
124
|
+
source: d.source,
|
|
125
|
+
target: d.target,
|
|
126
|
+
relation: d.relation,
|
|
127
|
+
}));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @satisfies {TransformFeatureFunc}
|
|
132
|
+
* @param {object} ctx
|
|
133
|
+
* @param {QueryClient} ctx.queryClient
|
|
134
|
+
* @param {KgNode} node
|
|
135
|
+
* @param {TargetModalityType} targetModality
|
|
136
|
+
* @returns {Promise<KgNode[]>}
|
|
137
|
+
*/
|
|
138
|
+
export async function transformFeature({ queryClient }, node, targetModality) {
|
|
139
|
+
if (targetModality === node.nodeType) {
|
|
140
|
+
// For example, if the target modality is gene and the node is already a gene node.
|
|
141
|
+
return [node];
|
|
142
|
+
}
|
|
143
|
+
if (targetModality === 'gene') {
|
|
144
|
+
const geneNodes = await queryClient.fetchQuery({
|
|
145
|
+
queryKey: ['geneNodes'],
|
|
146
|
+
staleTime: Infinity,
|
|
147
|
+
queryFn: loadGeneNodes,
|
|
148
|
+
});
|
|
149
|
+
if (node.nodeType === 'pathway') {
|
|
150
|
+
// Pathway to genes case.
|
|
151
|
+
const pathwayGeneEdges = await queryClient.fetchQuery({
|
|
152
|
+
queryKey: ['pathwayGeneEdges'],
|
|
153
|
+
staleTime: Infinity,
|
|
154
|
+
queryFn: loadPathwayToGeneEdges,
|
|
155
|
+
});
|
|
156
|
+
const matchingEdges = pathwayGeneEdges.filter((/** @type {KgEdge} */ d) => d.source === node.kgId);
|
|
157
|
+
const matchingGeneIds = matchingEdges.map((/** @type {KgEdge} */ d) => d.target);
|
|
158
|
+
const matchingGenes = geneNodes.filter(d => matchingGeneIds.includes(d.kgId));
|
|
159
|
+
return matchingGenes;
|
|
160
|
+
}
|
|
161
|
+
if (node.nodeType === 'cell-type') {
|
|
162
|
+
// Cell type to genes case.
|
|
163
|
+
// TODO: implement
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// TODO: handle other target modalities
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-types.d.ts","sourceRoot":"","sources":["../src/missing-types.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,QAAQ,CAAC;AACxB,OAAO,QAAQ,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scmd-ui.d.ts","sourceRoot":"","sources":["../src/scmd-ui.js"],"names":[],"mappings":"AAOA,gDAmEC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { Button, ButtonGroup, Grid, Tooltip } from '@material-ui/core';
|
|
5
|
+
import { ConfirmatoryStepper } from './confirmatory-stepper.js';
|
|
6
|
+
import { useStyles } from './styles.js';
|
|
7
|
+
export function ScmdUi(props) {
|
|
8
|
+
const { mode: modeProp, setMode, step: stepProp, setStep, currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection, currentModalitySpecificSelection, setCurrentModalitySpecificSelection, currentStratificationSelection, setCurrentStratificationSelection, autocompleteNode, getEdges, stratifications, onFinish, } = props;
|
|
9
|
+
const classes = useStyles();
|
|
10
|
+
const mode = modeProp || 'confirmatory';
|
|
11
|
+
const step = mode === 'confirmatory' ? (stepProp || 'select-biomarkers') : (stepProp || 'define-stratification');
|
|
12
|
+
const [tempIsVisible, setTempIsVisible] = useState(true);
|
|
13
|
+
const isConfirmatoryMode = mode === 'confirmatory';
|
|
14
|
+
return (_jsxs(Grid, { container: true, style: { border: '0px solid red' }, children: [_jsxs(Grid, { item: true, container: true, xs: 12, className: classes.header, children: [_jsx(Grid, { item: true, xs: 8 }), _jsx(Grid, { item: true, container: true, xs: 4, justifyContent: "flex-end", children: _jsxs(ButtonGroup, { variant: "outlined", size: "small", color: "secondary", "aria-label": "Toggle between confirmatory and exploratory modes", children: [_jsx(Tooltip, { arrow: true, title: "Start from biomarker(s) of interest", children: _jsx(Button, { variant: isConfirmatoryMode ? 'contained' : 'outlined', onClick: () => setMode('confirmatory'), children: "Confirmatory (Hypothesis-driven)" }) }), _jsx(Tooltip, { arrow: true, title: "Lacking biomarker(s) of interest", children: _jsx(Button, { variant: !isConfirmatoryMode ? 'contained' : 'outlined', onClick: () => setMode('exploratory'), children: "Exploratory (Hypothesis-generating)" }) })] }) })] }), tempIsVisible ? (_jsxs("div", { style: { border: '2px solid #eee', borderRadius: '5px' }, children: [mode === 'confirmatory' ? (_jsx(ConfirmatoryStepper, { autocompleteNode: autocompleteNode, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, getEdges: getEdges, stratifications: stratifications, onFinish: onFinish })) : null, mode === 'exploratory' ? _jsx("span", { children: "TODO" }) : null] })) : null] }));
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-agnostic.d.ts","sourceRoot":"","sources":["../src/select-agnostic.js"],"names":[],"mappings":"AAoEA,wDAuGC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useRef, useEffect } from 'react';
|
|
3
|
+
import { Grid, Button, TextField, Typography } from '@material-ui/core';
|
|
4
|
+
import { Add as AddIcon, Info as InfoIcon } from '@material-ui/icons';
|
|
5
|
+
import { Autocomplete } from '@material-ui/lab';
|
|
6
|
+
import { VariableSizeList } from 'react-window';
|
|
7
|
+
import { useStyles } from './styles.js';
|
|
8
|
+
const LIST_ROW_HEIGHT = 48;
|
|
9
|
+
// We use a context to pass the extra props from ListboxComponent to the outer div element.
|
|
10
|
+
const OuterElementContext = React.createContext({});
|
|
11
|
+
const OuterElementType = React.forwardRef((props, ref) => {
|
|
12
|
+
const outerProps = React.useContext(OuterElementContext);
|
|
13
|
+
return _jsx("div", { ref: ref, ...props, ...outerProps });
|
|
14
|
+
});
|
|
15
|
+
function ListRow(props) {
|
|
16
|
+
const { data, index, style } = props;
|
|
17
|
+
return React.cloneElement(data[index], {
|
|
18
|
+
style: {
|
|
19
|
+
...style,
|
|
20
|
+
top: style.top + 8,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function useResetCache(itemCount) {
|
|
25
|
+
const ref = useRef(null);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (ref.current != null) {
|
|
28
|
+
ref.current.resetAfterIndex(0, true);
|
|
29
|
+
}
|
|
30
|
+
}, [itemCount]);
|
|
31
|
+
return ref;
|
|
32
|
+
}
|
|
33
|
+
// Reference: https://mui.com/material-ui/react-autocomplete/#virtualization
|
|
34
|
+
const ListboxComponent = React.forwardRef((props, ref) => {
|
|
35
|
+
const { children, ...other } = props;
|
|
36
|
+
const itemCount = children.length;
|
|
37
|
+
const gridRef = useResetCache(itemCount);
|
|
38
|
+
return (_jsx("div", { ref: ref, children: _jsx(OuterElementContext.Provider, { value: other, children: _jsx(VariableSizeList, { height: Math.min(8, itemCount) * LIST_ROW_HEIGHT, itemSize: () => LIST_ROW_HEIGHT, width: "100%", innerElementType: "ul", outerElementType: OuterElementType, overscanCount: 5, ref: gridRef, itemCount: itemCount, itemData: children, children: ListRow }) }) }));
|
|
39
|
+
});
|
|
40
|
+
export function SelectAgnostic(props) {
|
|
41
|
+
const { autocompleteNode, currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection, } = props;
|
|
42
|
+
const classes = useStyles();
|
|
43
|
+
const [selectedItem, setSelectedItem] = React.useState(null);
|
|
44
|
+
const [potentialItems, setPotentialItems] = React.useState([]);
|
|
45
|
+
const handleChange = async (event) => {
|
|
46
|
+
const { value } = event.target;
|
|
47
|
+
if (!value) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const autocompleteResult = await autocompleteNode(value);
|
|
51
|
+
setPotentialItems(autocompleteResult);
|
|
52
|
+
};
|
|
53
|
+
function confirmSelectedItem() {
|
|
54
|
+
// eslint-disable-next-line max-len
|
|
55
|
+
if (selectedItem && !currentModalityAgnosticSelection?.find(item => item.kgId === selectedItem?.kgId)) {
|
|
56
|
+
setCurrentModalityAgnosticSelection([
|
|
57
|
+
...(currentModalityAgnosticSelection || []),
|
|
58
|
+
selectedItem,
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { item: true, container: true, xs: 12, children: _jsx(Typography, { variant: "h6", children: "Search by gene, protein, pathway (by term name), or cell type:" }) }), _jsxs(Grid, { item: true, container: true, xs: 12, children: [_jsx(Grid, { item: true, xs: 4, children: _jsx(Autocomplete, { options: potentialItems, autoComplete: true, includeInputInList: true, onInputChange: handleChange, onChange: (event, item) => setSelectedItem(item), classes: { input: classes.searchInput }, renderInput: params => (_jsx(TextField, { label: "Search", variant: "outlined", onChange: handleChange, ...params })), ListboxComponent: ListboxComponent, getOptionLabel: option => option.label, renderOption: option => (_jsxs(Typography, { noWrap: true, children: [option.label, " (", option.nodeType, ")"] })) }) }), _jsx(Grid, { item: true, xs: 8, style: {
|
|
63
|
+
border: selectedItem ? '1px solid gray' : '1px solid transparent',
|
|
64
|
+
borderRadius: '4px',
|
|
65
|
+
}, children: selectedItem ? (_jsxs(_Fragment, { children: [_jsxs(Grid, { container: true, item: true, xs: 12, children: [_jsx(Grid, { item: true, xs: 9, children: _jsx(Typography, { variant: "h4", title: selectedItem.term, children: selectedItem.label }) }), _jsx(Grid, { item: true, xs: 3, style: { position: 'relative' }, children: _jsx(Button, { className: classes.selectButton, variant: "contained", startIcon: _jsx(AddIcon, {}), onClick: confirmSelectedItem, children: "Select" }) })] }), _jsxs(Grid, { container: true, item: true, xs: 12, children: [_jsx(InfoIcon, {}), _jsxs(Typography, { variant: "h6", children: ["About this ", selectedItem.nodeType] })] }), _jsx(Grid, { container: true, item: true, xs: 12, children: _jsx("iframe", { title: `Embedded metadata page for ontology term ${selectedItem.term}`, src: `https://identifiers.org/${selectedItem.term}`, width: "100%", height: "500", style: { border: 0 } }) }, selectedItem.term)] })) : null })] })] }));
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-specific.d.ts","sourceRoot":"","sources":["../src/select-specific.js"],"names":[],"mappings":"AAuBA,wDAuFC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import { useQueries } from '@tanstack/react-query';
|
|
4
|
+
import { Grid, FormHelperText, Typography, Select, FormControl } from '@material-ui/core';
|
|
5
|
+
import { DataGrid } from '@mui/x-data-grid';
|
|
6
|
+
import { useStyles } from './styles.js';
|
|
7
|
+
const columns = [
|
|
8
|
+
{
|
|
9
|
+
field: 'targetLabel',
|
|
10
|
+
headerName: 'Gene symbol',
|
|
11
|
+
width: 200,
|
|
12
|
+
editable: false,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
field: 'sourceRelationship',
|
|
16
|
+
headerName: 'Relationship to modality-agnostic selections',
|
|
17
|
+
width: 400,
|
|
18
|
+
editable: false,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
export function SelectSpecific(props) {
|
|
22
|
+
const { currentModalityAgnosticSelection, currentModalitySpecificSelection, setCurrentModalitySpecificSelection, getEdges, } = props;
|
|
23
|
+
const classes = useStyles();
|
|
24
|
+
const queries = useQueries({
|
|
25
|
+
queries: currentModalityAgnosticSelection?.map(item => ({
|
|
26
|
+
queryKey: [item.nodeType, item.kgId],
|
|
27
|
+
queryFn: async () => {
|
|
28
|
+
const matchingGenes = await getEdges(item, 'gene');
|
|
29
|
+
return matchingGenes.map(d => ({ target: d, source: item }));
|
|
30
|
+
},
|
|
31
|
+
})) || [],
|
|
32
|
+
});
|
|
33
|
+
const anyLoading = queries.some(q => q.isFetching);
|
|
34
|
+
const anyError = queries.some(q => q.isError);
|
|
35
|
+
// eslint-disable-next-line no-nested-ternary
|
|
36
|
+
const dataStatus = anyLoading ? 'loading' : (anyError ? 'error' : 'success');
|
|
37
|
+
const data = queries
|
|
38
|
+
.flatMap(q => q.data)
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
const rows = useMemo(() => data.map(d => ({
|
|
41
|
+
id: d.target.kgId,
|
|
42
|
+
targetLabel: d.target.label,
|
|
43
|
+
sourceRelationship: (d.source.nodeType === 'gene'
|
|
44
|
+
? 'Directly-selected gene'
|
|
45
|
+
: `Member of ${d.source.label} ${d.source.nodeType}`),
|
|
46
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
|
+
})), [currentModalityAgnosticSelection, dataStatus]);
|
|
48
|
+
// Derive the set of selected row ids from the
|
|
49
|
+
// currentModalitySpecificSelection.
|
|
50
|
+
const rowSelectionModel = useMemo(() => currentModalitySpecificSelection?.map(d => d.kgId) || [], [rows, currentModalitySpecificSelection]);
|
|
51
|
+
function handleSelection(newRowSelectionModel) {
|
|
52
|
+
const newSelection = newRowSelectionModel
|
|
53
|
+
.map(kgId => data.find(d => d.target.kgId === kgId)?.target)
|
|
54
|
+
.filter(Boolean);
|
|
55
|
+
setCurrentModalitySpecificSelection(newSelection);
|
|
56
|
+
}
|
|
57
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { item: true, xs: 6, children: _jsx(Typography, { variant: "h6", children: "Select a feature type:" }) }), _jsx(Grid, { item: true, container: true, xs: 6, children: _jsx(Grid, { item: true, children: _jsxs(FormControl, { fullWidth: true, children: [_jsx(Select, { native: true, defaultValue: "gene", classes: { select: classes.selectInput }, inputProps: {
|
|
58
|
+
name: 'age',
|
|
59
|
+
id: 'uncontrolled-native',
|
|
60
|
+
}, children: _jsx("option", { value: "gene", children: "Gene (RNA-seq)" }) }), _jsx(FormHelperText, { children: "Feature type (experimental modality)" })] }) }) }), _jsx(Grid, { item: true, container: true, xs: 12, style: { height: '450px' }, children: _jsx(DataGrid, { rows: rows, columns: columns, pageSize: 10, checkboxSelection: true, onSelectionModelChange: handleSelection, rowSelectionModel: rowSelectionModel }) })] }));
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-stratification.d.ts","sourceRoot":"","sources":["../src/select-stratification.js"],"names":[],"mappings":"AAIA,8DAqFC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Grid, RadioGroup, FormControl, FormLabel, Radio, FormControlLabel, Typography } from '@material-ui/core';
|
|
4
|
+
export function SelectStratification(props) {
|
|
5
|
+
const { stratifications, currentStratificationSelection: currentStratificationSelectionProp, setCurrentStratificationSelection: setCurrentStratificationSelectionProp, } = props;
|
|
6
|
+
// Convert back and forth from the special value '__all__' to null.
|
|
7
|
+
const currentStratificationSelection = currentStratificationSelectionProp === null
|
|
8
|
+
? '__all__'
|
|
9
|
+
: currentStratificationSelectionProp.stratificationId;
|
|
10
|
+
function setCurrentStratificationSelection(event, value) {
|
|
11
|
+
setCurrentStratificationSelectionProp(stratifications.find(s => s.stratificationId === value) ?? null);
|
|
12
|
+
}
|
|
13
|
+
const sampleSetOptions = stratifications?.filter(s => s.stratificationType === 'sampleSet');
|
|
14
|
+
const structuralRegionOptions = stratifications?.filter(s => s.stratificationType === 'structural-region');
|
|
15
|
+
const hasSampleSetOptions = sampleSetOptions && sampleSetOptions.length > 0;
|
|
16
|
+
const hasStructuralRegionOptions = structuralRegionOptions && structuralRegionOptions.length > 0;
|
|
17
|
+
return (_jsxs(Grid, { item: true, xs: 12, children: [_jsx(Typography, { variant: "h6", children: "Select a stratification option:" }), _jsx(FormControl, { children: _jsxs(RadioGroup, { "aria-labelledby": "demo-radio-buttons-group-label", value: currentStratificationSelection, onChange: setCurrentStratificationSelection, name: "radio-buttons-group", children: [_jsx(FormControlLabel, { value: "__all__", control: _jsx(Radio, {}), label: "No stratification" }), hasSampleSetOptions ? (_jsxs(_Fragment, { children: [_jsx(FormLabel, { children: "By participant group (clinical):" }), sampleSetOptions.map(s => (_jsx(FormControlLabel, { value: s.stratificationId, control: _jsx(Radio, {}), label: s.name }, s.name)))] })) : null, hasStructuralRegionOptions ? (_jsxs(_Fragment, { children: [_jsx(FormLabel, { children: "By segmentation-defined spatial region (structural):" }), structuralRegionOptions.map(s => (_jsx(FormControlLabel, { value: s.stratificationId, control: _jsx(Radio, {}), label: s.name }, s.name)))] })) : null] }) })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.js"],"names":[],"mappings":"AAEA,8NA8BI"}
|