Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add animated temporal progress bar #41

Merged
merged 6 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
118 changes: 58 additions & 60 deletions src/app/src/components/AnimatedMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ import { useMap } from 'react-leaflet';
import {
Heading,
Spacer,
HStack,
Slider,
Box,
SliderTrack,
SliderFilledTrack,
SliderThumb,
SliderMark,
IconButton,
Progress,
Tag,
TagLabel,
} from '@chakra-ui/react';

import L from 'leaflet';
import UsaMapContainer from './UsaMapContainer';
import { StateGeometry, StateProperties } from './states.geojson';

import StatesLayer from './StatesLayer';
import TimeControlIcon from './TimeControlIcon';

import {
MonthlySpendingOverTimeResponse,
SpendingByGeographyAtMonth,
} from '../types/api';
import { spendingDataByMonth } from './dummySpendingDataByMonth';
import AnimatedMapLegend from './AnimatedMapLegend';

export default function AnimatedMap() {
return (
Expand All @@ -31,32 +31,10 @@ export default function AnimatedMap() {
Allocation of announced award funding over time
</Heading>
<Spacer></Spacer>
<AnimatedMapLegend />
<UsaMapContainer>
<StatesAndSliderLayer spending={spendingDataByMonth} />
</UsaMapContainer>
<HStack spacing='0px' border={'1px'}>
<Box w='40px' h='40px' bg='white'></Box>
<Box
w='40px'
h='40px'
bg='#94A4DF'
textAlign={'center'}
color={'white'}
fontSize={'sm'}
>
≥1% BIL
</Box>
<Box
w='40px'
h='40px'
bg='#465EB5'
textAlign={'center'}
color={'white'}
fontSize={'sm'}
>
≥2% BIL
</Box>
</HStack>
</>
);
}
Expand All @@ -66,11 +44,14 @@ function StatesAndSliderLayer({
}: {
spending: MonthlySpendingOverTimeResponse;
}) {
const SLIDER_PRESENT_STEP = 26;
const PROGRESS_FINAL_MONTH = 26;
const map = useMap();
const [timeValue, setTimeValue] = useState(0);
const [spendingAtTimeByState, setSpendingAtTimeByState] = useState(() =>
getSpendingByStateAtTime(1, spending)
);
const [animationEnabled, setAnimationEnabled] = useState(false);
const [restartTimeControl, setRestartTimeControl] = useState(false);

useEffect(() => {
map &&
Expand All @@ -95,8 +76,33 @@ function StatesAndSliderLayer({
});
}, [map, spendingAtTimeByState]);

function onSliderChange(timeValue: number) {
setSpendingAtTimeByState(getSpendingByStateAtTime(timeValue, spending));
useEffect(() => {
(timeValue % 1 === 0) && spending && setSpendingAtTimeByState(getSpendingByStateAtTime(timeValue, spending));
if(timeValue === PROGRESS_FINAL_MONTH){
setAnimationEnabled(false);
setRestartTimeControl(true);
}
}, [timeValue, spending])

useEffect(() => {
if(animationEnabled){
const monthlyInterval = setInterval(() => {
setTimeValue(currentTimeValue => Math.round((currentTimeValue + 0.1)*10)/10);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a minute to figure out why this math is necessary. Then I realized:

.1 + .1 + .1 === .30000000000000004 // true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bah I know, adding in those smaller increments really help to break down the progress frames and flow like an animation but this line still feels unnecessarily hacky.

},
25
);
return () => {
clearInterval(monthlyInterval);
};
}
}, [animationEnabled]);

function onSelectTimeAnimation(){
if(restartTimeControl){
setTimeValue(0);
setRestartTimeControl(false);
}
setAnimationEnabled(true);
}

return (
Expand All @@ -118,34 +124,26 @@ function StatesAndSliderLayer({
});
}}
/>
<Slider
colorScheme={'blackAlpha'}
aria-label='date-time-slider'
defaultValue={0}
min={0}
max={SLIDER_PRESENT_STEP}
onChange={val => onSliderChange(val)}
step={1}
mt='575px'
width='50%'
ml='20%'
>
<SliderThumb ml='50px' />
<SliderMark value={0} mt='-2' mr='15' fontSize='m'>
2021
</SliderMark>
<SliderMark
value={SLIDER_PRESENT_STEP}
mt='-2'
ml='70'
fontSize='m'
>
present
</SliderMark>
<SliderTrack ml='50px'>
<SliderFilledTrack />
</SliderTrack>
</Slider>
<Box mt='575px' textAlign={'center'}>
<IconButton aria-label='Play time progress animation' icon={<TimeControlIcon restart={restartTimeControl} />} mr='25px' background='none' onClick={onSelectTimeAnimation} isDisabled={animationEnabled} />
<Tag width='60%' maxWidth={'750px'} background='none'>
<TagLabel mt='-30px' mr='-35px' overflow={'none'}>2021</TagLabel>
<Progress
value={timeValue}
opacity={100}
colorScheme={'progress'}
aria-label='date-time-progress-bar'
min={0}
max={PROGRESS_FINAL_MONTH}
width='100%'
maxWidth={'750px'}
height='20px'
mt='10px'
display={'inline-block'}
/>
<TagLabel mt='-30px' ml='-35px' overflow={'none'}>Now</TagLabel>
</Tag>
</Box>
</>
);
}
Expand Down
29 changes: 29 additions & 0 deletions src/app/src/components/AnimatedMapLegend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { HStack, Box } from '@chakra-ui/react';

export function ColorRangeBox({ bg, text }: { bg: string, text?: String }) {
return (
<Box
w='70px'
h='40px'
bg={bg}
textAlign={'center'}
color={'white'}
fontSize={'sm'}
pt='8px'
>
{text}
</Box>
);
}

export default function AnimatedMapLegend() {
return (
<Box width='100%' pl='20%'>
<HStack spacing='0px' border={'1px'} width='210px'>
<ColorRangeBox bg='white' />
<ColorRangeBox bg='#94A4DF' text='≥1% BIL' />
<ColorRangeBox bg='#465EB5' text='≥2% BIL' />
</HStack>
</Box>
);
}
21 changes: 21 additions & 0 deletions src/app/src/components/TimeControlIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Icon } from '@chakra-ui/react';

export default function TimeControlIcon({ restart }: { restart: boolean }) {
return restart ? (
<Icon viewBox='0 0 512 512' color='black' width='45px' height='45px'>
<path
fill='currentColor'
d='M212.333 224.333H12c-6.627 0-12-5.373-12-12V12C0 5.373 5.373 0 12 0h48c6.627 0 12 5.373 12 12v78.112C117.773 39.279 184.26 7.47 258.175 8.007c136.906.994 246.448 111.623 246.157 248.532C504.041 393.258 393.12 504 256.333 504c-64.089 0-122.496-24.313-166.51-64.215-5.099-4.622-5.334-12.554-.467-17.42l33.967-33.967c4.474-4.474 11.662-4.717 16.401-.525C170.76 415.336 211.58 432 256.333 432c97.268 0 176-78.716 176-176 0-97.267-78.716-176-176-176-58.496 0-110.28 28.476-142.274 72.333h98.274c6.627 0 12 5.373 12 12v48c0 6.627-5.373 12-12 12z'
data-attibution='Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.'
/>
</Icon>
) : (
<Icon viewBox='0 0 384 512' color='black' width='55px' height='55px'>
<path
fill='currentColor'
d='M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z'
data-attibution='Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.'
/>
</Icon>
);
}
5 changes: 4 additions & 1 deletion src/app/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ const theme = extendTheme({
},
colors: {
tooltip: {
500: '#465EB5',
500: '#465EB5',
},
progress: {
500: '#000000',
},
},
});
Expand Down