Published
Edited
May 28, 2020
17 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = html`${style}
<svg class="covid" width="${width}" height="${height}">
<defs>
<clipPath id="${clip.id}">
<path d="
M ${x.range()[0]} ${y.range()[0]}
H ${x.range()[1]}
V ${y.range()[1]}
H ${x.range()[0]} Z
" />
</clipArea>
</defs>
<g transform="translate(${x.range()[0]}, ${y(threshold)})">
<text dy="1em" style="text-transform: uppercase; fill: gray;">
Days since ${threshold} cases
</text>
<path
d="M 0 0 H ${x.range()[1]}"
stroke="gray"
/>
</g>
<g style="text-transform: uppercase; fill: gray;">
<text x="${x.range()[0]}" y="${y.range()[1]}">Doubles every…</text>
${curves.map(
({ x0, T, data }) => svg`<g>
<path
d="${lineTrunc(data)}"
stroke-dasharray="2,2"
stroke="gray"
clip-path="${clip}"
/>
<text
x="${x(expSolveForX(x0, T)(y.domain()[1]))}"
y="${y.range()[1]}"
text-anchor="middle"
>
${T} days
</text>
</g>`
)}
</g>
<g clip-path="${clip}">
${series.filter(hasOffset).map(
({ key, latest, color, data, highlight, offset }) => svg`<g
opacity="${highlight ? 1 : .1}"
transform="translate(${-(x(offset) - x(0))}, 0)"
>
<path
d="${line(data.map(d => [d.i, d.value]))}"
stroke="white"
stroke-width="4"
/>
<path
d="${line(data.map(d => [d.i, d.value]))}"
stroke="${color}"
stroke-width="1.5"
/>
<text
x="${x(data.length - 1)}"
y="${y(latest)}"
fill="${color}"
dx=".2em"
dy=".3em"
font-weight="bold"
>
${key}
</text>
</g>`
)}
</g>
</svg>`
Insert cell
style = html`<style>
.covid path {
fill: none;
}
.covid text {
font-family: sans-serif;
font-size: 12px;
}
</style>`
Insert cell
// x0: initial value
// T: doubling time
exp = (x0, T) => t => x0 * 2 ** (t / T)
Insert cell
expSolveForX = (x0, T) => y => (T * Math.log(y / (x0 / 2))) / Math.LN2 - T
Insert cell
curves = d3.range(1, 8).map(T => ({
T,
x0: threshold,
data: range(0, domain.length, 1).map((d, i) => [i, exp(threshold, T)(d)])
}))
Insert cell
domain = Array.from(data.get(keys[0]).keys())
Insert cell
latest = domain[domain.length - 1]
Insert cell
keys = Array.from(data.keys())
Insert cell
series = keys.map(key => ({
key,
latest: data.get(key).get(latest),
color: hsl(hues.get(key)),
highlight: highlights.includes(key),
offset: xOffsets.get(key),
data: domain.map((d, i) => ({
date: dateParse(d),
i,
di: i - xOffsets.get(key),
value: data.get(key).get(d)
}))
}))
Insert cell
data = transformData({
statistic,
smoothing
})
Insert cell
xOffsets = mapEach(map =>
Array.from(map.values()).findIndex(d => d > threshold)
)(data)
Insert cell
hasOffset = ({ offset }) => offset !== -1
Insert cell
minOffset = d3.min(Array.from(xOffsets.values()).filter(d => d !== -1))
Insert cell
Insert cell
line = d3
.line()
.x(d => x(d[0]))
.y(d => y(d[1]))
.defined(d => logValid(d[1]))
Insert cell
lineTrunc = d => line(d.filter(([i, d]) => d < 20 * y.domain()[1]))
Insert cell
x.domain()
Insert cell
x = d3
.scaleLinear()
.domain([0, domain.length - minOffset + 1])
.range([margin.left, width - margin.right])
Insert cell
y = d3
.scaleLog()
.domain([threshold, max(data) * 1.1])
.range([height - margin.bottom, margin.top])
Insert cell
height = width / 2
Insert cell
max = map =>
d3.max(
d3
.merge(
Array.from(map, ([key, value]) =>
Array.from(value, ([key, value]) => value)
)
)
.filter(logValid)
)
Insert cell
hues = new Map(keys.map(d => [d, Math.floor(Math.random() * 360)]))
Insert cell
hsl = hue => `hsl(${hue}, 70%, 30%)`
Insert cell
valid = d => d !== null && d !== Infinity && d !== -Infinity && !isNaN(d)
Insert cell
logValid = d => valid(d) && d !== 0
Insert cell
margin = ({ top: 10, right: 40, bottom: 20, left: 10 })
Insert cell
clip = DOM.uid("clip")
Insert cell
// inclusive!
range = (min, max, step) => [...d3.range(min, max, step), max]
Insert cell
d3 = require("d3@5")
Insert cell
import { transformData, dateParse, stats } from "8961aecb496ce269"
Insert cell
import { mapEach } from "@tophtucker/scrapbook"
Insert cell
import { radio, slider, checkbox } from "@jashkenas/inputs"
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