function choroplethMap({
data = data,
geography = geography,
topographyBorder = {},
tickSize = 6,
width = 960,
height = 600,
marginTop = 60,
marginRight = 10,
marginBottom = 10,
marginLeft = 10,
colorVariable = 'value',
breaks = [],
colorBins = [],
colorInterpolator = d3.interpolateYlGnBu,
geographyIdVariable = 'id',
dataIdVariable = 'id',
titleFontSize = 20,
titleFontFamily = 'sans-serif',
titleFontWeight = 'bold',
titleMaxCharsPerLine = 65,
legend: legendOptions = {},
strokeColor = '#fff',
ordinalLabels = [],
hoverFn = null
} = {}) {
let dataById = {}
for (let i=0; i<data.length;i++){
dataById[data[i][dataIdVariable]] = data[i]
}
const extent = [
[marginLeft, marginTop],
[width - marginRight - marginLeft, height - marginBottom - marginTop]
];
const projection = d3.geoMercator()
.fitExtent(extent, geography);
const path = d3.geoPath()
.projection(projection);
const colorScale = d3.scaleThreshold()
.domain(breaks)
.range(colorBins);
const svg = d3.create('svg')
.attr('viewBox', [0, 0, 975, 610]);
svg.append('g')
.attr('fill', 'none')
.selectAll('path')
.data(geography.features)
.join('path')
.attr('d', path)
.attr('fill', (d) => {
const id = d.properties[geographyIdVariable];
if (dataById.hasOwnProperty(id)) {
const datum = dataById[id];
return colorScale(datum[colorVariable]);
}
})
.attr("stroke", d => strokeColor)
.style('opacity', d => hoverTarget === null || hoverTarget.includes(d.properties[geographyIdVariable]) ? 1 : .5)
.on('click', d => hoverFn !== null ? mutable hoverTarget = hoverFn(d.properties[geographyIdVariable]) : '')
.attr('class', 'feature');
svg.append('path')
.datum(topographyBorder)
.attr('fill', 'none')
.attr('stroke', '#434343')
.attr('stroke-width', 1)
.attr('d', path);
const options = Object.assign({
color: ordinalLabels.length
? d3.scaleOrdinal(ordinalLabels, colorBins)
: colorScale,
},legendOptions);
const legendSVG = legend(options);
const legendWidth = +d3.select(legendSVG).attr('width');
const legendHeight = +d3.select(legendSVG).attr('height');
const legendNode = svg.append('g')
.attr('transform', `translate(${width / 2 - legendWidth / 2}, 0)`);
Array.from(legendSVG.children).forEach((child) => {
legendNode.node().appendChild(child);
});
legendNode.select('g').selectAll('text')
.filter(function() { return d3.select(this).attr('font-weight') === 'bold'; })
.attr('font-size', 14);
// thanks! https://observablehq.com/@duynguyen1678/choropleth-with-tooltip
const tooltip = svg.append("g")
svg
.selectAll(".feature")
.on("touchmove mousemove", (d, event) => {
console.log(event)
tooltip.call(
callout,
`ID: ${d.properties[geographyIdVariable]}`
);
tooltip.attr("transform", `translate(${d3.pointer(event, this)})`);
d3.select(this)
.raise();
})
.on("touchend mouseleave", function() {
tooltip.call(callout, null);
d3.select(this)
.attr("stroke", null)
.lower();
});
return svg.node();
}