Published
Edited
Jul 5, 2021
Insert cell
Insert cell
Insert cell
wnc_zoom = {
again;
let w = window.innerWidth;
let h;
if (window.innerHeight < 2500) {
h = window.innerHeight;
} else {
h = 0.6 * w;
}
let div = d3
.create('div')
.style('width', `${w}px`)
.style('height', `${h}px`)
.style('position', 'relative');
let canvas = div
.append('canvas')
.attr('width', w)
.attr('height', h);
let context = canvas.node().getContext('2d');
let r = 9;
let projection = d3
.geoOrthographic()
.translate([w / 2, h / 2])
.scale(r);
let path = d3.geoPath(projection, context);
if (speed_test() < 50) {
let a0 = 40;
let ma = 82.88;
let spinSpeed = .2;
let s = ((8 * h) / r) ** (spinSpeed / (-a0 + ma));
canvas.angle = a0;
while (canvas.angle < ma) {
r = s * r;
projection.rotate([(canvas.angle += spinSpeed), -35.65]).scale(r);
context.clearRect(0, 0, w, h);
context.lineWidth = 2;
context.strokeStyle = 'black';
context.fillStyle = d3.interpolateRdBu(0.66);
context.beginPath();
context.arc(w / 2, h / 2, r, 0, 2 * Math.PI);
context.fill();
context.stroke();
draw(context, canvas, projection, path, r, w, h);
yield div.node();
}
}
if (r == 9) {
canvas.angle = 83;
r = 4800;
}
projection.rotate([canvas.angle, -35.65]).scale(r);
context.clearRect(0, 0, w, h);
context.lineWidth = 2;
context.strokeStyle = 'black';
context.fillStyle = d3.interpolateRdBu(0.66);
context.beginPath();
context.arc(w / 2, h / 2, r, 0, 2 * Math.PI);
context.fill();
context.stroke();
draw(context, canvas, projection, path, r, w, h);
yield div.node();
let svg = div
.append('svg')
.attr('width', w - 2)
.attr('height', h - 2)
.style('position', 'absolute')
.style('top', 0)
.style('left', 0);
let svg_path = d3.geoPath(projection);
svg
.append('path')
.attr('d', svg_path(features.d11_boundary.features[0]))
.style('stroke', 'black')
.style('stroke-linejoin', 'round')
.style('fill', 'rgba(0,0,0,0)')
.style("cursor", "pointer")
.on('mouseenter', function() {
d3.select(this).style('stroke-width', '3px');
})
.on('mouseleave', function() {
d3.select(this).style('stroke-width', '1px');
})
.on('click', function() {
window.open("https://wncviz.com/demos/Gerrymandering_in_NC/");
});
}
Insert cell
function draw(context, canvas, projection, path, r, angle, w, h) {
let ma = 82.88;
let landColor = '#fafafa';

// The land
context.lineWidth = 1;
context.strokeStyle = 'black';
context.fillStyle = landColor;
context.beginPath();
path(features.land);
context.fill();
context.stroke();

let ca = 65;
if (canvas.angle > ca) {
context.fillStyle = landColor;
context.strokeStyle = `rgba(0,0,0,${((canvas.angle - ca) / (ma - ca)) **
2})`;
context.beginPath();
path(features.states);
context.fill();
context.stroke();
features.d11_VTDs.features.forEach(function(d) {
context.strokeStyle = `rgba(0,0,0,${0.2 *
((canvas.angle - ca) / (ma - ca)) ** 2})`;
let c = d3.rgb(d3.interpolateRdBu(d.properties.dprop));
c.opacity = 0.75 * ((canvas.angle - ca) / (ma - ca)) ** 2;
context.fillStyle = c.toString();
context.beginPath();
path(d);
context.fill();
context.stroke();
});
context.strokeStyle = `rgba(0,0,0,${((canvas.angle - ca) / (ma - ca)) **
2})`;
context.lineWidth = 1;
context.beginPath();
path(features.d11_boundary);
context.stroke();
}
}
Insert cell
Insert cell
ortho_zoom = FileAttachment("ortho_zoom@3.json").json()
Insert cell
features = {
let land = topojson.feature(ortho_zoom, ortho_zoom.objects.land);
let states = topojson.feature(ortho_zoom, ortho_zoom.objects.states);
let d11_boundary = topojson.feature(
ortho_zoom,
ortho_zoom.objects.D11Boundary
);
let d11_VTDs = topojson.feature(ortho_zoom, ortho_zoom.objects.D11_VTDs);
return {
land: land,
states: states,
d11_boundary: d11_boundary,
d11_VTDs: d11_VTDs
};
}
Insert cell
import { Button } from '@observablehq/inputs'
Insert cell
d3 = require('d3-selection@2', 'd3-shape@2', 'd3-geo@2', 'd3-scale-chromatic@2', 'd3-color@2')
Insert cell
topojson = require("topojson-client@3")
Insert cell
function speed_test() {
let t = Date.now();
let T = 0;
for (let i = 0; i < 10 ** 7; i++) {
T = T + i;
}
return Date.now() - t;
}
Insert cell
{
// draw(context, canvas, projection, path, r, angle, w, h)
let r = 1800;
let w = 1200;
let h = 600;
let canvas = d3
.create('canvas')
.attr('width', w)
.attr('height', h);
let context = canvas.node().getContext('2d');
let a = 73;
canvas.angle = a;
let projection = d3
.geoOrthographic()
.translate([w / 2, h / 2])
.rotate([a, -35.65])
.scale(r);
let path = d3.geoPath(projection, context);
context.lineWidth = 2;
context.strokeStyle = 'black';
context.fillStyle = d3.interpolateRdBu(0.66);
context.beginPath();
context.arc(w / 2, h / 2, r, 0, 2 * Math.PI);
context.fill();
context.stroke();
let ma = 82.88;
let landColor = '#fafafa';

// The land
context.lineWidth = 1;
context.strokeStyle = 'black';
context.fillStyle = landColor;
context.beginPath();
path(features.land);
context.fill();
context.stroke();

let ca = 65;
if (canvas.angle > ca) {
context.fillStyle = landColor;
context.strokeStyle = `rgba(0,0,0,${((canvas.angle - ca) / (ma - ca)) **
2})`;
context.beginPath();
path(features.states);
context.fill();
context.stroke();
context.lineWidth = 0.6;
features.d11_VTDs.features.forEach(function(d) {
context.strokeStyle = `rgba(0,0,0,0.2)`;
let c = d3.rgb(d3.interpolateRdBu(d.properties.dprop));
c.opacity = 1; // 0.75 * ((canvas.angle - ca) / (ma - ca)) ** 2;
context.fillStyle = c.toString();
context.beginPath();
path(d);
context.fill();
context.stroke();
});
context.strokeStyle = `rgba(0,0,0,${((canvas.angle - ca) / (ma - ca)) **
2})`;
context.lineWidth = 1;
context.beginPath();
path(features.d11_boundary);
context.stroke();
}
return canvas.node();
}
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