Published
Edited
May 20, 2020
2 forks
18 stars
Also listed in…
Political Issues
Elections
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = FileAttachment("NC_Congressional_Data@1.json").json()
Insert cell
Insert cell
Insert cell
function make_map() {
console.log(['map_width is', map_width]);
let svg = d3
.create("svg")
.attr('width', map_width)
.attr('height', map_height);

const mapbg = DOM.uid('mapbg');

// Set up the SVG background
svg
.append("defs")
.append('pattern')
.attr('id', mapbg.id)
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', map_width)
.attr('height', map_height)
.append("image")
.attr("xlink:href", im_url)
.attr('width', map_width)
.attr('height', map_height);
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", map_width)
.attr("height", map_height)
.attr("fill", mapbg);

// Set up the district overlay.
let districts = svg
.append("g")
.attr('id', 'districts')
.selectAll("path")
.data(district_boundary_array[0].features)
.join("path")
.attr('class', 'district')
.attr("stroke-opacity", 0)
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr("stroke-linejoin", "round")
.attr("fill", 'none')
.attr("d", path);

// And react to a mouse hover.
districts
.on('mouseenter', function(d) {
districts
.attr('fill', 'white')
.style('stroke-width', 1)
.attr('fill-opacity', 0.8);
d3.select(this)
.style('stroke-width', 4)
.attr('fill', 'white')
.attr('fill-opacity', 0);
})
.on('mouseleave', function() {
districts
.attr('fill', 'white')
.attr('fill-opacity', 0)
.style('stroke-width', 2);
});

return svg.node();
}
Insert cell
// Transition when the Draw Date radio button is pushed.
function transition(n) {
let comm = d3.select(state_commentary);
Promises.delay(
1000,
comm
.transition()
.duration(1000)
.style('opacity', 0)
).then(function() {
comm.html(state_commentary_text(n));
comm
.transition()
.duration(1000)
.style('opacity', 1);
});

let districts = d3.select(map).select('#districts');
Promises.delay(
1000,
districts
.selectAll('path')
.transition()
.duration(1000)
.attr('stroke-opacity', 0)
)
.then(function() {
districts
.selectAll("path")
.data(district_boundary_array[n].features)
.join("path")
.attr("fill", 'none')
.attr("d", path)
.attr('title', function(d) {
let dprop = d.properties.DPROP;
let party, perc;
if (dprop < 0.5) {
party = 'Republican';
perc = d3.format('0.1%')(1 - dprop);
} else {
party = 'Democratic';
perc = d3.format('0.1%')(dprop);
}
return `District ${d.properties.DISTRICT}: ${perc} ${party}`;
});
})
.then(function() {
districts
.selectAll('path')
.attr('stroke-opacity', 0)
.transition()
.duration(1000)
.attr('stroke-opacity', 0.9);
})
.then(function() {
tippy('svg .district');
});
}
Insert cell
transition(w)
Insert cell
// Generate the image background
im_url = {
const context = DOM.context2d(map_width, map_height);
const path = d3
.geoPath()
.projection(projection)
.context(context);
context.canvas.style.maxWidth = "100%";
context.lineJoin = "round";
context.lineCap = "round";
context.strokeStyle = 'rgba(0,0,0,0.2)';

precinctFeatures.features.forEach(function(d) {
context.fillStyle = d3.interpolateRdBu(d.properties.DPROP);
context.beginPath();
path(d);
context.fill();
context.stroke();
});
return context.canvas.toDataURL();
}
Insert cell
district_boundary_array = [
"oldplan_boundaries",
"newplan_boundaries",
"court_boundaries"
].map(d => topojson.feature(data, data.objects[d]))
Insert cell
precinctFeatures = topojson.feature(data, data.objects.NC_VTD)
Insert cell
path = d3.geoPath().projection(projection)
Insert cell
projection = d3
.geoIdentity()
.reflectY(true)
.fitSize([map_width, map_height], precinctFeatures)
Insert cell
map_width = 0.98 * width
Insert cell
map_height = (4 * map_width) / 9
Insert cell
d3 = require('d3@5')
Insert cell
topojson = require("topojson-client@3")
Insert cell
import { radio } from "@jashkenas/inputs"
Insert cell
tippy = require("https://unpkg.com/tippy.js@2.5.4/dist/tippy.all.min.js")
Insert cell
Insert cell
efficiency_gap(district_boundary_array[0])
Insert cell
function efficiency_gap(districts) {
let wasted_dem_votes = d3.sum(
districts.features.map(d => wasted_votes(d.properties, 'dem'))
);
let wasted_rep_votes = d3.sum(
districts.features.map(d => wasted_votes(d.properties, 'rep'))
);
let total_votes = d3.sum(districts.features.map(d => d.properties.TVOTE));
return (wasted_dem_votes - wasted_rep_votes) / total_votes;
}
Insert cell
function wasted_votes(o, p) {
let dvote = o.DVOTE;
let rvote = o.RVOTE;
let tvote = o.TVOTE;
if (p == 'dem') {
if (dvote < tvote / 2) {
return dvote;
} else {
return dvote - tvote / 2;
}
} else {
if (rvote < tvote / 2) {
return rvote;
} else {
return rvote - tvote / 2;
}
}
}
Insert cell
function state_commentary_text(n) {
let eg = d3.format("0.1%")(efficiency_gap(district_boundary_array[n]));
let erc = expected_republican_count(n);
let text;
if (n == 0) {
text = `Districts drawn by Republicans after the 2010 election. Districts 1 and 12
are two of the most highly contentious districts with regard to gerrymandering in the US.`;
} else if (n == 1) {
text = `Districts redrawn by Republicans in 2016 after federal courts held that districts 1 and 12
were unconstitutionally gerrymandered based on race. This map was used in the 2018 elections
pending appeal.`;
} else if (n == 2) {
text = `Districts re-redrawn by Republicans in 2019 after state courts held
the previous maps were unconstitutionally gerrymandered based on party. This map will be used in the 2020 elections.`;
}
text =
text +
`<div style="text-align: center">${erc} Republican districts and ${13 -
erc} Democratic districts</div>
<div style="text-align: center"><a href="https://ballotpedia.org/Efficiency_gap" target='_blank'>Efficiency Gap</a> = ${eg}</div>`;
return text;
}
Insert cell
function expected_republican_count(n) {
return district_boundary_array[n].features.filter(
o => o.properties.DVOTE < o.properties.RVOTE
).length;
}
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