Published
Edited
May 3, 2019
3 stars
Insert cell
Insert cell
shadowSize = 3
Insert cell
shadowOpacity = .8
Insert cell
illuminatedContours = {
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext('2d');
// fill the canvas background
ctx.fillStyle = '#ccc';
ctx.fillRect(0, 0, width, height);
const projection = d3.geoEqualEarth()
.rotate([71.30631, 0])
.center([0, 44.2707])
.translate([width/2,height/2])
.scale(850000);
const path = d3.geoPath().projection(projection);

contours.features.forEach((feature, i) => {
if (i === 0) return; // lowest contour is essentially the background
ctx.drawImage(getIlluminatedContour(path, feature), 0, 0);
});
return canvas;
}
Insert cell
function getIlluminatedContour(path, feature) {
// a "stencil" that will be used for drawing shadows
const stencilCanvas = DOM.canvas(width, height);
const stencilCtx = stencilCanvas.getContext('2d');

stencilCtx.fillStyle = 'black'; // any opaque color; we won't see this in the end
stencilCtx.strokeStyle = 'black';
stencilCtx.lineWidth = 1;

// fill the whole canvas
stencilCtx.fillRect(0, 0, width, height);

// cut the contour shape out of it
stencilCtx.globalCompositeOperation = 'destination-out';
stencilCtx.beginPath();
path.context(stencilCtx)(feature);
stencilCtx.fill();
stencilCtx.stroke(); // a little stroke helps with the final appearance

// the canvas for the final returned contour shape
const contourCanvas = DOM.canvas(width, height);
const contourCtx = contourCanvas.getContext('2d');
contourCtx.fillStyle = '#ccc';

// draw the contour shape
contourCtx.beginPath();
path.context(contourCtx)(feature);
contourCtx.fill();

// we'll draw the stencil in 'source-atop' mode.
// only pixels overlapping existing content will be drawn — i.e., the drop shadows
contourCtx.globalCompositeOperation = 'source-atop';

contourCtx.shadowBlur = shadowSize;

// draw a dark shadow up and left of the stencil shape
// this will appear in the bottom right of the "hole" — the contour shape
contourCtx.shadowOffsetX = -shadowSize;
contourCtx.shadowOffsetY = -shadowSize;
contourCtx.shadowColor = `rgba(0,0,0,${shadowOpacity})`;
contourCtx.drawImage(stencilCanvas, 0, 0);

// light shadow on the other side
contourCtx.shadowOffsetX = shadowSize;
contourCtx.shadowOffsetY = shadowSize;
contourCtx.shadowColor = `rgba(255,255,255,${shadowOpacity})`;
contourCtx.drawImage(stencilCanvas, 0, 0);

return contourCanvas;
}
Insert cell
width = 1000;
Insert cell
height = 700;
Insert cell
contours = d3.json('https://gist.githubusercontent.com/awoodruff/71a401d9bbfa844ea30bc2c8634b7333/raw/bc67471592ca0165ae695e614f274519b8b9ac82/washington_contours.geojson')
Insert cell
d3 = require('d3@5')
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