Published
Edited
Apr 28, 2022
Insert cell
Insert cell
chartBubble = {
let width = 975;
let height = 610;
// Create SVG
const svg = d3.create("svg")
.attr("viewBox", [0,0, width, height]);


// Bind TopoJSON data (choropleth map).
// For some reason this code needed to be above the bubble chart code so it would show up as the background.
const state = svg.append("g")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.join("path")
.attr("fill", d => color(data.get(d.properties.name)))
.attr("d", path)
.attr("cursor", "pointer");
state
.append("title")
.text(d => `${d.properties.name}
${decimal(data.get(d.properties.name))} Acres`);

state
.on('mouseover.color', function() {
d3.select(this)
.attr('stroke', '#fff')
.attr('fill-opacity', 0.5);
})
.on('mouseout.color', function() {
d3.select(this)
.attr('stroke', null)
.attr("fill-opacity", 1);
});
// state lines
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 0.5)
.attr("stroke-linejoin", "round")
.attr("d", path);
// bubble map
const bubble = svg.append("g")
.selectAll("circle")
.data(data_bub
.filter(d => d.firesize)
.sort((a, b) => d3.descending(a.firesize, b.firesize)))
.enter()
.append("circle")
.attr("r", d => radius(d.firesize))
.attr("fill", "black")
.attr("fill-opacity", 0.6)
.attr("stroke", "#fff")
.attr("stroke-width", 0.5)
.attr("cursor", "pointer")
.attr("transform", d => `translate(${d})`);
bubble
.append("title")
.text(d => `Name: ${d.name}
Size: ${format(d.firesize)} Acres
Dates: ${formatdate(d.ddate)} thru ${formatdate(d.cdate)}`);
bubble
.on('mouseover.size', function (d, i) {
d3.select(this).transition()
.duration('100')
.attr("r", d => radius(d.firesize)*1.2);
})
.on('mouseover.color', function() {
d3.select(this)
.attr('stroke', '#fff')
.attr('stroke-width', 2)
.attr('fill-opacity', 0.8);
})
.on('mouseout.size', function (d, i) {
d3.select(this).transition()
.duration('200')
.attr("r", d => radius(d.firesize))
})
.on('mouseout.color', function() {
d3.select(this)
.attr('stroke', '#fff')
.attr('stroke-width', 0.5)
.attr('fill-opacity', 0.6);
});


// choropleth legend
svg.append("g")
.attr("transform", "translate(610,20)")
.append(() => legend({color,
title: 'Total Acres Burned',
width: 250,
tickFormat: '.0s'}));

// bubble legend
const legend_b = svg.append("g")
.attr("fill", "#777")
.attr("transform", "translate(915,608)")
.attr("text-anchor", "middle")
.style("font", "10px sans-serif")
.selectAll("g")
.data(radius.ticks(4).slice(1))
.join("g");

legend_b.append("circle")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 0.5)
.attr("cy", d => -radius(d))
.attr("r", radius);

legend_b.append("text")
.attr("y", d => -2 * radius(d))
.attr("dy", "1.3em")
.text(radius.tickFormat(4, "s"));

return svg.node();
}
Insert cell
# Choropleth+bubble Map updated
Insert cell
us = FileAttachment("states-albers-10m.json").json()
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("burnedacres_bystate@2.csv").text(), ({name, total}) => [name, +total])), {title: "Total Acres Burned"})
Insert cell
//burnedrange = d3.extent(data, d => d.total) //compute min and max acres burned
Insert cell
data_bub = (await FileAttachment("classg_2018_top50_edit.csv").csv({typed:true}))
.map(d => {
const p = projection([d.LONGITUDE, d.LATITUDE]);
p.name = d.FIRE_NAME;
p.ddate = parseDate(d.DISCOVERY_DATE);
p.cause = d.NWCG_CAUSE_CLASSIFICATION
p.cdate = parseDate(d.CONT_DATE);
p.firesize = d.FIRE_SIZE;
return p;
}) // project the data onto the map
Insert cell
projection = d3.geoAlbersUsa().scale(1280).translate([480, 300])// scale 1000 to make sure the data is corresponding to the map translate, default translate([480, 300])
Insert cell
path = d3.geoPath();
Insert cell
radius = d3.scaleLinear([0, d3.max(data_bub, d => d.firesize)], [5, 30])
Insert cell
//color = d3.scaleSequential(burnedrange, d3.schemeReds[3])
color = d3.scaleThreshold()
.domain([10, 100, 1000, 10000, 100000, 500000, 1000000])
.range(d3.schemeReds[8])
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
decimal = d3.format(",.3r")
Insert cell
format = d3.format(",.0f")
Insert cell
formatdate = d3.timeFormat("%b %d")
Insert cell
parseDate = d3.utcParse("%m/%d/%Y")
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