Public
Edited
Feb 19
Fork of Untitled
3 forks
Insert cell
Insert cell
import {Legend} from "@d3/color-legend"
Insert cell
import {Scrubber} from "@mbostock/scrubber";
Insert cell
co2 = FileAttachment("data.csv").csv({typed: true})
Insert cell
us = FileAttachment("counties-albers-10m.json").json() // 加载美国地理数据
Insert cell
statename = new Map(us.objects.states.geometries.map(d => [d.properties.name, d.id]))
Insert cell
years = Object.keys(co2[0]).filter(d => d.match(/^\d{4}$/)); // Extracts 2013-2022
Insert cell
Insert cell
// Compute min & max CO₂ values across all years for color scaling
rawValues = co2.flatMap(d => years.map(year => +d[year] || 0));
Insert cell
minValue = d3.min(rawValues);
Insert cell
maxValue = d3.quantile(rawValues, 0.95);
Insert cell
viewof yearFilter = Scrubber(
years.map(d => +d), // Convert year strings to numbers
{ autoplay: false, delay: 500, loop: false }
);

Insert cell
chart = {
const width = 975, height = 610;

const rawValues = co2.flatMap(d => years.map(year => +d[year] || 0));
const minValue = d3.min(rawValues);
const maxValue = d3.max(rawValues);

// Custom CO₂ bins with Green to Orange transition
const thresholds = [50, 100, 500, 1000, 10000, maxValue];
const colorScale = d3.scaleThreshold()
.domain(thresholds)
.range(["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"]);
// Green -> Yellow -> Orange

// Create a geographic path generator
const path = d3.geoPath();

// Function to create a state-to-CO₂ lookup map for a given year
function getCO2Values(year) {
return new Map(co2.map(d => [d["Row Labels"], d[year]]));
}

// Get CO₂ values for the currently selected year
let valuemap = getCO2Values(yearFilter);

// Create the SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Add a **Legend**
svg.append("g")
.attr("transform", "translate(600,20)")
.append(() => Legend(colorScale, {
title: "CO₂ per Million Forest Acres (ppm)",
width: 260,
tickFormat: d3.format(".1f")
}));

// Draw states with CO₂ coloring
const statePaths = svg.append("g")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.join("path")
.attr("d", path)
.attr("stroke", "#fff")
.attr("fill", d => {
const value = valuemap.get(d.properties.name);
return value != null ? colorScale(value) : "#e0e0e0"; // Gray for missing data
})
.append("title")
.text(d => `${d.properties.name}\n${yearFilter} CO₂: ${d3.format(".2f")(valuemap.get(d.properties.name) || 0)} ppm`);

// Draw state borders
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);

return svg.node();
}
Insert cell
mutable selectedState = null;
Insert cell
viewof lineChart = {
if (!selectedState) return html`<div>Click a state to view data</div>`;

const margin = { top: 30, right: 50, bottom: 60, left: 90 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;

const stateData = co2.find(d => d["Row Labels"] === selectedState);
if (!stateData) return html`<div>No data for this state</div>`;

const data = years.map(year => ({
year: +year,
value: +stateData[year] || 0
}));

const div = html`<div></div>`;
const svg = d3.select(div)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

const x = d3.scaleLinear()
.domain(d3.extent(data, d => d.year))
.range([0, width]);

svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")));

const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height, 0]);

svg.append("g").call(d3.axisLeft(y));

// x-axis title
svg.append("text")
.attr("x", width / 2)
.attr("y", height + 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Years");

// y-axis title
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -60)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("CO₂ Emissions per Million Forest Acres (ppm)");

const line = d3.line()
.x(d => x(d.year))
.y(d => y(d.value));

svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#ff5722")
.attr("stroke-width", 2)
.attr("d", line);

svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x(d.year))
.attr("cy", d => y(d.value))
.attr("r", 4)
.attr("fill", "#ff5722");

svg.append("text")
.attr("x", width / 2)
.attr("y", -10)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text(`${selectedState} CO₂ Emissions per Million Forest Acres (2013-2022)`);

return div;
}

Insert cell
viewof chart2 = {
const width = 975, height = 610;

const rawValues = co2.flatMap(d => years.map(year => +d[year] || 0));
const minValue = d3.min(rawValues);
const maxValue = d3.max(rawValues);

const thresholds = [50, 100, 500, 1000, 10000, maxValue];
const colorScale = d3.scaleThreshold()
.domain(thresholds)
.range(["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"]);

const path = d3.geoPath();

function getCO2Values(year) {
return new Map(co2.map(d => [d["Row Labels"], d[year]]));
}

let valuemap = getCO2Values(yearFilter);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

svg.append("g")
.attr("transform", "translate(600,20)")
.append(() => Legend(colorScale, {
title: "CO₂ per Million Forest Acres (ppm)",
width: 260,
tickFormat: d3.format(".1f")
}));

const statePaths = svg.append("g")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.join("path")
.attr("d", path)
.attr("stroke", "#fff")
.attr("fill", d => {
const value = valuemap.get(d.properties.name);
return value != null ? colorScale(value) : "#e0e0e0";
})
.on("click", (event, d) => {
mutable selectedState = d.properties.name;
})
.append("title")
.text(d => `${d.properties.name}\n${yearFilter} CO₂: ${d3.format(".2f")(valuemap.get(d.properties.name) || 0)} ppm`);

svg.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);

return svg.node();
}

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