Published
Edited
Jan 13, 2021
4 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
import { countyShapes } from '@enjalot/county-boundaries'
Insert cell
geojson.features[3]
Insert cell
geojson = {
const output = { "type": "FeatureCollection", features: []}
output.features = countyShapes.features.filter(shape => {
const d = deaths.get(shape.id)
if (just_state !== "") {
if (shape.id.slice(0, 2) !== just_state) {
// Dropping counties not in this state.
return false
}
}
const c = confirmed.get(shape.id)
if (!d) {return false}
for (let [k, v] of d.entries()) {
shape.properties["d-" + k] = v
}
for (let [k, v] of c.entries()) {
shape.properties["c-" + k] = v
}
return true
})
return output
}
Insert cell
Insert cell
Insert cell
Insert cell
canvases = d3.range(20).map(d => DOM.element("canvas", {width, height, style: "position: fixed"}))
Insert cell
featureset = projected_geojson
Insert cell
import { dot_density } from '@bmschmidt/dot-density'
Insert cell
mutable tick = {
return 0
}
Insert cell
N_REPRESENTED = {
return current_fps * 60 * 60 * 24 / (Math.exp(time_per_real_second))
}
Insert cell
cities_raw = d3.csvParse(await FileAttachment("cities.csv").text())
Insert cell
cities = {
let useful
if (just_state == "") {
useful = cities_raw.filter(d => +d['1880'] > 10 && +d['1930'] > 500 && d['2010'] > 2 * d['1880'] && !d.title.match("ownship|Babylon"))
} else {
useful = cities_raw.filter(d => {
const t = d.title.split(", ")
t.reverse()
const st = t[0]
return (state_info.get(just_state).name == st)
})
}
const pairs = useful.map(d => [+d['2010'], d.title]).filter(d => d[0] && d[1] != "NA")
pairs.sort((a, b)=> a[0] - b[0])
return pairs
}
Insert cell
state_info.get(just_state)
Insert cell
get_closest_city = function(number) {
return cities[d3.bisector(d => d[0]).center(cities, number)][1]
}
Insert cell
ddate = {
return new Date(date_ms).toISOString().slice(0, 10)
}
Insert cell
redraw = {
const current_pane = mutable tick++ % canvases.length
const ctx = canvases[current_pane].getContext("2d")
ctx.globalAlpha = 1
ctx.clearRect(0, 0, width, height)
const draw_flags = [ variables_to_draw.indexOf("Confirmed cases") > -1, variables_to_draw.indexOf("Deaths") > -1]
const size_baseline = Math.exp(time_per_real_second) < 20 ? 10 : Math.exp(time_per_real_second) < 120 ? 4 : 1
for (let i = 0; i < generated_points.x_array.length; i++) {
const rix = randoms[i % randoms.length]
const num = generated_points.field_array[i]
const which = generated_points.field_array[i];
if (!draw_flags[which]) {continue}
ctx.fillStyle = which == 0 ? "#EEEEEE" : "#d7191c";
ctx.fillRect(generated_points.x_array[i] + rix[0], generated_points.y_array[i] + rix[1],
// A trick to make the deaths 4x4 and the non-deaths 1x1
size_baseline * (which * 3 + 1), size_baseline * (which * 3 + 1))
if (which == 1) {
mutable total_deaths_drawn++
}
}
const root_ctx = foreground.getContext("2d")
root_ctx.clearRect(0, 0, width, height)
const current_tick = tick % canvases.length
root_ctx.globalAlpha = .1
root_ctx.globalAlpha = 1
for (let i = canvases.length + current_pane; i > current_pane; i--) {
const predrawn = canvases[i % canvases.length]
root_ctx.drawImage(predrawn, 0, 0)
root_ctx.globalAlpha = root_ctx.globalAlpha * .83
}
}
Insert cell
mutable total_deaths_drawn = 0
Insert cell
background_map = {
const ctx = bkgd.getContext("2d")
ctx.fillStyle = "#222222"
ctx.globalAlpha = 1;
ctx.fillRect(0, 0, width, height)
ctx.globalAlpha = 1;
const p = d3.geoPath(my_projection)
p.context(ctx)
let i = 0
for (let feature of states.features) {
ctx.strokeStyle = "rgba(255, 255, 255, .1)"
ctx.beginPath()
p(feature)
ctx.stroke()
}
ctx.strokeStyle = "rgba(255, 255, 255, .17)"
ctx.beginPath()
p(country)
ctx.stroke()
}
Insert cell
states = d3.json("https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json")
Insert cell
country = d3.json("https://raw.githubusercontent.com/scdoshi/us-geojson/master/geojson/nation/US.geojson")
Insert cell
Insert cell
md`## Random jitter

Precalculate a little random jitter rather than run it every tick.

`
Insert cell
randoms = {
const randoms = []
const radius_generator = d3.randomNormal(0, PIXEL_STD)
for (let i = 0; i < 5000; i++) {
const theta = Math.random() * Math.PI * 2
const r = radius_generator()
randoms.push([Math.cos(theta) * r, Math.sin(theta) * r])
}
return randoms
}
Insert cell
projected_geojson = d3.geoProject(geojson, my_projection)
Insert cell
my_projection = {
if (just_state === "") return betterAlbers().scale(width * 1.2).translate([width/2, height/2])
const proj = d3.geoAlbers()
.fitExtent([[10, 10], [width, height]], geojson);
return proj
}
Insert cell
import { geoAlbersUsaPr as betterAlbers } from '@almccon/u-s-map-with-puerto-rico-us-virgin-islands-american-samoa-gua'
Insert cell
Insert cell
fields =
["c-", "d-"].map(lab => lab + ddate)
Insert cell
import {checkbox, radio} from '@jashkenas/inputs'
Insert cell
fields
Insert cell
import { aq, op } from '@uwdata/arquero'
Insert cell
daily_deaths = Math.round(d3.sum([...deaths.values()].map(d => d.get(ddate))))
Insert cell
daily_confirmed = Math.round(d3.sum([...confirmed.values()].map(d => d.get(ddate))))
Insert cell
confirmed = hopkins_parser("confirmed")
Insert cell
d3.sum([...confirmed.get('36081').entries()].filter(d => d[0] < "2020-09-01"), d=> d[1])
Insert cell
deaths = hopkins_parser("deaths")
Insert cell
import { states as state_array, state_info } from '@bmschmidt/state-info'
Insert cell
hopkins_parser = async function(metric = "confirmed") {
const hopkins = aq.fromCSV(await fetch(`https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_${metric}_US.csv`).then(d => d.text()))
const date_names = hopkins.columnNames().map(d => [d, new Date(Date.parse(d))]).filter(d => Date.parse(d[0])).map(d => d[1].toISOString().slice(0, 10))
const t2 = hopkins
.select(aq.not(["Lat", "Long", "Long_", "code3", "iso3", "iso2", "UID", "Admin2", "Province_State", "Country_Region"]))
.derive({FIPS: d => op.padstart(d.FIPS, 5, "0")})
.fold(aq.range(2, hopkins.numCols() - 1))
.groupby(["FIPS", "Combined_Key"])
.orderby("key")
.rollup({values: d => op.values(d.value)})
const counts = new Float32Array(date_names.length)
const rolling = new Float32Array(date_names.length)
const pingpong = new Float32Array(date_names.length)
const datecounts = new Map()
for (let row of t2) {
// return row
const val = row.values
counts[0] = 0

let smooth = 2
for (let i = 1; i < val.length; i++) {
counts[i] = val[i] > val[i-1] ? val[i] - val[i-1] : 0
}
for (let i = 0; i < smooth; i++) {
rolling[i] = d3.mean(counts.slice(0, i))
}
for (let i = smooth; i < counts.length; i ++) {
rolling[i] = d3.mean(counts.slice(i-smooth, i + 1))
}

pingpong.fill(0)
// NB this must be even or else counts and delta have to be switched.
//blur(rolling, pingpong, 4)
if (!datecounts.get(row.FIPS)) {
datecounts.set(row.FIPS, new Map())
}
let fipcounts = datecounts.get(row.FIPS)
for (let i = 0; i < rolling.length; i++) {
const date = date_names[i]

fipcounts.set(date, rolling[i])
}
}
return datecounts
}
Insert cell
function blur(ping, pong, passes=1) {
if (passes == 0) {
return
}
for (let i=1; i < ping.length-1; i++) {
pong[i] = (ping[i-1] + ping[i] + ping[i+1])/3
}
pong[0] = (ping[0] + ping[1])/2
pong[pong.length-1] = (ping[pong.length-1] + ping[pong.length-2])/2
blur(pong, ping, passes-1)
}
Insert cell
import {date as dateView, select, slider} from "@severo/inputs-setter"

Insert cell
total_deaths = d3.sum([...deaths.values()].map(d => [...d.values()]).flat())
Insert cell
dates = [...deaths.get("01001").keys()].map(d => new Date(Date.parse(d)))
Insert cell
generated_points = {
// The first call here will also generate the triangulation.
const points = dot_density(featureset, fields, N_REPRESENTED)
yield points
while (true) {
const points = dot_density(featureset, fields, N_REPRESENTED)
yield points
}
}
Insert cell
d3 = require("d3@v6", "d3-geo-projection")
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