Skip to content

Commit

Permalink
Election differential viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
lukedigiovanna committed Sep 14, 2024
1 parent 014b6e1 commit b842877
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 24 deletions.
10 changes: 10 additions & 0 deletions data-api/parse_scripts/read_electiondata.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def pad_with_zeroes(number, total_length):
state_fp = fips[0:2]
county_fp = fips[2:]
candidate = row[6].lower()
candidate = ' '.join(map(lambda name: name[0].upper() + name[1:], candidate.split()))
party = row[7].lower()
votes = int(row[8])

Expand All @@ -61,6 +62,15 @@ def pad_with_zeroes(number, total_length):
state_results[party] = 0
state_results[party] += votes
state_results["total"] += votes

if fips not in results:
results[fips] = {"total": 0}
county_results = results[fips]
if party not in county_results:
county_results[party] = 0
county_results[party] += votes
county_results["total"] += votes


for year in state_data:
with open(f"../data/{year}_state_election_results.json", "w") as fo:
Expand Down
133 changes: 110 additions & 23 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ const usCountiesGeoJson = usCountiesGeoJsonData as FeatureCollection;
const usStatesGeoJson = useStatesGeoJsonData as FeatureCollection;

type CountryView = "county" | "state";

type ColorMode = "solid" | "gradient";

function App() {
const [countryView, setCountryView] = React.useState<CountryView>("state");
const [colorMode, setColorMode] = React.useState<ColorMode>("gradient");
const [year, setYear] = React.useState<number>(2020);
const [diffMode, setDiffMode] = React.useState<boolean>(false);

const keyPress = (event: KeyboardEvent) => {
if (event.key === "ArrowLeft") {
Expand Down Expand Up @@ -56,31 +56,66 @@ function App() {

const electionData = results[fp];
const parties = Object.keys(electionData);
parties.sort((party1, party2) => {
return electionData[party2] - electionData[party1]
});
// total is always the first

let color;
if (Object.hasOwn(colors.parties, parties[1])) {
color = (colors.parties as any)[parties[1]];
}
else {
color = colors.parties.other;
}
if (colorMode === "solid") {
return color;
if (diffMode) {
if (year === 2000) {
return colors.parties.undefined;
}
const previousElectionData = (electionResults as any)[year - 4]["results"][fp];
if (!previousElectionData) {
return colors.parties.undefined;
}
const diffs: any = {};
parties.forEach((party) => {
const thisProp = electionData[party] / electionData["total"];
if (!Object.hasOwn(previousElectionData, party)) {
diffs[party] = thisProp;
}
else {
const pastProp = previousElectionData[party] / previousElectionData["total"];
diffs[party] = thisProp - pastProp;
}
});
parties.sort((party1, party2) => {
return diffs[party2] - diffs[party1];
});
const color = (colors.parties as any)[parties[0]];
if (colorMode === "solid") {
return color;
}
else {
const r = Math.max(Math.min(diffs[parties[0]] / 0.15, 1), 0.15);
const colormap = interpolate(["white", color]);
return colormap(r);
}
}
else {
const firstPlacePercent = electionData[parties[1]] / electionData["total"];
const secondPlacePercent = electionData[parties[2]] / electionData["total"];
const diff = firstPlacePercent - secondPlacePercent;
const r = Math.max(Math.min(1, diff / 0.15), 0.2);
const colormap = interpolate(["white", color]);
return colormap(r);
parties.sort((party1, party2) => {
return electionData[party2] - electionData[party1]
});
// total is always the first

let color;
if (Object.hasOwn(colors.parties, parties[1])) {
color = (colors.parties as any)[parties[1]];
}
else {
color = colors.parties.other;
}
if (colorMode === "solid") {
return color;
}
else {
const firstPlacePercent = electionData[parties[1]] / electionData["total"];
const secondPlacePercent = electionData[parties[2]] / electionData["total"];
const diff = firstPlacePercent - secondPlacePercent;
const r = Math.max(Math.min(1, diff / 0.15), 0.2);
const colormap = interpolate(["white", color]);
return colormap(r);
}
}
}
}, [year, colorMode, countryView]);
}, [year, colorMode, countryView, diffMode]);

const tooltipFunction = React.useMemo(() => {
return (d: any) => {
Expand Down Expand Up @@ -119,7 +154,25 @@ function App() {
}
const votes = stateData[parties[i]];
const proportion = votes / stateData["total"];
p.innerText += " (" + (Math.round(proportion * 1000) / 10) + "%)";

p.innerText += " (" + (Math.round(proportion * 1000) / 10) + "%";
if (diffMode) {
let diff = 0;
if (year >= 2004) {
const previousElectionResults = (electionResults as any)[year - 4]["results"][fp];
if (Object.hasOwn(previousElectionResults, parties[i])) {
const oldProp = previousElectionResults[parties[i]] / previousElectionResults["total"];
diff = proportion - oldProp;
}
}
if (diff >= 0) {
p.innerText += ", +" + Math.round(diff * 1000) / 10 + "%";
}
else {
p.innerText += ", " + Math.round(diff * 1000) / 10 + "%";
}
}
p.innerText += ")"
row.appendChild(p);
div.appendChild(row);
const voteCount = document.createElement("p");
Expand Down Expand Up @@ -164,7 +217,7 @@ function App() {
</div>
<div className="border-b-2 border-b-black w-full my-2" />

<div className="flex">
<div className="grid grid-cols-3">
<div className="flex self-start mx-10 space-x-2">
<ToggleSwitch isChecked={countryView === "county"} onToggle={() => {
if (countryView === "county") {
Expand All @@ -191,7 +244,41 @@ function App() {
{ colorMode === "gradient" ? "Gradient" : "Solid" } Colors
</h1>
</div>
<div className="flex self-start mx-10 space-x-2">
<ToggleSwitch isChecked={diffMode} onToggle={() => {
setDiffMode(!diffMode);
}} />
<h1 className="self-center font-bold text-lg">
{ diffMode ? "Diff" : "Absolute" } Votes
</h1>
</div>
</div>

<div className="border-b-2 border-b-black w-full my-2" />

<div className="grid grid-cols-2 w-full my-1">
<div className="ml-8">
<p className="text-left font-bold text-2xl">
{
(electionResults as any)[year]["candidates"].find((e: any) => e.party === "democrat")["name"]
}
</p>
<p className="text-left italic text-lg mt-[-8px] text-gray-800">
(Democrat)
</p>
</div>
<div className="mr-8">
<p className="text-right font-bold text-2xl mb-0">
{
(electionResults as any)[year]["candidates"].find((e: any) => e.party === "republican")["name"]
}
</p>
<p className="text-right italic text-lg mt-[-8px] text-gray-800">
(Republican)
</p>
</div>
</div>

{
countryView === "county" ?
<CountyGeoJsonMap countiesGeoJson={usCountiesGeoJson} statesGeoJson={usStatesGeoJson} colorFunction={colorFunction} tooltipFunction={tooltipFunction} />
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const colors = {
"republican": "red",
"libertarian": "yellow",
"green": "green",
"other": "gray"
"other": "gray",
"undefined": "lightgray"
}
};

Expand Down

0 comments on commit b842877

Please sign in to comment.