Skip to content

Commit

Permalink
Add choropleth map for disadvantaged communities
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobtylerwalls committed Apr 12, 2023
1 parent aa81341 commit 337940d
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 22 deletions.
5 changes: 4 additions & 1 deletion src/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@chakra-ui/react';

import PerCapitaMap from './components/PerCapitaMap';
import { EquityMap } from './components/EquityMap';
import AnimatedArcsAndMap from './components/AnimatedArcsAndMap';
import DataSandbox from './components/DataSandbox';
import SimpleModal from './components/SimpleModal';
Expand Down Expand Up @@ -45,7 +46,9 @@ function App() {
<TopMapTabSelector value={topMap} onChange={setTopMap} />
{topMap === TopMap.PER_CAPITA ? (
<PerCapitaMap />
) : topMap === TopMap.EQUITY ? null : null}
) : topMap === TopMap.EQUITY ? (
<EquityMap />
) : null}
<div style={{ height: 100 }} />
<AnimatedArcsAndMap />
<div style={{ height: 36 }} />
Expand Down
10 changes: 7 additions & 3 deletions src/app/src/components/AnimatedArcsAndMap/AnimatedArcsAndMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import {
} from '@chakra-ui/react';

import UsaMapContainer from '../UsaMapContainer';
import PerCapitaMapLegend from '../PerCapitaMap/PerCapitaMapLegend';
import MapLegend from '../MapLegend';
import TimeControlIcon from './TimeControlIcon';

import AnimatedMap from './AnimatedMap';
import { useGetSpendingOverTimeQuery } from '../../api';
import { getSpendingByStateAtTime, PROGRESS_FINAL_STEP } from '../../util';
import { MONTHLY_TIME_DURATION } from '../../constants';
import { AMOUNT_CATEGORIES, MONTHLY_TIME_DURATION } from '../../constants';
import AnimatedTotalSpendingBucket from './AnimatedTotalSpendingBucket';

export default function AnimatedArcsAndMap() {
Expand Down Expand Up @@ -84,7 +84,11 @@ export default function AnimatedArcsAndMap() {
spendingAtTimeByState={spendingAtTimeByState}
/>
</UsaMapContainer>
<PerCapitaMapLegend />
<MapLegend
categories={AMOUNT_CATEGORIES}
label='Dollars per capita'
monetary
/>
<Box width='100%' textAlign={'center'}>
<IconButton
aria-label='Play time progress animation'
Expand Down
8 changes: 8 additions & 0 deletions src/app/src/components/Attribution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ export default function Attribution() {
category={category}
/>
))}
<Text fontSize={12} pt={1}>
The proportions of state populations living in disadvantaged
communities were taken from the{' '}
<Link href='https://screeningtool.geoplatform.gov/en/downloads#3/33.47/-97.5'>
Climate and Economic Justice Screening Tool
</Link>
, which is based on 2010 Census data.
</Text>
</details>
<Text fontSize={12} pt={4}>
The maps on this page were created with{' '}
Expand Down
59 changes: 59 additions & 0 deletions src/app/src/components/EquityMap/EquityMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useCallback } from 'react';
import StatesLayer from '../StatesLayer';
import {
StateFeature,
StatesLayer as StatesLayerType,
} from '../../types/states';

import MapLegend from '../MapLegend';
import UsaMapContainer from '../UsaMapContainer';
import * as disadvantagedTractsJson from '../../data/disadvantagedTracts.json';
import { QUARTILE_CATEGORIES, STATE_STYLE_BASE } from '../../constants';
import { getAmountCategory } from '../../util';

const disadvantagedTracts = Array.from(disadvantagedTractsJson);

export function EquityMap() {
const onEachFeature = useCallback(
(feature: StateFeature, layer: StatesLayerType) => {
const disadvantagedPopulationSums = disadvantagedTracts.find(
datum => datum['State'] === feature.properties.NAME
);
if (disadvantagedPopulationSums === undefined) {
return;
}
const proportionDisadvantaged =
Number.parseInt(disadvantagedPopulationSums.Disadvantaged) /
Number.parseInt(disadvantagedPopulationSums.Total);
layer.setStyle({
fillColor: getAmountCategory(
proportionDisadvantaged,
QUARTILE_CATEGORIES
).color,
});
layer.on('mouseover', () => {
layer.setStyle({ weight: 2 });
});
layer.on('mouseout', () => {
layer.setStyle({
weight: STATE_STYLE_BASE.weight,
});
});
},
[]
);

return (
<>
<div style={{ height: 125 }} />
<UsaMapContainer negativeMargin>
<StatesLayer onEachFeature={onEachFeature} />
</UsaMapContainer>
<MapLegend
categories={QUARTILE_CATEGORIES}
label='Population in disadvantaged communities'
monetary={false}
/>
</>
);
}
1 change: 1 addition & 0 deletions src/app/src/components/EquityMap/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EquityMap } from './EquityMap';
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
import { Box, Text, HStack, VStack } from '@chakra-ui/react';
import { AMOUNT_CATEGORIES } from '../../constants';
import useIsMobileMode from '../../useIsMobileMode';
import { AmountCategory } from '../types';
import useIsMobileMode from '../useIsMobileMode';

export default function PerCapitaMapLegend() {
export default function MapLegend({
categories,
label,
monetary,
}: {
categories: AmountCategory[];
label: string;
monetary: boolean;
}) {
const isMobileMode = useIsMobileMode();

const monetaryLabel = (
category: AmountCategory,
nextCategory: AmountCategory | undefined
) => {
if (nextCategory) {
return `$${category.min.toLocaleString()}-$${(
nextCategory.min - 1
).toLocaleString()}`;
}
return `$${category.min.toLocaleString()}+`;
};

const percentLabel = (
category: AmountCategory,
_nextCategory: AmountCategory | undefined
) => {
return `${category.min * 100}%`;
};

const getLabel = monetary ? monetaryLabel : percentLabel;

return (
<VStack
p={10}
Expand All @@ -14,18 +43,13 @@ export default function PerCapitaMapLegend() {
minWidth={isMobileMode ? '100%' : '520px'}
>
<Text alignSelf='flex-start' fontSize={24}>
Dollars per capita
{label}
</Text>
<HStack width='100%' spacing={0.5}>
{[...AMOUNT_CATEGORIES]
{[...categories]
.reverse()
.map((category, index, categories) => {
const nextCategory = categories[index + 1];
const label = nextCategory
? `$${category.min.toLocaleString()}-$${(
nextCategory.min - 1
).toLocaleString()}`
: `$${category.min.toLocaleString()}+`;

return (
<VStack flex={1} key={category.min}>
Expand All @@ -38,7 +62,7 @@ export default function PerCapitaMapLegend() {
fontSize={isMobileMode ? 12 : 14}
alignSelf='flex-start'
>
{label}
{getLabel(category, nextCategory)}
</Text>
</VStack>
);
Expand Down
10 changes: 7 additions & 3 deletions src/app/src/components/PerCapitaMap/PerCapitaMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import UsaMapContainer from '../UsaMapContainer';
import StatesLayer from '../StatesLayer';
import SpendingTooltip from './SpendingTooltip';
import SpendingCategorySelector from './SpendingCategorySelector';
import PerCapitaMapLegend from './PerCapitaMapLegend';
import MapLegend from '../MapLegend';

import { Category, isCategory } from '../../enums';
import useSpendingByCategoryByState from './useSpendingByCategoryByState';
Expand All @@ -16,7 +16,7 @@ import {
StatesLayer as StatesLayerType,
} from '../../types/states';
import { getAmountCategory } from '../../util';
import { STATE_STYLE_BASE } from '../../constants';
import { AMOUNT_CATEGORIES, STATE_STYLE_BASE } from '../../constants';
import { StateSpending } from '../../types/api';

export default function PerCapitaMap() {
Expand All @@ -41,7 +41,11 @@ export default function PerCapitaMap() {
</Center>
)}
</UsaMapContainer>
<PerCapitaMapLegend />
<MapLegend
categories={AMOUNT_CATEGORIES}
label='Dollars per capita'
monetary
/>
</VStack>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export const AMOUNT_CATEGORIES: AmountCategory[] = [
{ min: 0, color: theme.colors.green[100] },
];

export const QUARTILE_CATEGORIES: AmountCategory[] = [
{ min: 0.75, color: theme.colors.green[900] },
{ min: 0.5, color: theme.colors.green[600] },
{ min: 0.25, color: theme.colors.green[300] },
{ min: 0, color: theme.colors.green[100] },
];

export const STATE_STYLE_BASE = Object.freeze({
color: 'black',
weight: 0.62,
Expand Down
10 changes: 6 additions & 4 deletions src/app/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,13 @@ export function getCategoryForAgencies(agencies: Agency[]): Category {
throw new Error(`Category not found for this agency list ${agencies}`);
}

export function getAmountCategory(amount: number): AmountCategory {
export function getAmountCategory(
amount: number,
categories: AmountCategory[] = AMOUNT_CATEGORIES
): AmountCategory {
const category =
AMOUNT_CATEGORIES.find(
amountCategory => amount >= amountCategory.min
) ?? AMOUNT_CATEGORIES[AMOUNT_CATEGORIES.length - 1];
categories.find(amountCategory => amount >= amountCategory.min) ??
categories[categories.length - 1];

if (!category) {
throw Error(`Could not find amount category for amount: ${amount}`);
Expand Down

0 comments on commit 337940d

Please sign in to comment.