Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,442 changes: 2,442 additions & 0 deletions assets/population_data_v3.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions assets/population_data_v3.json.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
SPDX-License-Identifier: CC0-1.0
22 changes: 22 additions & 0 deletions locales/de-global.json5
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@
'next-day-tooltip': 'Nächster Tag',
'play-pause-tooltip': 'Start/Pause',
'fullscreen-tooltip': 'Vollbild',
'display-settings': {
tooltip: 'Anzeigeeinstellungen für Daten',
title: 'Anzeigeeinstellungen',
'time-period': 'Aggregation',
'time-period-tooltip': 'Wählen Sie aus, wie Werte am ausgewählten Datum aggregiert werden',
total: 'Gesamt',
'one-day': '1T-neu',
'seven-days': '7T-neu',
'time-period-total-tooltip': 'Gesamtzahl der Fälle am ausgewählten Datum anzeigen',
'time-period-one-day-tooltip': 'Neue Fälle in den letzten 24 Stunden am ausgewählten Datum anzeigen',
'time-period-seven-days-tooltip': 'Neue Fälle in den letzten 7 Tagen am ausgewählten Datum anzeigen',
'number-type': 'Zahlenanzeige',
'number-type-tooltip': 'Fallzahl pro 100.000 Einwohner',
relative: 'Relativ',
absolute: 'Absolut',
'relative-tooltip': 'Fälle pro 100.000 Einwohner anzeigen',
'absolute-tooltip': 'Absolute Fallzahl anzeigen',
},
'selection-chip': {
'per-100k': 'pro 100.000 Einwohner',
},
},
history: {
placeholder: 'history',
Expand Down Expand Up @@ -79,6 +100,7 @@
edit: 'Farblegende bearbeiten',
lock: 'Maximalwert feststellen',
select: 'Farblegende auswählen',
population: 'Bevölkerung',
},
bottomTabs: {
timeSeries: 'Zeitreihe',
Expand Down
21 changes: 21 additions & 0 deletions locales/en-global.json5
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@
'next-day-tooltip': 'Next Day',
'play-pause-tooltip': 'Play/Pause',
'fullscreen-tooltip': 'Fullscreen',
'display-settings': {
tooltip: 'Data display settings',
'time-period': 'Aggregation Window',
'time-period-tooltip': 'Choose how to aggregate values at the selected date',
total: 'Total',
'one-day': '1D-new',
'seven-days': '7D-new',
'time-period-total-tooltip': 'Show the total number of cases at the selected date',
'time-period-one-day-tooltip': 'Show new cases in the last 24 hours at the selected date',
'time-period-seven-days-tooltip': 'Show new cases in the last 7 days at the selected date',
'number-type': 'Number Display',
'number-type-tooltip': 'Number of cases per 100k population',
relative: 'Relative',
absolute: 'Absolute',
'relative-tooltip': 'Show cases per 100k population',
'absolute-tooltip': 'Show absolute number of cases',
},
'selection-chip': {
'per-100k': 'per 100k Population',
},
},
sideBar: {
placeholder: 'Sidebar Content',
Expand Down Expand Up @@ -88,6 +108,7 @@
edit: 'Edit heatmap colors',
lock: 'Lock maximum value',
select: 'Select heatmap preset',
population: 'Population',
},
bottomTabs: {
timeSeries: 'Time Series',
Expand Down
266 changes: 232 additions & 34 deletions src/components/IconBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
// SPDX-License-Identifier: Apache-2.0

import React, {useEffect, useState} from 'react';
import React, {useEffect, useState, useContext, useMemo} from 'react';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import {useFullscreen} from 'rooks';
import Box from '@mui/material/Box';
Expand All @@ -11,21 +11,65 @@ import PauseRounded from '@mui/icons-material/PauseRounded';
import PlayArrowRounded from '@mui/icons-material/PlayArrowRounded';
import SkipNextRounded from '@mui/icons-material/SkipNextRounded';
import SkipPreviousRounded from '@mui/icons-material/SkipPreviousRounded';
import ToggleButton, {toggleButtonClasses} from '@mui/material/ToggleButton';
import Popover from '@mui/material/Popover';
import SettingsIcon from '@mui/icons-material/Settings';
import {useAppDispatch, useAppSelector} from 'store/hooks';
import {nextDay, previousDay, selectDate} from 'store/DataSelectionSlice';
import {
AggregationWindow,
nextDay,
previousDay,
selectDate,
setAggregationWindow,
toggleRelativeNumbers,
} from 'store/DataSelectionSlice';
import {useTranslation} from 'react-i18next';
import {styled, ToggleButtonGroup, toggleButtonGroupClasses, Typography} from '@mui/material';
import InfoOutlined from '@mui/icons-material/InfoOutlined';
import useTheme from '@mui/material/styles/useTheme';
import SelectionChip from './SelectionChip';
import Divider from '@mui/material/Divider';
import {DataContext} from 'context/SelectedDataContext';

const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({theme}) => ({
gap: '1rem',
[`& .${toggleButtonGroupClasses.firstButton}, & .${toggleButtonGroupClasses.middleButton}`]: {
borderTopRightRadius: theme.shape.borderRadius,
borderBottomRightRadius: theme.shape.borderRadius,
},
[`& .${toggleButtonGroupClasses.lastButton}, & .${toggleButtonGroupClasses.middleButton}`]: {
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius,
borderLeft: `1px solid ${theme.palette.divider}`,
},
[`& .${toggleButtonGroupClasses.lastButton}.${toggleButtonClasses.disabled}, & .${toggleButtonGroupClasses.middleButton}.${toggleButtonClasses.disabled}`]:
{
borderLeft: `1px solid ${theme.palette.action.disabledBackground}`,
},
}));

export default function IconBar(): JSX.Element {
const fsApi = useFullscreen();
const dispatch = useAppDispatch();
const {t} = useTranslation();

const theme = useTheme();
const [isPlaying, setIsPlaying] = useState(false);
const [justStarted, setJustStarted] = useState(false);

const {compartments} = useContext(DataContext)!;
const {t: tBackend, i18n: i18nBackend} = useTranslation('backend');
const selectedDay = useAppSelector((state) => state.dataSelection.date);
const minDate = useAppSelector((state) => state.dataSelection.minDate);
const maxDate = useAppSelector((state) => state.dataSelection.maxDate);
const relativeNumbers = useAppSelector((state) => state.dataSelection.relativeNumbers ?? false);
const aggregationWindow = useAppSelector((state) => state.dataSelection.aggregationWindow ?? AggregationWindow.Total);
const selectedCompartment = useAppSelector((state) => state.dataSelection.compartment ?? '');

// Settings popover state
const [settingsAnchorEl, setSettingsAnchorEl] = useState<HTMLElement | null>(null);
const settingsOpen = Boolean(settingsAnchorEl);
const openSettings = (event: React.MouseEvent<HTMLElement>) => setSettingsAnchorEl(event.currentTarget);
const closeSettings = () => setSettingsAnchorEl(null);

const toggleFullscreen = () => {
if (fsApi.isFullscreenEnabled) {
Expand All @@ -35,6 +79,22 @@ export default function IconBar(): JSX.Element {
}
};

const compartmentNames = useMemo(() => {
return (
compartments?.map((compartment) => {
const name = i18nBackend.exists(`infection-states.${compartment.name}`, {ns: 'backend'})
? tBackend(`infection-states.${compartment.name}`)
: compartment.name;

return {id: compartment.id, name};
}) ?? []
);
}, [compartments, i18nBackend, tBackend]);

const selectedCompartmentName = useMemo(() => {
return compartmentNames.find((compartment) => compartment.id === selectedCompartment)?.name ?? '';
}, [compartmentNames, selectedCompartment]);

useEffect(() => {
if (isPlaying) {
// if we are already on the last day, we start from the first day
Expand Down Expand Up @@ -64,43 +124,181 @@ export default function IconBar(): JSX.Element {
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
justifyContent: 'space-between',
alignItems: 'center',
height: '60px',
}}
>
<Tooltip title={t('icon-bar.previous-day-tooltip')}>
<span>
<Button
aria-label='previous-day-button'
disabled={selectedDay === minDate || isPlaying}
onClick={() => dispatch(previousDay())}
>
<SkipPreviousRounded />
{/* Settings popover trigger */}
<Box>
<Tooltip title={t('icon-bar.display-settings.tooltip')}>
<Button aria-label='display-settings' onClick={openSettings}>
<SettingsIcon />
</Button>
</span>
</Tooltip>
<Tooltip title={t('icon-bar.play-pause-tooltip')}>
<Button aria-label='play-pause-button' onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? <PauseRounded /> : <PlayArrowRounded />}
</Button>
</Tooltip>
<Tooltip title={t('icon-bar.next-day-tooltip')}>
<span>
<Button
aria-label='next-day-button'
disabled={selectedDay === maxDate || isPlaying}
onClick={() => dispatch(nextDay())}
>
<SkipNextRounded />
</Tooltip>
<Tooltip title={t('icon-bar.previous-day-tooltip')}>
<span>
<Button
aria-label='previous-day-button'
disabled={selectedDay === minDate || isPlaying}
onClick={() => dispatch(previousDay())}
>
<SkipPreviousRounded />
</Button>
</span>
</Tooltip>
<Tooltip title={t('icon-bar.play-pause-tooltip')}>
<Button aria-label='play-pause-button' onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? <PauseRounded /> : <PlayArrowRounded />}
</Button>
</Tooltip>
<Tooltip title={t('icon-bar.next-day-tooltip')}>
<span>
<Button
aria-label='next-day-button'
disabled={selectedDay === maxDate || isPlaying}
onClick={() => dispatch(nextDay())}
>
<SkipNextRounded />
</Button>
</span>
</Tooltip>
<Tooltip title={t('icon-bar.fullscreen-tooltip')}>
<Button onClick={toggleFullscreen}>
<FullscreenIcon />
</Button>
</span>
</Tooltip>
<Tooltip title={t('icon-bar.fullscreen-tooltip')}>
<Button onClick={toggleFullscreen}>
<FullscreenIcon />
</Button>
</Tooltip>
</Tooltip>
</Box>

<SelectionChip
relativeNumbers={relativeNumbers}
aggregationWindow={aggregationWindow}
selectedCompartment={selectedCompartmentName}
/>
{/* Settings Popover */}
<Popover
open={settingsOpen}
anchorEl={settingsAnchorEl}
onClose={closeSettings}
anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}
transformOrigin={{vertical: 'top', horizontal: 'left'}}
slotProps={{paper: {sx: {p: 2, minWidth: 280}}}}
>
<Box sx={{display: 'flex', flexDirection: 'column', gap: 4, padding: 2}}>
{/* Window row */}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 3,
width: '100%',
}}
>
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2}}>
<Typography variant='h3'>{t('icon-bar.display-settings.time-period')}</Typography>
<Tooltip arrow placement='right-start' title={t('icon-bar.display-settings.time-period-tooltip')}>
<InfoOutlined
sx={{
color: theme.palette.info.light,
fontSize: '1rem',
}}
/>
</Tooltip>
</Box>

<StyledToggleButtonGroup
size='small'
color='primary'
exclusive
aria-label='Aggregation window'
sx={{width: '100%', gap: 3}}
>
<Tooltip title={t('icon-bar.display-settings.time-period-total-tooltip')} arrow placement='bottom-start'>
<ToggleButton
value={AggregationWindow.Total}
selected={aggregationWindow === AggregationWindow.Total}
onClick={() => dispatch(setAggregationWindow(AggregationWindow.Total))}
sx={{width: '100%'}}
>
{t('icon-bar.display-settings.total')}
</ToggleButton>
</Tooltip>
<Divider orientation='vertical' flexItem />
<Tooltip
title={t('icon-bar.display-settings.time-period-one-day-tooltip')}
arrow
placement='bottom-start'
>
<ToggleButton
value={AggregationWindow.OneDay}
selected={aggregationWindow === AggregationWindow.OneDay}
onClick={() => dispatch(setAggregationWindow(AggregationWindow.OneDay))}
sx={{width: '100%'}}
>
{t('icon-bar.display-settings.one-day')}
</ToggleButton>
</Tooltip>
<Tooltip
title={t('icon-bar.display-settings.time-period-seven-days-tooltip')}
arrow
placement='bottom-start'
>
<ToggleButton
value={AggregationWindow.SevenDays}
selected={aggregationWindow === AggregationWindow.SevenDays}
onClick={() => dispatch(setAggregationWindow(AggregationWindow.SevenDays))}
sx={{width: '100%'}}
>
{t('icon-bar.display-settings.seven-days')}
</ToggleButton>
</Tooltip>
</StyledToggleButtonGroup>
</Box>

{/* Number type row */}
<Box sx={{display: 'flex', flexDirection: 'column', gap: 3}}>
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2}}>
<Typography variant='h3'>{t('icon-bar.display-settings.number-type')}</Typography>
<Tooltip arrow placement='right-start' title={t('icon-bar.display-settings.number-type-tooltip')}>
<InfoOutlined
sx={{
color: theme.palette.info.light,
fontSize: '1rem',
}}
/>
</Tooltip>
</Box>
<StyledToggleButtonGroup
size='small'
color='primary'
exclusive
aria-label='Aggregation window'
sx={{width: '100%', gap: 3}}
>
<Tooltip title={t('icon-bar.display-settings.relative-tooltip')} arrow placement='bottom-start'>
<ToggleButton
value='relative-numbers'
selected={relativeNumbers}
onClick={() => dispatch(toggleRelativeNumbers())}
sx={{width: '100%'}}
>
{t('icon-bar.display-settings.relative')}
</ToggleButton>
</Tooltip>
<Tooltip title={t('icon-bar.display-settings.absolute-tooltip')} arrow placement='bottom-start'>
<ToggleButton
value='absolute-numbers'
selected={!relativeNumbers}
onClick={() => dispatch(toggleRelativeNumbers())}
sx={{width: '100%'}}
>
{t('icon-bar.display-settings.absolute')}
</ToggleButton>
</Tooltip>
</StyledToggleButtonGroup>
</Box>
</Box>
</Popover>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type {Threshold} from 'types/threshold';
import type {District} from 'types/district';
import {useTranslation} from 'react-i18next';
import ThresholdSettings from './ThresholdSettings/ThresholdSettings';

/**
* The different views that can be displayed in the settings popover.
* You can add more views here if you want to add more settings.
Expand Down
Loading