Published
Edited
Nov 11, 2020
2 stars
Also listed in…
Elections
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
states = Array.from(presidentByState.keys())
Insert cell
races = new Map([
["president", presidentRaces],
["state", stateRaces]
])
Insert cell
data = races.get(raceOption)
Insert cell
keys = Array.from(new Set(d3.range(0,1,0.01).map(grouper))).sort()
Insert cell
grouper = d => Math.floor(d * numberOfSections) / numberOfSections
Insert cell
groups = d3.group(d3.merge(data), ({ x }) => grouper(x), ({ y }) => grouper(y))
Insert cell
get = (x, y) =>
groups.has(x) && groups.get(x).has(y) ? groups.get(x).get(y) : []
Insert cell
fractionCorrect = arr =>
arr.length ? arr.filter(({ correct }) => correct).length / arr.length : NaN
Insert cell
sectionData = d3
.cross(keys, keys)
.map(([x, y]) => ({ x, y, value: fractionCorrect(get(x, y)) }))
Insert cell
presidentRaces = Array.from(presidentByState.values()).map(parseStateRace)
Insert cell
stateRaces = Array.from(state.values())
.flatMap(st => st.data.races
.filter(d => d.candidates && d.timeseries)
.map(parseStateRace)
)
.filter(d => d)
Insert cell
parseStateRace = race => {
const [a, b] = ["republican", "democrat"].map(party =>
race.candidates.find(d => d.party_id === party)
);
if (!a || !b) return;
return transformTimeseries(race.timeseries, a, b);
}
Insert cell
transformTimeseries = (ts, a, b) =>
ts
.map(d => {
const x =
d.vote_shares[a.candidate_key] /
(d.vote_shares[a.candidate_key] + d.vote_shares[b.candidate_key]);
const y = d.eevp / 100;
return { x, y };
})
.map(({ x, y }, i, arr) => ({
x,
y,
correct: x > 0.5 === arr[arr.length - 1].x > 0.5
}))
Insert cell
Insert cell
margin = ({ top: 70, right: 80, bottom: 35, left: 10 })
Insert cell
height = width - margin.left - margin.right + margin.top + margin.bottom
Insert cell
x = d3.scaleLinear([0, 1], [margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear([0, 1], [margin.top, height - margin.bottom])
Insert cell
xs = x(keys[1]) - x(keys[0])
Insert cell
ys = y(keys[1]) - y(keys[0])
Insert cell
color = d3.scaleSequential([0, 1], d3.interpolateRdYlBu)
Insert cell
line = d3.line().x(d => x(d.x)).y(d => y(d.y)).defined(d => !isNaN(d.x) && !isNaN(d.y))
Insert cell
Insert cell
xAxis = g =>
g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.format(".0%")))
.call(g =>
g
.select(".tick:last-of-type text")
.clone()
.attr("x", 3)
.attr("dy", "2em")
.attr("text-anchor", "end")
.attr("font-weight", "bold")
.text("Vote share for " + candidateName)
)
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${width - margin.right}, 0)`)
.call(d3.axisRight(y).tickFormat(d3.format(".0%")))
.call(g => g.select(".tick:first-of-type text").clone()
.attr("dy", "-1em")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Votes counted"))
Insert cell
candidateName = raceOption === "president"
? presidentByState
.values()
.next()
.value.candidates.find(d => d.party_id === "republican").last_name
: "Republican"
Insert cell
leg = legend({
color,
title: `Percentage of incomplete results that match latest results (2020
${raceOption === "president" ? "presidential" : "state and presidential"}
elections)`,
tickFormat: d3.format(".0%")
})
Insert cell
Insert cell
d3 = require("d3")
Insert cell
import { legend } from "@d3/color-legend"
Insert cell
import { state, president, presidentByState } from "1342cadc9354cc67"
Insert cell
import {checkbox, slider, radio} from "@jashkenas/inputs"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more