Published
Edited
Oct 31, 2020
Insert cell
Insert cell
Insert cell
{
//From https://observablehq.com/@tmcw/using-mapbox-gl-js
let container = html`<div id= "container" style='height:600px;' />`;

// Give the container dimensions.
yield container;

// Create the \`map\` object with the mapboxgl.Map constructor, referencing
// the container div
let map = new mapboxgl.Map({
container,
center: [-73.982364, 40.706839],
zoom: 9.5,
style: "mapbox://styles/mapbox/light-v10",
scrollZoom: false
});
//map.scrollZoom.disable()
map.addControl(new mapboxgl.NavigationControl(), "top-right");
const mapboxContainer = map.getCanvasContainer()
const svg = d3.select(mapboxContainer)
.append("svg")
.style("position", "absolute")
.attr("width", width)
.attr("height", height);
//From http://bl.ocks.org/enjalot/0d87f32a1ccb9a720d29ba74142ba36
function getD3() {
var bbox = document.querySelector("#container").getBoundingClientRect();
console.log(bbox)
var center = map.getCenter();
var zoom = map.getZoom();
// 512 is hardcoded tile size, might need to be 256 or changed to suit your map config
var scale = (512) * 0.5 / Math.PI * Math.pow(2, zoom);

let projection = d3.geoMercator()
.center([center.lng, center.lat])
.translate([bbox.width/2, bbox.height/2])
.scale(scale);

return projection;
}
// calculate the original d3 projection
let projection = getD3();
let path = d3.geoPath().projection(projection);
const colorScale = d3.scaleThreshold()
.domain([3, 5, 7, 10])
.range(d3.schemeBuPu[5]);

const dists = svg
.selectAll("path")
.data(geojson.features)
.join("path")
.attr("d", path)
.attr("class", "dists")
.attr("fill", function(d) {
if (d.properties.total_first2016 > 10)
return colorScale(d.properties.pct_total_first2016_total_voted2016)
else return "whitesmoke"
})
.style("stroke", "darkgray")
.style("stroke-width", ".25")
.style("cursor", "default")
//From http://bl.ocks.org/enjalot/0d87f32a1ccb9a720d29ba74142ba365
function render() {
projection = getD3();
path.projection(projection)
dists
.attr("d", path)
}

// re-render our visualization whenever the view changes
map.on("viewreset", function() {
render()
})
map.on("move", function() {
render()
})
// render our initial visualization
render();
//FROM https://observablehq.com/@duynguyen1678/choropleth-with-tooltip
const tooltip = svg.append("g");

svg
.selectAll(".dists")
.on("touchmove mousemove", function(event, d) {
if (d.properties.total_first2016 > 10 && d.properties.winner === 'trump') {
tooltip.call(
callout,
`${format(d.properties.pct_total_first2016_total_voted2016)}
(${d.properties.total_first2016} new voters of ${d.properties.total_voted2016})

${(d.properties.DEM) ? d.properties.DEM : 0} Democrats
${(d.properties.REP) ? d.properties.REP : 0} Republicans
${((d.properties.ALL_OTH) > 0) ? d.properties.ALL_OTH : 0} Other

Trump won this district by ${d.properties.trump_votes - d.properties.clinton_votes} votes.`
);
}
else if (d.properties.total_first2016 > 10 && d.properties.winner === 'clinton') {
tooltip.call(
callout,
`${format(d.properties.pct_total_first2016_total_voted2016)}
(${d.properties.total_first2016} new voters of ${d.properties.total_voted2016})

${(d.properties.DEM) ? d.properties.DEM : 0} Democrats
${(d.properties.REP) ? d.properties.REP : 0} Republicans
${((d.properties.ALL_OTH) > 0) ? d.properties.ALL_OTH : 0} Other

Clinton won this district by ${d.properties.clinton_votes - d.properties.trump_votes} votes.`
);
} else {
tooltip.call(
callout,
`This district had fewer than 10 new voters.`);
};
tooltip.attr("transform", `translate(${d3.pointer(event, this)})`);
d3.select(this)
.style("stroke", "black")
.style("stroke-width", "2")
.style("fill", function(d) {
if (d.properties.winner === 'trump')
return "darkred";
else if (d.properties.winner === 'clinton')
return "darkblue";
else return colorScale(d.properties.pct_total_first2016_total_voted2016);
})
})
.on("touchend mouseleave", function(d) {
tooltip.call(callout, null);
d3.select(this)
.style("stroke", null)
.style("fill", function(d) {
if (d.properties.total_first2016 > 10)
return colorScale(d.properties.pct_total_first2016_total_voted2016)
else return "whitesmoke"
})
});
//From https://observablehq.com/@tmcw/using-mapbox-gl-js
// Be careful to clean up the map's resources using \`map.remove()\` whenever
// this cell is re-evaluated.
invalidation.then(() => map.remove());
}
Insert cell
Insert cell
Insert cell
Insert cell
height = 600
Insert cell
margin = ({top: 25, right: 20, bottom: 35, left: 40})
Insert cell
format = d => `${d}%`
Insert cell
d3 = require("d3@6")
Insert cell
import { callout } from "@d3/line-chart-with-tooltip"
Insert cell
import {legend} 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