Public
Edited
May 8, 2024
3 forks
Importers
78 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
update = chart.update(line)
Insert cell
height = 500
Insert cell
margin = ({top: 20, right: 30, bottom: 30, left: 40})
Insert cell
dateFormat = d3.utcFormat("%Y-%m-%d")
Insert cell
valueFormat = d => (Math.abs(d) < 10 ? d.toFixed(3) : d3.format(".2s")(d))
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(
d3
.axisLeft(y)
.tickFormat(valueFormat)
.ticks(5)
)
.call(g => g.select(".domain").remove())
.call(g =>
g
.select(".tick:last-of-type text")
.clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y)
)
Insert cell
xAxis = g =>
g.attr("transform", `translate(0,${height - margin.bottom})`).call(
d3
.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0)
)
Insert cell
xAxisLeft = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(
d3
.axisLeft(xOn)
.ticks(width / 80)
.tickSizeOuter(0)
)
.call(g =>
g
.append("text")
.attr("x", 4)
.attr("y", x(d3.max(data.map(d => d.on))) - 4)
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.attr("fill", "black")
.text("On")
.call(halo)
)
Insert cell
xAxisTop = g =>
g
.attr("transform", `translate(0,${margin.top})`)
.call(
d3
.axisTop(x)
.ticks(width / 80)
.tickSizeOuter(0)
)
.call(g =>
g
.append("text")
.attr("x", width - margin.right)
.attr("y", 12)
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.attr("fill", "black")
.text("About")
.call(halo)
)
Insert cell
y = d3
.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([height - margin.bottom, margin.top])
Insert cell
x = d3
.scaleUtc()
.domain(d3.extent([...data.map(d => d.on), ...data.map(d => d.about)]))
.range([margin.left, width - margin.right])
Insert cell
xOn = d3
.scaleUtc()
.domain(d3.extent(data.map(d => d.on)))
.range([margin.left, x(d3.max(data.map(d => d.on)))])
.nice()
Insert cell
lineOn = d3
.line()
.defined(d => !isNaN(d.value))
.x(d => x(d.on))
.y(d => y(d.value))
Insert cell
lineAbout = d3
.line()
.defined(d => !isNaN(d.value))
.x(d => x(d.about))
.y(d => y(d.value))
Insert cell
line2D = d3
.line()
.defined(d => !isNaN(d.value))
.x(d => x(d.about))
.y(d => xOn(d.on))
Insert cell
function halo(text) {
text
.select(function() {
return this.parentNode.insertBefore(this.cloneNode(true), this);
})
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 4)
.attr("stroke-linejoin", "round");
}
Insert cell
Insert cell
data = d3.tsvParse(dataText, d3.autoType).filter(d => d.on && d.about && d.value)
Insert cell
// CBO has lots of data here: https://www.cbo.gov/topics/budget/accuracy-projections
// GDP and revenue forecasts from Supplemental Data for this report https://www.cbo.gov/publication/56499

examples = [
{ label: "LIBOR 1mo forward curve", value: "libor1", data: libor[0] },
// { label: "LIBOR 3mo forward curve", value: "libor3", data: libor[1] },
// { label: "LIBOR 6mo forward curve", value: "libor6", data: libor[2] },
{ label: "CBO rates forecasts", value: "cbo", data: cbo },
{
label: "CBO GDP forecasts",
value: "cbo-gdp",
data: await FileAttachment("cbo-gdp.csv").csv({typed: true})
},
{
label: "CBO revenue forecasts",
value: "cbo-rev",
data: await FileAttachment("cbo-revenue.csv").csv({typed: true})
},
{ label: "U.S. coronavirus cases", value: "covid", data: covid },
{ label: "Other", value: "other" }
]
Insert cell
examples[2]
Insert cell
libor = Promise.all(
[
FileAttachment("libor1mo.csv"),
FileAttachment("libor3mo.csv"),
FileAttachment("libor6mo.csv")
].map(async file =>
d3.csvParse(await file.text(), ({ on, about, value }) => ({
on: d3.timeMonth.round(new Date(on)),
about: d3.timeMonth.round(new Date(about)),
value: +value
}))
)
)
Insert cell
tsv = arr =>
d3.tsvFormat(
arr.map(({ on, about, value }) => ({
on: dateFormat(on),
about: dateFormat(about),
value
}))
)
Insert cell
covid = d3
.csvParse(
await FileAttachment("covid-combined@1.csv").text(),
({ on, about, positive }) => ({
on: new Date(on),
about: new Date(about),
value: +positive
})
)
.sort((a, b) => (+a.on === +b.on ? +a.about - b.about : +a.on - b.on))
Insert cell
cbo = d3.csvParse(
await FileAttachment("cbo-data.csv").text(),
({ on, about, value }) => ({
on: new Date(on),
about: new Date(about),
value: +value
})
)
Insert cell
dataByAbout = Array.from(d3.group(data, ({ about }) => +about).values())
Insert cell
dataByOn = Array.from(d3.group(data, ({ on }) => +on).values())
Insert cell
dataActual = data.filter(({ on, about }) => +on === +about)
Insert cell
dataActualContinuous = d3
.scaleLinear()
.domain(dataActual.map(d => +d.on))
.range(dataActual.map(d => +d.value))
.clamp(true)
Insert cell
normalized[normalizeAnimation]()
Insert cell
Insert cell
Insert cell
options = [
{
label: html`<b>Medusa</b>: Lines connect points <em>on</em> the same date; x-axis shows the date they’re <em>about</em>. Forecasts go right of when they’re made.`,
value: { name: "on-over-about", line: lineAbout }
},
{
label: html`<b>Fishhook</b>: Lines connect points <em>about</em> the same date; x-axis shows date they’re made <em>on</em>. Forecasts go right to the day they foretold.`,
value: { name: "about-over-on", line: lineOn }
}
]
Insert cell
Insert cell
Insert cell
import { html, svg } from "@observablehq/htl"
Insert cell
import { radio } from "@jashkenas/inputs"
Insert cell
import { tweet } from "@mbostock/tweet"
Insert cell
d3 = require("d3@6")
Insert cell
// Private notes https://observablehq.com/d/cc37680e8b3f4b5b
Insert cell
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