Public
Edited
Mar 13, 2024
1 fork
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
robotext = () => {
const { city, state_post, trend } = selected.data;
const wrapper = d3.create("div")
.style("font-family", franklinLight)
.style("text-align", "center")
.style("width", `${chartwidth + margin.left + margin.right}px`)

wrapper.append("style").html(`.pill { border-radius: 4px; padding: 1px 4px; }`)
wrapper.append("div")
.style("font-weight", "bold")
.text(`${city}, ${state_post}`);

const change = Math.round(trend);
const color = colorScale(trend);
const span = `<span class="pill" style="background: ${color}; color: ${foreground(color)}">`
const text = change === 0 ? `${span}at about the same time</span> as` : `${span}${Math.abs(change)} days ${trend < 0 ? "earlier" : "later"}</span> than`
wrapper.append("text")
.html(`Leaves are expected to appear ${text} in 1981`);

return wrapper.node();
}
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").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("x2", chartwidth);
yaxisTick.append("text")
.attr("dy", "0.32em")
.attr("font-weight", "bold")
.attr("x", -margin.left)
.text(selected.label);

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

const cityLine = city.append("line")
.attr("x1", d => x(new Date(dayOfYearToDate(d.data.line[0][1]))))
.attr("x2", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.attr("stroke", d => colorScale(d.data.trend))
.attr("stroke-width", 6);

const cityCircleStart = city.append("circle")
.attr("cx", d => x(new Date(dayOfYearToDate(d.data.line[0][1]))))
.attr("fill", "#fff")
.attr("stroke", "#888")
.attr("r", r);

const cityCircleEnd = city.append("circle")
.attr("cx", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.attr("fill", d => colorScale(d.data.trend))
.attr("stroke", d => d3.color(colorScale(d.data.trend)).darker(1))
.attr("r", r);

const cityLabelStart = city.append("text")
.attr("class", "city-label")
.attr("dx", d => (r + 4) * (d.data.trend < 0 ? 1 : -1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.data.trend < 0 ? "start" : "end")
.attr("x", d => x(new Date(dayOfYearToDate(d.data.line[0][1]))))
.attr("y", -18)
.text("Expected in 1981");

const cityLabelEnd = city.append("text")
.attr("class", "city-label")
.attr("dx", d => (r + 4) * (d.data.trend < 0 ? -1 : 1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.data.trend < 0 ? "end" : "start")
.attr("x", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.attr("y", -18)
.text("Expected in 2023");
const cityDateStart = city.append("text")
.attr("class", "city-date")
.attr("dx", d => (r + 4) * (d.data.trend < 0 ? 1 : -1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.data.trend < 0 ? "start" : "end")
.attr("x", d => x(new Date(dayOfYearToDate(d.data.line[0][1]))))
.text(d => formatDate(dayOfYearToDate(d.data.line[0][1])));

const cityDateEnd = city.append("text")
.attr("class", "city-date")
.attr("dx", d => (r + 4) * (d.data.trend < 0 ? -1 : 1))
.attr("dy", "0.32em")
.attr("text-anchor", d => d.data.trend < 0 ? "end" : "start")
.attr("x", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.text(d => formatDate(dayOfYearToDate(d.data.line[1][1])));

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(options)
.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(new Date(dayOfYearToDate(d.data.line[0][1]))))
.attr("x2", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.attr("stroke", d => colorScale(d.data.trend))
.attr("stroke-width", 6)

const cityCircleStart = city.append("circle")
.attr("cx", d => x(new Date(dayOfYearToDate(d.data.line[0][1]))))
.attr("fill", "#fff")
.attr("stroke", "#888")
.attr("r", r);

const cityCircleEnd = city.append("circle")
.attr("cx", d => x(new Date(dayOfYearToDate(d.data.line[1][1]))))
.attr("fill", d => colorScale(d.data.trend))
.attr("stroke", d => d3.color(colorScale(d.data.trend)).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: 16px;
paint-order: stroke fill;
stroke: white;
stroke-linejoin: round;
stroke-width: 8px;
text-anchor: start;
}

.city-label {
font-family: ${franklinLight};
font-size: 13px;
}
.city-date {
font-family: ${franklinLight};
font-size: 16px;
font-weight: bold;
paint-order: stroke fill;
stroke: white;
stroke-linejoin: round;
stroke-width: 8px;
}
`
Insert cell
Insert cell
backgroundColor = colorScale(decadalChange)
Insert cell
foregroundColor = foreground(backgroundColor)
Insert cell
colorScale = value => {
const fl = Math.floor(Math.abs(value));
if (value < 0) {
return colorsEarlier[fl] || colorsEarlier[colorsEarlier.length - 1];
}
else {
return colorsLater[fl] || colorsLater[colorsLater.length - 1];
}
}
Insert cell
colorsLater = [
// l95 - l75
"#f6eff6", "#f1e3f1", "#ecd7ec", "#e7cbe7", "#e2c0e1", "#dcb4dc", "#d7a8d7",

// l71.67 - l51.67
"#a2a1ce", "#9796c8", "#8d8ac2", "#827fbd", "#7774b7", "#6b6ab1", "#605fab"
]
Insert cell
colorsEarlier = [
// l95 - l75
"#ffffe5", "#fcfcd7", "#faf9ca", "#f6f5bc", "#f3f2ae", "#f0efa1", "#ecec93",

// l71.67 - l51.67
"#c5e085", "#beda7c", "#b7d473", "#b0ce6a", "#a9c860", "#a2c257", "#9bbc4e",

// l48.33 - l28.33
"#68a550", "#61994a", "#598d44", "#52823f", "#4b7639", "#446b33", "#3d602e",

// l25 - l5
"#375629", "#2f4a24", "#273f1f", "#1f341a", "#172a15", "#101f10", "#031607"
]
Insert cell
Insert cell
xaxisGenerator = (g, height) => {
const generator = d3.axisBottom(x)
.tickFormat(d => months[d.getUTCMonth()])
.tickSize(height - y.bandwidth() / 2 + 10)
.tickValues(d3.range(1, 6).map(mm => new Date(`2023-${mm.toString().padStart(2, 0)}-01`)));

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
startDate = new Date("2023-01-01")
Insert cell
endDate = new Date("2023-05-01")
Insert cell
x = d3.scaleTime([startDate, endDate], [0, chartwidth])
Insert cell
y = d3.scaleBand(options.map(d => d.label), [0, chartheight])
Insert cell
Insert cell
r = 6
Insert cell
margin = ({left: 90, right: 20, top: 5, bottom: 26})
Insert cell
chartwidth = Math.min(width, 640) - margin.left - margin.right
Insert cell
bandheight = 24
Insert cell
chartheight = options.length * bandheight
Insert cell
Insert cell
decadalChange = data.trend
Insert cell
data = selected.data
Insert cell
options = [
{
label: "Miami",
data: (await FileAttachment("3974.json").json())
},
{
label: "Houston",
data: (await FileAttachment("25433.json").json())
},
{
label: "Las Vegas",
data: (await FileAttachment("17834.json").json())
},
{
label: "Atlanta",
data: (await FileAttachment("4228.json").json())
},
{
label: "Nashville",
data: (await FileAttachment("24628.json").json())
},
{
label: "Washington",
data: (await FileAttachment("3606.json").json())
},
{
label: "New York",
data: (await FileAttachment("18875.json").json())
},
{
label: "Boston",
data: (await FileAttachment("10123.json").json())
}
]
Insert cell
Insert cell
months = ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."];
Insert cell
function formatDate(date) {
const [yyyy, mm, dd] = date.split("-");
return `${months[+mm - 1]} ${+dd}`;
}
Insert cell
function dayOfYearToDate(dayOfYear) {
return moment(`${2023}-01-01`)
.add(dayOfYear, "days")
.format("YYYY-MM-DD");
}
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
moment = require("moment@2.30.1/moment.js")
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