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

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