Published
Edited
Jul 19, 2021
10 forks
Importers
38 stars
Insert cell
Insert cell
md`A recreation of the visualization by [Aatish Bhatia](https://aatishb.com/covidtrends/) & [minutephysics](https://www.youtube.com/watch?v=54XLXg4fYsc).

- Data: https://github.com/covid19-data/covid19-data`


Insert cell
md`## Visualization`
Insert cell
viewof yTypeT = radio({
title: 'Show',
options: [
{label: "Confirmed cases", value: 'confirmed' },
{label: "Deaths", value: 'deaths'}
],
value: 'confirmed'
});
Insert cell
viewof selectedRegionsT = checkbox({
title: "World regions",
options: ["Europe & Central Asia", "North America", "US States", "East Asia & Pacific", "South Asia", "Sub-Saharan Africa", "Middle East & North Africa", "Latin America & Caribbean"],
value: ["Europe & Central Asia", "East Asia & Pacific", "North America", 'South Asia'],
})
Insert cell
viewof timeSliderT = rangeSlider({
min: 0,
max: d3.timeDay.count(startDate, all_dates[all_dates.length - 1]),
step: 1,
format: date => formatDate(d3.timeDay.offset(startDate, date)),
title: 'Adjust the date range as you need.'
})
Insert cell
viewof labelMyCountry = select({
title: "Label your country or area",
description: "You need to first make sure that the region your country / area resides is checked above.",
options: countryNameList,
value: "United States",
})
Insert cell
viewof playSpeedT = {
const phrases = [
'Very slow.',
'Slow.',
'Medium.',
'Fast.',
'Very fast.',
];
const s = slider({
value: 3, min: 0, max: 4, step: 1,
display: v => phrases[v],
title: 'Adjust the play speed',
});
s.input.style.width = "50%";
return s;
}
Insert cell
currDateT
Insert cell
viewof currDateT = Scrubber(d3.timeDays(d3.timeDay.offset(startDate, timeSliderT[0]), d3.timeDay.offset(startDate, timeSliderT[1] + 1)), {
autoplay: true,
loop: false,
alternate: false,
initial: 0,
format: date => date.toLocaleString("ko-KO", {year: "numeric", month: "numeric", day: "numeric"}),
delay: 500 - playSpeedT * 100,
})
Insert cell
Tchart = {
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
Tswatch = swatches({
color: color
});
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);
//d3.selectAll(`.${countryHighlightCode}`).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 draw_date(svg) {
svg.append("g") // Date
.append("text").text(d3.timeFormat("%b %d %Y")(currDateT))
.style("text-anchor", "end")
.attr("font-size", 70).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
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[yTypeT]))
.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[yTypeT][d[yTypeT].length-1][1]))
.attr("cy", d => y(d[yTypeT][d[yTypeT].length-1][2]))
.attr("r", 3)
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);
const countries_to_label = ['USA', 'ITA','CHN', 'KOR', 'IRN', 'SGP', 'GBR',
'JPN', "CA", "NY", "NJ", "IN", 'ESP', 'IND', 'FRA',
'RUS', 'MEX', 'BRA', 'CAN', 'IRN', 'TUR',
'KAZ', 'NLD', 'PAK', 'PHL', 'FIN', 'AUS', 'MMR', 'THA',
name_to_code(labelMyCountry),
]
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[yTypeT][d[yTypeT].length-1][1]))
.attr("y",d => y(d[yTypeT][d[yTypeT].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", 16)
.attr("x", -params.margin.left)
.attr("y", params.margin.top - 14)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(`↑ New ${yTypeT} (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 ${yTypeT} →`))
Insert cell
x = d3.scaleLog([params.xmin, params.xmax], [params.margin.left, params.width - params.margin.right])
Insert cell
Insert cell
color = d3.scaleOrdinal(cntry_data.filter(d => d.country_code != 'OWID_WRL').concat(us_state_data).map(d => d.region), ["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#17becf"])
// "#17becf", "#00cc66", '#ff3300', '#ff0066', 'black', "#8c564b", "#9467bd", 'brown'
//#d627728, #ff0066, #ff7f0e
Insert cell
params = ({
width: 800,
height: 500,
opac_weak: 0.5,
margin: ({top: 25, right: 35, bottom: 35, left: 60}),
xmin: 10,
xmax: 1e8,
ymin: 10,
ymax: 1e7,
});
Insert cell
startDate = d3.timeDay(new Date(2020, 0, 22))
Insert cell
endDate = all_dates[all_dates.length - 1]
Insert cell
formatDate = d3.timeFormat("%Y-%m-%d")
Insert cell
Insert cell
function code_to_name(country_code) {
return data.find(d => d.country_code === country_code).country_name;
}
Insert cell
function name_to_code(country_name) {
return data.find(d => d.country_name === country_name).country_code;
}
Insert cell
parseTime = d3.timeParse("%Y-%m-%d");
Insert cell
all_dates = data[0]['confirmed'].map(x => parseTime(x[0]));
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]) < currDateT),
'deaths': d.deaths.filter(d => new Date(d[0]) < currDateT),
}
})
Insert cell
last_index = data[0].confirmed.length - 1
Insert cell
countryNameList = {
let list = [];
for (let i = 0; i < data.length; i++) {
list.push(data[i].country_name)
}
return list;
}
Insert cell
data = cntry_data.concat(us_state_data).filter(d => selectedRegionsT.includes(d.region)).map(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 = cntry_data_raw.filter(d => d.region !== null)
Insert cell
cntry_data_raw = d3.json("https://raw.githubusercontent.com/covid19-data/covid19-data/master/output/cntry_stat_owid.json")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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