@lsteiner/map_module
Version:
a module for outputting locus-branded chloropleths
167 lines (155 loc) • 5.27 kB
JavaScript
const d3 = require('d3')
const topojson = require('topojson')
const geo = require ('./shapefile')
const scales = require ('./colorScales')
const draw = (config) => {
const defaults = {
id : "body",
data: [],
title: "a new datavisualization",
colorScheme: "green",
numBuckets: 8,
valueKey: "value",
countyKey: "GEO",
countyNameKey: "GEO_NAME",
pointInTime: true,
}
const blend = {...defaults, ...config}
const {id, data, title, colorScheme, numBuckets, valueKey, countyKey, countyNameKey, pointInTime} = blend
//page settings
const margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 960 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
//svg furniture set up
const svg = d3.select(id).append('svg')
svg.attr("width", width)
.attr("height", height)
.append('g')
.attr('class', 'map');
const path = d3.geoPath();
if(pointInTime) {
//create bucketed domain
const max = d3.max(data, d => d[valueKey])
const domainArr = [d3.min(data, d => d[valueKey])]
for (let i = 1; i <= numBuckets; i++ ){
domainArr.push((max/numBuckets) * i)
}
const color = d3.scaleQuantile().range(scales[colorScheme][numBuckets]).domain(domainArr)
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
//drawing map
svg.append('title').text(title);
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(geo, geo.objects.counties).features)
.enter().append("path")
.attr("d", path)
.attr('fill', function(d) {
const value = data.find(el => el[countyKey] === d.id)
if (value){ return color(value[valueKey])}
return 'black'
})
.style('tab-index', '0')
.attr('stroke', 'black')
.on("mouseover", function(d) {
tooltip.transition()
.duration(250)
.style("opacity", 1);
const obj = data.find(el => el[countyKey] === d.id)
if(!obj) return;
const value = obj[valueKey]
const name = obj[countyNameKey]
tooltip.html(
"<p><strong>" + name + ",</strong></p>" +
"<table><tbody><tr><td class='wide'>Population in 2017:</td><td>" + d3.format(',')(value) + "</td></tr>"
)
.style("left", (d3.event.pageX + 15) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.style('position', 'absolute')
.style('padding', '7px')
.style('font-size', '0.9em')
.style('pointer-events', 'none')
.style('background', '#fff')
.style('border', '1px solid #ccc')
.style('border-radius', '4px')
.style('-moz-box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)')
.style('-webkit-box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)')
.style('box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)');
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(250)
.style("opacity", 0);
})
.on('focus', (d) => {
const element = document.activeElement
const length = element.getTotalLength();
const point = element.getPointAtLength(length/2)
tooltip.transition()
.duration(250)
.style("opacity", 1);
const obj = data.find(el => el[countyKey] === d.id)
if(!obj) return;
const value = obj[valueKey]
const name = obj[countyNameKey]
tooltip.html(
"<p><strong>" + name + ",</strong></p>" +
"<table><tbody><tr><td class='wide'>Population in 2017:</td><td>" + d3.format(',')(value) + "</td></tr>"
)
.style("left", (point.x + 15) + "px")
.style("top", (point.y - 28) + "px")
.style('position', 'absolute')
.style('padding', '7px')
.style('font-size', '0.9em')
.style('pointer-events', 'none')
.style('background', '#fff')
.style('border', '1px solid #ccc')
.style('border-radius', '4px')
.style('-moz-box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)')
.style('-webkit-box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)')
.style('box-shadow', '3px 3px 10px 0px rgba(0, 0, 0, 0.25)');
})
.on('focusout', () => {
tooltip.transition()
.duration(250)
.style("opacity", 0);
});
//drawing legend
const x = d3.scaleLinear()
.domain([d3.min(data, (d) => d[valueKey]), d3.max(data, (d) => d[valueKey])])
.rangeRound([400, 860]);
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0,15)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });
g.call(d3.axisBottom(x)
.tickSize(13)
.tickFormat(function(x) { return d3.format(',')(parseInt(x))})
.tickValues(color.domain()))
g.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(title);
//uncomment to remove top border of legend
// .select(".domain")
// .remove();
}
}
module.exports = {draw}