Published
Edited
Oct 6, 2020
3 stars
Insert cell
md`# COVID-19 Live Dashboard`
Insert cell
viewof y_type = radio({
title: 'Show',
options: [
{label: "Confirmed cases", value: 'confirmed' },
{label: "Deaths", value: 'deaths'}
],
value: 'confirmed'
});

Insert cell
viewof selected_regions = checkbox({
title: "Regions",
options: ["Europe & Central Asia", "North America", "US States", "East Asia & Pacific", "South Asia", "Sub-Saharan Africa", "America & Caribbean", "Middle East & North Africa", "Latin America & Caribbean", "East & North Africa"],
value: ["Europe & Central Asia", "East Asia & Pacific", "North America"],
})

Insert cell
viewof curr_date = Scrubber(d3.timeDays(new Date(2020, 0, 22), new Date()), {
autoplay: true,
loop: false,
alternate: false,
initial: 0,
format: date => date.toLocaleString("ko-KR", {year: "numeric", month: "numeric", day: "numeric"}),
delay: 100
})
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, params.width, params.height]);
draw_axes(svg);
draw_trajectories(svg);
draw_date(svg);
svg.append("text").attr("id", "fullname");
return svg.node();
}
Insert cell
swatch = swatches({
color: color
});
Insert cell
import {autoSelect} from '@jashkenas/inputs'
Insert cell
import {swatches} from '@d3/color-legend'
Insert cell
import {Scrubber} from '@mbostock/scrubber'
Insert cell
import {checkbox} from '@jashkenas/inputs'
Insert cell
import {radio} from '@jashkenas/inputs'
Insert cell
d3 = require.alias({
"d3": "d3@5",
"d3-regression": "d3-regression@1",
})("d3", "d3-regression");

Insert cell
viz_data = data.map(function(d) {
return {
'country_code': d.country_code,
'country_name': d.country_name,
'population': d.population,
'region': d.region,
'confirmed': d.confirmed.filter(d => new Date(d[0]) < curr_date),
'deaths': d.deaths.filter(d => new Date(d[0]) < curr_date),
}
})
Insert cell
last_index = data[0].confirmed.length – 1
Insert cell
data = cntry_data_raw.concat(us_state_data).filter(d => selected_regions.includes(d.region)).map(function(d) {
d.confirmed = add_past_week_number(d.confirmed);
d.deaths = add_past_week_number(d.deaths);
return d;
})
Insert cell
function add_past_week_number(a) {
var new_array = [[a[0][0], a[0][1], a[0][1]]];
var curr_sum = 0;
var min_idx = 0;
for (var i = 1; i < a.length; i++) {
min_idx = d3.max([0, i-7]);
if (min_idx != 0) {
curr_sum = a[i][1] - a[min_idx][1];
} else {
curr_sum = a[i][1];
}
new_array.push([ a[i][0], a[i][1], curr_sum ])
}
return new_array;
}
Insert cell
function ts_diff(a) {
var new_array = [];
new_array.push([a[0][0], a[0][1]]);
for (var i = 1; i < a.length; i++) new_array.push([ a[i][0], a[i][1] - a[i-1][1] ])
return new_array;
}
Insert cell
us_state_data = us_state_data_raw.map(d => ({
'country_code': d.code,
'country_name': d.name,
'region': "US States",
'confirmed': d.confirmed,
'deaths': d.deaths,
}))
Insert cell
us_state_data_raw = d3.json("https://raw.githubusercontent.com/covid19-data/covid19-data/master/output/us_state_nyt.json")
Insert cell
cntry_data_raw = d3.json("https://raw.githubusercontent.com/covid19-data/covid19-data/master/output/cntry_stat_owid.json")
Insert cell
color = d3.scaleOrdinal(cntry_data_raw.concat(us_state_data).map(d => d.region), d3.schemeCategory10).unknown("black")
Insert cell
params = ({
width: 800,
height: 500,
opac_weak: 0.5,
margin: ({top: 25, right: 35, bottom: 35, left: 60}),
xmin: 10,
xmax: 1e6,
ymin: 10,
ymax: 1e6,
});
Insert cell
function draw_trajectories(svg) {
svg.append("g").selectAll("path").data(viz_data).join("path")
.attr("class", d => `${d.country_code}`)
.attr("stroke", d => color(d.region)).attr("fill", "none")
.attr("stroke-opacity", params.opac_weak)
.attr("d", d => get_path(d[y_type]))
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);;
svg.append("g").selectAll("circle").data(viz_data).join("circle")
.attr("class", d => `${d.country_code}`)
.attr("fill", d => color(d.region)).attr("fill-opacity", params.opac_weak)
.attr("cx", d => x(d[y_type][d[y_type].length-1][1]))
.attr("cy", d => y(d[y_type][d[y_type].length-1][2]))
.attr("r", 3)
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);;
const countries_to_label = ['USA', 'ITA','CHN', 'KOR', 'IRN', 'SGP', 'GBR',
'DNK', 'JPN', "CA", "NY", "NJ", "IN", 'ESP', //'DEU', 'FRA','NOR',
]
svg.append("g").selectAll("text")
.data(viz_data.filter(d => countries_to_label.includes(d.country_code))).join("text")
.text(d => d.country_name)
.attr("class", d => d.country_code)
.attr("fill", d => color(d.region)).attr("fill-opacity", params.opac_weak)
.attr("x", d => x(d[y_type][d[y_type].length-1][1]))
.attr("y",d => y(d[y_type][d[y_type].length-1][2]))
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);
}
Insert cell
function get_path(d) {
const l = d3.line();
return l( d.filter(d => (d[1] > 10 && d[2] > 10)).map(d => [x(d[1]), y(d[2])]))
}
Insert cell
grid = g => g
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1)
.call(g => g.append("g")
.selectAll("line")
.data(x.ticks())
.join("line")
.attr("x1", d => 0.5 + x(d))
.attr("x2", d => 0.5 + x(d))
.attr("y1", params.margin.top)
.attr("y2", params.height - params.margin.bottom))
.call(g => g.append("g")
.selectAll("line")
.data(y.ticks())
.join("line")
.attr("y1", d => 0.5 + y(d))
.attr("y2", d => 0.5 + y(d))
.attr("x1", params.margin.left)
.attr("x2", params.width - params.margin.right));
Insert cell
yAxis = g => g
.attr("transform", `translate(${params.margin.left},0)`)
.call(d3.axisLeft(y).ticks(params.height/90, ","))
.call(g => g.append("text")
.attr("font-size", 15)
.attr("x", -params.margin.left)
.attr("y", params.margin.top-10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(`↑ New ${y_type} (past week)`))
Insert cell
y = d3.scaleLog([params.ymin, params.ymax], [params.height - params.margin.bottom, params.margin.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${params.height - params.margin.bottom})`)
.call(d3.axisBottom(x).ticks(params.width / 80, ","))
.call(g => g.append("text")
.attr("font-size", 15)
.attr("x", params.width)
.attr("y", params.margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(`Total ${y_type} →`))
Insert cell
x = d3.scaleLog([params.xmin, params.xmax], [params.margin.left, params.width - params.margin.right])
Insert cell
function onMouseOver(selected) {
const country_code = d3.select(this).attr("class");
d3.selectAll(`.${country_code}`).attr("fill-opacity", 1).attr("stroke-opacity", 1).attr("stroke-width", 3)
.attr("r", 5);
//const country_circle = d3.select(`circle.${country_code}`);
//const label_x = country_circle.attr("cx");
//const label_y = country_circle.attr("cy");
d3.select("#fullname").text(code_to_name(country_code))
.style("text-anchor", "start")
.attr("font-size", 50).attr("fill-opacity", 0.1).attr("fill", "black")
.attr("font-style", "bold")
.attr("font-family", "Helvetica, sans-serif")
.attr("x", x(15)).attr("y", y(1e5));
}
Insert cell
function onMouseOut() {
const country_code = d3.select(this).attr("class");
d3.selectAll(`.${country_code}`)
.attr("fill-opacity", params.opac_weak).attr("stroke-opacity", params.opac_weak).attr("stroke-width", 1)
.attr("r", 3);
d3.select("#fullname").attr("fill-opacity", 0);
}
Insert cell
function code_to_name(country_code) {
return data.find(d => d.country_code === country_code).country_name;
}
Insert cell
function draw_date(svg) {
svg.append("g") // Date
.append("text").text(d3.timeFormat("%b %d")(curr_date))
.style("text-anchor", "end")
.attr("font-size", 100).attr("fill-opacity", 0.1)
.attr("font-style", "bold")
.attr("font-family", "Helvetica, sans-serif")
.attr("x", x(params.xmax)).attr("y", y(params.ymin));
}
Insert cell
function draw_axes(svg) {
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg.append("g").call(grid);
}
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