Published
Edited
Dec 14, 2020
1 fork
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
update = chart[shape]()
Insert cell
y = d3.scaleLinear([0, 1], [margin.top, height - margin.bottom])
Insert cell
x1 = d3.scaleLinear([0, 1], [margin.left, _width - margin.right])
Insert cell
x2 = d3.scaleLinear(
[-latest.votes, latest.votes],
[margin.left, _width - margin.right]
)
Insert cell
line1 = d3
.line()
.x(d => x1(d.share))
.y(d => y(d.counted))
Insert cell
line2 = d3
.line()
.x(d => x2((2 * d.share - 1) * d.votes))
.y(d => y(d.counted))
Insert cell
margin = ({ top: 10, right: 15, bottom: 30, left: 40 })
Insert cell
_width = Math.min(640, width)
Insert cell
height = _width - margin.left - margin.right + margin.top + margin.bottom
Insert cell
range = ([a, b], step) => [...d3.range(a, b, step), b]
Insert cell
Insert cell
rep = Object.assign(
raceData.candidates.find(d => d.party_id === "republican"),
{ color: "#dd2c35" }
)
Insert cell
dem = Object.assign(raceData.candidates.find(d => d.party_id === "democrat"), {
color: "#0080c9"
})
Insert cell
raceData = presidentByState.get(state_id)
Insert cell
timeseries = raceData.timeseries
.map(d => ({
share:
d.vote_shares[rep.candidate_key] /
(d.vote_shares[rep.candidate_key] + d.vote_shares[dem.candidate_key]) ||
0.5,
votes: d.votes,
counted: d.eevp / 100,
time: new Date(d.timestamp)
}))
.slice(1)
Insert cell
called = ({
...(raceData.leader_party_id === "democrat" ? dem : rep),
...timeseries.find(d => d.time >= raceData.winnerCalledTimestamp)
})
Insert cell
latest = timeseries[timeseries.length - 1]
Insert cell
Insert cell
// solve for z: z = (x * y - t) / (y - 1)
// solve for y: y = (t - z) / (x - z) and x!=z and t!=x
// solve for x: x = (t + (y - 1) * z) / y and y!=0 and y!=1
// solve for t: t = x y - y z + z and y!=1
contour = z => x => (0.5 - z) / (x - z)
Insert cell
gridlines = d3
.range(0.1, 1, 0.1)
.filter(z => z !== 0.5)
.map(z => ({
z,
data: range([0, 1], 0.02)
.map(d => {
const counted = contour(z)(d);
return { share: d, counted, votes: latest.votes * counted };
})
.filter(({ counted }) => counted >= 0 && counted <= 1)
}))
Insert cell
getEndzone = (domain, step, z) =>
range(domain, step)
.map(d => {
const counted = contour(z)(d);
return { share: d, counted, votes: latest.votes * counted };
})
.concat([{ share: +!z, votes: latest.votes, counted: 1 }])
Insert cell
endzones = [
{ d: getEndzone([0.5, 1], 0.01, 0), fill: rep.color },
{ d: getEndzone([0, 0.5], 0.01, 1), fill: dem.color }
]
Insert cell
field = [
{ share: 0, votes: 0, counted: 0 },
{ share: 1, votes: 0, counted: 0 },
{ share: 1, votes: latest.votes, counted: 1 },
{ share: 0, votes: latest.votes, counted: 1 }
]
Insert cell
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).tickFormat(d3.format(".0%")))
.call(g => g.select(".domain").remove())
.call(g =>
g
.select(".tick:first-of-type text")
.clone()
.attr("x", 3)
.attr("text-anchor", "start")
.text("Percent of votes counted in " + raceData.state_name)
)
Insert cell
x1Axis = g =>
g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(
d3
.axisBottom(x1)
.ticks(Math.min(width / 40, 10))
.tickFormat(d3.format(".0%"))
)
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Percent of votes won by " + rep.last_name)
)
Insert cell
x2Axis = g =>
g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(
d3
.axisBottom(x2)
.ticks(4)
.tickFormat(d3.format("+.2s"))
)
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Margin of votes won by " + rep.last_name)
)
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
import { radio, select } from "@jashkenas/inputs"
Insert cell
import { presidentByState, president } from "1342cadc9354cc67"
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