Public
Edited
Mar 1, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = () => {
const svg = d3.create("svg")
.attr("width", chartwidth + margin.left + margin.right)
.attr("height", chartheight + margin.top + margin.bottom);

const filter = svg.append("filter")
.attr("id", "blur");
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", "3")
.attr("result", "blurred");
const feFlood = filter.append("feFlood")
.attr("flood-color", backgroundColor)
.attr("result", "color");

filter.append("feComposite")
.attr("in", "color")
.attr("in2", "blurred")
.attr("operator", "in")
.attr("result", "coloredBlur");

filter.append("feComponentTransfer")
.attr("in", "coloredBlur")
.attr("result", "brightnessAdjust")
.append("feFuncA")
.attr("type", "linear")
.attr("slope", "2");
filter.append("feMerge").selectAll("feMergeNode")
.data(["brightnessAdjust", "SourceGraphic"])
.join("feMergeNode")
.attr("in", d => d);

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

g.append("g")
.call(yaxisGenerator);
g.append("g")
.call(xaxisGenerator);
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x(d.year))
.attr("cy", d => y(d.mean))
.attr("fill", "none")
.attr("r", r)
.attr("stroke", "black");

g.append("path")
.datum(regression)
.attr("d", lineGenerator)
.attr("filter", "url(#blur)")
.attr("stroke", backgroundColor)
.attr("stroke-width", 1.5)

g.append("path")
.datum(regression)
.attr("d", lineGenerator)
.attr("fill", "none")
.attr("stroke", "black");

g.append("text")
.datum(regression)
.attr("font-family", franklinLight)
.attr("font-weight", "bold")
.attr("transform", d => {
const line = d.map(([dx, dy]) => [x(dx), y(dy)])
const angle = geometric.lineAngle(line);
return `translate(${geometric.pointTranslate(geometric.lineMidpoint(line), angle + 90, -5)}) rotate(${angle})`;
})
.style("paint-order", "stroke fill")
.style("stroke", "white")
.style("stroke-linejoin", "round")
.style("stroke-opacity", 0.8)
.style("stroke-width", "2px")
.text("TREND");
return svg.node();
}
Insert cell
Insert cell
backgroundColor = colorScale(decadalChange)
Insert cell
foregroundColor = foreground(backgroundColor)
Insert cell
heatValues = [0.083, 0.167, 0.25, 0.333, 0.417, 0.5, 0.583, 0.667, 0.75, 0.833, 0.917, 1, 1.083, 1.167, 1.25, 1.333, 1.417, 1.5]
Insert cell
heatColors = [
"#f8f4e2", "#f9e9ce", "#fadfbb", "#fad5a7", "#f9ca94", "#f7c081", // 0.5
"#fd9c63", "#f69259", "#ee8850", "#e77d47", "#df733e", "#d76935", // 1
"#cc0000", "#bf0018", "#b20124", "#a5042b", "#980730", "#8b0a34", // 1.5
]
Insert cell
coldValues = [-0.083, -0.167, -0.25, -0.333, -0.417, -0.5, -0.583, -0.667, -0.75, -0.833, -0.917, -1]
Insert cell
coldColors = [
"#eff4e9", "#e0ede6", "#d2e5e1", "#c4deda", "#b6d7d2", "#a9d0ca", // -0.5
"#9fc4d7", "#95bed3", "#8cb7ce", "#82b1ca", "#78aac6", "#6ea4c1", // -1
]
Insert cell
colorScale = value => {
if (value < 0) {
let index = -1;

for (let i = 0, l = coldValues.length; i < l; i++) {
const v = coldValues[i];
if (value > v) {
index = i;
break;
}
}
return coldColors[index];
}
else {
let index = -1;
for (let i = 0, l = heatValues.length; i < l; i++){
const v = heatValues[i];
if (value < v) {
index = i;
break;
}
}
return heatColors[index]
}
}
Insert cell
Insert cell
xaxisGenerator = g => {
const generator = d3.axisBottom(x)
.tickFormat(d => d)
.tickSize(8)
.tickValues(years.filter(d => d % 10 === 0));

const axis = g.call(generator)
.attr("transform", `translate(0, ${chartheight})`);

axis.select(".domain").remove();
const tick = axis.selectAll(".tick");

tick.selectAll("text")
.attr("font-size", 14)
.attr("font-family", franklinLight);

return g;
}
Insert cell
yaxisGenerator = g => {
const generator = d3.axisLeft(y)
.tickFormat((d, i, e) => `${d}${i === e.length - 1 ? "°F" : ""}`)
.tickSize(chartwidth + 20)
.tickValues(d3.range(y.domain()[0], y.domain()[1] + interval, interval))

const axis = g.call(generator)
.attr("transform", `translate(${chartwidth})`);

axis.select(".domain").remove();
const tick = axis.selectAll(".tick");

const toptickOffset = 12;
tick.select("line")
.attr("stroke", "#e9e9e9")
.attr("x2", (d, i, e) => +d3.select(e[i]).attr("x2") + (i === e.length - 1 ? toptickOffset : 0))

tick.select("text")
.attr("font-size", 14)
.attr("font-family", franklinLight)
.attr("x", (d, i, e) => +d3.select(e[i]).attr("x") + (i === e.length - 1 ? toptickOffset : 0));
return g;
}
Insert cell
lineGenerator = d3.line()
.x(d => x(d[0]))
.y(d => y(d[1]))
Insert cell
Insert cell
x = d3.scaleLinear(d3.extent(data, d => d.year), [0, chartwidth])
Insert cell
y = d3.scaleLinear([Math.floor(extent[0] / interval) * interval, Math.ceil(extent[1] / interval) * interval], [chartheight, 0])
Insert cell
extent = d3.extent(data, d => d.mean)
Insert cell
range = extent[1] - extent[0]
Insert cell
interval = range <= 2 ? 0.5 : range <= 8 ? 1 : range <= 16 ? 2 : 5
Insert cell
Insert cell
r = 3
Insert cell
margin = ({left: 39, right: r, top: 5, bottom: 22})
Insert cell
chartwidth = Math.min(width, 640) - margin.left - margin.right
Insert cell
chartheight = Math.max(300, chartwidth * 9 / 16) - margin.top - margin.bottom
Insert cell
Insert cell
years = data.map(d => d.year)
Insert cell
decadalChange = regression.a * 10
Insert cell
regression = d3.regressionLinear()
.x(d => d.year)
.y(d => d.mean)
(data)
Insert cell
data = selected.data
Insert cell
options = [
{
label: "Boston",
data: (await FileAttachment("1840000455@2.json").json()).data
},
{
label: "Chicago",
data: (await FileAttachment("1840000494@2.json").json()).data
},
{
label: "Denver",
data: (await FileAttachment("1840018789@2.json").json()).data
},
{
label: "Houston",
data: (await FileAttachment("1840020925@2.json").json()).data
},
{
label: "Los Angeles",
data: (await FileAttachment("1840020491@2.json").json()).data
},
{
label: "Miami",
data: (await FileAttachment("1840015149@2.json").json()).data
},
{
label: "Minneapolis",
data: (await FileAttachment("1840007830@2.json").json()).data
},
{
label: "New York",
data: (await FileAttachment("1840034016@2.json").json()).data
},
{
label: "Washington, D.C.",
data: (await FileAttachment("1840006060@2.json").json()).data
}
]
Insert cell
Insert cell
d3 = require("d3@7", "d3-regression@1")
Insert cell
geometric = require("geometric@2")
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