Published
Edited
Oct 22, 2019
13 stars
Insert cell
Insert cell
Insert cell
innerGlowMap = {
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', 'border-blur');
// 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', 'SourceGraphic')
.attr('stdDeviation', 5)
.attr('result', 'shadow');
states.forEach((state) => {
defs.append('clipPath')
.attr('id', `clip-${state.properties.adm1_code}`)
.append('path')
.datum(state)
.attr('d', geoPath);
});
// water is a solid rectangle underneath everything
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', waterColor);
const statesLayer = svg.append('g');
// a white backround for states because they'll have semi-transparent fills
statesLayer.selectAll('path.fill')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'white')
.attr('fill', 'white');
// draw fill for each state
statesLayer.selectAll('path.fill')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'fill')
.attr('fill', d => d.properties.color)
.attr('fill-opacity', .2);
// now draw a wide, blurred stroke, clipped to the inside of the state
statesLayer.selectAll('path.glow')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'glow')
.attr('stroke', d => d.properties.color)
.attr('stroke-width', 20)
.attr('stroke-linejoin', 'round')
.attr('stroke-opacity', .75)
.attr('fill', 'none')
.attr('clip-path', d => `url(#clip-${d.properties.adm1_code})`)
.attr('filter', 'url(#border-blur)');
statesLayer.selectAll('path.stroke')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'stroke')
.attr('stroke', '#fff')
.attr('fill', 'none');
return svg.node();
}
Insert cell
Insert cell
coastalHatch = {
const svg = d3.select(DOM.svg(width, height));
const defs = svg.append('defs');
// a blur filter
const filter = defs.append('filter')
.attr('id', 'mask-blur');
filter.append('feGaussianBlur')
.attr('in', 'SourceGraphic')
.attr('stdDeviation', 10);
// a mask consisting of the land layer with a big stroke (extending into the ocean)
// that part of the stroke in the ocean is where the hatch pattern will appear
const mask = defs.append('mask')
.attr('id', 'coastal-mask')
.attr('maskUnits', 'userSpaceOnUse');
const maskG = mask.append('g');
maskG.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('fill', 'none')
.attr('stroke-width', 20)
.attr('stroke', '#fff')
.attr('stroke-linejoin', 'round');
// the mask is blurred so that the pattern will fade out
maskG.attr('filter', 'url(#mask-blur)');
// and the pattern itself, a simple horizontal stripe
const pattern = defs.append('pattern')
.attr('id', 'hatch-pattern')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 10)
.attr('height', 3);
pattern.append('path')
.attr('stroke', '#333')
.attr('stroke-width', 1)
.attr('fill', 'none')
.attr('d', 'M0 1.5L10 1.5');
// water background as usual
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', waterColor);
// here's the hatching layer
// it actually fills the entire view but is masked by that blurred coastline
const coastLayer = svg.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'url(#hatch-pattern)')
.attr('mask', 'url(#coastal-mask)');
// 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', '#ccc');
return svg.node();
}
Insert cell
Insert cell
paperMap = {
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', 'border-blur');
// 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', 'SourceGraphic')
.attr('stdDeviation', 5)
.attr('result', 'shadow');
const noiseFilter = defs.append('filter').attr("id", 'noise').attr('filterUnits', 'userSpaceOnUse')
noiseFilter.append("feTurbulence")
.attr('height', height * 2)
.attr('width', width)
.attr('result', 'waves')
.attr('type', 'turbulence')
.attr('baseFrequency', `0.8 0.8`)
.attr('numOctaves', 1)
.attr('seed', 53);
noiseFilter.append('feDisplacementMap')
.attr('in','SourceGraphic')
.attr('in2','waves')
.attr('height', height * 2)
.attr('scale', 50)
.attr('xChannelSelector', 'R')
.attr('yChannelSelector', 'B')
.attr('result','ripples');
noiseFilter.append('feGaussianBlur')
.attr('in', 'ripples')
.attr('color-interpolation-filters', 'sRGB') // for better results in Safari
.attr('stdDeviation', 2);
const paper = defs.append('filter')
.attr('id', 'paper-texture')
.attr('filterUnits', 'userSpaceOnUse');
paper.append('feTurbulence')
.attr('type', 'fractalNoise')
.attr('baseFrequency', '0.04')
.attr('result', 'noise')
.attr('numOctaves', '5');
paper.append('feDiffuseLighting')
.attr('in', 'noise')
.attr('lighting-color', 'white')
.attr('surfaceScale', '2')
.append('feDistantLight')
.attr('azimuth', '45')
.attr('elevation', '60')
const filterOffset = 17;
states.forEach((state) => {
defs.append('clipPath')
.attr('id', `paper-clip-${state.properties.adm1_code}`)
.append('path')
.datum(state)
.attr('d', geoPath)
.attr('transform', `translate(${filterOffset},${filterOffset})`)
});
// solid water fill
const waterLayer = svg.append('g');
waterLayer.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', waterColor);
// wide, filtered stroke for the "glow"
waterLayer.selectAll('path')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('stroke-linejoin', 'round')
.attr('fill', 'none')
.attr('stroke', waterGlowColor)
.attr('opacity',.75)
.attr('stroke-width', 40)
.attr('filter', 'url(#noise)')
.attr('transform', `translate(-${filterOffset},-${filterOffset})`);
const statesLayer = svg.append('g');
// white backround
statesLayer.selectAll('path.fill')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'white')
.attr('fill', 'white');
// state fills
statesLayer.selectAll('path.fill')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'fill')
.attr('fill', d => d.properties.color)
.attr('fill-opacity', .2);
// wide, blurred/textured stroke
statesLayer.selectAll('path.glow')
.data(states)
.enter()
.append('path')
.attr('d', geoPath)
.attr('class', 'glow')
.attr('stroke-linejoin', 'round')
.attr('stroke', d => d.properties.color)
.attr('opacity',.75)
.attr('stroke-width', 25)
.attr('fill', 'none')
.attr('clip-path', d => `url(#paper-clip-${d.properties.adm1_code})`)
.attr('filter', 'url(#noise)')
.attr('transform', `translate(-${filterOffset},-${filterOffset})`);
// overlay a rectangle with paper texture
svg.append('rect')
.attr('width','100%')
.attr('height', '100%')
.attr('filter', 'url(#paper-texture)')
.style('mix-blend-mode', 'multiply')
.style('opacity', .75)
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

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