Skip to content

Commit

Permalink
feat: added second dropdown for group comparison estimators and optio…
Browse files Browse the repository at this point in the history
…ns to select reference dataset
  • Loading branch information
nicoalee committed Nov 17, 2023
1 parent abc2a18 commit 9c7a1f4
Show file tree
Hide file tree
Showing 20 changed files with 360 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { Box, Step, StepLabel, Stepper } from '@mui/material';
import BaseDialog, { IDialog } from '../BaseDialog';
import { useEffect, useState } from 'react';
import { ENavigationButton } from 'components/Buttons/NavigationButtons/NavigationButtons';
import CreateMetaAnalysisSpecificationSelectionStep from './CreateMetaAnalysisSpecificationSelectionStep/CreateMetaAnalysisSpecificationSelectionStep';
import { EPropertyType } from 'components/EditMetadata';
import CreateMetaAnalysisSpecificationAlgorithmStep from './CreateMetaAnalysisSpecificationAlgorithmStep/CreateMetaAnalysisSpecificationAlgorithmStep';
import { IDynamicValueType } from 'components/MetaAnalysisConfigComponents';
import CreateMetaAnalysisSpecificationReview from './CreateMetaAnalysisSpecificationReview/CreateMetaAnalysisSpecificationReview';
import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete';
import CreateMetaAnalysisSpecificationDetailsStep from './CreateMetaAnalysisSpecificationDetailsStep/CreateMetaAnalysisSpecificationDetailsStep';
import { useProjectName } from 'pages/Projects/ProjectPage/ProjectStore';
import { useEffect, useState } from 'react';
import BaseDialog, { IDialog } from '../BaseDialog';
import CreateMetaAnalysisSpecificationAlgorithmStep from './CreateMetaAnalysisSpecificationAlgorithmStep/CreateMetaAnalysisSpecificationAlgorithmStep';
import CreateMetaAnalysisSpecificationDetailsStep from './CreateMetaAnalysisSpecificationDetailsStep/CreateMetaAnalysisSpecificationDetailsStep';
import {
IAlgorithmSelection,
IAnalysesSelection,
} from './CreateMetaAnalysisSpecificationDialogBase.types';
import { AnnotationNoteValue } from 'components/HotTables/HotTables.types';
import CreateMetaAnalysisSpecificationReview from './CreateMetaAnalysisSpecificationReview/CreateMetaAnalysisSpecificationReview';
import CreateMetaAnalysisSpecificationSelectionStep from './CreateMetaAnalysisSpecificationSelectionStep/CreateMetaAnalysisSpecificationSelectionStep';

const CreateMetaAnalysisSpecificationDialogBase: React.FC<IDialog> = (props) => {
const projectName = useProjectName();
Expand All @@ -24,7 +22,12 @@ const CreateMetaAnalysisSpecificationDialogBase: React.FC<IDialog> = (props) =>
name: `${projectName} Meta Analysis`,
description: `this is a meta-analysis for ${projectName}`,
});
const [selection, setSelection] = useState<IAnalysesSelection>();
const [selection, setSelection] = useState<IAnalysesSelection>({
selectionKey: undefined,
selectionValue: undefined,
type: undefined,
referenceDataset: undefined,
});
const [algorithm, setAlgorithm] = useState<IAlgorithmSelection>({
estimator: null,
estimatorArgs: {},
Expand All @@ -48,7 +51,12 @@ const CreateMetaAnalysisSpecificationDialogBase: React.FC<IDialog> = (props) =>
corrector: null,
correctorArgs: {},
});
setSelection(undefined);
setSelection({
selectionKey: undefined,
selectionValue: undefined,
type: undefined,
referenceDataset: undefined,
});
};

const handleNavigate = (button: ENavigationButton) => {
Expand All @@ -69,15 +77,9 @@ const CreateMetaAnalysisSpecificationDialogBase: React.FC<IDialog> = (props) =>
});
};

const handleChooseSelection = (
selectionKey: string,
type: EPropertyType,
selectionValue?: AnnotationNoteValue
) => {
const handleChooseSelection = (selection: IAnalysesSelection) => {
setSelection({
selectionKey,
type,
selectionValue: selectionValue ? selectionValue : undefined,
...selection,
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { IDynamicValueType } from 'components/MetaAnalysisConfigComponents';
import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete';

export interface IAnalysesSelection {
selectionKey: string;
type: EPropertyType;
selectionValue?: AnnotationNoteValue;
selectionKey: string | undefined;
type: EPropertyType | undefined;
selectionValue: AnnotationNoteValue | undefined; // defined if type is not a boolean
referenceDataset?: string; // defined if multi group analysis is used (i.e. ALESubtraction or MKDAChi2)
}

export interface IAlgorithmSelection {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Button } from '@mui/material';
import { Box, Button, Typography } from '@mui/material';
import LoadingButton from 'components/Buttons/LoadingButton/LoadingButton';
import { ENavigationButton } from 'components/Buttons/NavigationButtons/NavigationButtons';
import { IDynamicValueType } from 'components/MetaAnalysisConfigComponents';
Expand All @@ -23,7 +23,7 @@ import { getFilteredAnnotationNotes } from '../CreateMetaAnalysisSpecificationSe
const CreateMetaAnalysisSpecificationReview: React.FC<{
onNavigate: (button: ENavigationButton) => void;
onClose: () => void;
selection: IAnalysesSelection | undefined;
selection: IAnalysesSelection;
algorithm: {
estimator: IAutocompleteObject | null;
estimatorArgs: IDynamicValueType;
Expand Down Expand Up @@ -110,7 +110,15 @@ const CreateMetaAnalysisSpecificationReview: React.FC<{
title="Selection"
value={selectionText}
caption={numSelectedAnnotationsText}
></MetaAnalysisSummaryRow>
>
{props.selection.referenceDataset && (
<>
<Typography sx={{ marginTop: '1rem', color: 'gray' }}>
Reference Dataset: {props.selection.referenceDataset}
</Typography>
</>
)}
</MetaAnalysisSummaryRow>
<MetaAnalysisSummaryRow
title="Estimator"
value={props.algorithm.estimator?.label || ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,31 @@ import {
useProjectExtractionStudysetId,
} from 'pages/Projects/ProjectPage/ProjectStore';
import { useState } from 'react';
import SelectAnalysesComponent from './SelectAnalysesComponent/SelectAnalysesComponent';
import SelectAnalysesSummaryComponent from './SelectAnalysesComponent/SelectAnalysesSummaryComponent';
import {
IAlgorithmSelection,
IAnalysesSelection,
} from '../CreateMetaAnalysisSpecificationDialogBase.types';
import { AnnotationNoteValue } from 'components/HotTables/HotTables.types';
import SelectAnalysesComponent from './SelectAnalysesComponent/SelectAnalysesComponent';
import { isMultiGroupAlgorithm } from './SelectAnalysesComponent/SelectAnalysesComponent.helpers';
import SelectAnalysesSummaryComponent from './SelectAnalysesComponent/SelectAnalysesSummaryComponent';

const CreateMetaAnalysisSpecificationSelectionStep: React.FC<{
onChooseSelection: (
selectionKey: string,
type: EPropertyType,
selectionValue?: AnnotationNoteValue
) => void;
onChooseSelection: (selection: IAnalysesSelection) => void;
onNavigate: (button: ENavigationButton) => void;
selection: IAnalysesSelection | undefined;
selection: IAnalysesSelection;
algorithm: IAlgorithmSelection;
}> = (props) => {
const annotationId = useProjectExtractionAnnotationId();
const studysetId = useProjectExtractionStudysetId();
const [selectedValue, setSelectedValue] = useState<IAnalysesSelection | undefined>(
props.selection
);
const [selectedValue, setSelectedValue] = useState<IAnalysesSelection>(props.selection);

const isMultiGroup = isMultiGroupAlgorithm(props.algorithm?.estimator);

const handleNavigate = (button: ENavigationButton) => {
if (selectedValue?.selectionKey && selectedValue?.type !== EPropertyType.NONE)
props.onChooseSelection(
selectedValue.selectionKey,
selectedValue.type,
selectedValue.selectionValue
);
props.onChooseSelection({
...selectedValue,
});
props.onNavigate(button);
};

Expand All @@ -57,7 +51,7 @@ const CreateMetaAnalysisSpecificationSelectionStep: React.FC<{
<SelectAnalysesComponent
selectedValue={selectedValue}
onSelectValue={(val) => setSelectedValue(val)}
annotationdId={annotationId || ''}
annotationId={annotationId || ''}
algorithm={props.algorithm}
/>

Expand All @@ -79,7 +73,8 @@ const CreateMetaAnalysisSpecificationSelectionStep: React.FC<{
sx={{ width: '200px' }}
disabled={
!selectedValue?.selectionKey ||
selectedValue?.selectionValue === undefined
selectedValue?.selectionValue === undefined ||
(isMultiGroup && !selectedValue?.referenceDataset) // needs ref dataset for multigroup algos
}
onClick={() => handleNavigate(ENavigationButton.NEXT)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AnnotationNoteValue } from 'components/HotTables/HotTables.types';
import { NoteCollectionReturn } from 'neurostore-typescript-sdk';
import { IAnalysesSelection } from '../../CreateMetaAnalysisSpecificationDialogBase.types';
import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete';
import { DEFAULT_REFERENCE_DATASETS } from './SelectAnalysesComponent.types';

// TODO: instead of hardcoding this, we should add a property to the meta-analysis spec with something like: "multi-group" or something similar
export const MULTIGROUP_ALGORITHMS = ['MKDAChi2', 'ALESubtraction'];
Expand Down Expand Up @@ -87,3 +88,9 @@ export const annotationNotesToTableFormatHelper = (

return tableFormat;
};

export const selectedReferenceDatasetIsDefault = (selectedReferenceDataset: string | undefined) => {
if (!selectedReferenceDataset) return false;

return DEFAULT_REFERENCE_DATASETS.some((x) => x.label === selectedReferenceDataset);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,63 +11,96 @@ import {
} from '../../CreateMetaAnalysisSpecificationDialogBase.types';
import SelectAnalysesComponentTable from './SelectAnalysesComponentTable';
import SelectAnalysesStringValue from './SelectAnalysesStringValue';
import { isMultiGroupAlgorithm } from './SelectAnalysesComponent.helpers';
import {
isMultiGroupAlgorithm,
selectedReferenceDatasetIsDefault,
} from './SelectAnalysesComponent.helpers';
import SelectAnalysesMultiGroupComponent from './SelectAnalysesMultiGroupComponent';
import { DEFAULT_REFERENCE_DATASETS } from './SelectAnalysesComponent.types';

const SelectAnalysesComponent: React.FC<{
annotationdId: string;
selectedValue: IAnalysesSelection | undefined;
onSelectValue: (value: IAnalysesSelection | undefined) => void;
annotationId: string;
selectedValue: IAnalysesSelection;
onSelectValue: (value: IAnalysesSelection) => void;
algorithm: IAlgorithmSelection;
}> = (props) => {
const { annotationdId, selectedValue, onSelectValue, algorithm } = props;
const { data: annotation } = useGetAnnotationById(annotationdId);
const { annotationId, selectedValue, onSelectValue, algorithm } = props;
const { data: annotation } = useGetAnnotationById(annotationId);

// we need some notion of "touched" in order to know if the input is being seen for the first time (so we can set some default value)
// versus the input being cleared (in which case we dont do anything)
const selectionOccurred = useRef<boolean>(false);

useEffect(() => {
if (selectedValue?.selectionKey) {
if (selectedValue.selectionKey) {
selectionOccurred.current = true;
return;
}

if (!selectionOccurred.current && 'included' in (annotation?.note_keys || {})) {
onSelectValue({
} else if (!selectionOccurred.current && 'included' in (annotation?.note_keys || {})) {
const initialVal: IAnalysesSelection = {
selectionKey: 'included',
type: EPropertyType.BOOLEAN,
selectionValue: true,
});
};

if (isMultiGroupAlgorithm(algorithm?.estimator)) {
initialVal.referenceDataset = DEFAULT_REFERENCE_DATASETS[0].label;
}

onSelectValue(initialVal);
selectionOccurred.current = true;
}
}, [selectedValue?.selectionKey, annotation, onSelectValue, selectionOccurred]);
}, [
selectedValue.selectionKey,
annotation,
onSelectValue,
selectionOccurred,
algorithm?.estimator,
]);

const options = useMemo(() => {
return Object.entries(annotation?.note_keys || {})
.map(([key, value]) => ({
selectionKey: key,
type: value as EPropertyType,
selectionValue: undefined,
referenceDataset: undefined,
}))
.filter((x) => x.type === EPropertyType.BOOLEAN || x.type === EPropertyType.STRING);
}, [annotation?.note_keys]);

const stringInclusionColSelected = selectedValue?.type === EPropertyType.STRING;
const showTable = stringInclusionColSelected
const showInclusionSummary = stringInclusionColSelected
? !!selectedValue?.selectionValue
: !!selectedValue?.selectionKey;
const isMultiGroup = isMultiGroupAlgorithm(algorithm?.estimator);
const showMultiGroup = isMultiGroupAlgorithm(algorithm?.estimator);

const handleSelectColumn = (selectedVal: IAnalysesSelection | null | undefined) => {
const update = selectedVal;
if (update) {
if (update.type === EPropertyType.BOOLEAN) {
update.selectionValue = true;
} else {
update.selectionValue = undefined;
}
const handleSelectColumn = (newVal: IAnalysesSelection | undefined) => {
if (newVal?.selectionKey === selectedValue.selectionKey) return; // we selected the same option that is already selected

const referenceDatasetIsNowInvalid = !selectedReferenceDatasetIsDefault(
selectedValue.referenceDataset
);
if (!newVal) {
onSelectValue({
selectionKey: undefined,
type: undefined,
selectionValue: undefined,
referenceDataset: referenceDatasetIsNowInvalid
? undefined
: selectedValue.referenceDataset,
});
return;
}
onSelectValue(update || undefined);

const update: IAnalysesSelection = {
selectionKey: newVal.selectionKey,
type: newVal.type,
selectionValue: newVal.type === EPropertyType.BOOLEAN ? true : undefined,
referenceDataset: referenceDatasetIsNowInvalid
? undefined
: selectedValue.referenceDataset,
};
onSelectValue(update);
};

return (
Expand All @@ -83,7 +116,7 @@ const SelectAnalysesComponent: React.FC<{
isOptionEqualToValue={(option, value) =>
option?.selectionKey === value?.selectionKey
}
value={selectedValue}
value={selectedValue?.selectionKey ? selectedValue : undefined}
size="medium"
inputPropsSx={{
color: NeurosynthTableStyles[selectedValue?.type || EPropertyType.NONE],
Expand All @@ -100,7 +133,7 @@ const SelectAnalysesComponent: React.FC<{
</ListItem>
)}
getOptionLabel={(option) => option?.selectionKey || ''}
onChange={(_event, newVal, _reason) => handleSelectColumn(newVal)}
onChange={(_event, newVal, _reason) => handleSelectColumn(newVal || undefined)}
options={options}
/>
{stringInclusionColSelected && (
Expand All @@ -112,13 +145,13 @@ const SelectAnalysesComponent: React.FC<{
}}
>
<SelectAnalysesStringValue
annotationId={annotationId}
selectedValue={selectedValue}
onSelectValue={(newVal) => onSelectValue(newVal)}
allNotes={annotation?.notes as NoteCollectionReturn[] | undefined}
/>
</Box>
)}
{showTable && (
{showInclusionSummary && (
<Box
sx={{
marginTop: '1rem',
Expand All @@ -130,7 +163,14 @@ const SelectAnalysesComponent: React.FC<{
/>
</Box>
)}
{isMultiGroup && <SelectAnalysesMultiGroupComponent algorithm={algorithm} />}
{showMultiGroup && (
<SelectAnalysesMultiGroupComponent
onSelectValue={(newVal) => onSelectValue(newVal)}
annotationId={annotationId}
selectedValue={selectedValue}
algorithm={algorithm}
/>
)}
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete';

export type IMultiGroupOption = IAutocompleteObject & {
type: string;
id: string;
};

export const DEFAULT_REFERENCE_DATASETS: IMultiGroupOption[] = [
{ label: 'Neurostore Dataset', description: '', type: 'Reference Dataset', id: 'neurostore' },
{ label: 'Neuroquery Dataset', description: '', type: 'Reference Dataset', id: 'neuroquery' },
{ label: 'Neurosynth Dataset', description: '', type: 'Reference Dataset', id: 'neurosynth' },
];
Loading

0 comments on commit 9c7a1f4

Please sign in to comment.