Public
Edited
Apr 17
Insert cell
Insert cell
Insert cell
// **D3 Scatter Plot that Updates with Slider**
{
const width = 750,
height = 500,
margin = { top: 60, right: 220, bottom: 100, left: 80 }; // **Adjusted right margin for legends**

const svg = d3.create("svg").attr("width", width).attr("height", height);
console.log("Slider value:", monthSlider1);

if (!scatterData || scatterData.length === 0) {
console.error("scatterData is empty:", scatterData);
return "No data available for plotting.";
}

const xScale = d3
.scaleLinear()
.domain([0, d3.max(scatterData, (d) => d.totalPrecipitation)])
.range([margin.left, width - margin.right]);

const yScale = d3
.scaleLinear()
.domain([0, d3.max(scatterData, (d) => d.totalWildfires)])
.range([height - margin.bottom, margin.top]);

const seasonNames = ["Winter", "Spring", "Summer", "Fall"];
const seasonColors = ["#1f77b4", "#2ca02c", "#d62728", "#ff7f0e"];
const colorScale = d3.scaleOrdinal().domain(seasonNames).range(seasonColors);

// **Further Reduced Wind Speed Bubble Size**
const sizeScale = d3
.scaleSqrt()
.domain([0, d3.max(scatterData, (d) => d.avgWindSpeed)])
.range([3, 16]); // **Adjusted size**

svg
.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));

svg
.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

// **Graph Title**
svg
.append("text")
.attr("x", width / 2)
.attr("y", margin.top - 30)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.text("Total Precipitation vs. Wildfires (Monthly)");

// **X-Axis Label**
svg
.append("text")
.attr("x", (width - margin.right + margin.left) / 2) // Centering fix
.attr("y", height - 40) // Adjusted for spacing
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("font-weight", "bold")
.text("Total Precipitation (mm)");

// **Y-Axis Label**
svg
.append("text")
.attr("x", -height / 2)
.attr("y", margin.left / 3)
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.attr("font-size", "14px")
.attr("font-weight", "bold")
.text("Number of Wildfires");

// **Tooltip**
const tooltip = d3
.select("body")
.append("div")
.style("position", "absolute")
.style("background", "white")
.style("border", "1px solid #ccc")
.style("padding", "5px")
.style("display", "none");

const scatterGroup = svg.append("g");

function updateScatterByMonth(selectedMonth) {
// **Show all months up to the selected value**
const filteredData = scatterData.filter((d) => d.month <= selectedMonth);

console.log(`Filtering for months up to ${selectedMonth}`, filteredData);

scatterGroup.selectAll("circle").remove(); // **Clear old dots**

scatterGroup
.selectAll("circle")
.data(filteredData, (d) => d.month)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.totalPrecipitation))
.attr("cy", (d) => yScale(d.totalWildfires))
.attr("r", (d) => sizeScale(d.avgWindSpeed)) // **Updated smaller size**
.attr("fill", (d) => colorScale(d.season))
.attr("opacity", 0.8)
.on("mouseover", (event, d) => {
tooltip
.style("display", "block")
.html(
`Month: ${d.month}<br>
Precipitation: ${d.totalPrecipitation} mm<br>
Wildfires: ${d.totalWildfires}<br>
Wind Speed: ${d.avgWindSpeed}<br>
Season: ${d.season}`
)
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY - 20}px`);
})
.on("mouseout", () => tooltip.style("display", "none"));
}

// **Listen to Slider Changes in ObservableHQ**
(async () => {
for await (const value of Generators.input(viewof monthSlider1)) {
console.log("Slider changed to:", value);
updateScatterByMonth(value);
}
})();

updateScatterByMonth(1); // **Initial Call to Render Month 1**

// **Legend for Seasons**
const legend = svg
.append("g")
.attr("transform", `translate(${width - 160}, ${margin.top})`);

// **Title for Season Legend**
legend
.append("text")
.attr("x", 0)
.attr("y", -15) // **Ensure it's visible**
.attr("font-size", "14px")
.attr("font-weight", "bold")
.text("Season");

seasonNames.forEach((season, i) => {
legend
.append("circle")
.attr("cx", 0)
.attr("cy", i * 25)
.attr("r", 6)
.attr("fill", colorScale(season));

legend
.append("text")
.attr("x", 12)
.attr("y", i * 25 + 5)
.text(season)
.attr("font-size", "14px")
.attr("alignment-baseline", "middle");
});

// **Legend for Wind Speed (Bubble Size)**
const windSpeedLegend = svg
.append("g")
.attr("transform", `translate(${width - 160}, ${margin.top + 150})`);

// **Fix: Wind Speed Title Fully Visible**
windSpeedLegend
.append("text")
.attr("x", 0)
.attr("y", -30) // **Fixed cutoff issue**
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text("Average Wind Speed");

const windSpeeds = [0, 2, 4, 6, 8];
const legendSpacing = 45; // **Slightly increased spacing**

windSpeeds.forEach((speed, i) => {
windSpeedLegend
.append("circle")
.attr("cx", 30) // Keep circles aligned
.attr("cy", i * legendSpacing)
.attr("r", sizeScale(speed)) // **Updated smaller size**
.attr("fill", "black")
.attr("opacity", 0.5);

windSpeedLegend
.append("text")
.attr("x", 75)
.attr("y", i * legendSpacing + 5)
.text(speed)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("alignment-baseline", "middle");
});

return svg.node();
}
Insert cell
// **D3 Scatter Plot that Updates with Slider**
{
const width = 750,
height = 500,
margin = { top: 50, right: 200, bottom: 100, left: 80 };

const svg = d3.create("svg").attr("width", width).attr("height", height);
console.log("Slider value:", monthSlider1);

if (!scatterData || scatterData.length === 0) {
console.error("scatterData is empty:", scatterData);
return "No data available for plotting.";
}

const xScale = d3
.scaleLinear()
.domain([0, d3.max(scatterData, (d) => d.totalPrecipitation)])
.range([margin.left, width - margin.right]);

const yScale = d3
.scaleLinear()
.domain([0, d3.max(scatterData, (d) => d.totalWildfires)])
.range([height - margin.bottom, margin.top]);

const seasonNames = ["Winter", "Spring", "Summer", "Fall"];
const seasonColors = ["#1f77b4", "#2ca02c", "#d62728", "#ff7f0e"];
const colorScale = d3.scaleOrdinal().domain(seasonNames).range(seasonColors);

// 🔹 **Further Reduced Wind Speed Bubble Size**
const sizeScale = d3
.scaleSqrt()
.domain([0, d3.max(scatterData, (d) => d.avgWindSpeed)])
.range([3, 18]); // **Slightly smaller range for better proportion**

svg
.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));

svg
.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

// **Tooltip**
const tooltip = d3
.select("body")
.append("div")
.style("position", "absolute")
.style("background", "white")
.style("border", "1px solid #ccc")
.style("padding", "5px")
.style("display", "none");

const scatterGroup = svg.append("g");

function updateScatterByMonth(selectedMonth) {
// **Show all months up to the selected value**
const filteredData = scatterData.filter((d) => d.month <= selectedMonth);

console.log(`Filtering for months up to ${selectedMonth}`, filteredData);

scatterGroup.selectAll("circle").remove(); // **Clear old dots**

scatterGroup
.selectAll("circle")
.data(filteredData, (d) => d.month)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.totalPrecipitation))
.attr("cy", (d) => yScale(d.totalWildfires))
.attr("r", (d) => sizeScale(d.avgWindSpeed)) // **Updated smaller size**
.attr("fill", (d) => colorScale(d.season))
.attr("opacity", 0.8)
.on("mouseover", (event, d) => {
tooltip
.style("display", "block")
.html(
`Month: ${d.month}<br>
Precipitation: ${d.totalPrecipitation} mm<br>
Wildfires: ${d.totalWildfires}<br>
Wind Speed: ${d.avgWindSpeed}<br>
Season: ${d.season}`
)
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY - 20}px`);
})
.on("mouseout", () => tooltip.style("display", "none"));
}

// **Listen to Slider Changes in ObservableHQ**
(async () => {
for await (const value of Generators.input(viewof monthSlider1)) {
console.log("Slider changed to:", value);
updateScatterByMonth(value);
}
})();

updateScatterByMonth(1); // **Initial Call to Render Month 1**

// **Legend for Seasons**
const legend = svg
.append("g")
.attr("transform", `translate(${width - 160}, ${margin.top})`);

seasonNames.forEach((season, i) => {
legend
.append("circle")
.attr("cx", 0)
.attr("cy", i * 25)
.attr("r", 6)
.attr("fill", colorScale(season));

legend
.append("text")
.attr("x", 12)
.attr("y", i * 25 + 5)
.text(season)
.attr("font-size", "14px")
.attr("alignment-baseline", "middle");
});

// **Legend for Wind Speed (Bubble Size)**
const windSpeedLegend = svg
.append("g")
.attr("transform", `translate(${width - 160}, ${margin.top + 150})`);

// 🔹 **Fix: Ensure Wind Speed Title is Fully Visible**
windSpeedLegend
.append("text")
.attr("x", 0)
.attr("y", -30) // **Ensured proper spacing**
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text("Average Wind Speed");

const windSpeeds = [0, 2, 4, 6, 8];
const legendSpacing = 45; // **Slightly increased spacing**

windSpeeds.forEach((speed, i) => {
windSpeedLegend
.append("circle")
.attr("cx", 30) // Keep circles aligned
.attr("cy", i * legendSpacing)
.attr("r", sizeScale(speed)) // **Updated smaller size**
.attr("fill", "black")
.attr("opacity", 0.5);

windSpeedLegend
.append("text")
.attr("x", 75)
.attr("y", i * legendSpacing + 5)
.text(speed)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("alignment-baseline", "middle");
});

return svg.node();
}
Insert cell
fireData1
Insert cell
precipitationArray
Insert cell
scatterData = precipitationArray.map((d) => ({
month: d.month,
totalPrecipitation: d.total,
totalWildfires:
wildfiresPerMonth.find((w) => w.month === d.month)?.count || 0,
avgWindSpeed:
fireData1
.filter((w) => w.MONTH === d.month)
.reduce((sum, w) => sum + w.AVG_WIND_SPEED, 0) /
fireData1.filter((w) => w.MONTH === d.month).length || 0,
season: fireData1.find((w) => w.MONTH === d.month)?.SEASON || "Unknown"
}))
Insert cell
wildfiresPerMonth = Array.from(monthCounts, ([month, count]) => ({
month,
count // This now represents the total wildfires per month
})).sort((a, b) => a.month - b.month)
Insert cell
monthCounts = d3.rollup(
fireData1.filter((d) => d.FIRE_START_DAY == ["True"]), // Filter only wildfires
(v) => v.length, // Count the number of wildfires per month
(d) => d.MONTH // Group by month
)
Insert cell
fireData1.map((d) => d.FIRE_START_DAY)
Insert cell
precipitationArray
Insert cell
precipitationArray = Array.from(precipitationByMonth, ([month, total]) => ({
month,
total
})).sort((a, b) => a.month - b.month)
Insert cell
precipitationByMonth = d3.rollup(
fireData1,
(v) => d3.sum(v, (d) => d.PRECIPITATION),
(d) => new Date(d.DATE).getMonth() + 1
)
Insert cell
fireData1 = await FileAttachment("CA_Weather_Fire_Dataset_1984-2025.csv").csv({
typed: true
})
Insert cell
d3 = require("d3@6")
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