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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more