Published
Edited
Oct 22, 2019
21 stars
Insert cell
Insert cell
Insert cell
stateMap = {
const svg = d3.select(DOM.svg(width, height));
// never hurts to keep map layers in tidy groups
const statesLayer = svg.append('g');
// draw each state to an svg path
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', d => d.properties.color)
.attr('stroke', '#ccc');
return svg.node();
}
Insert cell
Insert cell
html`
<style>
.hover-example path:hover {
fill: #333;
}
</style>
`
Insert cell
stateMapWithHover = {
const svg = d3.select(DOM.svg(width, height));
// A little text field to appear when states are clicked
const label = svg.append('text')
.attr('text-anchor', 'end')
.attr('x', width - 10)
.attr('y', height - 10);
const statesLayer = svg.append('g')
.attr('class', 'hover-example');
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', d => d.properties.color)
.attr('stroke', '#ccc')
.on('click', (d) => { label.text(`You clicked ${d.properties.name}!`); });
return svg.node();
}
Insert cell
md`# Filters`
Insert cell
coastalGlow = {
const svg = d3.select(DOM.svg(width, height));
// append the <defs> element where filters, etc. are defined
const defs = svg.append('defs');
// create/append the filter
const filter = defs.append('filter')
.attr('id', 'coastal-glow');
// the glow filter needs three parts: a blur, a color, and a composite of the two as its final result
filter.append('feGaussianBlur')
.attr('in', 'SourceAlpha')
.attr('stdDeviation', 10)
.attr('result', 'shadow');
filter.append('feFlood')
.attr('flood-color', waterGlowColor)
.attr('result', 'color');
filter.append('feComposite')
.attr('in', 'color')
.attr('in2', 'shadow')
.attr('operator', 'in');
// water is a solid rectangle underneath everything
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', waterColor);
// this layer contains the glow (or shadow)
// it draws the states but the filter applied here turns them fuzzy and blue
const glowLayer = svg.append('g')
.attr('filter', 'url(#coastal-glow)');
glowLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath);
// finally, a normal land layer is drawn on top
const statesLayer = svg.append('g');
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', landColor)
.attr('stroke', '#ccc');
return svg.node();
}
Insert cell
Insert cell
Insert cell
coastalOffset = {
const svg = d3.select(DOM.svg(width, height));
const defs = svg.append('defs');
// water background as usual
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', waterColor);
function addOffsetCoastline(distance) {
const mask = defs.append('mask')
.attr('id', `coastline${distance}`);
mask.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white');
mask.selectAll('path.black')
.data(states)
.enter()
.append('path')
.attr('class', 'black')
.attr('stroke', 'black')
.attr('stroke-linejoin', 'round')
.attr('fill', 'none')
.attr('stroke-width', distance - 2)
.attr('d', geoPath);

waterLayer.append('g')
.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('stroke', '#37656b')
.attr('stroke-linejoin', 'round')
.attr('fill', 'none')
.attr('stroke-width', distance)
.attr('mask', `url(#coastline${distance})`)
.attr('d', geoPath);
}
addOffsetCoastline(40);
addOffsetCoastline(20);
addOffsetCoastline(10);
// finally, the land on top
const statesLayer = svg.append('g');
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', landColor)
.attr('stroke', '#37656b');
return svg.node();
}
Insert cell
Insert cell
simplePattern = {
const svg = d3.select(DOM.svg(width, height));
// append the <defs> element where filters, etc. are defined
const defs = svg.append('defs');
const patternSize = 10;
const waterPattern = defs.append('pattern')
.attr('id', 'waterDotPattern')
.attr('width', patternSize)
.attr('height', patternSize)
.attr('patternUnits', 'userSpaceOnUse')
waterPattern.append('circle')
.attr('cx', patternSize / 2)
.attr('cy', patternSize / 2)
.attr('r', patternSize / 4)
.attr('fill', waterColor);
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'url(#waterDotPattern)');
// a normal land layer is drawn on top
const statesLayer = svg.append('g');
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', landColor)
.attr('stroke', '#ccc');
return svg.node();
}
Insert cell
Insert cell
slightlyFancyPatterns = {
const svg = d3.select(DOM.svg(width, height));
const defs = svg.append('defs');
const patternSize = 20;
// this is a clipPath needed to cut circles into semicircles for the wave pattern
defs.append('clipPath')
.attr('id', 'patternClip')
.attr('clipPathUnits', 'objectBoundingBox') // units will be as % of the object size to which this is applied
.append('rect')
.attr('x', 0)
.attr('y', .5) // i.e. 50% of object height
.attr('width', 1)
.attr('height', .5); // 50% of object height
// ^ in other words, the object will be clipped to show only its bottom half
const waterPattern = defs.append('pattern')
.attr('id', 'waterPattern')
.attr('width', patternSize)
.attr('height', patternSize * 1.5)
.attr('patternUnits', 'userSpaceOnUse')
// solid background
waterPattern.append('rect')
.attr('width', patternSize)
.attr('height', patternSize * 2)
.attr('fill', waterColor);
const waves = waterPattern.append('g')
.style('fill', 'none')
.style('stroke', waterGlowColor)
.style('stroke-width', 0.5)
.attr('transform', 'scale(1, .75)'); // squished a bit vertically so waves aren't exactly circular
// three semicircles: one centered at the top left corner, one at top right corner, and one kind of in the center
// as the first two extend outside the pattern area, they're actually two quarter circles
// ...forming this kind of shape: _/\_
waves.append('circle')
.attr('clip-path', 'url(#patternClip)')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', patternSize * .5);
waves.append('circle')
.attr('clip-path', 'url(#patternClip)')
.attr('cx', patternSize)
.attr('cy', 0)
.attr('r', patternSize * .5);
// kind of centered
// bottom half of a circle: \_/
waves.append('circle')
.attr('clip-path', 'url(#patternClip)')
.attr('cx', patternSize * .5)
.attr('cy', patternSize)
.attr('r', patternSize * .5);
// see below for an image of what the above look like altogether

// the land pattern works similarly but is simpler, just some dots
const landPattern = defs.append('pattern')
.attr('id', 'landPattern')
.attr('width', patternSize)
.attr('height', patternSize)
.attr('patternUnits', 'userSpaceOnUse');
landPattern.append('rect')
.attr('width', patternSize)
.attr('height', patternSize)
.attr('fill', landColor);
const dotColor = '#eee';
// drawing dots could be done more concisely in a loop, but here they are explicitly
landPattern.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', patternSize/2)
.attr('cy', 0)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', patternSize)
.attr('cy', 0)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', patternSize/4)
.attr('cy', patternSize/2)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', 3 * patternSize/4)
.attr('cy', patternSize/2)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', 0)
.attr('cy', patternSize)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', patternSize/2)
.attr('cy', patternSize)
.attr('r', patternSize/8)
.attr('fill', dotColor);
landPattern.append('circle')
.attr('cx', patternSize)
.attr('cy', patternSize)
.attr('r', patternSize/8)
.attr('fill', dotColor);
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'url(#waterPattern)'); // use water pattern
const statesLayer = svg.append('g');
statesLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', 'url(#landPattern)') // use land pattern
.attr('stroke', '#ccc');
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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