You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.8 KiB
JavaScript
114 lines
3.8 KiB
JavaScript
2 years ago
|
// Copyright 2021 Observable, Inc.
|
||
|
// Released under the ISC license.
|
||
|
// https://observablehq.com/@d3/choropleth
|
||
|
function Choropleth({
|
||
|
id = d => d.id, // given d in data, returns the feature id
|
||
|
value = () => undefined, // given d in data, returns the quantitative value
|
||
|
title, // given a feature f and possibly a datum d, returns the hover text
|
||
|
format, // optional format specifier for the title
|
||
|
scale = d3.scaleSequential, // type of color scale
|
||
|
domain, // [min, max] values; input of color scale
|
||
|
range = d3.interpolateBlues, // output of color scale
|
||
|
width = 640, // outer width, in pixels
|
||
|
height, // outer height, in pixels
|
||
|
projection, // a D3 projection; null for pre-projected geometry
|
||
|
features, // a GeoJSON feature collection
|
||
|
featureId = d => d.id, // given a feature, returns its id
|
||
|
borders, // a GeoJSON object for stroking borders
|
||
|
outline = projection && projection.rotate ? {type: "Sphere"} : null, // a GeoJSON object for the background
|
||
|
unknown = "#ccc", // fill color for missing data
|
||
|
fill = "white", // fill color for outline
|
||
|
stroke = "white", // stroke color for borders
|
||
|
strokeLinecap = "round", // stroke line cap for borders
|
||
|
strokeLinejoin = "round", // stroke line join for borders
|
||
|
strokeWidth, // stroke width for borders
|
||
|
strokeOpacity, // stroke opacity for borders
|
||
|
duration = 0, // duration for transitions
|
||
|
} = {}) {
|
||
|
|
||
|
// Compute feture map
|
||
|
const If = d3.map(features.features, featureId);
|
||
|
|
||
|
// Compute the default height. If an outline object is specified, scale the projection to fit
|
||
|
// the width, and then compute the corresponding height.
|
||
|
if (height === undefined) {
|
||
|
if (outline === undefined) {
|
||
|
height = 400;
|
||
|
} else {
|
||
|
const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, outline)).bounds(outline);
|
||
|
const dy = Math.ceil(y1 - y0), l = Math.min(Math.ceil(x1 - x0), dy);
|
||
|
projection.scale(projection.scale() * (l - 1) / l).precision(0.2);
|
||
|
height = dy;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Construct a path generator.
|
||
|
const path = d3.geoPath(projection);
|
||
|
|
||
|
// Update DOM
|
||
|
const svg = d3.create("svg")
|
||
|
.classed('choropleth', true)
|
||
|
.attr("width", width)
|
||
|
.attr("height", height)
|
||
|
.attr("viewBox", [0, 0, width, height])
|
||
|
.attr("style", "width: 100%; height: auto; height: intrinsic;");
|
||
|
|
||
|
svg.append("g")
|
||
|
.classed("data-group", true);
|
||
|
|
||
|
if (outline != null) svg.append("path")
|
||
|
.classed('hidden', false)
|
||
|
.attr("fill", fill)
|
||
|
.attr("stroke", "currentColor")
|
||
|
.attr("d", path(outline));
|
||
|
|
||
|
if (borders != null) svg.append("path")
|
||
|
.attr("pointer-events", "none")
|
||
|
.attr("fill", "none")
|
||
|
.attr("stroke", stroke)
|
||
|
.attr("stroke-linecap", strokeLinecap)
|
||
|
.attr("stroke-linejoin", strokeLinejoin)
|
||
|
.attr("stroke-width", strokeWidth)
|
||
|
.attr("stroke-opacity", strokeOpacity)
|
||
|
.attr("d", path(borders));
|
||
|
|
||
|
// Object facet to update visualization
|
||
|
let obj = {
|
||
|
node: svg.node(),
|
||
|
update: function (data) {
|
||
|
|
||
|
const dataDomain = domain(data);
|
||
|
const color = scale(dataDomain, range);
|
||
|
if (color.unknown && unknown !== undefined) color.unknown(unknown);
|
||
|
|
||
|
// Update DOM
|
||
|
const svg = d3.select("svg.choropleth");
|
||
|
svg.select("g.data-group")
|
||
|
.selectAll("path")
|
||
|
.data(features.features)
|
||
|
.join(
|
||
|
(enter) => {
|
||
|
return enter.append("path")
|
||
|
.attr("fill",
|
||
|
(d, i) => color(value(data[d.id])))
|
||
|
.attr("d", path)
|
||
|
.append("title")
|
||
|
.text((d, i) => title(d, data[d.id]));
|
||
|
},
|
||
|
(update) => {
|
||
|
update
|
||
|
.transition()
|
||
|
.duration(duration)
|
||
|
.attr("fill", (d, i) =>
|
||
|
color(data[d.id].sevenDayMean));
|
||
|
return update
|
||
|
.select("title")
|
||
|
.text((d, i) => title(d, data[d.id]));
|
||
|
});
|
||
|
|
||
|
Object.assign(svg.node(), {scales: {color}});
|
||
|
}
|
||
|
}
|
||
|
return obj;
|
||
|
}
|