@vitessce/biomarker-select 3.5.7 → 3.5.9

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComparativeHeadingSubscriber.d.ts","sourceRoot":"","sources":["../src/ComparativeHeadingSubscriber.js"],"names":[],"mappings":"AA+BA,sEAuDC"}
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useCallback } from 'react';
3
+ import { useCoordination, } from '@vitessce/vit-s';
4
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
5
+ import { makeStyles } from '@material-ui/core';
6
+ const useStyles = makeStyles(theme => ({
7
+ buttonContainer: {
8
+ position: 'absolute',
9
+ right: 0,
10
+ display: 'flex',
11
+ flexDirection: 'column',
12
+ justifyContent: 'center',
13
+ height: '100%',
14
+ },
15
+ headingContainer: {
16
+ backgroundColor: 'transparent',
17
+ },
18
+ headingSectionContainer: {
19
+ width: '100%',
20
+ display: 'flex',
21
+ flexDirection: 'row',
22
+ position: 'relative',
23
+ },
24
+ headingText: {
25
+ margin: 0,
26
+ color: theme.palette.primaryForeground,
27
+ },
28
+ }));
29
+ export function ComparativeHeadingSubscriber(props) {
30
+ 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);
35
+ const classes = useStyles();
36
+ const swapSampleSets = useCallback(() => {
37
+ if (sampleSetSelection?.length === 2) {
38
+ setSampleSetSelection([
39
+ sampleSetSelection[1],
40
+ sampleSetSelection[0],
41
+ ]);
42
+ }
43
+ }, [sampleSetSelection, setSampleSetSelection]);
44
+ 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: _jsx("button", { onClick: swapSampleSets, type: "button", children: "Swap" }) })] })) : null, 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] }) })))] })) : null] }));
45
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"default-async-functions.d.ts","sourceRoot":"","sources":["../src/default-async-functions.js"],"names":[],"mappings":"AAqHA;;;;;;;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;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;iCAzPsD,uBAAuB;wCADsE,iBAAiB;4BAAjB,iBAAiB"}
1
+ {"version":3,"file":"default-async-functions.d.ts","sourceRoot":"","sources":["../src/default-async-functions.js"],"names":[],"mappings":"AAqHA;;;;;;;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,CAmC7B;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;iCA1PsD,uBAAuB;wCADsE,iBAAiB;4BAAjB,iBAAiB"}
@@ -161,6 +161,7 @@ export async function transformFeature({ queryClient }, node, targetModality) {
161
161
  staleTime: Infinity,
162
162
  queryFn: loadPathwayToGeneEdges,
163
163
  });
164
+ // TODO: support matching using ontology term (rather than requiring kgId)?
164
165
  const matchingEdges = pathwayGeneEdges.filter((/** @type {KgEdge} */ d) => d.source === node.kgId);
165
166
  const matchingGeneIds = matchingEdges.map((/** @type {KgEdge} */ d) => d.target);
166
167
  const matchingGenes = geneNodes.filter(d => matchingGeneIds.includes(d.kgId));
@@ -1,3 +1,4 @@
1
1
  export { BiomarkerSelectSubscriber } from "./BiomarkerSelectSubscriber.js";
2
+ export { ComparativeHeadingSubscriber } from "./ComparativeHeadingSubscriber.js";
2
3
  export { autocompleteFeature, transformFeature, getAlternativeTerms, getTermMapping } from "./default-async-functions.js";
3
4
  //# sourceMappingURL=index.d.ts.map
package/dist-tsc/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
2
+ export { ComparativeHeadingSubscriber } from './ComparativeHeadingSubscriber.js';
2
3
  export { autocompleteFeature, transformFeature, getAlternativeTerms, getTermMapping, } from './default-async-functions.js';
@@ -1 +1 @@
1
- {"version":3,"file":"scmd-ui.d.ts","sourceRoot":"","sources":["../src/scmd-ui.js"],"names":[],"mappings":"AAOA,gDAmEC"}
1
+ {"version":3,"file":"scmd-ui.d.ts","sourceRoot":"","sources":["../src/scmd-ui.js"],"names":[],"mappings":"AAQA,gDAkFC"}
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-unused-vars */
3
3
  import React, { useState } from 'react';
4
4
  import { Button, ButtonGroup, Grid, Tooltip } from '@material-ui/core';
5
- import { ConfirmatoryStepper } from './confirmatory-stepper.js';
5
+ import { ConfirmatoryStepper } from './stepper-confirmatory.js';
6
+ import { ExploratoryStepper } from './stepper-exploratory.js';
6
7
  import { useStyles } from './styles.js';
7
8
  export function ScmdUi(props) {
8
9
  const { mode: modeProp, setMode, step: stepProp, setStep, currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection, currentModalitySpecificSelection, setCurrentModalitySpecificSelection, currentStratificationSelection, setCurrentStratificationSelection, autocompleteNode, getEdges, stratifications, onFinish, } = props;
@@ -11,5 +12,5 @@ export function ScmdUi(props) {
11
12
  const step = mode === 'confirmatory' ? (stepProp || 'select-biomarkers') : (stepProp || 'define-stratification');
12
13
  const [tempIsVisible, setTempIsVisible] = useState(true);
13
14
  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
+ 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(ExploratoryStepper, { autocompleteNode: autocompleteNode, currentModalityAgnosticSelection: currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection: setCurrentModalityAgnosticSelection, currentModalitySpecificSelection: currentModalitySpecificSelection, setCurrentModalitySpecificSelection: setCurrentModalitySpecificSelection, currentStratificationSelection: currentStratificationSelection, setCurrentStratificationSelection: setCurrentStratificationSelection, getEdges: getEdges, stratifications: stratifications, onFinish: onFinish })) : null] })) : null] }));
15
16
  }
@@ -1,2 +1,2 @@
1
1
  export function ConfirmatoryStepper(props: any): JSX.Element;
2
- //# sourceMappingURL=confirmatory-stepper.d.ts.map
2
+ //# sourceMappingURL=stepper-confirmatory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepper-confirmatory.d.ts","sourceRoot":"","sources":["../src/stepper-confirmatory.js"],"names":[],"mappings":"AAcA,6DAkMC"}
@@ -0,0 +1,2 @@
1
+ export function ExploratoryStepper(props: any): JSX.Element;
2
+ //# sourceMappingURL=stepper-exploratory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepper-exploratory.d.ts","sourceRoot":"","sources":["../src/stepper-exploratory.js"],"names":[],"mappings":"AAUA,4DAoKC"}
@@ -0,0 +1,61 @@
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 { SelectStratification } from './select-stratification.js';
6
+ import { useStyles } from './styles.js';
7
+ const steps = [
8
+ 'Define case-control stratification',
9
+ ];
10
+ export function ExploratoryStepper(props) {
11
+ const { autocompleteNode, currentModalityAgnosticSelection, setCurrentModalityAgnosticSelection, currentModalitySpecificSelection, setCurrentModalitySpecificSelection, currentStratificationSelection, setCurrentStratificationSelection, getEdges, stratifications, onFinish, } = props;
12
+ const classes = useStyles();
13
+ const [activeStep, setActiveStep] = React.useState(0);
14
+ const [skipped, setSkipped] = React.useState(new Set());
15
+ const isStepOptional = step => step === 2;
16
+ const isStepSkipped = step => skipped.has(step);
17
+ const handleNext = () => {
18
+ let newSkipped = skipped;
19
+ if (isStepSkipped(activeStep)) {
20
+ newSkipped = new Set(newSkipped.values());
21
+ newSkipped.delete(activeStep);
22
+ }
23
+ setActiveStep(prevActiveStep => prevActiveStep + 1);
24
+ setSkipped(newSkipped);
25
+ };
26
+ const handleBack = () => {
27
+ setActiveStep(prevActiveStep => prevActiveStep - 1);
28
+ };
29
+ const handleSkip = () => {
30
+ if (!isStepOptional(activeStep)) {
31
+ // You probably want to guard against something like this,
32
+ // it should never occur unless someone's actively trying to break something.
33
+ throw new Error("You can't skip a step that isn't optional.");
34
+ }
35
+ setActiveStep(prevActiveStep => prevActiveStep + 1);
36
+ setSkipped((prevSkipped) => {
37
+ const newSkipped = new Set(prevSkipped.values());
38
+ newSkipped.add(activeStep);
39
+ return newSkipped;
40
+ });
41
+ };
42
+ const handleReset = () => {
43
+ setActiveStep(0);
44
+ };
45
+ useEffect(() => {
46
+ if (activeStep === steps.length) {
47
+ onFinish();
48
+ }
49
+ }, [activeStep, steps]);
50
+ 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) => {
51
+ const stepProps = {};
52
+ const labelProps = {};
53
+ if (isStepOptional(index)) {
54
+ labelProps.optional = (_jsx(Typography, { variant: "caption", children: "Optional" }));
55
+ }
56
+ if (isStepSkipped(index)) {
57
+ stepProps.completed = false;
58
+ }
59
+ return (_jsx(Step, { ...stepProps, children: _jsx(StepLabel, { ...labelProps, children: label }) }, label));
60
+ }) }) }) }), _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" }) })) : (_jsx(_Fragment, { children: activeStep === 0 ? (_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' })] })) })] })] }));
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/biomarker-select",
3
- "version": "3.5.7",
3
+ "version": "3.5.9",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -24,9 +24,9 @@
24
24
  "react-window": "^1.8.10",
25
25
  "fuse.js": "^7.0.0",
26
26
  "d3-dsv": "^1.1.1",
27
- "@vitessce/constants-internal": "3.5.7",
28
- "@vitessce/vit-s": "3.5.7",
29
- "@vitessce/types": "3.5.7"
27
+ "@vitessce/constants-internal": "3.5.9",
28
+ "@vitessce/vit-s": "3.5.9",
29
+ "@vitessce/types": "3.5.9"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@testing-library/jest-dom": "^5.16.4",
@@ -1,17 +1,20 @@
1
1
  /* eslint-disable max-len */
2
2
  /* eslint-disable no-unused-vars */
3
- import React, { useState, useCallback } from 'react';
3
+ import React, { useState, useCallback, useMemo } from 'react';
4
4
  import {
5
5
  useAsyncFunction,
6
6
  useViewConfigStoreApi,
7
7
  useSetViewConfig,
8
8
  useViewConfig,
9
9
  useCoordination,
10
+ useLoaders,
11
+ useComparisonMetadata,
12
+ useMatchingLoader,
13
+ useColumnNameMapping,
10
14
  } from '@vitessce/vit-s';
11
- import { AsyncFunctionType, ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
15
+ import { AsyncFunctionType, ViewType, COMPONENT_COORDINATION_TYPES, DataType } from '@vitessce/constants-internal';
12
16
  import { ScmdUi } from './scmd-ui.js';
13
17
 
14
-
15
18
  /**
16
19
  *
17
20
  * @param {object} props
@@ -21,10 +24,15 @@ import { ScmdUi } from './scmd-ui.js';
21
24
  export function BiomarkerSelectSubscriber(props) {
22
25
  const {
23
26
  coordinationScopes,
24
- stratificationOptions,
27
+ stratificationOptions: stratificationOptionsProp, // TODO: Remove; Get from comparisonMetadata instead
25
28
  } = props;
26
29
 
30
+ const loaders = useLoaders();
31
+
27
32
  const [{
33
+ dataset,
34
+ obsType,
35
+ sampleType,
28
36
  sampleSetSelection,
29
37
  featureSelection,
30
38
  }, {
@@ -40,6 +48,16 @@ export function BiomarkerSelectSubscriber(props) {
40
48
  const setViewConfig = useSetViewConfig(viewConfigStoreApi);
41
49
 
42
50
 
51
+ // Need obsSets and sampleSets options to obtain mapping between
52
+ // group names and column names.
53
+ const obsSetsLoader = useMatchingLoader(
54
+ loaders, dataset, DataType.OBS_SETS, { obsType },
55
+ );
56
+ const sampleSetsLoader = useMatchingLoader(
57
+ loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
58
+ );
59
+ const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
60
+
43
61
  // TODO: make isSelecting a coordination type plugin.
44
62
  // TODO: use store hooks from @vitessce/vit-s to update the view config based on the selections.
45
63
  const [isSelecting, setIsSelecting] = useState(true);
@@ -62,6 +80,43 @@ export function BiomarkerSelectSubscriber(props) {
62
80
  [transformFeature],
63
81
  );
64
82
 
83
+ const [{ comparisonMetadata }, cmpMetadataStatus, cmpMetadataUrls] = useComparisonMetadata(
84
+ loaders, dataset, false, {}, {}, { obsType, sampleType },
85
+ );
86
+ const stratificationOptions = useMemo(() => {
87
+ /*
88
+ return array of objects like {
89
+ stratificationId: 'aki-vs-hr',
90
+ name: 'Acute kidney injury (AKI) vs. Healthy reference',
91
+ stratificationType: 'sampleSet', // key changed from 'groupType'. value changed from 'clinical'
92
+ sampleSets: [
93
+ ['Disease Type', 'AKI'],
94
+ ['Disease Type', 'Reference'],
95
+ ],
96
+ },
97
+ */
98
+ if (comparisonMetadata?.sample_group_pairs) {
99
+ return comparisonMetadata.sample_group_pairs.map((sampleGroupPair) => {
100
+ const [sampleGroupCol, sampleGroupValues] = sampleGroupPair;
101
+ const [sampleGroupCtrl, sampleGroupCase] = sampleGroupValues;
102
+ const groupName = sampleSetsColumnNameMappingReversed?.[sampleGroupCol];
103
+ return {
104
+ stratificationId: `${sampleGroupCol}_${sampleGroupCtrl}-vs-${sampleGroupCase}`,
105
+ name: `${groupName}: ${sampleGroupCtrl} vs. ${sampleGroupCase}`,
106
+ stratificationType: 'sampleSet',
107
+ sampleSets: [
108
+ // With sampleSets coming from the comparison_metadata,
109
+ // need to use loader options from obsSets and sampleSets to get mapping
110
+ // from column name to group name.
111
+ [groupName, sampleGroupCtrl],
112
+ [groupName, sampleGroupCase],
113
+ ],
114
+ };
115
+ });
116
+ }
117
+ return null;
118
+ }, [comparisonMetadata, sampleSetsColumnNameMappingReversed]);
119
+
65
120
  return (
66
121
  <>
67
122
  {isSelecting ? (
@@ -80,20 +135,39 @@ export function BiomarkerSelectSubscriber(props) {
80
135
  getEdges={getEdges}
81
136
  stratifications={stratificationOptions}
82
137
  onFinish={() => {
83
- setViewConfig({
138
+ if (mode === 'exploratory') {
139
+ // mode is exploratory, configure accordingly.
140
+
141
+ // TODO
142
+
143
+ } else {
144
+ // mode is confirmatory, configure accordingly.
145
+
146
+ // TODO
147
+ }
148
+
149
+ const newViewConfig = {
84
150
  ...viewConfig,
85
151
  coordinationSpace: {
86
152
  ...viewConfig.coordinationSpace,
87
153
  sampleSetFilter: {
88
154
  ...viewConfig.coordinationSpace.sampleSetFilter,
89
- 'case-control': currentStratificationSelection?.sampleSets,
155
+ __comparison__: currentStratificationSelection?.sampleSets,
90
156
  },
91
157
  sampleSetSelection: {
92
158
  ...viewConfig.coordinationSpace.sampleSetSelection,
93
- 'case-control': currentStratificationSelection?.sampleSets,
159
+ __comparison__: currentStratificationSelection?.sampleSets,
160
+ },
161
+ featureSelection: {
162
+ ...viewConfig.coordinationSpace.featureSelection,
163
+ __comparison__: currentModalitySpecificSelection ? currentModalitySpecificSelection.map(d => d.label) : null,
94
164
  },
95
165
  },
96
- });
166
+ };
167
+
168
+ // TODO: can the normal coordination value setters be used instead?
169
+ // (e.g., setFeatureSelection, setSampleSetSelection)
170
+ setViewConfig(newViewConfig);
97
171
  }}
98
172
  />
99
173
  ) : null}
@@ -0,0 +1,87 @@
1
+ import React, { useCallback } from 'react';
2
+ import {
3
+ useCoordination,
4
+ } from '@vitessce/vit-s';
5
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
6
+ import { makeStyles } from '@material-ui/core';
7
+
8
+ const useStyles = makeStyles(theme => ({
9
+ buttonContainer: {
10
+ position: 'absolute',
11
+ right: 0,
12
+ display: 'flex',
13
+ flexDirection: 'column',
14
+ justifyContent: 'center',
15
+ height: '100%',
16
+ },
17
+ headingContainer: {
18
+ backgroundColor: 'transparent',
19
+ },
20
+ headingSectionContainer: {
21
+ width: '100%',
22
+ display: 'flex',
23
+ flexDirection: 'row',
24
+ position: 'relative',
25
+ },
26
+ headingText: {
27
+ margin: 0,
28
+ color: theme.palette.primaryForeground,
29
+ },
30
+ }));
31
+
32
+ export function ComparativeHeadingSubscriber(props) {
33
+ const {
34
+ coordinationScopes,
35
+ } = props;
36
+
37
+ const [{
38
+ sampleSetSelection,
39
+ featureSelection,
40
+ }, {
41
+ setSampleSetSelection,
42
+ // TODO: buttons for modification of featureSelection
43
+ // setFeatureSelection,
44
+ }] = useCoordination(
45
+ COMPONENT_COORDINATION_TYPES[ViewType.COMPARATIVE_HEADING],
46
+ coordinationScopes,
47
+ );
48
+
49
+ const classes = useStyles();
50
+
51
+ const swapSampleSets = useCallback(() => {
52
+ if (sampleSetSelection?.length === 2) {
53
+ setSampleSetSelection([
54
+ sampleSetSelection[1],
55
+ sampleSetSelection[0],
56
+ ]);
57
+ }
58
+ }, [sampleSetSelection, setSampleSetSelection]);
59
+
60
+ return (
61
+ <div className={classes.headingContainer}>
62
+ {sampleSetSelection && sampleSetSelection.length === 2 ? (
63
+ <div className={classes.headingSectionContainer}>
64
+ <div style={{ width: '45%' }}><h2 className={classes.headingText}>{sampleSetSelection?.[0]?.at(-1)}</h2></div>
65
+ <div style={{ width: '5%' }}><h2 className={classes.headingText} style={{ textAlign: 'right' }}>vs.&nbsp;</h2></div>
66
+ <div style={{ width: '50%' }}><h2 className={classes.headingText}>{sampleSetSelection?.[1]?.at(-1)}</h2></div>
67
+ <div className={classes.buttonContainer}>
68
+ <button onClick={swapSampleSets} type="button">Swap</button>
69
+ </div>
70
+ </div>
71
+ ) : null}
72
+ {featureSelection && featureSelection.length > 0 ? (
73
+ <div className={classes.headingSectionContainer}>
74
+ <div><h3 className={classes.headingText}>Selected Biomarkers:&nbsp;</h3></div>
75
+ {featureSelection?.map((featureName, i) => (
76
+ <div>
77
+ <h3 className={classes.headingText}>
78
+ {featureName}
79
+ {i < featureSelection.length - 1 ? (<span>,&nbsp;</span>) : null}
80
+ </h3>
81
+ </div>
82
+ ))}
83
+ </div>
84
+ ) : null}
85
+ </div>
86
+ );
87
+ }
@@ -180,6 +180,7 @@ export async function transformFeature({ queryClient }, node, targetModality) {
180
180
  queryFn: loadPathwayToGeneEdges,
181
181
  });
182
182
 
183
+ // TODO: support matching using ontology term (rather than requiring kgId)?
183
184
  const matchingEdges = pathwayGeneEdges.filter((/** @type {KgEdge} */ d) => d.source === node.kgId);
184
185
  const matchingGeneIds = matchingEdges.map((/** @type {KgEdge} */ d) => d.target);
185
186
  const matchingGenes = geneNodes.filter(d => matchingGeneIds.includes(d.kgId));
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
2
+ export { ComparativeHeadingSubscriber } from './ComparativeHeadingSubscriber.js';
2
3
  export {
3
4
  autocompleteFeature,
4
5
  transformFeature,
package/src/scmd-ui.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable no-unused-vars */
2
2
  import React, { useState } from 'react';
3
3
  import { Button, ButtonGroup, Grid, Tooltip } from '@material-ui/core';
4
- import { ConfirmatoryStepper } from './confirmatory-stepper.js';
4
+ import { ConfirmatoryStepper } from './stepper-confirmatory.js';
5
+ import { ExploratoryStepper } from './stepper-exploratory.js';
5
6
  import { useStyles } from './styles.js';
6
7
 
7
8
 
@@ -67,7 +68,22 @@ export function ScmdUi(props) {
67
68
  onFinish={onFinish}
68
69
  />
69
70
  ) : null}
70
- {mode === 'exploratory' ? <span>TODO</span> : null}
71
+ {mode === 'exploratory' ? (
72
+ <ExploratoryStepper
73
+ autocompleteNode={autocompleteNode}
74
+
75
+ currentModalityAgnosticSelection={currentModalityAgnosticSelection}
76
+ setCurrentModalityAgnosticSelection={setCurrentModalityAgnosticSelection}
77
+ currentModalitySpecificSelection={currentModalitySpecificSelection}
78
+ setCurrentModalitySpecificSelection={setCurrentModalitySpecificSelection}
79
+ currentStratificationSelection={currentStratificationSelection}
80
+ setCurrentStratificationSelection={setCurrentStratificationSelection}
81
+
82
+ getEdges={getEdges}
83
+ stratifications={stratifications}
84
+ onFinish={onFinish}
85
+ />
86
+ ) : null}
71
87
  </div>
72
88
  ) : null}
73
89
  </Grid>