diff --git a/packages/libs/eda/src/lib/core/hooks/workspace.ts b/packages/libs/eda/src/lib/core/hooks/workspace.ts index 2589da46f9..5949bd0264 100755 --- a/packages/libs/eda/src/lib/core/hooks/workspace.ts +++ b/packages/libs/eda/src/lib/core/hooks/workspace.ts @@ -24,13 +24,20 @@ import { import { ComputeClient } from '../api/ComputeClient'; import { DownloadClient } from '../api'; import { Filter } from '../types/filter'; -import { mapStructure } from '@veupathdb/wdk-client/lib/Utils/TreeUtils'; +import { + mapStructure, + preorder, +} from '@veupathdb/wdk-client/lib/Utils/TreeUtils'; import { useFeaturedFieldsFromTree, useFieldTree, useFlattenedFields, } from '../components/variableTrees/hooks'; import { findFirstVariable } from '../../workspace/Utils'; +import { + DataElementConstraintRecord, + filterVariablesByConstraint, +} from '../utils/data-element-constraints'; /** Return the study identifier and a hierarchy of the study entities. */ export function useStudyMetadata(): StudyMetadata { @@ -262,3 +269,62 @@ export function useGetDefaultVariableDescriptor() { [entities, featuredFields, fieldTree] ); } + +export const timeSliderVariableConstraints: DataElementConstraintRecord[] = [ + { + overlayVariable: { + isRequired: true, + minNumVars: 1, + maxNumVars: 1, + // TODO: testing with SCORE S. mansoni Cluster Randomized Trial study + // however, this study does not have date variable, thus temporarily use below for test purpose + // i.e., additionally allowing 'integer' + allowedTypes: ['date', 'integer'], + // TODO: below two are correct ones + // allowedTypes: ['date'], + // isTemporal: true, + }, + }, +]; + +export function useGetDefaultTimeVariableDescriptor() { + const entities = useStudyEntities(); + // filter constraint for time slider inputVariables component + + return useCallback( + function getDefaultTimeVariableDescriptor() { + const temporalVariableTree = filterVariablesByConstraint( + entities[0], + timeSliderVariableConstraints[0]['overlayVariable'] + ); + + // take the first suitable variable from the filtered variable tree + + // first find the first entity with some variables that passed the filter + const defaultTimeSliderEntity: StudyEntity | undefined = Array.from( + preorder(temporalVariableTree, (entity) => entity.children ?? []) + ) + // not all `variables` are actually variables, so we filter to be sure + .filter( + (entity) => + entity.variables.filter((variable) => Variable.is(variable)) + .length > 0 + )[0]; + + // then take the first variable from it + const defaultTimeSliderVariable: Variable | undefined = + defaultTimeSliderEntity.variables.filter( + (variable): variable is Variable => Variable.is(variable) + )[0]; + + return defaultTimeSliderEntity != null && + defaultTimeSliderVariable != null + ? { + entityId: defaultTimeSliderEntity.id, + variableId: defaultTimeSliderVariable.id, + } + : undefined; + }, + [entities, timeSliderVariableConstraints] + ); +} diff --git a/packages/libs/eda/src/lib/map/analysis/DraggableTimeFilter.tsx b/packages/libs/eda/src/lib/map/analysis/DraggableTimeFilter.tsx index 5403bfaee2..e56afb082d 100755 --- a/packages/libs/eda/src/lib/map/analysis/DraggableTimeFilter.tsx +++ b/packages/libs/eda/src/lib/map/analysis/DraggableTimeFilter.tsx @@ -1,33 +1,21 @@ -import { useState, useMemo, useEffect, useCallback } from 'react'; +import { useMemo, useEffect, useCallback } from 'react'; import { DraggablePanel } from '@veupathdb/coreui/lib/components/containers'; import EzTimeFilter, { EZTimeFilterDataProp, } from '@veupathdb/components/lib/components/plotControls/EzTimeFilter'; import { InputVariables } from '../../core/components/visualizations/InputVariables'; -import { - VariablesByInputName, - DataElementConstraintRecord, - filterVariablesByConstraint, -} from '../../core/utils/data-element-constraints'; -import { AnalysisState, usePromise } from '../../core'; +import { VariablesByInputName } from '../../core/utils/data-element-constraints'; +import { timeSliderVariableConstraints, usePromise } from '../../core'; import { DateVariable, NumberVariable, StudyEntity, - Variable, } from '../../core/types/study'; import { VariableDescriptor } from '../../core/types/variable'; import { SubsettingClient } from '../../core/api'; import Spinner from '@veupathdb/components/lib/components/Spinner'; import { useFindEntityAndVariable, Filter } from '../../core'; -import { - DateRange, - NumberRange, -} from '@veupathdb/components/lib/types/general'; -import { DateRangeFilter, NumberRangeFilter } from '../../core/types/filter'; -import { Tooltip } from '@material-ui/core'; -import { preorder } from '@veupathdb/wdk-client/lib/Utils/TreeUtils'; import { zip } from 'lodash'; import { AppState } from './appState'; @@ -62,58 +50,6 @@ export default function DraggableTimeFilter({ active, // to do - add a toggle to enable/disable setActive, // the small filter and grey everything out }: Props) { - // filter constraint for time slider inputVariables component - const timeSliderVariableConstraints: DataElementConstraintRecord[] = [ - { - overlayVariable: { - isRequired: true, - minNumVars: 1, - maxNumVars: 1, - // TODO: testing with SCORE S. mansoni Cluster Randomized Trial study - // however, this study does not have date variable, thus temporarily use below for test purpose - // i.e., additionally allowing 'integer' - allowedTypes: ['date', 'integer'], - // TODO: below two are correct ones - // allowedTypes: ['date'], - // isTemporal: true, - }, - }, - ]; - - const temporalVariableTree = filterVariablesByConstraint( - entities[0], - timeSliderVariableConstraints[0]['overlayVariable'] - ); - - // take the first suitable variable from the filtered variable tree - - // first find the first entity with some variables that passed the filter - const defaultTimeSliderEntity: StudyEntity | undefined = Array.from( - preorder(temporalVariableTree, (entity) => entity.children ?? []) - ) - // not all `variables` are actually variables, so we filter to be sure - .filter( - (entity) => - entity.variables.filter((variable) => Variable.is(variable)).length > 0 - )[0]; - - // then take the first variable from it - const defaultTimeSliderVariable: Variable | undefined = - defaultTimeSliderEntity.variables.filter((variable): variable is Variable => - Variable.is(variable) - )[0]; - - // sorry, a useEffect for initialising the default variable - useEffect(() => { - if (variable == null && defaultTimeSliderVariable != null) { - setVariable({ - variableId: defaultTimeSliderVariable.id, - entityId: defaultTimeSliderEntity.id, - }); - } - }, [variable, defaultTimeSliderVariable]); - - // find variable metadata: use timeSliderVariable, not defaultTimeSlider ids const findEntityAndVariable = useFindEntityAndVariable(); const variableMetadata = findEntityAndVariable(variable); @@ -214,7 +150,7 @@ export default function DraggableTimeFilter({ }, [getTimeSliderData]); // if no variable in a study is suitable to time slider, do not show time slider - return defaultTimeSliderVariable != null ? ( + return variable != null ? ( ({ viewport: defaultViewport, mouseMode: 'default', activeMarkerConfigurationType: 'pie', + timeSliderVariable: defaultTimeVariable, markerConfigurations: [ { type: 'pie', @@ -182,11 +188,16 @@ export function useAppState(uiStateKey: string, analysisState: AnalysisState) { ) ); - if (missingMarkerConfigs.length > 0) { + const timeVariableIsMissing = appState.timeSliderVariable == null; + + if (missingMarkerConfigs.length > 0 || timeVariableIsMissing) { setVariableUISettings((prev) => ({ ...prev, [uiStateKey]: { ...appState, + ...(timeVariableIsMissing + ? { timeSliderVariable: defaultTimeVariable } + : {}), markerConfigurations: [ ...appState.markerConfigurations, ...missingMarkerConfigs, @@ -196,7 +207,14 @@ export function useAppState(uiStateKey: string, analysisState: AnalysisState) { } } } - }, [analysis, appState, setVariableUISettings, uiStateKey, defaultAppState]); + }, [ + analysis, + appState, + setVariableUISettings, + uiStateKey, + defaultAppState, + defaultTimeVariable, + ]); function useSetter(key: T) { return useCallback(