Published
Edited
Jul 23, 2020
Insert cell
Insert cell
Insert cell
map = {
let div = d3
.create('div')
.style("width", `${width}px`)
.style("height", `${height}px`)
.style('overflow', 'hidden');

let svg = div
.append('svg')
.style('overflow', 'hidden')
.attr("viewBox", [0, 0, width, height]);

let path = d3.geoPath().projection(proj);

let map = svg
.append('g')
.attr('id', 'the_map')
.attr('transform', 'scale(1)');
map
.selectAll("path")
.data(states.features)
.join("path")
.attr('class', 'state')
.attr('d', path)
.style("fill", function(d) {
return d3.interpolateBlues(
(state_total_map.get(d.properties.fips) / state_total_map.max) ** 0.4
);
})
.attr("stroke-width", '1.5px')
.attr("stroke", "#ddd")
.attr("stroke-linejoin", "round");

let froth = map.append('g').attr('id', 'froth');
let bubbles = froth
.selectAll('circle.bubble')
.data(
city_info.sort((a, b) => d3.descending(a.cnt, b.cnt)).slice(0, 10 ** 2)
)
.join('circle')
.attr('class', 'bubble')
.attr("fill", "brown")
.attr("fill-opacity", 0.5)
.attr("stroke", "#fff")
.attr("stroke-width", 0.4)
.attr('r', d => d3.max([1, Math.sqrt(d.cnt) / 10]))
.attr('cx', d => proj([d.x, d.y])[0])
.attr('cy', d => proj([d.x, d.y])[1])
.attr('title', function(d) {
return `${d.city}, ${d.state}: ${d.cnt} hits`;
});
bubbles.nodes().forEach(d => tippy(d, { followCursor: true }));

let city_markers = map
.selectAll('circle.city')
.data(cities_to_show)
.join('circle')
.attr('class', 'city')
.attr('cx', d => proj([d.x, d.y])[0])
.attr('cy', d => proj([d.x, d.y])[1])
.attr('r', 3);
let city_labels = map
.selectAll('text.city')
.data(cities_to_show)
.join('text')
.attr('class', 'city')
.attr('x', d => proj([d.x, d.y])[0])
.attr('y', d => proj([d.x, d.y])[1])
.text(d => d.city.replace(' City', ''))
.style('font-family', 'sans-serif')
.style('font-size', '14px');

svg.call(
d3
.zoom()
.extent([[0, 0], [width, height]])
.translateExtent([[0, 0], [width, height]])
.scaleExtent([1, 5])
.duration(750)
.on('zoom', function() {
map.attr("transform", d3.event.transform);
map
.selectAll("path")
.attr("stroke-width", `${1.5 / d3.event.transform.k}px`);
map.selectAll("circle.city").attr("r", `${3 / d3.event.transform.k}px`);
map
.selectAll("text.city")
.style("font-size", `${14 / d3.event.transform.k ** 0.5}px`);
})
);

return div.node();
}
Insert cell
function set_bubbles(n) {
let froth = d3.select(map).select('g#froth');
froth.selectAll('circle.bubble').remove();
let bubbles = froth
.selectAll('circle.bubble')
.data(city_info.sort((a, b) => d3.descending(a.cnt, b.cnt)).slice(0, n))
.enter()
.append('circle')
.attr('class', 'bubble')
.style("fill", "brown")
.style("fill-opacity", 0.5)
.style("stroke", "#fff")
.attr("stroke-width", 0.4)
.attr('r', d => d3.max([1, Math.sqrt(d.cnt) / 10]))
.attr('cx', d => proj([d.x, d.y])[0])
.attr('cy', d => proj([d.x, d.y])[1])
.attr('title', function(d) {
return `${d.city}, ${d.state}: ${d.cnt} hits`;
});
bubbles.nodes().forEach(d => tippy(d, { followCursor: true }));
}
Insert cell
Insert cell
// state totals
state_total_map = {
let state_total_map = new Map();
city_info.forEach(function(c) {
let key = c.state;
if (typeof state_total_map.get(key) == 'undefined') {
state_total_map.set(key, c.cnt);
} else {
state_total_map.set(key, state_total_map.get(key) + c.cnt);
}
});
state_total_map.max = d3.max(Array.from(state_total_map.values()));
return state_total_map;
}
Insert cell
cities_to_show = {
let cities_to_show = [
"San Francisco, CA",
"Los Angeles, CA",
"Chicago, IL",
"New York City, NY",
"Washington, DC",
"Atlanta, GA",
"Miami, FL",
"Seattle, WA"
];
return city_info.filter(
c => cities_to_show.indexOf(`${c.city}, ${c.state}`) != -1
);
}
Insert cell
city_info = d3
.csvParse(await FileAttachment("city_counts@4.csv").text(), d3.autoType)
.sort((a, b) => d3.descending(a.cnt, b.cnt))
.slice(0, N)
Insert cell
states = {
let map_file = await FileAttachment("states.json").json();
let states = topojson.feature(map_file, map_file.objects.states);
return states;
}
Insert cell
proj = d3
.geoIdentity()
.reflectY(true)
.fitSize([width, height], states)
Insert cell
Insert cell
// Max number of citie bubbles to display
N = 2500
Insert cell
height = 0.625 * width
Insert cell
Insert cell
tippy = require("https://unpkg.com/tippy.js@2.5.4/dist/tippy.all.min.js")
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require('d3@5')
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