Published
Edited
Oct 28, 2020
1 fork
Insert cell
md`# Changes to polling places`
Insert cell
Insert cell
chart('Cabarrus', 'North Carolina', cabarrus, 'vote');
Insert cell
chart = (county, state, data, row) => {
const pack = data => d3.pack()
.size([size, size])
.padding(3)(d3.hierarchy(data)
.sum(d => d.population_total_2018)
.sort((a, b) => b.d_2016 < a.d_2016 ? -1 : 1)
);

const root = pack(
{
children: [
{
name: 'same',
children: data.filter(d => d.poll_count_2016 === d.poll_count_2020)
},
{
name: 'loss',
children: data.filter(d => d.poll_count_2016 > d.poll_count_2020)
},
{
name: 'gain',
children: data.filter(d => d.poll_count_2016 < d.poll_count_2020)
}
]
}
);

const svg = d3.create("svg")
.attr('width', size + 20)
.attr('height', size + 20)
.attr("viewBox", [0, 0, size + 20, size + 20])
svg.append('rect').attr('width', size + 20).attr('height', size + 20).style('fill', 'white');
const guts = svg
.append('g')
.attr('transform', `translate(${10},${10})`)
guts.append('text')
.text(county + ' County, ' + state)
.attr('y', 40)
.style('font-family', 'Helvetica')
.style('font-size', 40)
.style('font-weight', 800)

const same = guts.selectAll("g.same")
.data(root.children.find(c => c.data.name === 'same').children)
.enter("g")
.append("g")
.classed('same', true)
.classed('circle-wrapper', true)
.attr("transform", d => `translate(${d.x},${d.y})`);
const samePopulation = numeral(Math.round(
root
.children
.find(c => c.data.name === 'same')
.children
.reduce((a, c) => (a += c.data.population_total_2018), 0)
)).format(',');

guts
.append('text')
.classed('small', true)
.text(`${samePopulation} possible voters live in precincts that stayed the same.`)
.attr('x', 0)
.attr('y', 100)

const loss = guts.selectAll("g.removed")
.data(root.children.find(c => c.data.name === 'loss').children)
.enter("g")
.append("g")
.classed('removed', true)
.classed('circle-wrapper', true)
.attr("transform", d => `translate(${d.x},${d.y})`);

const lossPopulation = numeral(Math.round(
root
.children
.find(c => c.data.name === 'loss')
.children
.reduce((a, c) => (a += c.data.population_total_2018), 0)
)).format(',');

guts
.append('text')
.classed('small', true)
.text(`${lossPopulation} possible voters live`)
.attr('x', size)
.attr('y', 230)
.style('text-anchor', 'end')
guts
.append('text')
.classed('small', true)
.text('in precincts that lost polls.')
.attr('x', size)
.attr('y', 245)
.style("text-anchor", 'end');

const gain = guts.selectAll("g.gain")
.data(root.children.find(c => c.data.name === 'gain').children)
.enter("g")
.append("g")
.classed('removed', true)
.classed('circle-wrapper', true)
.attr("transform", d => `translate(${d.x},${d.y})`);

const gainPopulation = numeral(Math.round(
root
.children
.find(c => c.data.name === 'gain')
.children
.reduce((a, c) => (a += c.data.population_total_2018), 0)
)).format(',');

guts
.append('text')
.classed('small', true)
.text(`${gainPopulation} possible voters live`)
.attr('x', size - 100)
.attr('y', size - 100)
.style('text-anchor', 'end')
guts
.append('text')
.classed('small', true)
.text('in precincts that gained polls.')
.attr('x', size - 100)
.attr('y', size - 100 + 15)
.style("text-anchor", 'end');

guts.selectAll('.small')
.style('font-size', 12.5)
.style('font-family', 'Helvetica')
.style("font-weight", 800)

same.append('circle')
loss.append("circle")
gain.append("circle")
guts.selectAll('circle')
.attr("r", d => d.r)
.style('fill', d => {
return d3.interpolate(
scales.vote((d.data.d_2016 / d.data.t_2016) - (d.data.r_2016 / d.data.t_2016)),
'#ffffff'
)(0.5)
})
.style('stroke', d => {
return scales.vote((d.data.d_2016 / d.data.t_2016) - (d.data.r_2016 / d.data.t_2016))
})
.style('stroke-width', 1);
makeLegend(guts, size, row);

return svg.node();
}
Insert cell
grid(cuyahoga)
Insert cell
grid(fulton)
Insert cell
grid = (data) => {
const s = width / 3;
const svg = d3.create("svg")
.style('background-color', 'white')
.attr('width', s * 3)
.attr('height', s + 60)
.attr("viewBox", [0, 0, s * 3, s])
const same = data.filter(d => d.poll_count_2016 === d.poll_count_2020).sort((a, b) => {
return (a.d_2016 / a.t_2016) - (a.r_2016 / a.t_2016) > (b.d_2016 / b.t_2016) - (b.r_2016 / b.t_2016)
? 1
: -1
})
const loss = data.filter(d => d.poll_count_2016 > d.poll_count_2020).sort((a, b) => {
return (a.d_2016 / a.t_2016) - (a.r_2016 / a.t_2016) > (b.d_2016 / b.t_2016) - (b.r_2016 / b.t_2016)
? 1
: -1
})
const gain = data.filter(d => d.poll_count_2016 < d.poll_count_2020).sort((a, b) => {
return (a.d_2016 / a.t_2016) - (a.r_2016 / a.t_2016) > (b.d_2016 / b.t_2016) - (b.r_2016 / b.t_2016)
? 1
: -1
})
const rowsize = Math.ceil(Math.sqrt(same.length));
const u = Math.floor((s - 20) / rowsize);
const r = d3.scaleLinear()
.domain(d3.extent(data, d => d.population_total_2018))
.range([(u - 1) / 4, (u - 1) / 2])

svg.append('g')
.classed('same', true)
.attr('transform', `translate(${u / 2},0)`)
.selectAll('circle')
.data(same)
.enter()
.append('rect')
.attr('width', u - 1)
.attr('height', u - 1)
.attr('x', (d, i) => (i % rowsize) * u)
.attr('y', (d, i) => Math.floor(i / rowsize) * u)
.style('fill', d => {
return scales.vote((d.d_2016 / d.t_2016) - (d.r_2016 / d.t_2016))
});
// .append('circle')
// .attr('cx', (d, i) => (i % rowsize) * u)
// .attr('cy', (d, i) => Math.floor(i / rowsize) * u)
// .attr('r', d => r(d.population_total_2018))
// .style('fill', d => {
// return d3.interpolate(
// scales.vote((d.d_2016 / d.t_2016) - (d.r_2016 / d.t_2016)),
// '#ffffff'
// )(0.5)
// })
// .style('stroke', d => {
// return scales.vote((d.d_2016 / d.t_2016) - (d.r_2016 / d.t_2016))
// })
// .style('stroke-width', 1)
svg.append('g')
.classed('loss', true)
.attr('transform', `translate(${s},0)`)
.selectAll('rect')
.data(loss)
.enter()
.append('rect')
.attr('width', u - 1)
.attr('height', u - 1)
.attr('x', (d, i) => (i % rowsize) * u)
.attr('y', (d, i) => Math.floor(i / rowsize) * u)
.style('fill', d => {
return scales.vote((d.d_2016 / d.t_2016) - (d.r_2016 / d.t_2016))
});
svg.append('g')
.classed('gain', true)
.attr('transform', `translate(${s * 2},0)`)
.selectAll('rect')
.data(gain)
.enter()
.append('rect')
.attr('width', u - 1)
.attr('height', u - 1)
.attr('x', (d, i) => (i % rowsize) * u)
.attr('y', (d, i) => Math.floor(i / rowsize) * u)
.style('fill', d => {
return scales.vote((d.d_2016 / d.t_2016) - (d.r_2016 / d.t_2016))
});
makeLegend(svg, s + 40, 'vote');
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
cuyahogaNice = await FileAttachment("cuyahoga-detail.json").json()
Insert cell
cabarrus = d3.csvParse(await FileAttachment("nc-cabarrus.csv").text()).map(row => {
const datum = {};
for (const key in row) {
if (['key', 'CNTYFIPS', 'COUNTY', 'CODE', 'PRECODE', 'PRECINT', 'PRENAME'].indexOf(key) === -1) {
datum[key] = +row[key]
} else {
datum[key] = row[key]
}
}
return datum;
})
Insert cell
fulton = d3.csvParse(await FileAttachment("ga-fulton.csv").text()).map(row => {
const datum = {};
for (const key in row) {
if (['key', 'CNTYFIPS', 'COUNTY', 'CODE', 'PRECODE', 'PRECINT', 'PRENAME'].indexOf(key) === -1) {
datum[key] = +row[key]
} else {
datum[key] = row[key]
}
}
return datum;
})
Insert cell
Insert cell
Insert cell
d3 = require('d3')
Insert cell
numeral = require('numeral')
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