scaleSequential = (await import("https://cdn.jsdelivr.net/npm/d3@7/+esm")).scaleSequential
// a format for dates
formatDate = d3.utcFormat("%b %d, %Y") // e.g., "Apr 18, 2025"
// counties
//counties_file = await d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/counties-10m.json");
us = await d3.json("https://sustainable-fsa.github.io/fsa-counties-dd17/fsa-counties-dd17.topojson");
//us = await d3.json("assets/fsa-counties-dd17-albers.topojson");
counties = topojson.feature(us, us.objects.counties)
// states, but just the boundaries between them
//states_file = FileAttachment("assets/fsa_states.topojson").json()
states = topojson.mesh(us, us.objects.states, (a, b) => a !== b)
// normal grazing period data, simplified
data = FileAttachment("assets/fsa-lfp-eligibility-simple.csv").csv({typed: true})
// Load color palettes
color =
new Map([
["1 Month", "#FFFF54"],
["2 Month", "#F3AE3D"],
["3 Month", "#6D4E16"],
["4 Month", "#EA3323"],
["5 Month", "#7316A2"],
["Eligible", "#EA3323"]
])
variable = "payment_type"
filtered = data.filter(d => d.year === year && d.type === type && d.disaster === disaster)
// Create a lookup table keyed by `id`
valueById = Object.fromEntries(filtered.map(d => [String(d.id).padStart(5, "0"), d[variable]]))
// Merge values into counties.features
countiesFiltered = {
// Build lookup from filtered CSV: id → full row
const rowById = Object.fromEntries(
filtered.map(d => [String(d.id).padStart(5, "0"), d])
);
// Rebuild counties with joined properties
return {
type: "FeatureCollection",
features: counties.features.map(f => {
const id = String(f.id);
const row = rowById[id];
return {
...f,
value: row ? row[variable] : null,
properties: {
id: f.id,
...f.properties,
...(row ?? {}),
value: row ? row[variable] : null // explicitly add value for coloring
}
};
})
};
}
viewof disaster = Inputs.radio(
["Drought", "Fire"],
{
label: "Disaster",
value: "Drought"
}
)
viewof year = Inputs.range(
d3.extent(data, d => d.year),
{
step: 1,
label: "Year",
value: 2024
}
)
viewof type = {
const options = disaster === "Fire"
? ["Rangeland"]
: [...new Set(data.map(d => d.type))].sort();
return Inputs.select(options, { label: "Type" });
}
Plot.plot({
projection: {
type: "albers",
domain: counties
},
grid: true,
color: (disaster === "Drought" ?
{
domain: ["1 Month", "2 Month", "3 Month", "4 Month", "5 Month"],
range: ["#FFFF54", "#F3AE3D", "#6D4E16", "#EA3323", "#7316A2"],
unknown: "#ccc",
legend: true
} :
{
domain: ["Eligible"],
range: ["#EA3323"],
unknown: "#ccc",
legend: true
}),
marks: [
Plot.geo(countiesFiltered, {
fill: "value"
}),
Plot.dot(
countiesFiltered.features.map(f => {
const [x, y] = d3.geoCentroid(f);
return {
...f.properties,
x,
y
};
}),
{
x: "x",
y: "y",
r: 3,
fill: "transparent",
stroke: "none",
tip: true,
title: d => `${d.county} County, ${d.state}
FIPS: ${String(d.id).padStart(5, "0")}
Program Year: ${year}
Crop Type: ${type}
Disaster: ${d.disaster ? d.disaster : "None"}
Drought Qualifier: ${d.qualifier ? d.qualifier : "None"}
Start Date: ${d.disaster_start_date ? formatDate(d.disaster_start_date) : "None"}
`
}
),
Plot.geo(states, {stroke: "white"}),
]
})