Public
Edited
May 15, 2024
Insert cell
Insert cell
<div>

<svg viewBox="0 0 2000 1200">
<text text-anchor="middle" font-family="ubuntu" font-size="40" font-weight="bold" x="1000" y="50">
Envisioning California's Hydroclimate and its Environmental Effects</text>

<text text-anchor="middle" font-family="ubuntu" font-size="20" fill="#888888" x="1000" y="75">
<tspan x="990" dy="1.2em">Understanding trends in California's winter hydroclimate is crucial for policy decisions, ecosystem health, and inhabitants’ well-being. This visualization stems out of research</tspan>
<tspan x="990" dy="1.2em">conducted with Dr. Antonio Mamalakis of the School of Data Science, which seeks to predict the state’s winter precipitation totals by utilizing Deep Learning techniques. We hope</tspan>
<tspan x="990" dy="1.2em">that our research improves upon existing literature on the topic, which divides California into three regions (North, Central, and South) and attempts to predict average monthly</tspan>
<tspan x="990" dy="1.2em">precipitation during the rainy season (November - March) using global sea surface temperatures from the summer months (April - October), which also roughly corresponds to</tspan>
<tspan x="990" dy="1.2em">California’s fire season. By combining precipitation data with data on California wildfires, we can see how patterns of rainfall in the state impact the devastating yearly cycle of fires.</tspan>
</text>

<text text-anchor="end" font-family="ubuntu" font-size="17" fill="#888888" x="2000" y="1090">
<tspan x="2000" dy="1.2em">Precipitation data was collected from Climate Earth System Model 2 and is measured in millimeters per day each month on a grid of geographic</tspan>
<tspan x="2000" dy="1.2em">coordinates across the state. For this visualization the data is aggregated to show the total amount of rain in millimeters each month. Wildfire</tspan>
<tspan x="2000" dy="1.2em">data was collected from Kaggle and measures the total number of acres burned by a given fire, measured at that fire's start date. A 5-month</tspan>
<tspan x="2000" dy="1.2em">moving average of acres burned was calculated in order to give a more accurate picture of wildfire progression throughout the fire season.</tspan>
</text>

<style>
.area {fill:#ffb9ad; stroke: red;}
.sparkline {fill: none; stroke: #0982d5; stroke-width: 4;}
#prect_dots circle {stroke: #ccc; opacity: 0.75;}
#opac_rect rect {opacity: 0.8; fill: #ffffff}
#calif path {fill: none; stroke: #8c8c8c; stroke-width: 4;}
#legend path {stroke: #8c8c8c; stroke-width: 2;}
</style>
<g id="layer1" transform="translate(50 10)"></g>
<g id="layer2" transform="translate(50 400)"></g>
<g id="prect_dots"></g>
<g id="calif"></g>
<g id="legend"></g>
<g id="opac_rect">
<rect id="area_1" x=878 y=380 width=0 height=291></rect>
<rect id="area_2" x=1685 y=380 width=0 height=291></rect>
<rect id="spark_1" x=878 y=699 width=0 height=291></rect>
<rect id="spark_2" x=1685 y=699 width=0 height=291></rect>
</g>

</svg>

<div id="region_controls">
<div>
Region: <button id="btn-north">North</button> <button id="btn-central">Central</button> <button id="btn-south">South</button>
</div>
</div>

<div id="start_date_controls">
<div>
Start Date: <input id="start_datepicker" name="start_date" type="date" value="2013-01-01" />
</div>
</div>

<div id="end_date_controls">
<div>
End Date: <input id="end_datepicker" name="end_date" type="date" value="2019-12-01" />
</div>
</div>

<style>
#region_controls {position: absolute; left: 120px; top: 100px; width: 400px; font-size: 12px; font-weight: 500; font-family: ubuntu;}
#start_date_controls {position: absolute; left: 320px; top: 100px; width: 400px; font-size: 12px; font-weight: 500; font-family: ubuntu;}
#end_date_controls {position: absolute; left: 550px; top: 100px; width: 400px; font-size: 12px; font-weight: 500; font-family: ubuntu;}
button {border: 1px solid #aaa; border-radius: 5px; background-color: #eee; font-size: 10px;}
button:hover {cursor: pointer; background-color: #ccc;}
</style>
</div>
Insert cell
viewof start_date = Inputs.text({
type: "date",
label: html`<b>Start Date</b>`,
value: filterStart
})
Insert cell
viewof end_date = Inputs.text({
type: "date",
label: html`<b>End Date</b>`,
value: filterEnd
});
Insert cell
btns = d3.select(container).select("#region_controls");
Insert cell
{
// then we attach an .on("click", () => doSomething) to each of the buttons or control inputs above
// register event listeners
btns.select("#btn-north").on("click", () => region_select("North"));
btns.select("#btn-central").on("click", () => region_select("Central"));
btns.select("#btn-south").on("click", () => region_select("South"));
}
Insert cell
{
d3.select(container).select("#start_date_controls").select("#start_datepicker").on("change", () => date_select());
d3.select(container).select("#end_date_controls").select("#end_datepicker").on("change", () => date_select());
}
Insert cell
Insert cell
map = d3.select(container).select("svg");
Insert cell
projection = d3
.geoMercator()
.center([-119, 37.4])
.scale((1 << 18) / (28 * Math.PI))
.translate([395, 685]);
Insert cell
path = d3.geoPath().projection(projection);
Insert cell
usstates = (await(basemaps.us_states20m.geojson).json());
Insert cell
calif = usstates.features.find(feature => feature.properties.NAME === "California");
Insert cell
map.append("path")
.datum(calif)
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "#8c8c8c")
.attr("stroke-width", 4);
Insert cell
Insert cell
scaleX = d3.scaleTime().domain([new Date("2013-01-01"), new Date("2019-12-01")]).range([830,1830]);
Insert cell
scaleY = d3.scaleLinear().domain([0,300000]).range([660,360]);
Insert cell
scaleY2 = d3.scaleLinear().domain([0,6000]).range([690,990]);
Insert cell
xAxis = d3.axisBottom().scale(scaleX);
Insert cell
xAxis2 = d3.axisTop().scale(scaleX);
Insert cell
yAxis = d3.axisLeft().scale(scaleY);
Insert cell
yAxis2 = d3.axisLeft().scale(scaleY2);
Insert cell
graph1elem.append("g")
.attr("transform","translate(0 662)")
.call(xAxis)
.style("color","#888888")
.selectAll("text")
.style("font-family", "ubuntu")
.style("font-size", "13px")
.attr("y", 8);
Insert cell
graph1elem.append("g")
.call(yAxis
.ticks(6)
.tickFormat(function(d) {
// Check if the tick value is 0, return an empty string if it is
return d === 0 ? "" : d.toLocaleString();
}))
.style("color","#888888")
.style("font-family", "ubuntu")
.style("font-size", "13px")
.attr("transform", "translate(825, 0)");
Insert cell
graph1elem.append("text")
.attr("x", 845) // Adjust the position of the title
.attr("y", 360) // Adjust the vertical position of the title
.attr("text-anchor", "left")
.style("font-size", "16px")
.style("fill", "#888888")
.style("font-family", "ubuntu")
.style("font-weight", "bold")
.text("Acres Burned by Wildfires (5-month moving average)");
Insert cell
graph2elem.append("g")
.attr("transform","translate(0 688)")
.call(xAxis2)
.style("color","#888888")
.selectAll("text")
.style("font-family", "ubuntu")
.style("font-size", "13px")
.attr("y", -5)
.attr("dy", "-0.3em");
Insert cell
graph2elem.append("g")
.call(yAxis2
.ticks(6)
.tickFormat(function(d) {
// Check if the tick value is 0, return an empty string if it is
return d === 0 ? "" : d.toLocaleString();
}))
.style("color","#888888")
.style("font-family", "ubuntu")
.style("font-size", "13px")
.attr("transform", "translate(825, 0)");
Insert cell
graph2elem.append("text")
.attr("x", 845) // Adjust the position of the title
.attr("y", 995) // Adjust the vertical position of the title
.attr("text-anchor", "left")
.style("font-size", "16px")
.style("fill", "#888888")
.style("font-family", "ubuntu")
.style("font-weight", "bold")
.text("Monthly Precipitation (mm)");
Insert cell
Insert cell
filtered_fire_data = fire_data.filter(d => {
// Convert the date string from the dataset to a Date object
const dataDate = new Date(d["Date"]);
// Compare the dates
return dataDate.getTime() >= startDate.getTime() && dataDate.getTime() <= endDate.getTime();
});
Insert cell
mutable area_data = filtered_fire_data;
Insert cell
rollupData = d3.rollup(area_data, v => d3.sum(v, d => d.AcresBurned), d => d.Date)
Insert cell
data = Array.from(rollupData, ([key, value]) => ({ date: new Date(key), value }))
Insert cell
ma_values = movingAverage(data, 6);
Insert cell
maData = data.map((d, i) => ({...d, value: ma_values[i]}));
Insert cell
fire_max = Math.ceil(d3.max(ma_values) / 100000) * 100000;
Insert cell
Insert cell
sparkData = prect_data.filter(d => {
// Convert the date string from the dataset to a Date object
const dataDate = new Date(d["date"]);
// Compare the dates
return dataDate.getTime() >= startDate.getTime() && dataDate.getTime() <= endDate.getTime();
});
Insert cell
caliSparkData = sparkData.filter(d => {
const point = [d.longitude, d.latitude];
return d3.geoContains(calif, point);
});
Insert cell
mutable regionSparkData = caliSparkData;
Insert cell
rollupData2 = d3.rollup(regionSparkData, v => d3.sum(v, d => d.prect), d => d.date)
Insert cell
data2 = Array.from(rollupData2, ([key, value]) => ({ date: new Date(key), value }))
Insert cell
mysum = d3.sum(data2, function(d) {
return d["value"];
});
Insert cell
values2 = data2.map(item => item.value)
Insert cell
spark_max = Math.ceil(d3.max(values2) / 1000) * 1000;
Insert cell
Insert cell
graph1elem = d3.select(container).select("#layer1");
Insert cell
myline1 = areaGraph(graph1elem, maData, "date", "value", 0, scaleX, scaleY);
Insert cell
Insert cell
graph2elem = d3.select(container).select("#layer1");
Insert cell
mySparkLine = dvSparkline(graph2elem, data2, "date", "value", scaleX, scaleY2);
Insert cell
Insert cell
prect_data2 = prect_data.filter(d => {
// Convert the date string from the dataset to a Date object
const dataDate = new Date(d["date"]);
// Compare the dates
const filterStart = new Date("2013-01-01")
const filterEnd = new Date("2019-12-01")
return dataDate.getTime() >= filterStart.getTime() && dataDate.getTime() <= filterEnd.getTime();
});
Insert cell
mutable mapData = prect_data2;
Insert cell
caliData = mapData.filter(d => {
const point = [d.longitude, d.latitude];
return d3.geoContains(calif, point);
});
Insert cell
groupedData = d3.rollup(
caliData,
// Reducer function to compute the sum of prect and return the region
values => ({
sumPrect: d3.sum(values, d => d.prect),
region: values[0].Region // Assuming the region is the same for all entries in a group
}),
d => `${d.latitude},${d.longitude}` // Key function to group by latitude and longitude
);
Insert cell
groupedArray = Array.from(groupedData, ([key, value]) => ({
latitude: +key.split(",")[0], // Extracting latitude from the key
longitude: +key.split(",")[1], // Extracting longitude from the key
sumPrect: value.sumPrect,
region: value.region
}));
Insert cell
mysum2 = d3.sum(groupedArray, function(d) {
return d["sumPrect"];
});
Insert cell
prect_max = Math.ceil(d3.max(groupedArray, d => d.sumPrect) / 5) * 5;
Insert cell
prect_min = Math.floor(d3.min(groupedArray, d => d.sumPrect) / 5) * 5;
Insert cell
prect_dots = {
let prect_dots = map.select("#prect_dots").selectAll("circle")
.data(groupedArray)
.join("circle")
.attr("cx", d => projection([d["longitude"],d["latitude"]])[0])
.attr("cy", d => projection([d["longitude"],d["latitude"]])[1])
.attr("r", 6)
.style("fill", d => colorScale(d["sumPrect"]));
return prect_dots;
}
Insert cell
colorScale = d3.scaleLinear().domain([prect_min, (prect_max - prect_min) / 2,prect_max]).range(["#f9d045", "#60df71", "#0982d5"])
Insert cell
Insert cell
legend = d3.select(container).select("svg");
Insert cell
gradient = legend.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "0%");
Insert cell
colors = ['#f9d045', '#60df71', '#0982d5'];
Insert cell
gradient.selectAll('stop')
.data(colors)
.enter()
.append('stop')
.style('stop-color', function(d){ return d; })
.attr('offset', function(d, i) {
// If there are only three colors, distribute them evenly
if (colors.length === 3) {
if (i === 0) return '0%';
if (i === 1) return '50%';
if (i === 2) return '100%';
} else {
// For more than three colors, use the original calculation
return 100 * (i / (colors.length - 1)) + '%';
}
});
Insert cell
legend.append("rect")
.attr("x", 200)
.attr("y", 1100)
.attr("width", 400)
.attr("height", 30)
.style("fill", "url(#gradient)")
.style('opacity', 0.8);
Insert cell
legend.append("text")
.attr("x", 160) // Adjust the position of the title
.attr("y", 1120) // Adjust the vertical position of the title
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#888888")
.style("font-family", "ubuntu")
.text("" + prect_min + " mm");
Insert cell
legend.append("text")
.attr("x", 640) // Adjust the position of the title
.attr("y", 1120) // Adjust the vertical position of the title
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#888888")
.style("font-family", "ubuntu")
.text("" + prect_max + " mm");
Insert cell
legend.append("text")
.attr("x", 400) // Adjust the position of the title
.attr("y", 1085) // Adjust the vertical position of the title
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("fill", "#888888")
.style("font-family", "ubuntu")
.style("font-weight", "bold")
.text("Total Precipitation");
Insert cell
Insert cell
prect_data.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
fire_data@2.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
startDate = new Date("2013-01-01")
Insert cell
endDate = new Date("2019-12-01")
Insert cell
filterStart = new Date("2013-01-01")
Insert cell
filterEnd = new Date("2019-12-01")
Insert cell
Insert cell
import {areaGraph} from "@emfielduva/dvlib_layout"
Insert cell
import {dvSparkline} from "@emfielduva/dvlib_layout"
Insert cell
import {toNum} from "@emfielduva/dvlib"
Insert cell
import {basemaps, drawMapLayer, geoCentroids} with {projection} from "@emfielduva/dvlib_maps"
Insert cell
function movingAverage(data, windowSize) {
var movingAverages = [];
for (var i = 0; i < data.length; i++) {
if (i + windowSize <= data.length) {
var window = data.slice(i, i + windowSize);
var sum = window.reduce(function(acc, obj) { return acc + obj.value; }, 0);
var average = sum / windowSize;
movingAverages.push(average);
} else {
var remaining = data.length - i;
var window = data.slice(i, i + remaining);
var sum = window.reduce(function(acc, obj) { return acc + obj.value; }, 0);
var average = sum / remaining;
movingAverages.push(average);
if (movingAverages.length >= data.length) {
break;
}
}
}
return movingAverages;
}
Insert cell
selectedRegions = ["North", "Central", "South"];
Insert cell
dateRange = ["2013-01-01", "2019-12-01"]
Insert cell
function region_select(regionName) {
// Check if the region is already selected
const index = selectedRegions.indexOf(regionName);
const button = document.getElementById("btn-" + regionName.toLowerCase())
if (index === -1) {
// Region is not selected, add it to the list
selectedRegions.push(regionName);
button.style.backgroundColor = "#eee";
} else {
// Region is already selected, remove it from the list
selectedRegions.splice(index, 1);
button.style.backgroundColor = "#5f5f5f";
}

prect_dots.style("opacity", function(d) {
return selectedRegions.includes(d.region) ? 0.75 : 0.3;
});

mutable area_data = filtered_fire_data.filter(function(d,i){ return selectedRegions.indexOf(d.Region) >= 0 });
mutable regionSparkData = caliSparkData.filter(function(d,i){ return selectedRegions.indexOf(d.Region) >= 0 });

}
Insert cell
val = scaleX(new Date("2017-12-01"))
Insert cell
function date_select() {
// Get the updated start and end dates from your UI elements
var newStartDate = document.getElementById("start_datepicker").value;
var newEndDate = document.getElementById("end_datepicker").value;
// Update dateRange based on which input was changed
dateRange[0] = newStartDate;
dateRange[1] = newEndDate;
// You can perform any additional operations here, such as updating your visualization
// or triggering other functions based on the updated date range

d3.select('#opac_rect #area_1')
.attr('width', scaleX(new Date(dateRange[0])) - 830); // Update the width relative to the x position
d3.select('#opac_rect #area_2')
.attr('x', scaleX(new Date(dateRange[1])) + 60)
.attr('width', 1860 - scaleX(new Date(dateRange[1]))); // Update the width relative to the x position
d3.select('#opac_rect #spark_1')
.attr('width', scaleX(new Date(dateRange[0])) - 830);// Update the width relative to the x position
d3.select('#opac_rect #spark_2')
.attr('x', scaleX(new Date(dateRange[1])) + 60)
.attr('width', 1860 - scaleX(new Date(dateRange[1])));// Update the width relative to the x position

mutable mapData = prect_data.filter(d => {
// Convert the date string from the dataset to a Date object
const dataDate = new Date(d["date"]);
// Compare the dates
const filterStart = new Date(dateRange[0])
const filterEnd = new Date(dateRange[1])
return dataDate.getTime() >= filterStart.getTime() && dataDate.getTime() <= filterEnd.getTime();
});

};
Insert cell
ubuntu = html`<style>
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap');
</style>
`
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more