Public
Edited
Nov 13, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
robotext = () => {
const { label, sfara } = selected;

const sentence = (
sfara <= -0.060 ? "much less" :
sfara <= -0.030 ? "less" :
sfara <= 0 ? "slightly less" :
sfara > 0.060 ? "much more" :
sfara > 0.030 ? "more" :
"slightly more"
);
const background = (
sfara <= -0.060 ? pillColors[1] :
sfara <= -0.030 ? pillColors[2] :
sfara <= 0 ? pillColors[3] :
sfara > 0.060 ? pillColors[6] :
sfara > 0.025 ? pillColors[5] :
pillColors[4]
);
const color = foreground(background);
const explanation = sfara
? `Snowfall is forecast to be <span style="background: ${background}; border-radius: 2px; color: ${color}; padding: 0px 4px;">${sentence}</span> than normal.`
: `There is not enough annual snowfall to make a forecast.`;
return html`<div style='font-family: ${franklinLight}'>${explanation}</div>`;
};
Insert cell
chartSelect = () => {
const marginTop = 13;
const svg = d3.create("svg")
.attr("width", chartwidth + margin.left + margin.right)
.attr("height", bandheight + marginTop + margin.bottom);

svg.append("style").html(css);

const g = svg.append("g")
.attr("transform", `translate(${[margin.left, marginTop]})`);

g.append("g")
.attr("opacity", selected.sf_1964_1993 ? 1 : 0)
.call(g => xaxisGenerator(g, bandheight));
const yaxis = g.append("g").attr("class", "axis y-axis");
const yaxisTick = yaxis.append("g").attr("class", "tick")
.attr("transform", `translate(0, ${y.bandwidth() / 2})`);
yaxisTick.append("line")
.attr("opacity", selected.sf_1964_1993 ? 1 : 0)
.attr("x2", chartwidth);
yaxisTick.append("text")
.attr("dy", "0.32em")
.attr("x", -margin.left)
.html(`<tspan style="font-weight: bold">${selected.label}</tspan>${selected.sf_1964_1993 ? "" : " has insufficient snowfall data to calculate a trend."}`);

const city = g.selectAll(".city")
.data([selected])
.join("g")
.attr("class", "city")
.attr("opacity", selected.sf_1964_1993 ? 1 : 0)
.attr("transform", d => `translate(0, ${y.bandwidth() / 2})`);

const cityLine = city.append("line")
.attr("x1", d => x(d.sf_1964_1993))
.attr("x2", d => x(d.sf_1994_2023))
.attr("stroke", d => colorScale(d.sf_change))
.attr("stroke-width", 6);

const cityCircleStart = city.append("circle")
.attr("cx", d => x(d.sf_1964_1993))
.attr("fill", "#fff")
.attr("stroke", "#888")
.attr("r", r);

const cityCircleEnd = city.append("circle")
.attr("cx", d => x(d.sf_1994_2023))
.attr("fill", d => colorScale(d.sf_change))
.attr("stroke", d => d3.color(colorScale(d.sf_change)).darker(1))
.attr("r", r);

const cityLabelStart = city.append("text")
.attr("class", "city-label")
.attr("dx", d => (r + 4) * (d.sf_change < 0 ? 1 : -1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.sf_change < 0 ? "start" : "end")
.attr("x", d => x(d.sf_1964_1993))
.attr("y", -18)
.text("1964-1993");

const cityLabelEnd = city.append("text")
.attr("class", "city-label")
.attr("dx", d => (r + 4) * (d.sf_change < 0 ? -1 : 1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.sf_change < 0 ? "end" : "start")
.attr("x", d => x(d.sf_1994_2023))
.attr("y", -18)
.text("1994-2023");
const cityInchesStart = city.append("text")
.attr("class", "county-inches county-inches-start")
.attr("dx", d => (r + 4) * (d.sf_change < 0 ? 1 : -1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.sf_change < 0 ? "start" : "end")
.attr("x", d => x(d.sf_1964_1993))
.text(d => `${Math.round(d.sf_1964_1993)} inches`);

const cityInchesEnd = city.append("text")
.attr("class", "county-inches county-inches-end")
.attr("dx", d => (r + 4) * (d.sf_change < 0 ? -1 : 1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.sf_change < 0 ? "end" : "start")
.attr("x", d => x(d.sf_1994_2023))
.text(d => `${Math.round(d.sf_1994_2023)} inches`);

return svg.node()
}
Insert cell
chart = () => {
const svg = d3.create("svg")
.attr("width", chartwidth + margin.left + margin.right)
.attr("height", chartheight + margin.top + margin.bottom);

svg.append("style").html(css);

const g = svg.append("g")
.attr("transform", `translate(${[margin.left, margin.top]})`);

g.append("g").call(g => xaxisGenerator(g, chartheight));
g.append("g").call(yaxisGenerator);

const city = g.selectAll(".city")
.data(chartData)
.join("g")
.attr("class", "city")
.attr("transform", d => `translate(0, ${y(d.label) + y.bandwidth() / 2})`);

const cityLine = city.append("line")
.attr("x1", d => x(d.sf_1964_1993))
.attr("x2", d => x(d.sf_1994_2023))
.attr("stroke", d => colorScale(d.sf_change))
.attr("stroke-width", 6)

const cityCircleStart = city.append("circle")
.attr("cx", d => x(d.sf_1964_1993))
.attr("fill", "#fff")
.attr("stroke", "#888")
.attr("r", r);

const cityCircleEnd = city.append("circle")
.attr("cx", d => x(d.sf_1994_2023))
.attr("fill", d => colorScale(d.sf_change))
.attr("stroke", d => d3.color(colorScale(d.sf_change)).darker(1))
.attr("r", r);

return svg.node()
}
Insert cell
Insert cell
css = `
.axis .tick line {
stroke: #ccc;
}
.axis .tick text {
font-family: ${franklinLight};
font-size: 14px;
}
.axis .domain {
display: none;
}

.axis.y-axis .tick text {
font-size: 14px;
paint-order: stroke fill;
stroke: white;
stroke-linejoin: round;
stroke-width: 8px;
text-anchor: start;
}

.axis.x-axis .tick:first-of-type text {
text-anchor: start;
transform: translate(-4px);
}

.city-label {
font-family: ${franklinLight};
font-size: 13px;
}
.city-inches, .county-inches {
font-family: ${franklinLight};
font-size: 14px;
paint-order: stroke fill;
stroke: white;
stroke-linejoin: round;
stroke-width: 8px;

&.city-inches-end, &.county-inches-end {
font-weight: bold;
}
}
`
Insert cell
Insert cell
pillColors = ["#543005", "#a3661a", "#dbb871", "#f6ecd2", "#ddebf9", "#7e9fd4", "#3973bb", "#0a3258"]
Insert cell
colorScale = d3.scaleThreshold([-30, -20, -10, 0, 10, 20], pillColors)
Insert cell
Insert cell
xaxisGenerator = (g, height) => {
const xMax = x.domain()[1];
const tickValues = (
xMax >= 600 ? d3.range(0, xMax, chartwidth <= 400 ? 300 : 200) :
xMax >= 300 ? d3.range(0, xMax, chartwidth <= 400 ? 150 : 100) :
xMax >= 150 ? d3.range(0, xMax, chartwidth <= 400 ? 100 : 50) :
d3.range(0, 150, chartwidth <= 400 ? 60 : 30)
);
const generator = d3.axisBottom(x)
.tickFormat((d, i) => `${d}${i ? "" : " inches"}`)
.tickSize(height - y.bandwidth() / 2 + 6)
.tickValues(tickValues);

g.append("g")
.attr("class", "axis x-axis")
.attr("transform", `translate(0, ${y.bandwidth() / 2})`)
.call(generator);

return g;
}
Insert cell
yaxisGenerator = g => {
const generator = d3.axisLeft(y)
.tickSize(chartwidth);

g.append("g")
.attr("class", "axis y-axis")
.attr("transform", `translate(${chartwidth})`)
.call(generator)
.selectAll("text")
.attr("x", -chartwidth - margin.left);
return g;
}
Insert cell
Insert cell
xDefault = [0, 120]
Insert cell
xMax = Math.max(selected.sf_1964_1993, selected.sf_1994_2023)
Insert cell
x = d3.scaleLinear([0, Math.max(xDefault[1], xMax)], [0, chartwidth])
Insert cell
y = d3.scaleBand(chartData.map(d => d.label), [0, chartheight])
Insert cell
Insert cell
r = 6
Insert cell
margin = ({left: 140, right: 20, top: 5, bottom: 20})
Insert cell
chartwidth = Math.min(width, 640) - margin.left - margin.right
Insert cell
bandheight = 24
Insert cell
chartheight = chartData.length * bandheight
Insert cell
Insert cell
countyData = FileAttachment("county_snowfall_summary@2.json").json()
Insert cell
chartData = [
"49035", // Salt Lake County, Utah
"25017", // Middlesex County, Mass.
"26163", // Wayne County, Mich.
"27053", // Hennepin County, Minn.
"08031", // Denver County, Colo.
"36061", // New York County, N.Y.
"17031", // Cook County, Ill.
"06037", // Los Angeles County, Calif.
]
.map(county_fips => countyData.find(f => f.county_fips === county_fips))
.sort((a, b) => d3.descending(a.sf_1994_2023, b.sf_1994_2023))
Insert cell
Insert cell
import { foreground } from "@climatelab/contrasting-foreground-text@166";
Insert cell
import { franklinLight } from "@climatelab/fonts@46"
Insert cell
import { toc } from "@climatelab/toc@44"
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