Published
Edited
Nov 10, 2020
Insert cell
Insert cell
Insert cell
Insert cell
// outcome = svg`<svg width="${width}" height="45">
// <path
// d="${`M 0 0 L ${x(final)} 0 V 25 H 0 Z`}"
// fill="${color1}" stroke="white" />
// <path
// d="${`M ${x(final)} 0 L ${width} 0 V 25 H ${x(
// final
// )} Z`}" fill="${color2}" stroke="white" />
// <text text-anchor="middle" font-family="sans-serif" font-size="14" font-weight="bold" x="${x(
// final
// )}" y="25" dy="1.2em" fill="${final > .5 ? color1 : color2}">${
// final > .5 ? `${a.name_display} wins` : `${b.name_display} wins`
// }</text>
// </svg>`
Insert cell
Insert cell
// file = FileAttachment("florida@1.json").json()
// file = FileAttachment("arizona.json").json()
// file = FileAttachment("ohio.json").json()
// file = FileAttachment("pennsylvania.json").json()
// file = FileAttachment("iowa@1.json").json()
// file = FileAttachment("texas.json").json()
// file = FileAttachment("arizona (1).json").json()
// file = FileAttachment("pennsylvania (1).json").json()
// file = FileAttachment("massachusetts.json").json()
// file = FileAttachment("new-york.json").json()
// file = FileAttachment("north-dakota.json").json()
// file = FileAttachment("michigan.json").json()
// file = FileAttachment("michigan (1).json").json()
// file = FileAttachment("nevada.json").json()
// file = FileAttachment("nevada (1).json").json()
// file = FileAttachment("wisconsin.json").json()
// file = FileAttachment("georgia.json").json()
// file = FileAttachment("north-carolina.json").json()
// file = FileAttachment("florida@2.json").json()

// file = FileAttachment("wisconsin (1).json").json()
// file = FileAttachment("michigan (2).json").json()
// file = FileAttachment("pennsylvania (2).json").json()
// file = FileAttachment("georgia (1).json").json()

// file = FileAttachment("georgia@1.json").json()
// file = FileAttachment("arizona@1.json").json()
// file = FileAttachment("nevada@1.json").json()
file = FileAttachment("pennsylvania@1.json").json()
// file = FileAttachment("oregon.json").json()
Insert cell
a = file.data.races[0].candidates.find(d => d.party_id === "republican")
Insert cell
b = file.data.races[0].candidates.find(d => d.party_id === "democrat")
Insert cell
stateName = file.data.races[0].state_name
Insert cell
timeseries = file.data.races[0].timeseries.map(d => ({
x:
d.vote_shares[a.candidate_key] /
(d.vote_shares[a.candidate_key] + d.vote_shares[b.candidate_key]) || 0.5,
y: d.eevp / 100,
t: new Date(d.timestamp)
}))
Insert cell
final = timeseries.slice(-1)[0].x
Insert cell
color1 = "#dd2c35"
Insert cell
color2 = "#0080c9"
Insert cell
counties = []
Insert cell
// how's this file different??
(await FileAttachment("summary.json").json()).races[0].timeseries
Insert cell
Insert cell
accumulate = arr => {
const result = [];
const _N = d3.sum(arr, ({ n }) => n);
let _a = 0;
let _n = 0;
arr.forEach(function({ a, n }) {
_a += a;
_n += n;
result.push({ x: _a / _n, y: _n / _N });
});
return result;
}
Insert cell
getBest = data =>
accumulate(data.slice().sort((a, b) => d3.descending(a.a / a.n, b.a / b.n)))
Insert cell
getWorst = data =>
accumulate(data.slice().sort((a, b) => d3.ascending(a.a / a.n, b.a / b.n)))
Insert cell
bestRun = getBest(counties)
Insert cell
worstRun = getWorst(counties)
Insert cell
line = d3
.line()
.x(d => x(d.x))
.y(d => y(d.y))
Insert cell
function halo(text) {
text
.select(function() {
return this.parentNode.insertBefore(this.cloneNode(true), this);
})
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 4)
.attr("stroke-linejoin", "round");
}
Insert cell
renderHypotheticals = g =>
g
.selectAll("g.hypothetical")
.data([bestRun, worstRun])
.join("g")
.attr("class", "hypothetical")
.each(function(d, i) {
const uid = DOM.uid("hypo");
const sel = d3.select(this);
const path = sel
.append("path")
.attr("id", uid.id)
.attr("stroke-dasharray", "3,3")
.attr("d", line(i ? d : d.slice().reverse()));
sel
.append("text")
.attr("x", d => path.node().getTotalLength() / 2)
.attr("dy", 15)
.attr("fill", "white")
.attr("stroke", "none")
.attr("text-anchor", "middle")
.append("textPath")
.attr("href", uid.href)
.text(`If ${(i ? b : a).last_name} counties reported first`);
})
Insert cell
Insert cell
timeTicks = [
d3.min(timeseries, d => d.t),
...d3.timeHour
.range(...d3.extent(timeseries, d => d.t))
.filter(d => timeScale(d) < .99)
]
Insert cell
timeScale = d3.scaleTime(timeseries.map(d => d.t), timeseries.map(d => d.y))
Insert cell
timeFormat = d3.timeFormat("%I:%M %p")
Insert cell
timeAxis = g =>
g
.attr("fill", "white")
.selectAll("g")
.data(timeTicks)
.join("g")
.attr("transform", d => `translate(${width}, ${y(timeScale(d))})`)
.call(g =>
g
.append("line")
.attr("stroke", "white")
.attr("x2", -5)
)
.call(g =>
g
.append("text")
.attr("text-anchor", "end")
.attr("x", -7)
.attr("dy", "0.3em")
.text(d => timeFormat(d))
)
Insert cell
pctAxis = g =>
g
.attr("fill", "white")
.selectAll("g")
.data([.25, .5, .75])
.join("g")
.attr("transform", d => `translate(0, ${y(d)})`)
.call(g =>
g
.append("line")
.attr("stroke", "white")
.attr("x2", 5)
)
.call(g =>
g
.append("text")
.attr("x", 7)
.attr("dy", "0.3em")
.text(d => pct(d))
)
Insert cell
pct = d3.format(".0%")
Insert cell
Insert cell
// currentShare ∈ [0,1] fraction of votes so far cast for Party A
// votesCounted ∈ [0,1] fraction of total votes that have been counted
// shareNeeded ∈ [0,1] fraction of remaining votes needed for Party A to win
value = (currentShare, votesCounted) =>
votesCounted === 1
? currentShare > .5
? -Infinity
: currentShare < .5
? Infinity
: NaN
: (currentShare * votesCounted - 0.5) / (votesCounted - 1)
Insert cell
_color = d3.scaleSequential(
[0, 1],
d3.interpolateRgb.gamma(2.2)(color1, color2)
)
Insert cell
color = d =>
d >= 1
? `url(${window.location.href}#checkerboard2)`
: d < 0
? `url(${window.location.href}#checkerboard1)`
: _color(d)
Insert cell
defs = `
${makeCheckerboard("checkerboard1", color1, checkSize)}
${makeCheckerboard("checkerboard2", color2, checkSize)}
${makeCheckerboard("smallCheckerboard1", color1, 5)}
${makeCheckerboard("smallCheckerboard2", color2, 5)}
</pattern>
`
Insert cell
makeCheckerboard = (name, color, checkSize) => `
<pattern id="${name}" patternUnits="userSpaceOnUse" width="${checkSize *
2}" height="${checkSize * 2}">
<rect width="${checkSize * 2}" height="${checkSize *
2}" x="0" y="0" fill="#ddd" />
<rect width="${checkSize * 2}" height="${checkSize *
2}" x="0" y="0" fill="${color}" opacity="0.3"/>
<rect width="${checkSize}" height="${checkSize}" x="0" y="0" fill="${color}" opacity="${
checkSize < 10 ? 1 : 0.2
}" />
<rect width="${checkSize}" height="${checkSize}" x="${checkSize}" y="${checkSize}" fill="${color}" opacity="${
checkSize < 10 ? 1 : 0.2
}" />`
Insert cell
checkSize = 16
Insert cell
thresholds = [-Infinity, ...d3.range(0, 1, .1), 1]
Insert cell
grid = {
const q = 4; // The level of detail, e.g., sample every 4 pixels in x and y.
const x0 = x.range()[0] - q / 2,
x1 = x.range()[1] + q;
const y0 = y.range()[0] - q / 2,
y1 = y.range()[1] + q;
const n = Math.ceil((x1 - x0) / q);
const m = Math.ceil((y1 - y0) / q);
const grid = new Array(n * m);
for (let j = 0; j < m; ++j) {
for (let i = 0; i < n; ++i) {
grid[j * n + i] = value(x.invert(i * q + x0), y.invert(j * q + y0));
}
}
grid.x = -q;
grid.y = -q;
grid.k = q;
grid.n = n;
grid.m = m;
return grid;
}
Insert cell
// Converts from grid coordinates (indexes) to screen coordinates (pixels).
transform = ({type, value, coordinates}) => {
return {type, value, coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([x, y]) => ([
grid.x + grid.k * x,
grid.y + grid.k * y
]));
});
})};
}
Insert cell
contours = d3
.contours()
.size([grid.n, grid.m])
.thresholds(thresholds)(grid)
.map(transform)
Insert cell
x = d3.scaleLinear([0, 1], [0, width])
Insert cell
y = d3.scaleLinear([0, 1], [0, height])
Insert cell
height = Math.floor((width + 28) / 4) * 4
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
import { ramp } from "@d3/color-legend"
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