diff --git a/packages/libs/coreui/src/components/inputs/SelectList.tsx b/packages/libs/coreui/src/components/inputs/SelectList.tsx index 1de1366795..ffd5cd57d1 100644 --- a/packages/libs/coreui/src/components/inputs/SelectList.tsx +++ b/packages/libs/coreui/src/components/inputs/SelectList.tsx @@ -42,9 +42,14 @@ export default function SelectList({ useEffect(() => { setSelected(value); setButtonDisplayContent( - value.length ? value.join(', ') : defaultButtonDisplayContent + value.length + ? items + .filter((item) => value.includes(item.value)) + .map((item) => item.display) + .join(', ') + : defaultButtonDisplayContent ); - }, [value, defaultButtonDisplayContent]); + }, [value, defaultButtonDisplayContent, items]); const buttonLabel = ( { + return this.getVisualizationData( + computationName, + 'map-markers/collections', + params, + StandaloneCollectionsMarkerDataResponse + ); + } + // filter-aware continuous overlay variable metadata getContinousVariableMetadata( params: ContinousVariableMetadataRequestParams diff --git a/packages/libs/eda/src/lib/core/api/DataClient/types.ts b/packages/libs/eda/src/lib/core/api/DataClient/types.ts index 4547887010..59f3684220 100755 --- a/packages/libs/eda/src/lib/core/api/DataClient/types.ts +++ b/packages/libs/eda/src/lib/core/api/DataClient/types.ts @@ -23,8 +23,13 @@ import { TimeUnit, NumberOrNull, } from '../../types/general'; -import { VariableDescriptor, StringVariableValue } from '../../types/variable'; +import { + VariableDescriptor, + StringVariableValue, + VariableCollectionDescriptor, +} from '../../types/variable'; import { ComputationAppOverview } from '../../types/visualization'; +import { DerivedVariable } from '../../types/analysis'; export const AppsResponse = type({ apps: array(ComputationAppOverview), @@ -970,6 +975,63 @@ export const StandaloneMapBubblesLegendResponse = type({ maxSizeValue: number, }); +export interface StandaloneCollectionsMarkerDataRequest { + studyId: string; + filters?: Filter[]; + derivedVariables?: DerivedVariable[]; + config: { + outputEntityId: string; + geoAggregateVariable: VariableDescriptor; + longitudeVariable: VariableDescriptor; + latitudeVariable: VariableDescriptor; + viewport: LatLonViewport; + collectionOverlay: { + collection: VariableCollectionDescriptor; + selectedMembers: string[]; + }; + aggregatorConfig: + | { + overlayType: 'continuous'; + aggregator: 'mean' | 'median'; + } + | { + overlayType: 'categorical'; + numeratorValues: string[]; + denominatorValues: string[]; + }; + }; +} + +export type StandaloneCollectionsMarkerDataResponse = TypeOf< + typeof StandaloneCollectionsMarkerDataResponse +>; + +export const StandaloneCollectionsMarkerDataResponse = type({ + markers: array( + type({ + geoAggregateValue: string, + entityCount: number, + avgLat: number, + avgLon: number, + minLat: number, + minLon: number, + maxLat: number, + maxLon: number, + overlayValues: array( + type({ + variableId: string, + value: union([number, string]), + confidenceInterval: type({ + min: union([number, string]), + max: union([number, string]), + }), + n: number, + }) + ), + }) + ), +}); + export interface ContinousVariableMetadataRequestParams { studyId: string; filters: Filter[]; diff --git a/packages/libs/eda/src/lib/core/types/study.ts b/packages/libs/eda/src/lib/core/types/study.ts index 6eff7c782e..42ed334f6c 100644 --- a/packages/libs/eda/src/lib/core/types/study.ts +++ b/packages/libs/eda/src/lib/core/types/study.ts @@ -232,6 +232,7 @@ export const CollectionVariableTreeNode = t.intersection([ distributionDefaults: NumberDistributionDefaults, member: t.string, memberPlural: t.string, + vocabulary: t.array(t.string), }), ]); diff --git a/packages/libs/eda/src/lib/map/analysis/DraggableLegendPanel.tsx b/packages/libs/eda/src/lib/map/analysis/DraggableLegendPanel.tsx index 3fa28d493a..bdafca810f 100644 --- a/packages/libs/eda/src/lib/map/analysis/DraggableLegendPanel.tsx +++ b/packages/libs/eda/src/lib/map/analysis/DraggableLegendPanel.tsx @@ -1,7 +1,7 @@ import DraggablePanel, { DraggablePanelCoordinatePair, } from '@veupathdb/coreui/lib/components/containers/DraggablePanel'; -import { PanelConfig } from './appState'; +import { PanelConfig } from './Types'; export const DEFAULT_DRAGGABLE_LEGEND_POSITION = { x: window.innerWidth, diff --git a/packages/libs/eda/src/lib/map/analysis/DraggableVisualization.tsx b/packages/libs/eda/src/lib/map/analysis/DraggableVisualization.tsx index 7fea560a37..7d3de91a7c 100644 --- a/packages/libs/eda/src/lib/map/analysis/DraggableVisualization.tsx +++ b/packages/libs/eda/src/lib/map/analysis/DraggableVisualization.tsx @@ -12,17 +12,7 @@ import { Filter } from '../../core/types/filter'; import { FilledButton } from '@veupathdb/coreui'; import { DraggablePanel } from '@veupathdb/coreui/lib/components/containers'; import { ComputationPlugin } from '../../core/components/computations/Types'; -import { PanelConfig } from './appState'; - -export const DEFAULT_DRAGGABLE_VIZ_POSITION = { - x: 535, - y: 220, -}; - -export const DEFAULT_DRAGGABLE_VIZ_DIMENSIONS = { - width: 'auto', - height: 'auto', -}; +import { PanelConfig } from './Types'; interface Props { analysisState: AnalysisState; diff --git a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx index 4bae4f6381..36a7573171 100755 --- a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx +++ b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx @@ -1,5 +1,20 @@ import { useCallback, useMemo, useState } from 'react'; +import { EditLocation, InfoOutlined, Notes, Share } from '@material-ui/icons'; +import MapVEuMap from '@veupathdb/components/lib/map/MapVEuMap'; +import { + CheckIcon, + Download, + FilledButton, + Filter as FilterIcon, + FloatingButton, + H5, + Modal, + Plus, + Table, +} from '@veupathdb/coreui'; +import { useWdkService } from '@veupathdb/wdk-client/lib/Hooks/WdkServiceHook'; +import { useHistory, useLocation, useRouteMatch } from 'react-router'; import { AnalysisState, DEFAULT_ANALYSIS_NAME, @@ -16,76 +31,61 @@ import { useStudyRecord, useSubsettingClient, } from '../../core'; -import MapVEuMap from '@veupathdb/components/lib/map/MapVEuMap'; -import { useGeoConfig } from '../../core/hooks/geoConfig'; +import FilterChipList from '../../core/components/FilterChipList'; +import { VariableLinkConfig } from '../../core/components/VariableLink'; import { DocumentationContainer } from '../../core/components/docs/DocumentationContainer'; -import { - CheckIcon, - Download, - Plus, - FilledButton, - Filter as FilterIcon, - FloatingButton, - H5, - Table, - Modal, -} from '@veupathdb/coreui'; import { useEntityCounts } from '../../core/hooks/entityCounts'; +import { useGeoConfig } from '../../core/hooks/geoConfig'; +import { ComputationAppOverview } from '../../core/types/visualization'; import ShowHideVariableContextProvider from '../../core/utils/show-hide-variable-context'; -import { - AppState, - MarkerConfiguration, - useAppState, - LegacyRedirectState, -} from './appState'; +import NotesTab from '../../workspace/NotesTab'; import Subsetting from '../../workspace/Subsetting'; -import { MapHeader } from './MapHeader'; -import FilterChipList from '../../core/components/FilterChipList'; -import { VariableLinkConfig } from '../../core/components/VariableLink'; -import { MapSidePanel } from './MapSidePanel'; -import { EditLocation, InfoOutlined, Notes, Share } from '@material-ui/icons'; -import { ComputationAppOverview } from '../../core/types/visualization'; -import { useWdkService } from '@veupathdb/wdk-client/lib/Hooks/WdkServiceHook'; +import ConfirmShareAnalysis from '../../workspace/sharing/ConfirmShareAnalysis'; import Login from '../../workspace/sharing/Login'; -import { useLoginCallbacks } from '../../workspace/sharing/hooks'; import NameAnalysis from '../../workspace/sharing/NameAnalysis'; -import NotesTab from '../../workspace/NotesTab'; -import ConfirmShareAnalysis from '../../workspace/sharing/ConfirmShareAnalysis'; -import { useHistory, useLocation, useRouteMatch } from 'react-router'; +import { useLoginCallbacks } from '../../workspace/sharing/hooks'; +import { MapHeader } from './MapHeader'; +import { MapSidePanel } from './MapSidePanel'; +import { AppState, LegacyRedirectState, MarkerConfiguration } from './Types'; +import { useAppState } from './appState'; +import { getStudyId } from '@veupathdb/study-data-access/lib/shared/studies'; +import { RecordController } from '@veupathdb/wdk-client/lib/Controllers'; import { uniq } from 'lodash'; import Path from 'path'; -import DownloadTab from '../../workspace/DownloadTab'; -import { RecordController } from '@veupathdb/wdk-client/lib/Controllers'; -import { - BarPlotMarkerIcon, - DonutMarkerIcon, - BubbleMarkerIcon, -} from './MarkerConfiguration/icons'; -import { AllAnalyses } from '../../workspace/AllAnalyses'; -import { getStudyId } from '@veupathdb/study-data-access/lib/shared/studies'; -import { isSavedAnalysis } from '../../core/utils/analysis'; import { GeoConfig } from '../../core/types/geoConfig'; +import { isSavedAnalysis } from '../../core/utils/analysis'; +import { AllAnalyses } from '../../workspace/AllAnalyses'; +import DownloadTab from '../../workspace/DownloadTab'; +import { SideNavigationItems } from './MapSideNavigation'; import { SidePanelItem, SidePanelMenuEntry, SiteInformationProps, } from './Types'; -import { SideNavigationItems } from './MapSideNavigation'; import { barMarkerPlugin, bubbleMarkerPlugin, + collectionBarMarkerPlugin, donutMarkerPlugin, } from './mapTypes'; -import { MapTypeMapLayerProps } from './mapTypes/types'; import { defaultViewport } from '@veupathdb/components/lib/map/config/map'; -import AnalysisNameDialog from '../../workspace/AnalysisNameDialog'; +import SettingsButton from '@veupathdb/coreui/lib/components/containers/DraggablePanel/SettingsButton'; +import useSnackbar from '@veupathdb/coreui/lib/components/notifications/useSnackbar'; import { Page } from '@veupathdb/wdk-client/lib/Components'; import { AnalysisError } from '../../core/components/AnalysisError'; -import useSnackbar from '@veupathdb/coreui/lib/components/notifications/useSnackbar'; -import SettingsButton from '@veupathdb/coreui/lib/components/containers/DraggablePanel/SettingsButton'; import { getGeoConfig } from '../../core/utils/geoVariables'; +import AnalysisNameDialog from '../../workspace/AnalysisNameDialog'; +import { MapTypeMapLayerProps } from './mapTypes/types'; + +const singleVariablePlugins = [ + donutMarkerPlugin, + barMarkerPlugin, + bubbleMarkerPlugin, +]; + +const groupedVariablePlugins = [collectionBarMarkerPlugin]; enum MapSideNavItemLabels { Download = 'Download', @@ -393,91 +393,64 @@ function MapAnalysisImpl(props: ImplProps) { { type: 'subheading', labelText: MapSideNavItemLabels.SingleVariableMaps, - children: [ - { - type: 'item', - id: 'single-variable-pie', - labelText: donutMarkerPlugin.displayName, - rightIcon: , - leftIcon: - activeMarkerConfigurationType === 'pie' ? : null, - onActive: () => { - setActiveMarkerConfigurationType('pie'); - }, - renderSidePanelDrawer(apps) { - return ( - - ); - }, - }, - { - type: 'item', - id: 'single-variable-bar', - labelText: barMarkerPlugin.displayName, - leftIcon: - activeMarkerConfigurationType === 'barplot' ? ( - - ) : null, - rightIcon: , - onActive: () => { - setActiveMarkerConfigurationType('barplot'); - }, - renderSidePanelDrawer(apps) { - return ( - - ); - }, + children: singleVariablePlugins.map((plugin) => ({ + type: 'item', + id: plugin.type, + labelText: plugin.displayName, + rightIcon: , + leftIcon: + activeMarkerConfigurationType === plugin.type ? ( + + ) : null, + onActive: () => { + setActiveMarkerConfigurationType(plugin.type); }, - { - type: 'item', - id: 'single-variable-bubble', - labelText: bubbleMarkerPlugin.displayName, - rightIcon: , - leftIcon: - activeMarkerConfigurationType === 'bubble' ? ( - - ) : null, - onActive: () => setActiveMarkerConfigurationType('bubble'), - renderSidePanelDrawer(apps) { - return ( - - ); - }, + renderSidePanelDrawer: (apps) => ( + + ), + })), + }, + { + type: 'subheading', + labelText: MapSideNavItemLabels.GroupedVariableMaps, + children: groupedVariablePlugins.map((plugin) => ({ + type: 'item', + id: `grouped-variable-${plugin.type}`, + labelText: plugin.displayName, + rightIcon: , + leftIcon: + activeMarkerConfigurationType === plugin.type ? ( + + ) : null, + onActive: () => { + setActiveMarkerConfigurationType(plugin.type); }, - ], + renderSidePanelDrawer: (apps) => ( + + ), + })), }, ], }, @@ -794,14 +767,14 @@ function MapAnalysisImpl(props: ImplProps) { setIsSidePanelExpanded, ]); + // TODO Add `type` to plugin def and use to look up, so we can remove hard coded strings. const activeMapTypePlugin = - activeMarkerConfiguration?.type === 'barplot' - ? barMarkerPlugin - : activeMarkerConfiguration?.type === 'bubble' - ? bubbleMarkerPlugin - : activeMarkerConfiguration?.type === 'pie' - ? donutMarkerPlugin - : undefined; + singleVariablePlugins.find( + (plugin) => plugin.type === activeMarkerConfigurationType + ) ?? + groupedVariablePlugins.find( + (plugin) => plugin.type === activeMarkerConfigurationType + ); return ( @@ -810,21 +783,22 @@ function MapAnalysisImpl(props: ImplProps) { const activeSideNavigationItemMenu = activePanelItem?.renderSidePanelDrawer(apps) ?? null; - const mapTypeMapLayerProps: MapTypeMapLayerProps = { - apps, - analysisState, - appState, - studyId, - filters, - studyEntities, - geoConfigs, - configuration: activeMarkerConfiguration, - updateConfiguration: updateMarkerConfigurations as any, - totalCounts, - filteredCounts, - setStudyDetailsPanelConfig, - headerButtons: HeaderButtons, - }; + const mapTypeMapLayerProps: MapTypeMapLayerProps = + { + apps, + analysisState, + appState, + studyId, + filters, + studyEntities, + geoConfigs, + configuration: activeMarkerConfiguration, + updateConfiguration: updateMarkerConfigurations, + totalCounts, + filteredCounts, + setStudyDetailsPanelConfig, + headerButtons: HeaderButtons, + }; return ( diff --git a/packages/libs/eda/src/lib/map/analysis/MapVizManagement.tsx b/packages/libs/eda/src/lib/map/analysis/MapVizManagement.tsx index 65a22789b5..6d4f858996 100644 --- a/packages/libs/eda/src/lib/map/analysis/MapVizManagement.tsx +++ b/packages/libs/eda/src/lib/map/analysis/MapVizManagement.tsx @@ -13,7 +13,7 @@ import { useVizIconColors } from '../../core/components/visualizations/implement import { GeoConfig } from '../../core/types/geoConfig'; import { ComputationAppOverview } from '../../core/types/visualization'; import './MapVizManagement.scss'; -import { MarkerConfiguration } from './appState'; +import { MarkerConfiguration } from './Types'; import { ComputationPlugin } from '../../core/components/computations/Types'; import { VisualizationPlugin } from '../../core/components/visualizations/VisualizationPlugin'; import { StartPage } from '../../core/components/computations/StartPage'; @@ -28,7 +28,7 @@ interface Props { // visualizationPlugins: Partial>; geoConfigs: GeoConfig[]; mapType?: MarkerConfiguration['type']; - setIsSidePanelExpanded: MapTypeConfigPanelProps['setIsSidePanelExpanded']; + setIsSidePanelExpanded: MapTypeConfigPanelProps['setIsSidePanelExpanded']; } const mapVizManagementClassName = makeClassNameHelper('MapVizManagement'); diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/index.ts b/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/index.ts deleted file mode 100644 index 3b67e0b8c1..0000000000 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BarPlotMarkerConfigurationMenu } from './BarPlotMarkerConfigurationMenu'; -import { PieMarkerConfigurationMenu } from './PieMarkerConfigurationMenu'; -import { MarkerConfigurationSelector } from './MarkerConfigurationSelector'; -import { BubbleMarkerConfigurationMenu } from './BubbleMarkerConfigurationMenu'; - -export { - MarkerConfigurationSelector, - PieMarkerConfigurationMenu, - BarPlotMarkerConfigurationMenu, - BubbleMarkerConfigurationMenu, -}; diff --git a/packages/libs/eda/src/lib/map/analysis/SubStudies.tsx b/packages/libs/eda/src/lib/map/analysis/SubStudies.tsx index e2954b8465..53a17891ff 100644 --- a/packages/libs/eda/src/lib/map/analysis/SubStudies.tsx +++ b/packages/libs/eda/src/lib/map/analysis/SubStudies.tsx @@ -4,10 +4,10 @@ import { usePermissions } from '@veupathdb/study-data-access/lib/data-restrictio import { useMemo } from 'react'; import { Link } from 'react-router-dom'; import { DraggablePanel } from '@veupathdb/coreui/lib/components/containers'; -import { PanelConfig } from './appState'; import { useDebouncedCallback } from '../../core/hooks/debouncing'; import Spinner from '@veupathdb/components/lib/components/Spinner'; import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; +import { PanelConfig } from './Types'; interface Props { studyId: string; diff --git a/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx index c4a46cb075..b922251a19 100755 --- a/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx +++ b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx @@ -12,7 +12,7 @@ import { VariableDescriptor } from '../../core/types/variable'; import Spinner from '@veupathdb/components/lib/components/Spinner'; import { useFindEntityAndVariable, Filter } from '../../core'; import { zip } from 'lodash'; -import { AppState } from './appState'; +import { AppState } from './Types'; import { timeSliderVariableConstraints } from './config/eztimeslider'; import { useUITheme } from '@veupathdb/coreui/lib/components/theming'; import HelpIcon from '@veupathdb/wdk-client/lib/Components/Icon/HelpIcon'; diff --git a/packages/libs/eda/src/lib/map/analysis/Types.ts b/packages/libs/eda/src/lib/map/analysis/Types.ts index 80d375cc25..e0d87854e2 100644 --- a/packages/libs/eda/src/lib/map/analysis/Types.ts +++ b/packages/libs/eda/src/lib/map/analysis/Types.ts @@ -1,5 +1,7 @@ +import * as t from 'io-ts'; import { ReactNode } from 'react'; import { ComputationAppOverview } from '../../core/types/visualization'; +import { VariableDescriptor } from '../../core/types/variable'; export type SidePanelMenuEntry = | SidePanelItem @@ -35,3 +37,96 @@ export interface SiteInformationProps { siteName: string; siteLogoSrc: string; } + +export const PanelPositionConfig = t.type({ + x: t.number, + y: t.number, +}); + +export const PanelConfig = t.intersection([ + t.type({ + isVisible: t.boolean, + position: PanelPositionConfig, + dimensions: t.type({ + height: t.union([t.number, t.string]), + width: t.union([t.number, t.string]), + }), + }), + t.partial({ + hideVizControl: t.boolean, + }), +]); + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type PanelConfig = t.TypeOf; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type PanelPositionConfig = t.TypeOf; + +const LatLngLiteral = t.type({ lat: t.number, lng: t.number }); + +export type MarkerConfiguration = t.TypeOf; +// TODO Make `uknown` and use plugin-specific decoder +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const MarkerConfiguration = t.intersection([ + t.type({ + type: t.string, + }), + t.partial({ + selectedMarkers: t.array(t.string), + selectedVariable: VariableDescriptor, + activeVisualizationId: t.string, + geoEntityId: t.string, + }), +]); + +export type LegacyRedirectState = t.TypeOf; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const LegacyRedirectState = t.union([ + t.undefined, + t.type({ + projectId: t.union([t.string, t.undefined]), + showLegacyMapRedirectModal: t.boolean, + }), +]); + +export const AppState = t.intersection([ + t.type({ + viewport: t.type({ + center: t.tuple([t.number, t.number]), + zoom: t.number, + }), + activeMarkerConfigurationType: t.string, + markerConfigurations: t.array(MarkerConfiguration), + isSidePanelExpanded: t.boolean, + }), + t.partial({ + studyDetailsPanelConfig: PanelConfig, + boundsZoomLevel: t.type({ + zoomLevel: t.number, + bounds: t.type({ + southWest: LatLngLiteral, + northEast: LatLngLiteral, + }), + }), + subsetVariableAndEntity: t.partial({ + entityId: t.string, + variableId: t.string, + }), + isSubsetPanelOpen: t.boolean, + timeSliderConfig: t.type({ + variable: t.union([VariableDescriptor, t.undefined]), + selectedRange: t.union([ + t.type({ + start: t.string, + end: t.string, + }), + t.undefined, + ]), + active: t.boolean, + }), + }), +]); + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type AppState = t.TypeOf; diff --git a/packages/libs/eda/src/lib/map/analysis/appState.ts b/packages/libs/eda/src/lib/map/analysis/appState.ts index fd0057a7a5..ca21d49847 100644 --- a/packages/libs/eda/src/lib/map/analysis/appState.ts +++ b/packages/libs/eda/src/lib/map/analysis/appState.ts @@ -8,198 +8,11 @@ import { useGetDefaultVariableDescriptor, useStudyMetadata, } from '../../core'; -import { VariableDescriptor } from '../../core/types/variable'; import { useGetDefaultTimeVariableDescriptor } from './hooks/eztimeslider'; import { defaultViewport } from '@veupathdb/components/lib/map/config/map'; +import * as plugins from './mapTypes'; import { STUDIES_ENTITY_ID, STUDY_ID_VARIABLE_ID } from '../constants'; -import { - DEFAULT_DRAGGABLE_VIZ_DIMENSIONS, - DEFAULT_DRAGGABLE_VIZ_POSITION, -} from './DraggableVisualization'; -import { DEFAULT_DRAGGABLE_LEGEND_POSITION } from './DraggableLegendPanel'; - -const defaultVisualizationPanelConfig = { - isVisible: false, - hideVizControl: false, - position: DEFAULT_DRAGGABLE_VIZ_POSITION, - dimensions: DEFAULT_DRAGGABLE_VIZ_DIMENSIONS, -}; - -const LatLngLiteral = t.type({ lat: t.number, lng: t.number }); - -const MarkerType = t.keyof({ - barplot: null, - pie: null, - bubble: null, -}); - -const PanelPositionConfig = t.type({ - x: t.number, - y: t.number, -}); - -const PanelConfig = t.intersection([ - t.type({ - isVisible: t.boolean, - position: PanelPositionConfig, - dimensions: t.type({ - height: t.union([t.number, t.string]), - width: t.union([t.number, t.string]), - }), - }), - t.partial({ - hideVizControl: t.boolean, - }), -]); - -const BubbleLegendPositionConfig = t.type({ - variable: PanelPositionConfig, - count: PanelPositionConfig, -}); - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type PanelConfig = t.TypeOf; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type PanelPositionConfig = t.TypeOf; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type BubbleLegendPositionConfig = t.TypeOf< - typeof BubbleLegendPositionConfig ->; - -// user-specified selection -export type SelectedValues = t.TypeOf; -// eslint-disable-next-line @typescript-eslint/no-redeclare -const SelectedValues = t.union([t.array(t.string), t.undefined]); - -export type BinningMethod = t.TypeOf; -// eslint-disable-next-line @typescript-eslint/no-redeclare -const BinningMethod = t.union([ - t.literal('equalInterval'), - t.literal('quantile'), - t.literal('standardDeviation'), - t.undefined, -]); - -export type SelectedCountsOption = t.TypeOf; -// eslint-disable-next-line @typescript-eslint/no-redeclare -const SelectedCountsOption = t.union([ - t.literal('filtered'), - t.literal('visible'), - t.undefined, -]); - -export type MarkerConfiguration = t.TypeOf; -// eslint-disable-next-line @typescript-eslint/no-redeclare -export const MarkerConfiguration = t.intersection([ - t.type({ - type: MarkerType, - selectedVariable: VariableDescriptor, - }), - t.partial({ - activeVisualizationId: t.string, - geoEntityId: t.string, - }), - t.union([ - t.intersection([ - t.type({ - type: t.literal('barplot'), - selectedValues: SelectedValues, - selectedPlotMode: t.union([ - t.literal('count'), - t.literal('proportion'), - ]), - binningMethod: BinningMethod, - dependentAxisLogScale: t.boolean, - selectedCountsOption: SelectedCountsOption, - legendPanelConfig: PanelPositionConfig, - visualizationPanelConfig: PanelConfig, - }), - t.partial({ - // yes all the modes have selectedMarkers but maybe in the future one won't - selectedMarkers: t.array(t.string), - }), - ]), - t.intersection([ - t.type({ - type: t.literal('pie'), - selectedValues: SelectedValues, - binningMethod: BinningMethod, - selectedCountsOption: SelectedCountsOption, - legendPanelConfig: PanelPositionConfig, - visualizationPanelConfig: PanelConfig, - }), - t.partial({ - selectedMarkers: t.array(t.string), - }), - ]), - t.intersection([ - t.type({ - type: t.literal('bubble'), - }), - t.partial({ - aggregator: t.union([t.literal('mean'), t.literal('median')]), - numeratorValues: t.union([t.array(t.string), t.undefined]), - denominatorValues: t.union([t.array(t.string), t.undefined]), - selectedMarkers: t.array(t.string), - legendPanelConfig: BubbleLegendPositionConfig, - visualizationPanelConfig: PanelConfig, - }), - ]), - ]), -]); - -export type LegacyRedirectState = t.TypeOf; -// eslint-disable-next-line @typescript-eslint/no-redeclare -export const LegacyRedirectState = t.union([ - t.undefined, - t.type({ - projectId: t.union([t.string, t.undefined]), - showLegacyMapRedirectModal: t.boolean, - }), -]); - -export const AppState = t.intersection([ - t.type({ - viewport: t.type({ - center: t.tuple([t.number, t.number]), - zoom: t.number, - }), - activeMarkerConfigurationType: MarkerType, - markerConfigurations: t.array(MarkerConfiguration), - isSidePanelExpanded: t.boolean, - }), - t.partial({ - studyDetailsPanelConfig: PanelConfig, - boundsZoomLevel: t.type({ - zoomLevel: t.number, - bounds: t.type({ - southWest: LatLngLiteral, - northEast: LatLngLiteral, - }), - }), - subsetVariableAndEntity: t.partial({ - entityId: t.string, - variableId: t.string, - }), - isSubsetPanelOpen: t.boolean, - timeSliderConfig: t.type({ - variable: t.union([VariableDescriptor, t.undefined]), - selectedRange: t.union([ - t.type({ - start: t.string, - end: t.string, - }), - t.undefined, - ]), - active: t.boolean, - }), - }), -]); - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type AppState = t.TypeOf; +import { AppState } from './Types'; export function useAppState( uiStateKey: string, @@ -249,6 +62,9 @@ export function useAppState( active: true, selectedRange: undefined, }, + markerConfigurations: Object.values(plugins).map((plugin) => + plugin.getDefaultConfig({ defaultVariable, study: studyMetadata }) + ), hideVizControl: false, ...(isMegaStudy ? { @@ -262,45 +78,8 @@ export function useAppState( }, } : {}), - markerConfigurations: [ - { - type: 'pie', - selectedVariable: defaultVariable, - selectedValues: undefined, - binningMethod: undefined, - selectedCountsOption: 'filtered', - legendPanelConfig: DEFAULT_DRAGGABLE_LEGEND_POSITION, - visualizationPanelConfig: defaultVisualizationPanelConfig, - }, - { - type: 'barplot', - selectedPlotMode: 'count', - selectedVariable: defaultVariable, - selectedValues: undefined, - binningMethod: undefined, - dependentAxisLogScale: false, - selectedCountsOption: 'filtered', - legendPanelConfig: DEFAULT_DRAGGABLE_LEGEND_POSITION, - visualizationPanelConfig: defaultVisualizationPanelConfig, - }, - { - type: 'bubble', - selectedVariable: defaultVariable, - aggregator: 'mean', - numeratorValues: undefined, - denominatorValues: undefined, - legendPanelConfig: { - variable: DEFAULT_DRAGGABLE_LEGEND_POSITION, - count: { - x: window.innerWidth, - y: 420, - }, - }, - visualizationPanelConfig: defaultVisualizationPanelConfig, - }, - ], }), - [defaultTimeVariable, isMegaStudy, defaultVariable] + [defaultTimeVariable, isMegaStudy, defaultVariable, studyMetadata] ); useEffect(() => { diff --git a/packages/libs/eda/src/lib/map/analysis/littleFilters.ts b/packages/libs/eda/src/lib/map/analysis/littleFilters.ts index 87a0adb5b2..b8a0ab7486 100644 --- a/packages/libs/eda/src/lib/map/analysis/littleFilters.ts +++ b/packages/libs/eda/src/lib/map/analysis/littleFilters.ts @@ -5,7 +5,7 @@ import { useFindEntityAndVariable, } from '../../core'; import { useMemo } from 'react'; -import { AppState } from './appState'; +import { AppState } from './Types'; import { GeoConfig } from '../../core/types/geoConfig'; import { useDeepValue } from '../../core/hooks/immutability'; import { VariableDescriptor } from '../../core/types/variable'; diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx similarity index 97% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx index 3e172fa138..9c66280c3e 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerConfigurationTable.tsx @@ -4,15 +4,14 @@ import { MesaSortObject, MesaStateProps, } from '@veupathdb/coreui/lib/components/Mesa/types'; -import { AllValuesDefinition } from '../../../core'; +import { AllValuesDefinition } from '../../../../core'; import { Tooltip } from '@veupathdb/coreui'; import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots'; import RadioButtonGroup from '@veupathdb/components/lib/components/widgets/RadioButtonGroup'; -import { UNSELECTED_TOKEN } from '../../constants'; +import { UNSELECTED_TOKEN } from '../../../constants'; import { orderBy } from 'lodash'; -import { SelectedCountsOption } from '../appState'; import Spinner from '@veupathdb/components/lib/components/Spinner'; -import { SharedMarkerConfigurations } from '../mapTypes/shared'; +import { SelectedCountsOption, SharedMarkerConfigurations } from '../shared'; type Props = { overlayValues: string[]; diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerPreview.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerPreview.tsx similarity index 96% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerPreview.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerPreview.tsx index f8095eed43..aa9e380685 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/CategoricalMarkerPreview.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/CategoricalMarkerPreview.tsx @@ -1,16 +1,16 @@ -import { AllValuesDefinition, OverlayConfig } from '../../../core'; +import { AllValuesDefinition, OverlayConfig } from '../../../../core'; import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots'; import { ChartMarkerStandalone, getChartMarkerDependentAxisRange, } from '@veupathdb/components/lib/map/ChartMarker'; import { DonutMarkerStandalone } from '@veupathdb/components/lib/map/DonutMarker'; -import { UNSELECTED_TOKEN } from '../../constants'; +import { UNSELECTED_TOKEN } from '../../../constants'; import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; import { kFormatter, mFormatter, -} from '../../../core/utils/big-number-formatters'; +} from '../../../../core/utils/big-number-formatters'; import { MAXIMUM_ALLOWABLE_VALUES } from './CategoricalMarkerConfigurationTable'; type Props = { diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MapTypeConfigurationMenu.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MapTypeConfigurationMenu.tsx similarity index 95% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MapTypeConfigurationMenu.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MapTypeConfigurationMenu.tsx index 327cbc9fee..0394dfae11 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MapTypeConfigurationMenu.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MapTypeConfigurationMenu.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { H5 } from '@veupathdb/coreui'; -import { MarkerConfiguration } from '../appState'; +import { MarkerConfiguration } from '../../Types'; import TabbedDisplay, { TabbedDisplayProps, } from '@veupathdb/coreui/lib/components/grids/TabbedDisplay'; diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MarkerConfigurationSelector.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MarkerConfigurationSelector.tsx similarity index 98% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MarkerConfigurationSelector.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MarkerConfigurationSelector.tsx index 02d43d584d..38d279713c 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/MarkerConfigurationSelector.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/MarkerConfigurationSelector.tsx @@ -1,6 +1,6 @@ import { H6 } from '@veupathdb/coreui'; import { useUITheme } from '@veupathdb/coreui/lib/components/theming'; -import { MarkerConfiguration } from '../appState'; +import { MarkerConfiguration } from '../../Types'; export interface MarkerConfigurationOption { displayName: string; diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BarPlotMarkerIcon.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BarPlotMarkerIcon.tsx similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BarPlotMarkerIcon.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BarPlotMarkerIcon.tsx diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BarPlotMarkersIcon.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BarPlotMarkersIcon.tsx similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BarPlotMarkersIcon.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BarPlotMarkersIcon.tsx diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BubbleMarkerIcon.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BubbleMarkerIcon.tsx similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/BubbleMarkerIcon.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/BubbleMarkerIcon.tsx diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/DonutMarkerIcon.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/DonutMarkerIcon.tsx similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/DonutMarkerIcon.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/DonutMarkerIcon.tsx diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/DonutMarkersIcon.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/DonutMarkersIcon.tsx similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/DonutMarkersIcon.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/DonutMarkersIcon.tsx diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/index.ts b/packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/index.ts similarity index 100% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/icons/index.ts rename to packages/libs/eda/src/lib/map/analysis/mapTypes/MarkerConfiguration/icons/index.ts diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/index.ts b/packages/libs/eda/src/lib/map/analysis/mapTypes/index.ts index 715596ebd1..e1fa390702 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/index.ts +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/index.ts @@ -1,3 +1,4 @@ -export { plugin as donutMarkerPlugin } from './plugins/DonutMarkerMapType'; -export { plugin as barMarkerPlugin } from './plugins/BarMarkerMapType'; -export { plugin as bubbleMarkerPlugin } from './plugins/BubbleMarkerMapType'; +export { plugin as donutMarkerPlugin } from './plugins/donut/DonutMarkerMapType'; +export { plugin as barMarkerPlugin } from './plugins/barplot/BarMarkerMapType'; +export { plugin as bubbleMarkerPlugin } from './plugins/bubble/BubbleMarkerMapType'; +export { plugin as collectionBarMarkerPlugin } from './plugins/collection-barplot/CollectionBarMarkerType'; diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarMarkerMapType.tsx similarity index 90% rename from packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarMarkerMapType.tsx index c08d7109aa..1fb856e38b 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarMarkerMapType.tsx @@ -1,22 +1,22 @@ import { useCallback, useMemo } from 'react'; -import { Variable } from '../../../../core/types/study'; -import { findEntityAndVariable } from '../../../../core/utils/study-metadata'; +import { Variable } from '../../../../../core/types/study'; +import { findEntityAndVariable } from '../../../../../core/utils/study-metadata'; import { BarPlotMarkerConfiguration, BarPlotMarkerConfigurationMenu, -} from '../../MarkerConfiguration/BarPlotMarkerConfigurationMenu'; +} from './BarPlotMarkerConfigurationMenu'; import { MapTypeConfigPanelProps, MapTypeMapLayerProps, MapTypePlugin, -} from '../types'; +} from '../../types'; import { OverlayConfig, StandaloneMapMarkersResponse, -} from '../../../../core/api/DataClient/types'; -import { getDefaultAxisRange } from '../../../../core/utils/computeDefaultAxisRange'; +} from '../../../../../core/api/DataClient/types'; +import { getDefaultAxisRange } from '../../../../../core/utils/computeDefaultAxisRange'; import { NumberRange } from '@veupathdb/components/lib/types/general'; -import { mFormatter } from '../../../../core/utils/big-number-formatters'; +import { mFormatter } from '../../../../../core/utils/big-number-formatters'; import ChartMarker, { BaseMarkerData, ChartMarkerProps, @@ -36,7 +36,7 @@ import { STUDY_ID_VARIABLE_ID, UNSELECTED_DISPLAY_TEXT, UNSELECTED_TOKEN, -} from '../../../constants'; +} from '../../../../constants'; import SemanticMarkers from '@veupathdb/components/lib/map/SemanticMarkers'; import { DistributionMarkerDataProps, @@ -57,34 +57,53 @@ import { getLegendErrorMessage, selectedMarkersLittleFilter, useFloatingPanelHandlers, -} from '../shared'; + defaultVisualizationPanelConfig, +} from '../../shared'; import { useFindEntityAndVariable, useSubsettingClient, -} from '../../../../core/hooks/workspace'; -import { DraggableLegendPanel } from '../../DraggableLegendPanel'; -import { MapLegend } from '../../MapLegend'; +} from '../../../../../core/hooks/workspace'; +import { + DEFAULT_DRAGGABLE_LEGEND_POSITION, + DraggableLegendPanel, +} from '../../../DraggableLegendPanel'; +import { MapLegend } from '../../../MapLegend'; import { sharedStandaloneMarkerProperties } from '../../MarkerConfiguration/CategoricalMarkerPreview'; -import { useToggleStarredVariable } from '../../../../core/hooks/starredVariables'; -import DraggableVisualization from '../../DraggableVisualization'; -import { useStandaloneVizPlugins } from '../../hooks/standaloneVizPlugins'; +import { useToggleStarredVariable } from '../../../../../core/hooks/starredVariables'; +import DraggableVisualization from '../../../DraggableVisualization'; +import { useStandaloneVizPlugins } from '../../../hooks/standaloneVizPlugins'; import { MapTypeConfigurationMenu, MarkerConfigurationOption, } from '../../MarkerConfiguration/MapTypeConfigurationMenu'; import { BarPlotMarkerIcon } from '../../MarkerConfiguration/icons'; import { TabbedDisplayProps } from '@veupathdb/coreui/lib/components/grids/TabbedDisplay'; -import MapVizManagement from '../../MapVizManagement'; +import MapVizManagement from '../../../MapVizManagement'; import Spinner from '@veupathdb/components/lib/components/Spinner'; -import { useLittleFilters } from '../../littleFilters'; -import TimeSliderQuickFilter from '../../TimeSliderQuickFilter'; -import { MapTypeHeaderStudyDetails } from '../MapTypeHeaderStudyDetails'; -import { SubStudies } from '../../SubStudies'; +import { useLittleFilters } from '../../../littleFilters'; +import TimeSliderQuickFilter from '../../../TimeSliderQuickFilter'; +import { MapTypeHeaderStudyDetails } from '../../MapTypeHeaderStudyDetails'; +import { SubStudies } from '../../../SubStudies'; const displayName = 'Bar plots'; -export const plugin: MapTypePlugin = { +export const plugin: MapTypePlugin = { + type: 'barplot', + IconComponent: BarPlotMarkerIcon, displayName, + getDefaultConfig({ defaultVariable }) { + return { + type: 'barplot', + selectedPlotMode: 'count', + selectedVariable: defaultVariable, + selectedValues: undefined, + binningMethod: undefined, + dependentAxisLogScale: false, + selectedCountsOption: 'filtered', + legendPanelConfig: DEFAULT_DRAGGABLE_LEGEND_POSITION, + visualizationPanelConfig: defaultVisualizationPanelConfig, + }; + }, ConfigPanelComponent, MapLayerComponent, MapOverlayComponent, @@ -100,7 +119,9 @@ type ChartMarkerPropsWithCounts = Omit & { data: ChartMarkerDataWithCounts[]; }; -function ConfigPanelComponent(props: MapTypeConfigPanelProps) { +function ConfigPanelComponent( + props: MapTypeConfigPanelProps +) { const { apps, analysisState, @@ -343,18 +364,16 @@ function ConfigPanelComponent(props: MapTypeConfigPanelProps) { ); } -function MapLayerComponent(props: MapTypeMapLayerProps) { +function MapLayerComponent( + props: MapTypeMapLayerProps +) { const { studyEntities, studyId, filters, geoConfigs, appState, - appState: { - boundsZoomLevel, - markerConfigurations, - activeMarkerConfigurationType, - }, + appState: { boundsZoomLevel }, updateConfiguration, } = props; @@ -365,6 +384,7 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { dependentAxisLogScale, selectedPlotMode, activeVisualizationId, + selectedMarkers, } = props.configuration as BarPlotMarkerConfiguration; const { filters: filtersForMarkerData } = useLittleFilters( @@ -422,11 +442,6 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { )); - const selectedMarkers = markerConfigurations.find( - (markerConfiguration) => - markerConfiguration.type === activeMarkerConfigurationType - )?.selectedMarkers; - return ( <> {markerData.isFetching && } @@ -447,7 +462,9 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { ); } -function MapOverlayComponent(props: MapTypeMapLayerProps) { +function MapOverlayComponent( + props: MapTypeMapLayerProps +) { const { studyEntities, studyId, @@ -581,7 +598,9 @@ function MapOverlayComponent(props: MapTypeMapLayerProps) { ); } -function MapTypeHeaderDetails(props: MapTypeMapLayerProps) { +function MapTypeHeaderDetails( + props: MapTypeMapLayerProps +) { const { studyEntities, geoConfigs, @@ -633,7 +652,9 @@ const substudyFilterFuncs = [ selectedMarkersLittleFilter, ]; -export function TimeSliderComponent(props: MapTypeMapLayerProps) { +export function TimeSliderComponent( + props: MapTypeMapLayerProps +) { const { studyId, studyEntities, diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BarPlotMarkerConfigurationMenu.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarPlotMarkerConfigurationMenu.tsx similarity index 92% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BarPlotMarkerConfigurationMenu.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarPlotMarkerConfigurationMenu.tsx index 050b972401..6432961ef5 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BarPlotMarkerConfigurationMenu.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/barplot/BarPlotMarkerConfigurationMenu.tsx @@ -2,33 +2,32 @@ import { useCallback } from 'react'; import { InputVariables, Props as InputVariablesProps, -} from '../../../core/components/visualizations/InputVariables'; +} from '../../../../../core/components/visualizations/InputVariables'; import RadioButtonGroup from '@veupathdb/components/lib/components/widgets/RadioButtonGroup'; -import { VariablesByInputName } from '../../../core/utils/data-element-constraints'; +import { VariablesByInputName } from '../../../../../core/utils/data-element-constraints'; import { usePromise, AllValuesDefinition, OverlayConfig, Variable, Filter, -} from '../../../core'; -import { CategoricalMarkerConfigurationTable } from './CategoricalMarkerConfigurationTable'; -import { CategoricalMarkerPreview } from './CategoricalMarkerPreview'; +} from '../../../../../core'; +import { CategoricalMarkerConfigurationTable } from '../../MarkerConfiguration/CategoricalMarkerConfigurationTable'; +import { CategoricalMarkerPreview } from '../../MarkerConfiguration/CategoricalMarkerPreview'; import Barplot from '@veupathdb/components/lib/plots/Barplot'; -import { SubsettingClient } from '../../../core/api'; +import { SubsettingClient } from '../../../../../core/api'; import { Toggle } from '@veupathdb/coreui'; -import { useUncontrolledSelections } from '../hooks/uncontrolledSelections'; +import { useUncontrolledSelections } from '../../../hooks/uncontrolledSelections'; import { BinningMethod, - PanelConfig, - PanelPositionConfig, SelectedCountsOption, SelectedValues, -} from '../appState'; +} from '../../shared'; import { gray } from '@veupathdb/coreui/lib/definitions/colors'; -import { SharedMarkerConfigurations } from '../mapTypes/shared'; -import { GeoConfig } from '../../../core/types/geoConfig'; -import { findLeastAncestralGeoConfig } from '../../../core/utils/geoVariables'; +import { SharedMarkerConfigurations } from '../../shared'; +import { GeoConfig } from '../../../../../core/types/geoConfig'; +import { findLeastAncestralGeoConfig } from '../../../../../core/utils/geoVariables'; +import { PanelConfig, PanelPositionConfig } from '../../../Types'; interface MarkerConfiguration { type: T; diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BubbleMarkerConfigurationMenu.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerConfigurationMenu.tsx similarity index 85% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BubbleMarkerConfigurationMenu.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerConfigurationMenu.tsx index 34bde9bc66..85b96852ec 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/BubbleMarkerConfigurationMenu.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerConfigurationMenu.tsx @@ -1,23 +1,23 @@ import { InputVariables, Props as InputVariablesProps, -} from '../../../core/components/visualizations/InputVariables'; -import { VariableTreeNode } from '../../../core/types/study'; -import { VariablesByInputName } from '../../../core/utils/data-element-constraints'; -import { findEntityAndVariable } from '../../../core/utils/study-metadata'; +} from '../../../../../core/components/visualizations/InputVariables'; +import { VariableTreeNode } from '../../../../../core/types/study'; +import { VariablesByInputName } from '../../../../../core/utils/data-element-constraints'; +import { findEntityAndVariable } from '../../../../../core/utils/study-metadata'; import HelpIcon from '@veupathdb/wdk-client/lib/Components/Icon/HelpIcon'; -import { BubbleOverlayConfig } from '../../../core'; -import PluginError from '../../../core/components/visualizations/PluginError'; +import { BubbleOverlayConfig } from '../../../../../core'; +import PluginError from '../../../../../core/components/visualizations/PluginError'; import { aggregationHelp, AggregationInputs, -} from '../../../core/components/visualizations/implementations/LineplotVisualization'; -import { DataElementConstraint } from '../../../core/types/visualization'; // TO DO for dates: remove -import { SharedMarkerConfigurations } from '../mapTypes/shared'; -import { invalidProportionText } from '../utils/defaultOverlayConfig'; -import { BubbleLegendPositionConfig, PanelConfig } from '../appState'; -import { GeoConfig } from '../../../core/types/geoConfig'; -import { findLeastAncestralGeoConfig } from '../../../core/utils/geoVariables'; +} from '../../../../../core/components/visualizations/implementations/LineplotVisualization'; +import { DataElementConstraint } from '../../../../../core/types/visualization'; // TO DO for dates: remove +import { SharedMarkerConfigurations } from '../../shared'; +import { invalidProportionText } from '../../../utils/defaultOverlayConfig'; +import { GeoConfig } from '../../../../../core/types/geoConfig'; +import { findLeastAncestralGeoConfig } from '../../../../../core/utils/geoVariables'; +import { PanelConfig, PanelPositionConfig } from '../../../Types'; type AggregatorOption = typeof aggregatorOptions[number]; const aggregatorOptions = ['mean', 'median'] as const; @@ -26,6 +26,11 @@ interface MarkerConfiguration { type: T; } +interface BubbleLegendPositionConfig { + variable: PanelPositionConfig; + count: PanelPositionConfig; +} + export interface BubbleMarkerConfiguration extends MarkerConfiguration<'bubble'>, SharedMarkerConfigurations { diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BubbleMarkerMapType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerMapType.tsx similarity index 92% rename from packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BubbleMarkerMapType.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerMapType.tsx index 42e8537f85..71e32a00a3 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BubbleMarkerMapType.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/bubble/BubbleMarkerMapType.tsx @@ -15,26 +15,31 @@ import { Filter, useDataClient, useStudyEntities, -} from '../../../../core'; +} from '../../../../../core'; import { BubbleOverlayConfig, StandaloneMapBubblesLegendRequestParams, StandaloneMapBubblesRequestParams, StandaloneMapBubblesResponse, -} from '../../../../core/api/DataClient/types'; -import { useToggleStarredVariable } from '../../../../core/hooks/starredVariables'; -import { DraggableLegendPanel } from '../../DraggableLegendPanel'; -import { MapLegend } from '../../MapLegend'; -import MapVizManagement from '../../MapVizManagement'; -import { BubbleMarkerConfigurationMenu } from '../../MarkerConfiguration'; -import { BubbleMarkerConfiguration } from '../../MarkerConfiguration/BubbleMarkerConfigurationMenu'; +} from '../../../../../core/api/DataClient/types'; +import { useToggleStarredVariable } from '../../../../../core/hooks/starredVariables'; +import { + DEFAULT_DRAGGABLE_LEGEND_POSITION, + DraggableLegendPanel, +} from '../../../DraggableLegendPanel'; +import { MapLegend } from '../../../MapLegend'; +import MapVizManagement from '../../../MapVizManagement'; +import { + BubbleMarkerConfiguration, + BubbleMarkerConfigurationMenu, +} from './BubbleMarkerConfigurationMenu'; import { MapTypeConfigurationMenu, MarkerConfigurationOption, } from '../../MarkerConfiguration/MapTypeConfigurationMenu'; import { BubbleMarkerIcon } from '../../MarkerConfiguration/icons'; -import { useStandaloneVizPlugins } from '../../hooks/standaloneVizPlugins'; -import { getDefaultBubbleOverlayConfig } from '../../utils/defaultOverlayConfig'; +import { useStandaloneVizPlugins } from '../../../hooks/standaloneVizPlugins'; +import { getDefaultBubbleOverlayConfig } from '../../../utils/defaultOverlayConfig'; import { MAX_FILTERSET_VALUES, defaultAnimation, @@ -48,32 +53,52 @@ import { useSelectedMarkerSnackbars, selectedMarkersLittleFilter, useFloatingPanelHandlers, -} from '../shared'; + defaultVisualizationPanelConfig, +} from '../../shared'; import { MapTypeConfigPanelProps, MapTypeMapLayerProps, MapTypePlugin, -} from '../types'; -import DraggableVisualization from '../../DraggableVisualization'; -import { VariableDescriptor } from '../../../../core/types/variable'; +} from '../../types'; +import DraggableVisualization from '../../../DraggableVisualization'; +import { VariableDescriptor } from '../../../../../core/types/variable'; import { useQuery, UseQueryResult } from '@tanstack/react-query'; import { BoundsViewport } from '@veupathdb/components/lib/map/Types'; -import { GeoConfig } from '../../../../core/types/geoConfig'; +import { GeoConfig } from '../../../../../core/types/geoConfig'; import Spinner from '@veupathdb/components/lib/components/Spinner'; import { useLittleFilters, UseLittleFiltersFuncProps, -} from '../../littleFilters'; -import TimeSliderQuickFilter from '../../TimeSliderQuickFilter'; -import { SubStudies } from '../../SubStudies'; -import { MapTypeHeaderStudyDetails } from '../MapTypeHeaderStudyDetails'; -import { STUDIES_ENTITY_ID, STUDY_ID_VARIABLE_ID } from '../../../constants'; -import { PanelConfig } from '../../appState'; +} from '../../../littleFilters'; +import TimeSliderQuickFilter from '../../../TimeSliderQuickFilter'; +import { SubStudies } from '../../../SubStudies'; +import { MapTypeHeaderStudyDetails } from '../../MapTypeHeaderStudyDetails'; +import { STUDIES_ENTITY_ID, STUDY_ID_VARIABLE_ID } from '../../../../constants'; +import { PanelConfig } from '../../../Types'; const displayName = 'Bubbles'; -export const plugin: MapTypePlugin = { +export const plugin: MapTypePlugin = { + type: 'bubble', + IconComponent: BubbleMarkerIcon, displayName, + getDefaultConfig({ defaultVariable }) { + return { + type: 'bubble', + selectedVariable: defaultVariable, + aggregator: 'mean', + numeratorValues: undefined, + denominatorValues: undefined, + legendPanelConfig: { + variable: DEFAULT_DRAGGABLE_LEGEND_POSITION, + count: { + x: window.innerWidth, + y: 420, + }, + }, + visualizationPanelConfig: defaultVisualizationPanelConfig, + }; + }, ConfigPanelComponent: BubbleMapConfigurationPanel, MapLayerComponent: BubbleMapLayer, MapOverlayComponent: BubbleLegendsAndFloater, @@ -93,7 +118,9 @@ type BubbleLegendData = { bubbleValueToLegendTickMapper: ((val: number) => string) | undefined; }; -function BubbleMapConfigurationPanel(props: MapTypeConfigPanelProps) { +function BubbleMapConfigurationPanel( + props: MapTypeConfigPanelProps +) { const { apps, analysisState, @@ -214,16 +241,14 @@ function BubbleMapConfigurationPanel(props: MapTypeConfigPanelProps) { /** * Renders markers */ -function BubbleMapLayer(props: MapTypeMapLayerProps) { +function BubbleMapLayer( + props: MapTypeMapLayerProps +) { const { studyId, filters, appState, - appState: { - boundsZoomLevel, - markerConfigurations, - activeMarkerConfigurationType, - }, + appState: { boundsZoomLevel }, updateConfiguration, geoConfigs, } = props; @@ -284,10 +309,7 @@ function BubbleMapLayer(props: MapTypeMapLayerProps) { )); - const selectedMarkers = markerConfigurations.find( - (markerConfiguration) => - markerConfiguration.type === activeMarkerConfigurationType - )?.selectedMarkers; + const selectedMarkers = configuration.selectedMarkers; return ( <> @@ -309,7 +331,9 @@ function BubbleMapLayer(props: MapTypeMapLayerProps) { ); } -function BubbleLegendsAndFloater(props: MapTypeMapLayerProps) { +function BubbleLegendsAndFloater( + props: MapTypeMapLayerProps +) { const { studyId, filters, @@ -496,7 +520,9 @@ function BubbleLegendsAndFloater(props: MapTypeMapLayerProps) { ); } -function MapTypeHeaderDetails(props: MapTypeMapLayerProps) { +function MapTypeHeaderDetails( + props: MapTypeMapLayerProps +) { const { studyEntities, filters, @@ -547,7 +573,9 @@ const substudyFilterFuncs = [ selectedMarkersLittleFilter, ]; -export function TimeSliderComponent(props: MapTypeMapLayerProps) { +export function TimeSliderComponent( + props: MapTypeMapLayerProps +) { const { studyId, studyEntities, @@ -1009,7 +1037,7 @@ function markerConfigLittleFilter(props: UseLittleFiltersFuncProps): Filter[] { const activeMarkerConfiguration = markerConfigurations.find( (markerConfig) => markerConfig.type === activeMarkerConfigurationType - ); + ) as BubbleMarkerConfiguration; // This doesn't seem ideal. Do we ever have no active config? if (activeMarkerConfiguration == null) return []; diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarMarkerType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarMarkerType.tsx new file mode 100644 index 0000000000..452f840589 --- /dev/null +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarMarkerType.tsx @@ -0,0 +1,430 @@ +import { preorder } from '@veupathdb/wdk-client/lib/Utils/TreeUtils'; +import { + MapTypeConfigPanelProps, + MapTypeMapLayerProps, + MapTypePlugin, +} from '../../types'; +import { + CollectionVariableTreeNode, + Filter, + StandaloneCollectionsMarkerDataRequest, + StudyEntity, + Variable, + useDataClient, + useFindEntityAndVariableCollection, + useStudyEntities, +} from '../../../../../core'; +import { VariableCollectionSelectList } from '../../../../../core/components/variableSelectors/VariableCollectionSingleSelect'; +import { SelectList } from '@veupathdb/coreui'; +import { Item } from '@veupathdb/coreui/lib/components/inputs/checkboxes/CheckboxList'; +import { DraggableLegendPanel } from '../../../DraggableLegendPanel'; +import { MapLegend } from '../../../MapLegend'; +import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend'; +import { BarPlotMarkerIcon } from '../../MarkerConfiguration/icons'; +import { difference, noop, union, uniq } from 'lodash'; +import { Mesa } from '@veupathdb/coreui'; +import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots'; +import { useQuery } from '@tanstack/react-query'; +import { BoundsViewport } from '@veupathdb/components/lib/map/Types'; +import { GeoConfig } from '../../../../../core/types/geoConfig'; +import { defaultAnimation, useCommonData } from '../../shared'; +import SemanticMarkers from '@veupathdb/components/lib/map/SemanticMarkers'; +import ChartMarker, { + BaseMarkerData, +} from '@veupathdb/components/lib/map/ChartMarker'; +import { BoundsDriftMarkerProps } from '@veupathdb/components/lib/map/BoundsDriftMarker'; +import { MapFloatingErrorDiv } from '../../../MapFloatingErrorDiv'; +import { mFormatter } from '../../../../../core/utils/big-number-formatters'; +import Spinner from '@veupathdb/components/lib/components/Spinner'; + +const displayName = 'Bar plots'; + +interface CollectionBarMarkerConfiguration { + type: 'collection-barplot'; + entityId: string; + collectionId: string; + selectedVariableIds: string[]; + selectedValues: string[]; +} + +export const plugin: MapTypePlugin = { + type: 'collection-barplot', + IconComponent: BarPlotMarkerIcon, + displayName, + getDefaultConfig({ study }) { + const firstCollectionWithEntity = Array.from( + preorder(study.rootEntity, (e) => e.children ?? []) + ) + .flatMap( + (e) => + e.collections + ?.filter((c) => c.dataShape === 'categorical') + .map((c): [StudyEntity, CollectionVariableTreeNode] => [e, c]) ?? [] + ) + .at(0); + if (firstCollectionWithEntity == null) + throw new Error('This study does not have any collections.'); + const [entity, collection] = firstCollectionWithEntity; + return { + type: 'collection-barplot', + entityId: entity.id, + collectionId: collection.id, + selectedVariableIds: collection.memberVariableIds.slice(0, 8), + selectedValues: collection.vocabulary ?? [], + }; + }, + ConfigPanelComponent, + MapLayerComponent, + MapOverlayComponent, + MapTypeHeaderDetails, +}; + +function ConfigPanelComponent( + props: MapTypeConfigPanelProps +) { + const configuration = props.configuration as CollectionBarMarkerConfiguration; + const { updateConfiguration } = props; + const findEntityAndCollection = useFindEntityAndVariableCollection(); + const { entity, variableCollection } = + findEntityAndCollection({ + entityId: configuration.entityId, + collectionId: configuration.collectionId, + }) ?? {}; + const variablesById = new Map(entity?.variables.map((v) => [v.id, v])); + const variables: Variable[] = + variableCollection?.memberVariableIds + .map((id) => variablesById.get(id)) + .filter(Variable.is) ?? []; + + const valueCheckboxListItems = variableCollection?.vocabulary?.map( + (value): Item => ({ + display: value, + value, + }) + ); + return ( +
+

+ Marker preview: +

+
TBD
+ +

+ Color: +

+
+
+ + c.dataShape === 'categorical'} + value={{ + entityId: configuration.entityId, + collectionId: configuration.collectionId, + }} + onSelect={function ( + value?: + | { entityId: string; collectionId: string } + | string + | undefined + ): void { + if (value == null || typeof value === 'string') return; + const { entity, variableCollection } = + findEntityAndCollection(value) ?? {}; + if (entity == null || variableCollection == null) return; + const selectedVariableIds = + variableCollection.memberVariableIds.slice(0, 8); + const selectedValues = uniq( + entity.variables + .filter((v): v is Variable => + selectedVariableIds.includes(v.id) + ) + .flatMap((v) => v.vocabulary ?? []) + ); + + updateConfiguration({ + type: 'collection-barplot', + entityId: entity.id, + collectionId: variableCollection.id, + selectedVariableIds, + selectedValues, + }); + }} + /> +
+
+ + configuration.selectedVariableIds.includes(row.id), + }, + eventHandlers: { + onSearch: noop, + onSort: noop, + onRowSelect: (row) => { + updateConfiguration({ + ...configuration, + selectedVariableIds: union( + configuration.selectedVariableIds, + [row.id] + ), + }); + }, + onRowDeselect: (row) => { + updateConfiguration({ + ...configuration, + selectedVariableIds: difference( + configuration.selectedVariableIds, + [row.id] + ), + }); + }, + onMultipleRowDeselect: (rows) => { + updateConfiguration({ + ...configuration, + selectedVariableIds: difference( + configuration.selectedVariableIds, + rows.map((r) => r.id) + ), + }); + }, + onMultipleRowSelect: (rows) => { + updateConfiguration({ + ...configuration, + selectedVariableIds: union( + configuration.selectedVariableIds, + rows.map((r) => r.id) + ), + }); + }, + }, + }} + /> +
+
+ + +
+
+
+ ); +} + +function MapLayerComponent( + props: MapTypeMapLayerProps +) { + const markerData = useMarkerData({ + studyId: props.studyId, + boundsZoomLevel: props.appState.boundsZoomLevel, + configuration: props.configuration as CollectionBarMarkerConfiguration, + geoConfigs: props.geoConfigs, + }); + + if (markerData.isError) { + return ; + } + + // TODO Order based on collection vocab + const markers = markerData.data?.markers.map((marker, index) => { + const data: BaseMarkerData[] = marker.overlayValues.map((entry, index) => ({ + color: ColorPaletteDefault[index], + label: entry.variableId, + value: Number(entry.value) || 0, + count: Number(entry.value) || 0, + })); + const bounds: BoundsDriftMarkerProps['bounds'] = { + southWest: { lat: marker.minLat, lng: marker.minLon }, + northEast: { lat: marker.maxLat, lng: marker.maxLon }, + }; + const position = { lat: marker.avgLat, lng: marker.avgLon }; + + return ( + + ); + }); + + if (markers == null) return null; + + return ( + <> + {markerData.isFetching && } + ; + + ); +} + +function MapOverlayComponent( + props: MapTypeMapLayerProps +) { + const configuration = props.configuration as CollectionBarMarkerConfiguration; + const { headerButtons } = props; + + const findEntityAndCollection = useFindEntityAndVariableCollection(); + const { entity, variableCollection } = + findEntityAndCollection(configuration) ?? {}; + + const noDataError = null; + + const legendItems = entity?.variables + .filter((variable) => + configuration.selectedVariableIds.includes(variable.id) + ) + .map( + (variable, index): LegendItemsProps => ({ + label: variable.displayName, + marker: 'square', + markerColor: ColorPaletteDefault[index], + hasData: true, + }) + ); + + return ( + +
+ {noDataError ?? ( + + )} +
+
+ ); +} + +function MapTypeHeaderDetails() { + return
I am a map type header details component
; +} + +interface MarkerDataProps { + boundsZoomLevel?: BoundsViewport; + configuration: CollectionBarMarkerConfiguration; + geoConfigs: GeoConfig[]; + studyId: string; + filters?: Filter[]; +} + +function useMarkerData({ + boundsZoomLevel, + configuration, + geoConfigs, + studyId, + filters, +}: MarkerDataProps) { + const dataClient = useDataClient(); + const studyEntities = useStudyEntities(); + + const { + outputEntity, + latitudeVariable, + longitudeVariable, + geoAggregateVariable, + viewport, + } = useCommonData( + // FIXME Using a fake overlay variable. We don't need this here. + { + variableId: configuration.selectedVariableIds[0], + entityId: configuration.entityId, + }, + geoConfigs, + studyEntities, + boundsZoomLevel + ); + + const collection = studyEntities + .filter((entity) => entity.id === configuration.entityId) + .flatMap((entity) => + entity.collections?.filter( + (collection) => collection.id === configuration.collectionId + ) + ) + .at(0); + + if (collection == null) { + throw new Error('Could not find collection'); + } + + const requestParams: StandaloneCollectionsMarkerDataRequest = { + studyId, + filters, + config: { + outputEntityId: outputEntity.id, + geoAggregateVariable, + longitudeVariable, + latitudeVariable, + viewport, + collectionOverlay: { + collection: { + entityId: configuration.entityId, + collectionId: configuration.collectionId, + }, + selectedMembers: configuration.selectedVariableIds, + }, + aggregatorConfig: { + overlayType: collection.dataShape as any, + numeratorValues: configuration.selectedValues, + // FIXME Use all values for denominator + denominatorValues: (collection as any).vocabulary, + }, + }, + }; + + return useQuery({ + queryKey: [requestParams], + queryFn: async () => { + return dataClient.getStandaloneCollectionsMarkerData( + 'standalone-map', + requestParams + ); + }, + }); +} diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarPlotMarkerConfigurationMenu.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarPlotMarkerConfigurationMenu.tsx new file mode 100644 index 0000000000..bc87fda65d --- /dev/null +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/collection-barplot/CollectionBarPlotMarkerConfigurationMenu.tsx @@ -0,0 +1,336 @@ +import { useCallback } from 'react'; +import { + InputVariables, + Props as InputVariablesProps, +} from '../../../../../core/components/visualizations/InputVariables'; +import RadioButtonGroup from '@veupathdb/components/lib/components/widgets/RadioButtonGroup'; +import { VariablesByInputName } from '../../../../../core/utils/data-element-constraints'; +import { + usePromise, + AllValuesDefinition, + OverlayConfig, + Variable, + Filter, +} from '../../../../../core'; +import { CategoricalMarkerConfigurationTable } from '../../MarkerConfiguration/CategoricalMarkerConfigurationTable'; +import { CategoricalMarkerPreview } from '../../MarkerConfiguration/CategoricalMarkerPreview'; +import Barplot from '@veupathdb/components/lib/plots/Barplot'; +import { SubsettingClient } from '../../../../../core/api'; +import { Toggle } from '@veupathdb/coreui'; +import { useUncontrolledSelections } from '../../../hooks/uncontrolledSelections'; +import { gray } from '@veupathdb/coreui/lib/definitions/colors'; +import { + BinningMethod, + SelectedCountsOption, + SelectedValues, + SharedMarkerConfigurations, +} from '../../shared'; + +interface MarkerConfiguration { + type: T; +} + +export interface BarPlotMarkerConfiguration + extends MarkerConfiguration<'collection-barplot'>, + SharedMarkerConfigurations { + selectedPlotMode: 'count' | 'proportion'; + dependentAxisLogScale: boolean; + binningMethod: BinningMethod; + selectedValues: SelectedValues; + selectedCountsOption: SelectedCountsOption; +} + +interface Props + extends Omit< + InputVariablesProps, + 'onChange' | 'selectedVariables' | 'selectedPlotMode' | 'onPlotSelected' + > { + onChange: (configuration: BarPlotMarkerConfiguration) => void; + configuration: BarPlotMarkerConfiguration; + overlayConfiguration: OverlayConfig | undefined; + overlayVariable: Variable | undefined; + subsettingClient: SubsettingClient; + studyId: string; + filters: Filter[] | undefined; + continuousMarkerPreview: JSX.Element | undefined; + /** + * Always used for categorical marker preview. Also used in categorical table if selectedCountsOption is 'filtered' + */ + allFilteredCategoricalValues: AllValuesDefinition[] | undefined; + /** + * Only defined and used in categorical table if selectedCountsOption is 'visible' + */ + allVisibleCategoricalValues: AllValuesDefinition[] | undefined; +} + +// TODO: generalize this and PieMarkerConfigMenu into MarkerConfigurationMenu. Lots of code repetition... + +export function BarPlotMarkerConfigurationMenu({ + entities, + onChange, + starredVariables, + toggleStarredVariable, + configuration, + constraints, + overlayConfiguration, + overlayVariable, + subsettingClient, + studyId, + filters, + continuousMarkerPreview, + allFilteredCategoricalValues, + allVisibleCategoricalValues, +}: Props) { + /** + * Used to track the CategoricalMarkerConfigurationTable's selection state, which allows users to + * select more than the allowable limit. Doing so results in a message to the user that they've selected + * too many values. The state is lifted up (versus living in CategoricalMarkerConfigurationTable) in order + * to pass its length to CategoricalMarkerPreview. + */ + const { uncontrolledSelections, setUncontrolledSelections } = + useUncontrolledSelections( + overlayConfiguration?.overlayType === 'categorical' + ? overlayConfiguration?.overlayValues + : undefined + ); + + const barplotData = usePromise( + useCallback(async () => { + if ( + !overlayVariable || + overlayConfiguration?.overlayType !== 'continuous' || + !('distributionDefaults' in overlayVariable) + ) + return; + const binSpec = { + displayRangeMin: + overlayVariable.distributionDefaults.rangeMin + + (overlayVariable.type === 'date' ? 'T00:00:00Z' : ''), + displayRangeMax: + overlayVariable.distributionDefaults.rangeMax + + (overlayVariable.type === 'date' ? 'T00:00:00Z' : ''), + binWidth: overlayVariable.distributionDefaults.binWidth ?? 1, + binUnits: + 'binUnits' in overlayVariable.distributionDefaults + ? overlayVariable.distributionDefaults.binUnits + : undefined, + }; + const distributionResponse = await subsettingClient.getDistribution( + studyId, + configuration.selectedVariable.entityId, + configuration.selectedVariable.variableId, + { + valueSpec: 'count', + filters: filters ?? [], + binSpec, + } + ); + return { + name: '', + value: distributionResponse.histogram.map((d) => d.value), + label: distributionResponse.histogram.map((d) => d.binLabel), + showValues: false, + color: '#333', + }; + }, [ + studyId, + overlayVariable, + overlayConfiguration?.overlayType, + subsettingClient, + filters, + configuration.selectedVariable, + ]) + ); + + function handleInputVariablesOnChange(selection: VariablesByInputName) { + if (!selection.overlayVariable) { + console.error( + `Expected overlay to defined but got ${typeof selection.overlayVariable}` + ); + return; + } + + onChange({ + ...configuration, + selectedVariable: selection.overlayVariable, + selectedValues: undefined, + }); + } + function handlePlotModeSelection(option: string) { + onChange({ + ...configuration, + selectedPlotMode: + option as BarPlotMarkerConfiguration['selectedPlotMode'], + }); + } + function handleBinningMethodSelection(option: string) { + onChange({ + ...configuration, + binningMethod: option as BarPlotMarkerConfiguration['binningMethod'], + }); + } + function handleLogScaleChange(option: boolean) { + onChange({ + ...configuration, + dependentAxisLogScale: option, + }); + } + + return ( +
+

+ Color: +

+ {/* limit inputVariables width */} +
+ +
+
+
+ Summary marker (all filtered data) +
+ {overlayConfiguration?.overlayType === 'categorical' ? ( + <> + + + ) : ( + continuousMarkerPreview + )} +
+
+
+ Marker X-axis controls +
+ +
+
+
+ Marker Y-axis controls +
+ + +
+ {overlayConfiguration?.overlayType === 'categorical' && ( +
+ +
+ )} + {overlayConfiguration?.overlayType === 'continuous' && barplotData.value && ( +
+ + Raw distribution of overall filtered data + + +
+ )} +
+ ); +} diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/DonutMarkerMapType.tsx similarity index 90% rename from packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/DonutMarkerMapType.tsx index 34ed1d2b28..31e35d1253 100755 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/DonutMarkerMapType.tsx @@ -17,23 +17,26 @@ import { STUDY_ID_VARIABLE_ID, UNSELECTED_DISPLAY_TEXT, UNSELECTED_TOKEN, -} from '../../../constants'; +} from '../../../../constants'; import { StandaloneMapMarkersResponse, Variable, useFindEntityAndVariable, useSubsettingClient, -} from '../../../../core'; -import { useToggleStarredVariable } from '../../../../core/hooks/starredVariables'; -import { kFormatter } from '../../../../core/utils/big-number-formatters'; -import { findEntityAndVariable } from '../../../../core/utils/study-metadata'; -import { DraggableLegendPanel } from '../../DraggableLegendPanel'; -import { MapLegend } from '../../MapLegend'; +} from '../../../../../core'; +import { useToggleStarredVariable } from '../../../../../core/hooks/starredVariables'; +import { kFormatter } from '../../../../../core/utils/big-number-formatters'; +import { findEntityAndVariable } from '../../../../../core/utils/study-metadata'; +import { + DEFAULT_DRAGGABLE_LEGEND_POSITION, + DraggableLegendPanel, +} from '../../../DraggableLegendPanel'; +import { MapLegend } from '../../../MapLegend'; import { sharedStandaloneMarkerProperties } from '../../MarkerConfiguration/CategoricalMarkerPreview'; import { PieMarkerConfiguration, PieMarkerConfigurationMenu, -} from '../../MarkerConfiguration/PieMarkerConfigurationMenu'; +} from './PieMarkerConfigurationMenu'; import { DistributionMarkerDataProps, defaultAnimation, @@ -53,31 +56,48 @@ import { useSelectedMarkerSnackbars, selectedMarkersLittleFilter, useFloatingPanelHandlers, -} from '../shared'; + defaultVisualizationPanelConfig, +} from '../../shared'; import { MapTypeConfigPanelProps, MapTypeMapLayerProps, MapTypePlugin, -} from '../types'; -import DraggableVisualization from '../../DraggableVisualization'; -import { useStandaloneVizPlugins } from '../../hooks/standaloneVizPlugins'; +} from '../../types'; +import DraggableVisualization from '../../../DraggableVisualization'; +import { useStandaloneVizPlugins } from '../../../hooks/standaloneVizPlugins'; import { MapTypeConfigurationMenu, MarkerConfigurationOption, } from '../../MarkerConfiguration/MapTypeConfigurationMenu'; -import { DonutMarkersIcon } from '../../MarkerConfiguration/icons'; +import { + DonutMarkerIcon, + DonutMarkersIcon, +} from '../../MarkerConfiguration/icons'; import { TabbedDisplayProps } from '@veupathdb/coreui/lib/components/grids/TabbedDisplay'; -import MapVizManagement from '../../MapVizManagement'; +import MapVizManagement from '../../../MapVizManagement'; import Spinner from '@veupathdb/components/lib/components/Spinner'; -import { MapTypeHeaderStudyDetails } from '../MapTypeHeaderStudyDetails'; -import { SubStudies } from '../../SubStudies'; -import { useLittleFilters } from '../../littleFilters'; -import TimeSliderQuickFilter from '../../TimeSliderQuickFilter'; +import { MapTypeHeaderStudyDetails } from '../../MapTypeHeaderStudyDetails'; +import { SubStudies } from '../../../SubStudies'; +import { useLittleFilters } from '../../../littleFilters'; +import TimeSliderQuickFilter from '../../../TimeSliderQuickFilter'; const displayName = 'Donuts'; -export const plugin: MapTypePlugin = { +export const plugin: MapTypePlugin = { + type: 'pie', + IconComponent: DonutMarkerIcon, displayName, + getDefaultConfig({ defaultVariable }) { + return { + type: 'pie', + selectedVariable: defaultVariable, + selectedValues: undefined, + binningMethod: undefined, + selectedCountsOption: 'filtered', + legendPanelConfig: DEFAULT_DRAGGABLE_LEGEND_POSITION, + visualizationPanelConfig: defaultVisualizationPanelConfig, + }; + }, ConfigPanelComponent, MapLayerComponent, MapOverlayComponent, @@ -85,7 +105,9 @@ export const plugin: MapTypePlugin = { TimeSliderComponent, }; -function ConfigPanelComponent(props: MapTypeConfigPanelProps) { +function ConfigPanelComponent( + props: MapTypeConfigPanelProps +) { const { apps, analysisState, @@ -304,16 +326,14 @@ function ConfigPanelComponent(props: MapTypeConfigPanelProps) { ); } -function MapLayerComponent(props: MapTypeMapLayerProps) { +function MapLayerComponent( + props: MapTypeMapLayerProps +) { const { studyId, studyEntities, appState, - appState: { - boundsZoomLevel, - markerConfigurations, - activeMarkerConfigurationType, - }, + appState: { boundsZoomLevel }, geoConfigs, filters, updateConfiguration, @@ -326,7 +346,8 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { binningMethod, selectedValues, activeVisualizationId, - } = configuration; + selectedMarkers, + } = props.configuration as PieMarkerConfiguration; const { filters: filtersForMarkerData } = useLittleFilters( { @@ -383,11 +404,6 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { )); - const selectedMarkers = markerConfigurations.find( - (markerConfiguration) => - markerConfiguration.type === activeMarkerConfigurationType - )?.selectedMarkers; - return ( <> {markerDataResponse.isFetching && } @@ -408,7 +424,9 @@ function MapLayerComponent(props: MapTypeMapLayerProps) { ); } -function MapOverlayComponent(props: MapTypeMapLayerProps) { +function MapOverlayComponent( + props: MapTypeMapLayerProps +) { const { studyId, studyEntities, @@ -550,7 +568,9 @@ function MapOverlayComponent(props: MapTypeMapLayerProps) { ); } -function MapTypeHeaderDetails(props: MapTypeMapLayerProps) { +function MapTypeHeaderDetails( + props: MapTypeMapLayerProps +) { const { studyEntities, appState, @@ -600,7 +620,9 @@ const substudyFilterFuncs = [ selectedMarkersLittleFilter, ]; -export function TimeSliderComponent(props: MapTypeMapLayerProps) { +export function TimeSliderComponent( + props: MapTypeMapLayerProps +) { const { studyId, studyEntities, diff --git a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/PieMarkerConfigurationMenu.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/PieMarkerConfigurationMenu.tsx similarity index 91% rename from packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/PieMarkerConfigurationMenu.tsx rename to packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/PieMarkerConfigurationMenu.tsx index e2a9739421..1e6ca89373 100644 --- a/packages/libs/eda/src/lib/map/analysis/MarkerConfiguration/PieMarkerConfigurationMenu.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/donut/PieMarkerConfigurationMenu.tsx @@ -2,31 +2,30 @@ import { useCallback } from 'react'; import { InputVariables, Props as InputVariablesProps, -} from '../../../core/components/visualizations/InputVariables'; -import { VariablesByInputName } from '../../../core/utils/data-element-constraints'; +} from '../../../../../core/components/visualizations/InputVariables'; +import { VariablesByInputName } from '../../../../../core/utils/data-element-constraints'; import { usePromise, AllValuesDefinition, OverlayConfig, Variable, Filter, -} from '../../../core'; -import { CategoricalMarkerConfigurationTable } from './CategoricalMarkerConfigurationTable'; -import { CategoricalMarkerPreview } from './CategoricalMarkerPreview'; +} from '../../../../../core'; +import { CategoricalMarkerConfigurationTable } from '../../MarkerConfiguration/CategoricalMarkerConfigurationTable'; +import { CategoricalMarkerPreview } from '../../MarkerConfiguration/CategoricalMarkerPreview'; import Barplot from '@veupathdb/components/lib/plots/Barplot'; -import { SubsettingClient } from '../../../core/api'; +import { SubsettingClient } from '../../../../../core/api'; import RadioButtonGroup from '@veupathdb/components/lib/components/widgets/RadioButtonGroup'; -import { useUncontrolledSelections } from '../hooks/uncontrolledSelections'; +import { useUncontrolledSelections } from '../../../hooks/uncontrolledSelections'; import { BinningMethod, - PanelConfig, - PanelPositionConfig, SelectedCountsOption, SelectedValues, -} from '../appState'; -import { SharedMarkerConfigurations } from '../mapTypes/shared'; -import { GeoConfig } from '../../../core/types/geoConfig'; -import { findLeastAncestralGeoConfig } from '../../../core/utils/geoVariables'; + SharedMarkerConfigurations, +} from '../../shared'; +import { GeoConfig } from '../../../../../core/types/geoConfig'; +import { findLeastAncestralGeoConfig } from '../../../../../core/utils/geoVariables'; +import { PanelConfig, PanelPositionConfig } from '../../../Types'; interface MarkerConfiguration { type: T; @@ -137,6 +136,7 @@ export function PieMarkerConfigurationMenu({ color: '#333', }; }, [ + studyId, overlayVariable, overlayConfiguration?.overlayType, subsettingClient, diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/shared.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/shared.tsx index 1ccc45cc0a..3916c97713 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/shared.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/shared.tsx @@ -1,3 +1,4 @@ +import * as t from 'io-ts'; import geohashAnimation from '@veupathdb/components/lib/map/animation_functions/geohash'; import { defaultAnimationDuration } from '@veupathdb/components/lib/map/config/map'; import { VariableDescriptor } from '../../../core/types/variable'; @@ -39,15 +40,13 @@ import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; import { NoDataError } from '../../../core/api/DataClient/NoDataError'; import { useCallback, useState } from 'react'; import useSnackbar from '@veupathdb/coreui/lib/components/notifications/useSnackbar'; -import { - BubbleLegendPositionConfig, - PanelConfig, - PanelPositionConfig, -} from '../appState'; +import { PieMarkerConfiguration } from './plugins/donut/PieMarkerConfigurationMenu'; +import { BarPlotMarkerConfiguration } from './plugins/barplot/BarPlotMarkerConfigurationMenu'; import { findLeastAncestralGeoConfig, getGeoConfig, } from '../../../core/utils/geoVariables'; +import { PanelConfig, PanelPositionConfig } from '../Types'; export const defaultAnimation = { method: 'geohash', @@ -55,6 +54,53 @@ export const defaultAnimation = { duration: defaultAnimationDuration, }; +const BubbleLegendPositionConfig = t.type({ + variable: PanelPositionConfig, + count: PanelPositionConfig, +}); + +// eslint-disable-next-line @typescript-eslint/no-redeclare +type BubbleLegendPositionConfig = t.TypeOf; + +export const DEFAULT_DRAGGABLE_VIZ_POSITION = { + x: 535, + y: 220, +}; + +export const DEFAULT_DRAGGABLE_VIZ_DIMENSIONS = { + width: 'auto', + height: 'auto', +}; + +export const defaultVisualizationPanelConfig = { + isVisible: false, + hideVizControl: false, + position: DEFAULT_DRAGGABLE_VIZ_POSITION, + dimensions: DEFAULT_DRAGGABLE_VIZ_DIMENSIONS, +}; + +// user-specified selection +export type SelectedValues = t.TypeOf; +// eslint-disable-next-line @typescript-eslint/no-redeclare +const SelectedValues = t.union([t.array(t.string), t.undefined]); + +export type BinningMethod = t.TypeOf; +// eslint-disable-next-line @typescript-eslint/no-redeclare +const BinningMethod = t.union([ + t.literal('equalInterval'), + t.literal('quantile'), + t.literal('standardDeviation'), + t.undefined, +]); + +export type SelectedCountsOption = t.TypeOf; +// eslint-disable-next-line @typescript-eslint/no-redeclare +const SelectedCountsOption = t.union([ + t.literal('filtered'), + t.literal('visible'), + t.undefined, +]); + export const markerDataFilterFuncs = [timeSliderLittleFilter]; export const floaterFilterFuncs = [ timeSliderLittleFilter, @@ -593,7 +639,7 @@ export function pieOrBarMarkerConfigLittleFilter( const activeMarkerConfiguration = markerConfigurations.find( (markerConfig) => markerConfig.type === activeMarkerConfigurationType - ); + ) as PieMarkerConfiguration | BarPlotMarkerConfiguration; // This doesn't seem ideal. Do we ever have no active config? if (activeMarkerConfiguration == null) return []; diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/types.ts b/packages/libs/eda/src/lib/map/analysis/mapTypes/types.ts index c28674f83a..b3150ae005 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/types.ts +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/types.ts @@ -1,20 +1,24 @@ -import { ComponentType } from 'react'; +import { Bounds as BoundsProp } from '@veupathdb/components/lib/map/Types'; +import { ComponentType, SVGProps } from 'react'; import { AnalysisState, Filter, PromiseHookState, StudyEntity, + StudyMetadata, } from '../../../core'; +import { EntityCounts } from '../../../core/hooks/entityCounts'; import { GeoConfig } from '../../../core/types/geoConfig'; +import { VariableDescriptor } from '../../../core/types/variable'; import { ComputationAppOverview } from '../../../core/types/visualization'; -import { AppState, PanelConfig } from '../appState'; -import { EntityCounts } from '../../../core/hooks/entityCounts'; -import { SiteInformationProps } from '../Types'; -import { Bounds as BoundsProp } from '@veupathdb/components/lib/map/Types'; +import { + AppState, + MarkerConfiguration, + PanelConfig, + SiteInformationProps, +} from '../Types'; -// should we just use one type: MapTypeMapLayerProps? -// and get rid of this one? -export interface MapTypeConfigPanelProps { +export interface MapTypeConfigPanelProps { apps: ComputationAppOverview[]; analysisState: AnalysisState; appState: AppState; @@ -23,11 +27,11 @@ export interface MapTypeConfigPanelProps { studyEntities: StudyEntity[]; geoConfigs: GeoConfig[]; configuration: unknown; - updateConfiguration: (configuration: unknown) => void; + updateConfiguration: (configuration: T) => void; setIsSidePanelExpanded: (isExpanded: boolean) => void; } -export interface MapTypeMapLayerProps { +export interface MapTypeMapLayerProps { apps: ComputationAppOverview[]; analysisState: AnalysisState; appState: AppState; @@ -36,7 +40,7 @@ export interface MapTypeMapLayerProps { studyEntities: StudyEntity[]; geoConfigs: GeoConfig[]; configuration: unknown; - updateConfiguration: (configuration: unknown) => void; + updateConfiguration: (configuration: T) => void; totalCounts: PromiseHookState; filteredCounts: PromiseHookState; setSelectedMarkers?: React.Dispatch>; @@ -54,29 +58,45 @@ export interface MapTypeMapLayerProps { * A plugin containing the pieces needed to render * and configure a map type */ -export interface MapTypePlugin { +export interface MapTypePlugin { + /** + * Unique identifier for the map type + */ + type: T['type']; /** * Display name of map type used for menu, etc. */ displayName: string; + /** + * Icon component + */ + IconComponent: ComponentType>; + /** + * Returns a default configuration for this MapType. This is used to + * create a set of default configurations for new analyses. + */ + getDefaultConfig(props: { + defaultVariable: VariableDescriptor; + study: StudyMetadata; + }): T; /** * Returns a ReactNode used for configuring the map type */ - ConfigPanelComponent: ComponentType; + ConfigPanelComponent: ComponentType>; /** * Returns a ReactNode that is rendered as a leaflet map layer */ - MapLayerComponent?: ComponentType; + MapLayerComponent?: ComponentType>; /** * Returns a ReactNode that is rendered on top of the map */ - MapOverlayComponent?: ComponentType; + MapOverlayComponent?: ComponentType>; /** * Returns a ReactNode that is rendered in the map header */ - MapTypeHeaderDetails?: ComponentType; + MapTypeHeaderDetails?: ComponentType>; /** * Returns a ReactNode that is rendered under the map header */ - TimeSliderComponent?: ComponentType; + TimeSliderComponent?: ComponentType>; } diff --git a/packages/libs/eda/src/lib/map/analysis/utils/defaultOverlayConfig.ts b/packages/libs/eda/src/lib/map/analysis/utils/defaultOverlayConfig.ts index d1453d9645..5d82e6ae67 100644 --- a/packages/libs/eda/src/lib/map/analysis/utils/defaultOverlayConfig.ts +++ b/packages/libs/eda/src/lib/map/analysis/utils/defaultOverlayConfig.ts @@ -11,8 +11,8 @@ import { Variable, } from '../../../core'; import { DataClient, SubsettingClient } from '../../../core/api'; -import { BinningMethod } from '../appState'; -import { BubbleMarkerConfiguration } from '../MarkerConfiguration/BubbleMarkerConfigurationMenu'; +import { BubbleMarkerConfiguration } from '../mapTypes/plugins/bubble/BubbleMarkerConfigurationMenu'; +import { BinningMethod } from '../mapTypes/shared'; // This async function fetches the default overlay config. // For continuous variables, this involves calling the filter-aware-metadata/continuous-variable diff --git a/packages/libs/web-common/src/controllers/LegacyMapRedirectHandler.tsx b/packages/libs/web-common/src/controllers/LegacyMapRedirectHandler.tsx index d39ab124e5..34e99d1b82 100644 --- a/packages/libs/web-common/src/controllers/LegacyMapRedirectHandler.tsx +++ b/packages/libs/web-common/src/controllers/LegacyMapRedirectHandler.tsx @@ -5,7 +5,7 @@ import { useConfiguredAnalysisClient } from '@veupathdb/eda/lib/core/hooks/clien import { createComputation } from '@veupathdb/eda/lib/core/components/computations/Utils'; import { makeNewAnalysis } from '@veupathdb/eda/lib/core'; import { RouteComponentProps } from 'react-router'; -import { LegacyRedirectState } from '@veupathdb/eda/lib/map/analysis/appState'; +import { LegacyRedirectState } from '@veupathdb/eda/lib/map/analysis/Types'; // Define constants to create new computations and analyses const MEGA_STUDY_ID = 'DS_480c976ef9';