From ff1e90ebceb7d366079dbabe5ff3cd424ed9da3e Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Thu, 12 Dec 2024 14:17:57 -0500 Subject: [PATCH] feat: added ordering of maps --- .../components/DisplayMetaAnalysisResults.tsx | 40 +++++- .../components/DisplayParsedNiMareFile.tsx | 123 ++++++++++++------ .../MetaAnalysisResultStatusAlert.tsx | 23 +++- .../NoMetaAnalysisResultDisplay.tsx | 6 +- 4 files changed, 137 insertions(+), 55 deletions(-) diff --git a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayMetaAnalysisResults.tsx b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayMetaAnalysisResults.tsx index eaa1a56f..f674703d 100644 --- a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayMetaAnalysisResults.tsx +++ b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayMetaAnalysisResults.tsx @@ -4,10 +4,10 @@ import NiiVueVisualizer from 'components/Visualizer/NiiVueVisualizer'; import { MetaAnalysisReturn, NeurovaultFile, ResultReturn } from 'neurosynth-compose-typescript-sdk'; import MetaAnalysisResultStatusAlert from './MetaAnalysisResultStatusAlert'; import useGetMetaAnalysisResultById from 'hooks/metaAnalyses/useGetMetaAnalysisResultById'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; import useGetNeurovaultImages, { INeurovault } from 'hooks/metaAnalyses/useGetNeurovault'; -import DisplayParsedNiMareFile from './DisplayParsedNiMareFile'; +import DisplayParsedNiMareFile, { NimareOutputs, parseNimareFileName } from './DisplayParsedNiMareFile'; import ImageIcon from '@mui/icons-material/Image'; const DisplayMetaAnalysisResults: React.FC<{ @@ -37,6 +37,36 @@ const DisplayMetaAnalysisResults: React.FC<{ setSelectedNeurovaultImage(neurovaultFiles[0]); }, [neurovaultFiles]); + const sortedNeurovaultFiles = useMemo(() => { + const orderMap = new Map(NimareOutputs.map((output, index) => [output.type, index])); + const sorted = neurovaultFiles?.sort((a, b) => { + const filenameA = parseNimareFileName(a.name); + const filenameB = parseNimareFileName(b.name); + + const longerFilename = filenameA.length > filenameB.length ? filenameA : filenameB; + for (let i = 0; i < longerFilename.length; i++) { + if (!filenameA[i]) return -1; + if (!filenameB[i]) return 1; + + const orderA = + orderMap.get(filenameA[i].isValueType ? filenameA[i].value : filenameA[i].key) ?? Infinity; + const orderB = + orderMap.get(filenameB[i].isValueType ? filenameB[i].value : filenameB[i].key) ?? Infinity; + if (orderA === orderB) { + if (filenameA[i].value === filenameB[i].value) { + continue; + } + return filenameB[i].value.localeCompare(filenameA[i].value); + } else { + return orderB - orderA; + } + } + return 0; + }); + return sorted?.reverse(); + // if (props.metaAnalysis?.specification) // check for meta analysis mkdachi2 + }, [neurovaultFiles]); + return ( - + - {(neurovaultFiles || []).map((neurovaultFile) => ( + {(sortedNeurovaultFiles || []).map((neurovaultFile) => ( setSelectedNeurovaultImage(neurovaultFile)} @@ -67,7 +97,7 @@ const DisplayMetaAnalysisResults: React.FC<{ borderBottomRightRadius: '8', }} > - + {selectedNeurovaultImage?.file ? ( diff --git a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayParsedNiMareFile.tsx b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayParsedNiMareFile.tsx index 87633fb4..c33c6f8e 100644 --- a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayParsedNiMareFile.tsx +++ b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayParsedNiMareFile.tsx @@ -2,57 +2,95 @@ import { HelpOutline } from '@mui/icons-material'; import { Box, Icon, Paper, Tooltip, Typography } from '@mui/material'; import { useMemo } from 'react'; -const nimareOutputs = { +// must turn this into an array to make it orderable +export const NimareOutputs = [ // possible value types - z: 'Z-statistic', - t: 'T-statistic', - p: 'p-value', - logp: 'Negative base-ten logarithm of p-value', - chi2: 'Chi-squared value', - prob: 'Probability value', - stat: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)', - est: 'Parameter estimate (IBMA only)', - se: 'Standard error of the parameter estimate (IBMA only)', - tau2: 'Estimated between-study variance (IBMA only)', - sigma2: 'Estimated within-study variance (IBMA only)', - label: 'Label map', + { type: 'z', isValueType: true, description: 'Z-statistic' }, + { type: 't', isValueType: true, description: 'T-statistic' }, + { type: 'p', isValueType: true, description: 'p-value' }, + { type: 'logp', isValueType: true, description: 'Negative base-ten logarithm of p-value' }, + { type: 'chi2', isValueType: true, description: 'Chi-squared value' }, + { type: 'prob', isValueType: true, description: 'Probability value' }, + { + type: 'stat', + isValueType: true, + description: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)', + }, + { type: 'est', isValueType: true, description: 'Parameter estimate (IBMA only)' }, + { type: 'se', isValueType: true, description: 'Standard error of the parameter estimate (IBMA only)' }, + { type: 'tau2', isValueType: true, description: 'Estimated between-study variance (IBMA only)' }, + { type: 'sigma2', isValueType: true, description: 'Estimated within-study variance (IBMA only)' }, + { type: 'label', isValueType: true, description: 'Label map' }, // methods of meta analysis - desc: 'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.', - level: 'Level of multiple comparisons correction. Either cluster or voxel.', - corr: 'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).', - method: 'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).', - diag: 'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).', - tab: 'Type of table. Either clust (clusters table) or counts (contribution table).', - tail: 'Sign of the tail for label maps. Either positive or negative.', -}; + { + type: 'desc', + isValueType: false, + description: + 'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.', + }, + { + type: 'level', + isValueType: false, + description: 'Level of multiple comparisons correction. Either cluster or voxel.', + }, + { + type: 'corr', + isValueType: false, + description: + 'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).', + }, + { + type: 'method', + isValueType: false, + description: + 'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).', + }, + { + type: 'diag', + isValueType: false, + description: + 'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).', + }, + { + type: 'tab', + isValueType: false, + description: 'Type of table. Either clust (clusters table) or counts (contribution table).', + }, + { type: 'tail', isValueType: false, description: 'Sign of the tail for label maps. Either positive or negative.' }, +]; -const parseSegment = (segment: string): { key: string; keyDesc: string; value: string } => { - const [key, value] = segment.split('-'); - if (value === undefined) { - // not a method - return { - key: 'type', - keyDesc: 'The type of data in the map.', - value: `${nimareOutputs[key as keyof typeof nimareOutputs]}`, - }; - } else { - return { - key: key, - keyDesc: nimareOutputs[key as keyof typeof nimareOutputs], - value: value, - }; - } +export const parseNimareFileName = (fileName: string | undefined) => { + if (!fileName) return []; + const segments = fileName.replace('.nii.gz', '').split('_'); + return segments.map((segment) => { + const [key, value] = segment.split('-'); + const nimareOutput = NimareOutputs.find((output) => output.type === key); + if (value === undefined) { + // not a method + return { + key: 'type', + isValueType: nimareOutput?.isValueType || false, + keyDesc: 'The type of data in the map.', + value: nimareOutput?.type || '', + }; + } else { + return { + key: key, + isValueType: nimareOutput?.isValueType || false, + keyDesc: nimareOutput?.description || '', + value: value, + }; + } + }); }; const DisplayParsedNiMareFile: React.FC<{ nimareFileName: string | undefined }> = (props) => { const fileNameSegments = useMemo(() => { - if (!props.nimareFileName) return []; - const segments = props.nimareFileName.replace('.nii.gz', '').split('_'); - return segments.map(parseSegment); + return parseNimareFileName(props.nimareFileName); }, [props.nimareFileName]); return ( - + {fileNameSegments.map((segment) => ( variant="elevation" display="flex" flexDirection="column" - width="30%" + width="29%" + marginRight="2%" marginBottom="0.5rem" padding="0.5rem" elevation={1} diff --git a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/MetaAnalysisResultStatusAlert.tsx b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/MetaAnalysisResultStatusAlert.tsx index a323e4b5..4d805726 100644 --- a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/MetaAnalysisResultStatusAlert.tsx +++ b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/MetaAnalysisResultStatusAlert.tsx @@ -1,7 +1,7 @@ import { Alert } from '@mui/material'; import { getResultStatus } from 'helpers/MetaAnalysis.helpers'; import { MetaAnalysisReturn, ResultReturn } from 'neurosynth-compose-typescript-sdk'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; const localStorageResultAlertKey = 'hide-meta-analysis-result-alert'; @@ -9,13 +9,22 @@ const MetaAnalysisResultStatusAlert: React.FC<{ metaAnalysis?: MetaAnalysisReturn; metaAnalysisResult?: ResultReturn; }> = ({ metaAnalysis, metaAnalysisResult }) => { - const shouldHide = !!localStorage.getItem(`${localStorageResultAlertKey}-${metaAnalysis?.id}`); - const [hideAlert, setHideAlert] = useState(shouldHide); - const resultStatus = useMemo(() => { return getResultStatus(metaAnalysis, metaAnalysisResult); }, [metaAnalysis, metaAnalysisResult]); + const [hideAlert, setHideAlert] = useState(); + + useEffect(() => { + if (!resultStatus || !metaAnalysis?.id) return; + const shouldHide = !!localStorage.getItem( + `${localStorageResultAlertKey}-${resultStatus.severity}-${metaAnalysis?.id}` + ); + setHideAlert(shouldHide); + }, [metaAnalysis?.id, resultStatus]); + + if (hideAlert === undefined) return null; + return ( <> {!hideAlert && ( @@ -24,11 +33,13 @@ const MetaAnalysisResultStatusAlert: React.FC<{ color={resultStatus.color} onClose={() => { setHideAlert(true); - localStorage.setItem(`${localStorageResultAlertKey}-${metaAnalysis?.id}`, 'true'); + localStorage.setItem( + `${localStorageResultAlertKey}-${resultStatus?.severity}-${metaAnalysis?.id}`, + 'true' + ); }} sx={{ padding: '4px 10px', - marginBottom: '1rem', alignItems: 'center', }} > diff --git a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/NoMetaAnalysisResultDisplay.tsx b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/NoMetaAnalysisResultDisplay.tsx index d3f0eb4a..997970c2 100644 --- a/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/NoMetaAnalysisResultDisplay.tsx +++ b/compose/neurosynth-frontend/src/pages/MetaAnalysis/components/NoMetaAnalysisResultDisplay.tsx @@ -3,7 +3,7 @@ import NeurosynthAccordion from 'components/NeurosynthAccordion/NeurosynthAccord import DisplayMetaAnalysisSpecification from './MetaAnalysisSpecification'; import EditSpecificationDialog from './EditSpecificationDialog'; import MetaAnalysisResultStatusAlert from './MetaAnalysisResultStatusAlert'; -import { useUserCanEdit } from 'hooks'; +import { useGetMetaAnalysisById, useUserCanEdit } from 'hooks'; import { useProjectUser } from 'pages/Project/store/ProjectStore'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -14,17 +14,19 @@ function NoMetaAnalysisResultDisplay() { projectId: string; metaAnalysisId: string; }>(); + const { data: metaAnalysis } = useGetMetaAnalysisById(metaAnalysisId); const projectUser = useProjectUser(); const editsAllowed = useUserCanEdit(projectUser || undefined); const [editSpecificationDialogIsOpen, setEditSpecificationDialogIsOpen] = useState(false); return ( <> - +