@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.js +32224 -0
  3. package/dist-tsc/BiomarkerSelectSubscriber.d.ts +14 -0
  4. package/dist-tsc/BiomarkerSelectSubscriber.d.ts.map +1 -0
  5. package/dist-tsc/BiomarkerSelectSubscriber.js +53 -0
  6. package/dist-tsc/cart.d.ts +2 -0
  7. package/dist-tsc/cart.d.ts.map +1 -0
  8. package/dist-tsc/cart.js +27 -0
  9. package/dist-tsc/confirmatory-stepper.d.ts +2 -0
  10. package/dist-tsc/confirmatory-stepper.d.ts.map +1 -0
  11. package/dist-tsc/confirmatory-stepper.js +65 -0
  12. package/dist-tsc/default-async-functions.d.ts +26 -0
  13. package/dist-tsc/default-async-functions.d.ts.map +1 -0
  14. package/dist-tsc/default-async-functions.js +168 -0
  15. package/dist-tsc/index.d.ts +3 -0
  16. package/dist-tsc/index.d.ts.map +1 -0
  17. package/dist-tsc/index.js +2 -0
  18. package/dist-tsc/missing-types.d.ts +3 -0
  19. package/dist-tsc/missing-types.d.ts.map +1 -0
  20. package/dist-tsc/missing-types.js +1 -0
  21. package/dist-tsc/scmd-ui.d.ts +2 -0
  22. package/dist-tsc/scmd-ui.d.ts.map +1 -0
  23. package/dist-tsc/scmd-ui.js +15 -0
  24. package/dist-tsc/select-agnostic.d.ts +2 -0
  25. package/dist-tsc/select-agnostic.d.ts.map +1 -0
  26. package/dist-tsc/select-agnostic.js +66 -0
  27. package/dist-tsc/select-specific.d.ts +2 -0
  28. package/dist-tsc/select-specific.d.ts.map +1 -0
  29. package/dist-tsc/select-specific.js +61 -0
  30. package/dist-tsc/select-stratification.d.ts +2 -0
  31. package/dist-tsc/select-stratification.d.ts.map +1 -0
  32. package/dist-tsc/select-stratification.js +18 -0
  33. package/dist-tsc/styles.d.ts +2 -0
  34. package/dist-tsc/styles.d.ts.map +1 -0
  35. package/dist-tsc/styles.js +32 -0
  36. package/package.json +52 -0
  37. package/src/BiomarkerSelectSubscriber.js +107 -0
  38. package/src/cart.js +95 -0
  39. package/src/confirmatory-stepper.js +209 -0
  40. package/src/default-async-functions.js +187 -0
  41. package/src/index.js +5 -0
  42. package/src/missing-types.ts +2 -0
  43. package/src/scmd-ui.js +75 -0
  44. package/src/select-agnostic.js +172 -0
  45. package/src/select-specific.js +111 -0
  46. package/src/select-stratification.js +90 -0
  47. 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,2 @@
1
+ export function ConfirmatoryCart(props: any): JSX.Element;
2
+ //# sourceMappingURL=cart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../src/cart.js"],"names":[],"mappings":"AAMA,0DAwFC"}
@@ -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,2 @@
1
+ export function ConfirmatoryStepper(props: any): JSX.Element;
2
+ //# sourceMappingURL=confirmatory-stepper.d.ts.map
@@ -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,3 @@
1
+ export { BiomarkerSelectSubscriber } from "./BiomarkerSelectSubscriber.js";
2
+ export { autocompleteFeature, transformFeature } from "./default-async-functions.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { BiomarkerSelectSubscriber } from './BiomarkerSelectSubscriber.js';
2
+ export { autocompleteFeature, transformFeature, } from './default-async-functions.js';
@@ -0,0 +1,3 @@
1
+ declare module 'd3-dsv';
2
+ declare module 'fuse.js/basic';
3
+ //# sourceMappingURL=missing-types.d.ts.map
@@ -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,2 @@
1
+ export function ScmdUi(props: any): JSX.Element;
2
+ //# sourceMappingURL=scmd-ui.d.ts.map
@@ -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,2 @@
1
+ export function SelectAgnostic(props: any): JSX.Element;
2
+ //# sourceMappingURL=select-agnostic.d.ts.map
@@ -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,2 @@
1
+ export function SelectSpecific(props: any): JSX.Element;
2
+ //# sourceMappingURL=select-specific.d.ts.map
@@ -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,2 @@
1
+ export function SelectStratification(props: any): JSX.Element;
2
+ //# sourceMappingURL=select-stratification.d.ts.map
@@ -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,2 @@
1
+ export const useStyles: (props?: any) => import("@material-ui/core/styles/withStyles").ClassNameMap<"header" | "fullWidthBox" | "selectButton" | "accordion" | "accordionDetails" | "cartUl" | "selectInput" | "searchInput">;
2
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.js"],"names":[],"mappings":"AAEA,8NA8BI"}