Skip to content

Commit

Permalink
Merge branch 'main' into update-load-data-script
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Nov 19, 2024
2 parents e000b51 + 46f3e47 commit 24fc419
Show file tree
Hide file tree
Showing 23 changed files with 696 additions and 316 deletions.
14 changes: 9 additions & 5 deletions app/src/app/components/sidebar/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Button, Checkbox, CheckboxGroup} from '@radix-ui/themes';
import {styled} from '@stitches/react';
import * as RadioGroup from '@radix-ui/react-radio-group';
import {blackA} from '@radix-ui/colors';
import {useMapStore} from '@/app/store/mapStore';

type ColorPickerProps<T extends boolean = false> = T extends true
? {
Expand All @@ -27,6 +28,8 @@ export const ColorPicker = <T extends boolean>({
colorArray,
multiple,
}: ColorPickerProps<T>) => {
const mapDocument = useMapStore(state => state.mapDocument);

if (multiple) {
return (
<div>
Expand Down Expand Up @@ -63,11 +66,12 @@ export const ColorPicker = <T extends boolean>({
value={value !== undefined ? colorArray[value] : undefined}
defaultValue={colorArray[defaultValue]}
>
{colorArray.map((color, i) => (
<RadioGroupItem key={i} style={{backgroundColor: color}} value={color}>
<RadioGroupIndicator />
</RadioGroupItem>
))}
{mapDocument &&
colorArray.slice(0, mapDocument.num_districts ?? 0).map((color, i) => (
<RadioGroupItem key={i} style={{backgroundColor: color}} value={color}>
<RadioGroupIndicator />
</RadioGroupItem>
))}
</RadioGroupRoot>
</div>
);
Expand Down
96 changes: 9 additions & 87 deletions app/src/app/components/sidebar/Evaluation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,24 @@ import {useMapStore} from '@/app/store/mapStore';
import {useQuery} from '@tanstack/react-query';
import {
CleanedP1ZoneSummaryStats,
CleanedP1ZoneSummaryStatsKeys,
getP1SummaryStats,
P1ZoneSummaryStats,
P1ZoneSummaryStatsKeys,
} from '@/app/utils/api/apiHandlers';
import {Button, Checkbox, CheckboxGroup} from '@radix-ui/themes';
import {Heading, Flex, Spinner, Text} from '@radix-ui/themes';
import {Button, CheckboxGroup} from '@radix-ui/themes';
import {Flex, Spinner, Text} from '@radix-ui/themes';
import {queryClient} from '@utils/api/queryClient';
import {formatNumber, NumberFormats} from '@/app/utils/numbers';
import {colorScheme} from '@/app/constants/colors';
import {
getEntryTotal,
getStdDevColor,
stdDevArray,
stdDevColors,
sumArray,
} from '@utils/summaryStats';
import {interpolateBlues, interpolateGreys} from 'd3-scale-chromatic';
import {getEntryTotal} from '@utils/summaryStats';
import {interpolateGreys} from 'd3-scale-chromatic';

type EvalModes = 'share' | 'count' | 'totpop';
type ColumnConfiguration<T extends Record<string, any>> = Array<{label: string; column: keyof T}>;
type EvaluationProps = {
columnConfig?: ColumnConfiguration<P1ZoneSummaryStats>;
};

// const calculateColumn = (
// mode: EvalModes,
// entry: P1ZoneSummaryStats,
// totals: P1ZoneSummaryStats,
// column: keyof Omit<CleanedP1ZoneSummaryStats, 'zone'>
// ) => {
// const count = entry[column];
// switch (mode) {
// case 'count':
// return count;
// case 'pct':
// return count / entry['total'];
// case 'share':
// return count / totals[column];
// }
// };

const defaultColumnConfig: ColumnConfiguration<P1ZoneSummaryStats> = [
{
label: 'White',
Expand All @@ -66,6 +42,10 @@ const defaultColumnConfig: ColumnConfiguration<P1ZoneSummaryStats> = [
label: 'Pacific Isl.',
column: 'nhpi_pop',
},
{
label: 'Two or More Races',
column: 'two_or_more_races_pop',
},
{
label: 'Other',
column: 'other_pop',
Expand All @@ -81,10 +61,6 @@ const modeButtonConfig: Array<{label: string; value: EvalModes}> = [
label: 'Population by Count',
value: 'count',
},
// {
// label: "Population by Percent of Zone",
// value: 'totpop'
// }
];

const numberFormats: Record<EvalModes, NumberFormats> = {
Expand All @@ -104,8 +80,6 @@ const getColConfig = (evalMode: EvalModes) => {

const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConfig}) => {
const [evalMode, setEvalMode] = useState<EvalModes>('share');
// const [showAverages, setShowAverages] = useState<boolean>(true);
// const [showStdDev, setShowStdDev] = useState<boolean>(false);
const [colorBg, setColorBg] = useState<boolean>(true);
const [showUnassigned, setShowUnassigned] = useState<boolean>(true);

Expand Down Expand Up @@ -152,18 +126,10 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
unassigned[`${key}_pct`] = total / unassigned[key];
unassigned[key] = total;
});
// const averages: Record<string, number> = {};
// const stdDevs: Record<string, number> = {};
// CleanedP1ZoneSummaryStatsKeys.forEach(key => {
// const values = data.results.map(row => row[key]);
// averages[key] = sumArray(values) / data.results.length;
// stdDevs[key] = stdDevArray(values);
// });

return {
unassigned,
maxValues,
// averages,
// stdDevs
};
}, [data?.results, totPop]);

Expand Down Expand Up @@ -210,29 +176,9 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
<CheckboxGroup.Item value="unassigned" onClick={() => setShowUnassigned(v => !v)}>
Show Unassigned Population
</CheckboxGroup.Item>
{/* <CheckboxGroup.Item value="averages" onClick={() => setShowAverages(v => !v)}>
Show Zone Averages
</CheckboxGroup.Item>
<CheckboxGroup.Item value="stddev" onClick={() => setShowStdDev(v => !v)}>
Show Zone Std. Dev.
</CheckboxGroup.Item> */}
<CheckboxGroup.Item value="colorBg" onClick={() => setColorBg(v => !v)}>
<Flex gap="3">
<p>Color Cells By Values</p>
{/* {colorByStdDev && (
<span>
{Object.entries(stdDevColors)
.sort((a, b) => +a[0] - +b[0])
.map(([stdev, backgroundColor], i) => (
<span
className="inline-flex items-center justify-center size-6"
style={{backgroundColor}}
>
{+stdev > 0 ? `+${stdev}`: stdev}
</span>
))}
</span>
)} */}
</Flex>
</CheckboxGroup.Item>
</CheckboxGroup.Root>
Expand All @@ -250,30 +196,6 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
</tr>
</thead>
<tbody>
{/* {!!(averages && showAverages) && (
<tr className="border-b hover:bg-gray-50">
<td className="py-2 px-4 font-medium flex flex-row items-center gap-1">
Zone Averages
</td>
{columnConfig.map((f, i) => (
<td className="py-2 px-4 text-right">
{formatNumber(averages[columnGetter(f.column)], numberFormat)}
</td>
))}
</tr>
)}
{!!(stdDevs && showStdDev) && (
<tr className="border-b hover:bg-gray-50">
<td className="py-2 px-4 font-medium flex flex-row items-center gap-1">
Zone Std. Dev.
</td>
{columnConfig.map((f, i) => (
<td className="py-2 px-4 text-right">
{formatNumber(stdDevs[columnGetter(f.column)], numberFormat)}
</td>
))}
</tr>
)} */}
{rows
.sort((a, b) => a.zone - b.zone)
.map(row => {
Expand Down
94 changes: 78 additions & 16 deletions app/src/app/components/sidebar/charts/HorizontalBarChart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import {useMapStore} from '@/app/store/mapStore';
import {Card, Flex, Heading, Text} from '@radix-ui/themes';
import {BarChart, Bar, ResponsiveContainer, Tooltip, XAxis, YAxis, Cell} from 'recharts';
import {
BarChart,
Bar,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
Cell,
ReferenceLine,
Label,
} from 'recharts';
import {colorScheme} from '@/app/constants/colors';
import {useState, useMemo} from 'react';

type TooltipInput = {
active?: boolean;
Expand All @@ -24,6 +35,38 @@ const CustomTooltip = ({active, payload: items}: TooltipInput) => {

export const HorizontalBar = () => {
const mapMetrics = useMapStore(state => state.mapMetrics);
const summaryStats = useMapStore(state => state.summaryStats);
const numDistricts = useMapStore(state => state.mapDocument?.num_districts);
const idealPopulation = summaryStats?.idealpop?.data;
const maxNumberOrderedBars = 40; // max number of zones to consider while keeping blank spaces for missing zones
const [totalExpectedBars, setTotalExpectedBars] = useState<
Array<{zone: number; total_pop: number}>
>([]);

const calculateChartObject = () => {
if ((numDistricts ?? 0) < maxNumberOrderedBars) {
return mapMetrics && mapMetrics.data && numDistricts
? Array.from({length: numDistricts}, (_, i) => i + 1).reduce(
(acc, district) => {
const totalPop = mapMetrics.data.reduce((acc, entry) => {
return entry.zone === district ? acc + entry.total_pop : acc;
}, 0);
return [...acc, {zone: district, total_pop: totalPop}];
},
[] as Array<{zone: number; total_pop: number}>
)
: [];
} else {
return mapMetrics?.data ?? [];
}
};

useMemo(() => {
if (mapMetrics) {
const chartObject = calculateChartObject();
setTotalExpectedBars(chartObject);
}
}, [mapMetrics]);

if (mapMetrics?.isPending) {
return <div>Loading...</div>;
Expand All @@ -44,30 +87,49 @@ export const HorizontalBar = () => {
return (
<Flex gap="3" direction="column">
<Heading as="h3" size="3">
Population by Zone
Population by district
</Heading>
<ResponsiveContainer
width="100%"
// should this instead be set based on the target number of zones? see https://github.com/districtr/districtr-v2/issues/92
height={colorScheme.length * 18}
minHeight="200px"
>
<BarChart width={500} data={mapMetrics.data} layout="vertical" barGap={0.5} maxBarSize={50}>
<ResponsiveContainer width="100%" minHeight="350px">
<BarChart
width={500}
data={totalExpectedBars}
layout="vertical"
barGap={0.5}
maxBarSize={50}
>
<XAxis
allowDataOverflow={true}
type="number"
domain={[0, 'maxData']}
domain={[
0,
(dataMax: number) =>
idealPopulation
? Math.round(Math.max(idealPopulation * 2, dataMax + 1000))
: dataMax,
]}
tickFormatter={value => numberFormat.format(value)}
/>
<YAxis type="category" hide />
<YAxis type="category" hide allowDataOverflow={true} padding={{bottom: 40}} />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="total_pop">
{mapMetrics.data
.sort((a, b) => a.zone - b.zone)
.map((entry, index) => (
<Cell key={`cell-${index}`} fill={colorScheme[entry.zone - 1]} />
))}
{totalExpectedBars &&
totalExpectedBars
.sort((a, b) => a.zone - b.zone)
.map((entry, index) => (
<Cell key={`cell-${index}`} fill={colorScheme[entry.zone - 1]} />
))}
</Bar>
<ReferenceLine x={idealPopulation ?? 0} stroke="black" strokeDasharray="3 3">
<Label
value={`Ideal: ${new Intl.NumberFormat('en-US').format(
Math.round(idealPopulation ?? 0) ?? 0
)}`}
position="insideBottomLeft"
fill="black"
fontSize={18}
offset={10}
/>
</ReferenceLine>
</BarChart>
</ResponsiveContainer>
</Flex>
Expand Down
Loading

0 comments on commit 24fc419

Please sign in to comment.