Last active
September 28, 2018 11:59
-
-
Save curran/f3de248a91fe2995a97aedc6ea0c39ad to your computer and use it in GitHub Desktop.
Choropleth Map with Interactive Filtering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (topojson,d3) { | |
'use strict'; | |
const loadAndProcessData = () => | |
Promise | |
.all([ | |
d3.tsv('https://unpkg.com/world-atlas@1.1.4/world/50m.tsv'), | |
d3.json('https://unpkg.com/world-atlas@1.1.4/world/50m.json') | |
]) | |
.then(([tsvData, topoJSONdata]) => { | |
const rowById = tsvData.reduce((accumulator, d) => { | |
accumulator[d.iso_n3] = d; | |
return accumulator; | |
}, {}); | |
const countries = topojson.feature(topoJSONdata, topoJSONdata.objects.countries); | |
countries.features.forEach(d => { | |
Object.assign(d.properties, rowById[d.id]); | |
}); | |
return countries; | |
}); | |
const colorLegend = (selection, props) => { | |
const { | |
colorScale, | |
circleRadius, | |
spacing, | |
textOffset, | |
backgroundRectWidth, | |
onClick, | |
selectedColorValue | |
} = props; | |
const backgroundRect = selection.selectAll('rect') | |
.data([null]); | |
const n = colorScale.domain().length; | |
backgroundRect.enter().append('rect') | |
.merge(backgroundRect) | |
.attr('x', -circleRadius * 2) | |
.attr('y', -circleRadius * 2) | |
.attr('rx', circleRadius * 2) | |
.attr('width', backgroundRectWidth) | |
.attr('height', spacing * n + circleRadius * 2) | |
.attr('fill', 'white') | |
.attr('opacity', 0.8); | |
const groups = selection.selectAll('.tick') | |
.data(colorScale.domain()); | |
const groupsEnter = groups | |
.enter().append('g') | |
.attr('class', 'tick'); | |
groupsEnter | |
.merge(groups) | |
.attr('transform', (d, i) => | |
`translate(0, ${i * spacing})` | |
) | |
.attr('opacity', d => | |
(!selectedColorValue || d === selectedColorValue) | |
? 1 | |
: 0.2 | |
) | |
.on('click', d => onClick( | |
d === selectedColorValue | |
? null | |
: d | |
)); | |
groups.exit().remove(); | |
groupsEnter.append('circle') | |
.merge(groups.select('circle')) | |
.attr('r', circleRadius) | |
.attr('fill', colorScale); | |
groupsEnter.append('text') | |
.merge(groups.select('text')) | |
.text(d => d) | |
.attr('dy', '0.32em') | |
.attr('x', textOffset); | |
}; | |
const projection = d3.geoNaturalEarth1(); | |
const pathGenerator = d3.geoPath().projection(projection); | |
const choroplethMap = (selection, props) => { | |
const { | |
features, | |
colorScale, | |
colorValue, | |
selectedColorValue | |
} = props; | |
console.log(features); | |
const gUpdate = selection.selectAll('g').data([null]); | |
const gEnter = gUpdate.enter().append('g'); | |
const g = gUpdate.merge(gEnter); | |
gEnter | |
.append('path') | |
.attr('class', 'sphere') | |
.attr('d', pathGenerator({type: 'Sphere'})) | |
.merge(gUpdate.select('.sphere')) | |
.attr('opacity', selectedColorValue ? 0.05 : 1); | |
selection.call(d3.zoom().on('zoom', () => { | |
g.attr('transform', d3.event.transform); | |
})); | |
const countryPaths = g.selectAll('.country') | |
.data(features); | |
const countryPathsEnter = countryPaths | |
.enter().append('path') | |
.attr('class', 'country'); | |
countryPaths | |
.merge(countryPathsEnter) | |
.attr('d', pathGenerator) | |
.attr('fill', d => colorScale(colorValue(d))) | |
.attr('opacity', d => | |
(!selectedColorValue || selectedColorValue === colorValue(d)) | |
? 1 | |
: 0.1 | |
) | |
.classed('highlighted', d => | |
selectedColorValue && selectedColorValue === colorValue(d) | |
); | |
countryPathsEnter.append('title') | |
.text(d => d.properties.name + ': ' + colorValue(d)); | |
}; | |
const svg = d3.select('svg'); | |
const choroplethMapG = svg.append('g'); | |
const colorLegendG = svg.append('g') | |
.attr('transform', `translate(40,310)`); | |
const colorScale = d3.scaleOrdinal(); | |
// const colorValue = d => d.properties.income_grp; | |
const colorValue = d => d.properties.economy; | |
let selectedColorValue; | |
let features; | |
const onClick = d => { | |
selectedColorValue = d; | |
render(); | |
}; | |
loadAndProcessData().then(countries => { | |
features = countries.features; | |
render(); | |
}); | |
const render = () => { | |
colorScale | |
.domain(features.map(colorValue)) | |
.domain(colorScale.domain().sort().reverse()) | |
.range(d3.schemeSpectral[colorScale.domain().length]); | |
colorLegendG.call(colorLegend, { | |
colorScale, | |
circleRadius: 8, | |
spacing: 20, | |
textOffset: 12, | |
backgroundRectWidth: 235, | |
onClick, | |
selectedColorValue | |
}); | |
choroplethMapG.call(choroplethMap, { | |
features, | |
colorScale, | |
colorValue, | |
selectedColorValue | |
}); | |
}; | |
}(topojson,d3)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
geoPath, | |
geoNaturalEarth1, | |
zoom, | |
event | |
} from 'd3'; | |
const projection = geoNaturalEarth1(); | |
const pathGenerator = geoPath().projection(projection); | |
export const choroplethMap = (selection, props) => { | |
const { | |
features, | |
colorScale, | |
colorValue, | |
selectedColorValue | |
} = props; | |
console.log(features); | |
const gUpdate = selection.selectAll('g').data([null]); | |
const gEnter = gUpdate.enter().append('g'); | |
const g = gUpdate.merge(gEnter); | |
gEnter | |
.append('path') | |
.attr('class', 'sphere') | |
.attr('d', pathGenerator({type: 'Sphere'})) | |
.merge(gUpdate.select('.sphere')) | |
.attr('opacity', selectedColorValue ? 0.05 : 1); | |
selection.call(zoom().on('zoom', () => { | |
g.attr('transform', event.transform); | |
})); | |
const countryPaths = g.selectAll('.country') | |
.data(features); | |
const countryPathsEnter = countryPaths | |
.enter().append('path') | |
.attr('class', 'country'); | |
countryPaths | |
.merge(countryPathsEnter) | |
.attr('d', pathGenerator) | |
.attr('fill', d => colorScale(colorValue(d))) | |
.attr('opacity', d => | |
(!selectedColorValue || selectedColorValue === colorValue(d)) | |
? 1 | |
: 0.1 | |
) | |
.classed('highlighted', d => | |
selectedColorValue && selectedColorValue === colorValue(d) | |
) | |
countryPathsEnter.append('title') | |
.text(d => d.properties.name + ': ' + colorValue(d)); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const colorLegend = (selection, props) => { | |
const { | |
colorScale, | |
circleRadius, | |
spacing, | |
textOffset, | |
backgroundRectWidth, | |
onClick, | |
selectedColorValue | |
} = props; | |
const backgroundRect = selection.selectAll('rect') | |
.data([null]); | |
const n = colorScale.domain().length; | |
backgroundRect.enter().append('rect') | |
.merge(backgroundRect) | |
.attr('x', -circleRadius * 2) | |
.attr('y', -circleRadius * 2) | |
.attr('rx', circleRadius * 2) | |
.attr('width', backgroundRectWidth) | |
.attr('height', spacing * n + circleRadius * 2) | |
.attr('fill', 'white') | |
.attr('opacity', 0.8); | |
const groups = selection.selectAll('.tick') | |
.data(colorScale.domain()); | |
const groupsEnter = groups | |
.enter().append('g') | |
.attr('class', 'tick'); | |
groupsEnter | |
.merge(groups) | |
.attr('transform', (d, i) => | |
`translate(0, ${i * spacing})` | |
) | |
.attr('opacity', d => | |
(!selectedColorValue || d === selectedColorValue) | |
? 1 | |
: 0.2 | |
) | |
.on('click', d => onClick( | |
d === selectedColorValue | |
? null | |
: d | |
)); | |
groups.exit().remove(); | |
groupsEnter.append('circle') | |
.merge(groups.select('circle')) | |
.attr('r', circleRadius) | |
.attr('fill', colorScale); | |
groupsEnter.append('text') | |
.merge(groups.select('text')) | |
.text(d => d) | |
.attr('dy', '0.32em') | |
.attr('x', textOffset); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Choropleth Map with Interactive Filtering</title> | |
<link rel="stylesheet" href="styles.css"> | |
<script src="https://unpkg.com/d3@5.6.0/dist/d3.min.js"></script> | |
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.min.js"></script> | |
</head> | |
<body> | |
<svg width="960" height="500"></svg> | |
<script src="bundle.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
select, | |
scaleOrdinal, | |
schemeSpectral | |
} from 'd3'; | |
import { loadAndProcessData } from './loadAndProcessData'; | |
import { colorLegend } from './colorLegend'; | |
import { choroplethMap } from './choroplethMap'; | |
const svg = select('svg'); | |
const choroplethMapG = svg.append('g'); | |
const colorLegendG = svg.append('g') | |
.attr('transform', `translate(40,310)`); | |
const colorScale = scaleOrdinal(); | |
// const colorValue = d => d.properties.income_grp; | |
const colorValue = d => d.properties.economy; | |
let selectedColorValue; | |
let features; | |
const onClick = d => { | |
selectedColorValue = d; | |
render(); | |
}; | |
loadAndProcessData().then(countries => { | |
features = countries.features; | |
render(); | |
}); | |
const render = () => { | |
colorScale | |
.domain(features.map(colorValue)) | |
.domain(colorScale.domain().sort().reverse()) | |
.range(schemeSpectral[colorScale.domain().length]); | |
colorLegendG.call(colorLegend, { | |
colorScale, | |
circleRadius: 8, | |
spacing: 20, | |
textOffset: 12, | |
backgroundRectWidth: 235, | |
onClick, | |
selectedColorValue | |
}); | |
choroplethMapG.call(choroplethMap, { | |
features, | |
colorScale, | |
colorValue, | |
selectedColorValue | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { feature } from 'topojson'; | |
import { tsv, json } from 'd3'; | |
export const loadAndProcessData = () => | |
Promise | |
.all([ | |
tsv('https://unpkg.com/world-atlas@1.1.4/world/50m.tsv'), | |
json('https://unpkg.com/world-atlas@1.1.4/world/50m.json') | |
]) | |
.then(([tsvData, topoJSONdata]) => { | |
const rowById = tsvData.reduce((accumulator, d) => { | |
accumulator[d.iso_n3] = d; | |
return accumulator; | |
}, {}); | |
const countries = feature(topoJSONdata, topoJSONdata.objects.countries); | |
countries.features.forEach(d => { | |
Object.assign(d.properties, rowById[d.id]); | |
}); | |
return countries; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"scripts": { | |
"build": "rollup -c" | |
}, | |
"devDependencies": { | |
"rollup": "latest" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default { | |
input: 'index.js', | |
external: ['d3'], | |
output: { | |
file: 'bundle.js', | |
format: 'iife', | |
sourcemap: true, | |
globals: { d3: 'd3' } | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 0px; | |
overflow: hidden; | |
} | |
.sphere { | |
fill: #4242e4; | |
} | |
.country { | |
stroke: black; | |
stroke-width: 0.05px; | |
} | |
.country.highlighted { | |
stroke-width: 0.5px; | |
} | |
.country:hover { | |
fill: red; | |
} | |
.tick { | |
cursor: pointer; | |
} | |
.tick text { | |
font-size: 1em; | |
fill: black; | |
font-family: sans-serif; | |
} | |
.tick circle { | |
stroke: black; | |
stroke-opacity: 0.5; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment