diff --git a/client/src/components/MetaDataViewerComponents/CustomTreeItem.js b/client/src/components/MetaDataViewerComponents/CustomTreeItem.js index 8d0a325..114a5a4 100644 --- a/client/src/components/MetaDataViewerComponents/CustomTreeItem.js +++ b/client/src/components/MetaDataViewerComponents/CustomTreeItem.js @@ -19,11 +19,10 @@ const CustomTreeItem = React.forwardRef(function MyTreeItem(props, ref) { const [expanded, setExpanded] = useState(false); const[hovered, setHovered] = useState(false); const activeActivityMaps = useSelector(state => state.viewer.activityMaps); - const activityMapsMetadata = useSelector(state => state.model.ActivityMaps); const { showRightSideContent } = props const dispatch = useDispatch(); const activeAtlas = useSelector(state => state.viewer.atlas); - + const activityMapsMetadata = useSelector(state => state.model.ActivityMaps); const handleMouseEnter = () => { setHovered(true); }; @@ -40,7 +39,7 @@ const CustomTreeItem = React.forwardRef(function MyTreeItem(props, ref) { e.preventDefault(); dispatch(fetchAndSetExperimentAndAtlas(experiment.itemId, activeAtlas.id)) } - + return ( , expandIcon: () => , diff --git a/client/src/components/MetaDataViewerComponents/Experiments.js b/client/src/components/MetaDataViewerComponents/Experiments.js index 33c0e02..0f0b6bb 100644 --- a/client/src/components/MetaDataViewerComponents/Experiments.js +++ b/client/src/components/MetaDataViewerComponents/Experiments.js @@ -1,25 +1,31 @@ -import React, {useState} from "react"; -import { Stack, Typography} from "@mui/material"; +import React, { useState, useEffect } from "react"; +import { Stack, Typography } from "@mui/material"; import variables from "../../theme/variables"; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; import { useSelector} from "react-redux"; import ExperienceDetailsDialog from "./ExperienceDetailsDialog"; import CustomTreeItem from "./CustomTreeItem"; +const { gray300, gray25 } = variables; -const { gray300, gray25 } = variables const Experiments = () => { const currentExperiment = useSelector(state => state.currentExperiment); const experimentsActivityMaps = useSelector(state => state.model.ExperimentsActivityMap); - const activityMapsMetadata = useSelector(state => state.model.ActivityMaps); const experimentAtlas = useSelector(state => state.model.ExperimentsAtlas); const activeAtlas = useSelector(state => state.viewer.atlas); - const [openDialogDetails, setOpenDialogDetails] = React.useState(false); const ExperimentsMetadata = useSelector(state => state.model.ExperimentsMetadata); - const [selectedExperiment, setSelectedExperiment] = useState(currentExperiment) + const [openDialogDetails, setOpenDialogDetails] = useState(false); + const [selectedExperiment, setSelectedExperiment] = useState(currentExperiment); - const handleClickOpenDialogDetails = (event, experiment) => { - setSelectedExperiment(experiment.itemId) + useEffect(() => { + return () => { + // Cleanup function to clear selectedExperiment + setSelectedExperiment(null); + }; + }, []); + + const handleClickOpenDialogDetails = (event, experiment) => { + setSelectedExperiment(experiment.itemId); event.stopPropagation(); setOpenDialogDetails(true); }; @@ -28,13 +34,13 @@ const Experiments = () => { setOpenDialogDetails(false); }; - const orderedExperiments = [currentExperiment?.id, ...Object.keys(experimentsActivityMaps) .filter(experiment => experiment !== currentExperiment?.id)].filter(row => experimentAtlas[row].includes(activeAtlas.id) && currentExperiment.id !== row); const experimentsList = orderedExperiments.map((experimentName, index) => { const experimentActivityMaps = experimentsActivityMaps[experimentName] || []; + return { id: experimentName, label: experimentName, @@ -43,20 +49,22 @@ const Experiments = () => { label: 'Activity Maps', children: experimentActivityMaps.map(activityMapID => ({ id: activityMapID, - label:activityMapsMetadata[activityMapID]?.name + label:activityMapID })), }] - } - }) - - return <> - - These are the experiments that share the same currently loaded atlas. You may activate the statistical maps from these experiments to the viewer. - Other experiments associated with the loaded atlas - }} /> - - - -} + }; + }); + + return ( + <> + + These are the experiments that share the same currently loaded atlas. You may activate the statistical maps from these experiments to the viewer. + Other experiments associated with the loaded atlas + }} /> + + + + ); +}; -export default Experiments \ No newline at end of file +export default Experiments; diff --git a/client/src/components/MetaDataViewerComponents/Images.js b/client/src/components/MetaDataViewerComponents/Images.js index c8289d9..3102ecc 100644 --- a/client/src/components/MetaDataViewerComponents/Images.js +++ b/client/src/components/MetaDataViewerComponents/Images.js @@ -1,78 +1,70 @@ -import React from "react"; -import { - Divider, Stack, Typography -} from "@mui/material"; -import { useSelector } from "react-redux"; +import React, {useEffect, useState} from "react"; +import {Divider, Stack, Typography} from "@mui/material"; +import {useSelector} from "react-redux"; import variables from "../../theme/variables"; import {RichTreeView} from "@mui/x-tree-view/RichTreeView"; import CustomTreeItem from "./CustomTreeItem"; +import { + BuildHierarchyTree, + DoDataPreprocessing, + filterDictByKeys, + getCurrentExperimentActivityMaps, GetUniqueHierarchyRoots +} from "../../helpers/ImagesTreeViewHelpers"; const { gray300, gray25} = variables; - -const filterDictByKeys = (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key))); - const Images = () => { const activeAtlas = useSelector(state => state.viewer.atlas); const activityMapsMetadata = useSelector(state => state.model.ActivityMaps); const atlasActivityMaps = useSelector( state => state.model.AtlasActivityMap ); const currentExperiment = useSelector(state => state.currentExperiment); const ExperimentsActivityMap = useSelector(state => state.model.ExperimentsActivityMap); + const [treeData, setTreeData] = useState([]); - const experimentsIds = Object.keys(ExperimentsActivityMap) - const groupByHierarchy = (activityMaps, experimentId) => { - const grouped = {}; - - Object.entries(activityMaps).forEach(([key, value]) => { - let hierarchy = value?.hierarchy?.filter((exp) => !experimentsIds.includes(exp)) - - if ( value.experiment && experimentId ) { - hierarchy?.unshift(experimentId); - } - - const hierarchyKey = hierarchy?.join(', '); - - if (!grouped[hierarchyKey]) { - grouped[hierarchyKey] = []; - } - - // Add the entire entry, including the key if necessary - grouped[hierarchyKey].push({...value, key}); - }); - - return grouped; - } const getData = () => { const atlas = activeAtlas; const selectedAtlasActivityMaps = atlas?.id && atlasActivityMaps[atlas.id] ? atlasActivityMaps[atlas.id] : []; - const filteredActivityMaps = filterDictByKeys(activityMapsMetadata, selectedAtlasActivityMaps); - const groupByHierarchyActivityMaps = groupByHierarchy(filteredActivityMaps, currentExperiment?.id); - const buildTree = (levels, maps, parentId) => { - if (levels.length === 0) return null; - - const [currentLevel, ...remainingLevels] = levels; + const experimentActivityMaps = ExperimentsActivityMap[currentExperiment.id]; + const currentExperimentActivityMaps = getCurrentExperimentActivityMaps( + activityMapsMetadata, + experimentActivityMaps + ); + const filteredActivityMaps = filterDictByKeys( + currentExperimentActivityMaps, + selectedAtlasActivityMaps + ); + const processedFilteredActivityMaps = DoDataPreprocessing(filteredActivityMaps, currentExperiment?.id); + const hierarchyRoots = GetUniqueHierarchyRoots(processedFilteredActivityMaps); + // build the tree + let finalTree = [] + for(let hierarchyRoot of hierarchyRoots){ + // Filter the data for the current hierarchy root which represents the root of the hierarchy tree + const hierarchyTreeAllData = Object.values(processedFilteredActivityMaps).filter(obj => obj.hierarchy[0] === hierarchyRoot) - const node = { - id: `${currentLevel}-${parentId}`, - label: currentLevel, - children: [] - }; + // Get the max level of the hierarchy tree of the current hierarchy root + const hierarchyTreeMaxLevel = hierarchyTreeAllData.reduce((acc, obj) => Math.max(acc, obj.hierarchy.length), 0) - if (remainingLevels.length === 0) { - node.children = maps.map((activityMap) => ({ - id: activityMap.key, - label: activityMap.name - })); - } else { - node.children.push(buildTree(remainingLevels, maps, node.id)); - } - return node; - }; + // Get max hierarchy array for the current hierarchy root + const deepestHierarchyArray = hierarchyTreeAllData.reduce((acc, obj) => { + if(obj.hierarchy.length > acc.length){ + return obj.hierarchy + } + return acc + }, []) + // Build the hierarchy tree for the current hierarchy root + finalTree.push(...BuildHierarchyTree(hierarchyTreeAllData, hierarchyTreeMaxLevel, deepestHierarchyArray, 0)) + } + return finalTree + }; + + useEffect(() => { + const newData = getData(); + setTreeData(newData); - return Object.entries(groupByHierarchyActivityMaps).map(([activityMapKey, maps], index) => { - const levels = activityMapKey.split(','); - return buildTree(levels, maps, index); - }); - } + return () => { + // Cleanup function to empty the tree data + setTreeData([]); + }; + }, [activeAtlas, activityMapsMetadata, atlasActivityMaps, currentExperiment, ExperimentsActivityMap]); return @@ -80,7 +72,7 @@ const Images = () => { {activeAtlas.id} - }} /> + }} /> } diff --git a/client/src/helpers/ImagesTreeViewHelpers.js b/client/src/helpers/ImagesTreeViewHelpers.js new file mode 100644 index 0000000..14e01bb --- /dev/null +++ b/client/src/helpers/ImagesTreeViewHelpers.js @@ -0,0 +1,88 @@ + +export const filterDictByKeys = (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key))); +export const getCurrentExperimentActivityMaps = (activityMapsMetadata, experimentActivityMaps) => Object.keys(activityMapsMetadata) + .filter(key => experimentActivityMaps.includes(key)) + .reduce((obj, key) => { + obj[key] = activityMapsMetadata[key]; + return obj; + }, {}); + +export const DoDataPreprocessing = (filteredActivityMaps, currentExperimentName) => { + /** + * Data preprocessing + * If the experiment key or hierarchy key are not exists then add hierarchy array with one element "Experiment Name" + * Target is : we have the hierarchy array for each object + */ + for(const obj of Object.values(filteredActivityMaps)){ + // if experiment key exists + if(obj.experiment !== undefined){ + if(obj.experiment === true){ + // experiment is true + if(obj.hierarchy === undefined){ // no hierarchy key + obj.hierarchy = [currentExperimentName] + }else{ + // hierarchy key exists + obj.hierarchy =[currentExperimentName,...obj.hierarchy] + } + }else { + // experiment is false + obj.hierarchy = ['others'] + } + }else { + // experiment key not exists + if(obj.hierarchy === undefined){ + obj.hierarchy =[currentExperimentName] + }else { + obj.hierarchy =[currentExperimentName,...obj.hierarchy] + } + } + } + + + /** + * Set the level key for each object and the level + * Convert map to array + */ + for(const [key, value] of Object.entries(filteredActivityMaps)) { + value.key = key + } + + return filteredActivityMaps +} + +export const GetUniqueHierarchyRoots = (processedFilteredActivityMaps) => { + // Get unique roots of the tree + const hierarchyRoots= new Set() + for(const obj of Object.values(processedFilteredActivityMaps)){ + if(obj.hierarchy.length > 0){ + hierarchyRoots.add(obj.hierarchy[0]) + } + } + return Array.from(hierarchyRoots) +} + +export const BuildHierarchyTree = (hierarchyTreeAllData,hierarchyTreeMaxLevel, deepestHierarchyArray, hierarchyArrayCurrentIndex) => { + const tree = [] + + // stop condition + if(hierarchyArrayCurrentIndex === hierarchyTreeMaxLevel){ + return tree + } + + // get children of the current hierarchy level + const levelChildren = hierarchyTreeAllData.filter(obj => obj.hierarchy.length === hierarchyArrayCurrentIndex + 1).map(obj => { + return { + id: obj.key, + label: obj.key + } + }) + + // create the new level of the tree + tree.push({ + id: deepestHierarchyArray[hierarchyArrayCurrentIndex], + label: deepestHierarchyArray[hierarchyArrayCurrentIndex], + children: [...levelChildren, ...BuildHierarchyTree(hierarchyTreeAllData, hierarchyTreeMaxLevel, deepestHierarchyArray, hierarchyArrayCurrentIndex+1)] + }) + + return tree +} diff --git a/server/main.py b/server/main.py index 08f38ce..95e7262 100644 --- a/server/main.py +++ b/server/main.py @@ -111,7 +111,7 @@ def metadata(): @app.route('/cfos-visualizer-stanford-dev/index.json') def index(): - return download_as_json("index_95v3.json") + return download_as_json("index.json") @app.errorhandler(404) def page_not_found(error):