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

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