Published
Edited
Jul 12, 2021
6 forks
Importers
30 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof goBack = {
const s = slider({
title: "Go back to:",
min: 0,
max: goBackSpan,
step: 1,
value: goBackSpan,
format: date => formatDate(d3.timeDay.offset(startDate, date)),
description: `You can view the trend in the past by going back. The starting point of the slider is ${formatDate(startDate)}, and the end point is ${formatDate(goBackEnd)}.`
})
s.input.style.width = "40%";
return s;
}
Insert cell
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.select("#fullname").text(code_to_name(country_code))
.style("text-anchor", "end")
.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(params.xmax)).attr("y", y(params.ymin)-20);
const circle = d3.select(`circle.${country_code}`);
//d3.select("#guides").attr("transform", `translate(${circle.attr("cx")} ${-circle.attr("cy")})`)
}
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);
d3.select("#guides").attr("transform", `translate(0 0)`)
}
Insert cell
function draw_axes(svg) {
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
}
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.shifted))
.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.shifted.length-1)).attr("cy", d => y(d.shifted[d.shifted.length - 1])).attr("r", 3)
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);
svg.append("g").selectAll("text").data(viz_data).join("text").text(d => d.country_code)
.attr("class", d => d.country_code)
.attr("fill", d => color(d.region)).attr("fill-opacity", params.opac_weak)
.attr("x", d => d3.min( [x(d.shifted.length-1)+4, x(params.xmax)-10] ))
.attr("y", d => y(d.shifted[d.shifted.length - 1]))
.on("mouseover", onMouseOver).on("mouseout", onMouseOut);
}
Insert cell
function draw_doubling_lines(svg) {
// (0, params.ymin) -> (params.xmax, Math.pow(2, params.ymin*params.xmax/period))
// every day doubling -> (1, params.ymin*2), (2, params.ymin*2*2), ...
svg.append("g").attr("id", "guides").selectAll("path").data(doubling_periods).join("path")
.attr("class", "guidelines")
.attr("stroke", "orange").attr("stroke-opacity", 0.8).attr("stroke-dasharray", 2)
.attr("d", d => d3.line()([
[x(0), y(params.ymin)],
[x(params.xmax), y(params.ymin*Math.pow(2, params.xmax/d))]
]));
svg.append("g").selectAll("text").data(doubling_period_labels).join("text")
.text(d => d.period > 1 ? `Doubling every ${d.period} days`: "Doubling every day")
.attr("font-size", 12)
.style("text-anchor", "end")
.attr("fill", "orange")
.attr("transform", d => `rotate(${get_text_rotation_angle(d.period)} ${x(d.x)} ${y(d.y)})`)
.attr("x", d => x(d.x))
.attr("y", d => y(d.y));
}
Insert cell
doubling_period_labels = doubling_periods.map(function(d) {
d = Number(d);
const candidate_n = params.xmax / d;
const candidate_y = Math.pow(2, candidate_n) * params.ymin;
if (candidate_y < params.ymax) {
return { 'period': d, 'x': params.xmax, 'y': candidate_y };
} else {
return { 'period': d, 'x': Math.log2(params.ymax/params.ymin) * d, 'y': params.ymax };
}
});
Insert cell
function draw_date(svg) {
svg.append("g") // Date
.append("text").text(d3.timeFormat("%b %d %Y")(d3.timeDay.offset(startDate, goBack)))
.style("text-anchor", "end")
.attr("font-size", 50).attr("fill-opacity", 0.2)
.attr("font-style", "bold")
.attr("font-family", "Helvetica, sans-serif")
.attr("x", x(params.xmax) + params.margin.right)
.attr("y", y(params.ymax) + params.margin.top);
}
Insert cell
function get_path(d) {
const l = d3.line();
return l(d.map(function(e, i) { return [x(i), y(e)] }));
}
Insert cell
Insert cell
color = d3.scaleOrdinal(data.map(d => d.region), ["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#17becf"])
// color = d3.scaleOrdinal(data.map(d => d.region), d3.schemeTableau10).unknown("black")
//d3.schemeCategory10, d3.schemeTableau10
Insert cell
Insert cell
Insert cell
yAxis = g => g
.attr("transform", `translate(${params.margin.left},0)`)
.call(d3.axisLeft(y).ticks(params.height/80, ","))
.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(`↑ Number of ${y_type}`))
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(width/80))
.call(g => g.append("text")
.attr("x", params.width)
.attr("y", params.margin.bottom - 4)
.attr("font-size", 15)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(`Days since ${pivot_values[pivot_n]} ${y_type}→`))
Insert cell
x = d3.scaleLinear().domain([params.xmin, params.xmax])
.range([params.margin.left, params.width - params.margin.right]);
Insert cell
Insert cell
function get_text_rotation_angle(doubling_period) {
const radians = 0.0174532925;
return -Math.atan( (y(params.ymin)-y(params.ymin*2))/(x(doubling_period)-x(0)) ) / radians;
}
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
viz_data = filtered_data.filter(d => selected_regions.includes(d.region)).filter(d => d[y_type][last_index][1] > pivot).map(function(d) {
const start_idx = pivot_bisector.right(d[y_type], pivot);
d['shifted'] = d[y_type].slice(start_idx).map(d => d[1]);
return d;
})
Insert cell
pivot_bisector = d3.bisector(d => d[1])
Insert cell
Insert cell
countryNameList = {
let list = [];
for (let i = 0; i < data.length; i++) {
list.push(data[i].country_name)
}
return list;
}
Insert cell
pivot = pivot_values[pivot_n]
Insert cell
pivot_values = [1, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 13000, 15000, 20000, 50000, 100000]
Insert cell
today = all_dates[all_dates.length - 1];
Insert cell
parseTime = d3.timeParse("%Y-%m-%d");
Insert cell
all_dates = data[0]['confirmed'].map(x => parseTime(x[0]));
Insert cell
// startDate = d3.timeDay(new Date(2019, 11, 31))
startDate = parseTime(data[0].confirmed[0][0])
Insert cell
d3.timeDay.count(startDate, parseTime("2020-01-02"))
Insert cell
d3.timeDay.count(startDate, today)
Insert cell
d3.timeDay(parseTime(data[0].confirmed[10][0]))
Insert cell
d3.timeDay.count(d3.timeDay(parseTime(data[0].confirmed[0][0])), today)
Insert cell
formatDate = d3.timeFormat("%Y-%m-%d")
Insert cell
last_index = goBack
Insert cell
filtered_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(e => d3.timeDay.count(startDate, parseTime(e[0])) <= goBackSpan),
'deaths': d.deaths.filter(e => d3.timeDay.count(startDate, parseTime(e[0])) <= goBackSpan),
'confirmed_fraction': d.confirmed_fraction.filter(e => d3.timeDay.count(startDate, parseTime(e[0])) <= goBackSpan),
'confirmed_lastweek': d.confirmed_lastweek.filter(e => d3.timeDay.count(startDate, parseTime(e[0])) <= goBackSpan),
'deaths_lastweek': d.deaths_lastweek.filter(e => d3.timeDay.count(startDate, parseTime(e[0])) <= goBackSpan),
}
})
Insert cell
data = data_raw.filter(d => d.country_code != 'OWID_WRL' && d.region !== null).map(function(d) {
d.confirmed_fraction = d.confirmed.map(x => [x[0], x[1] / d.population]);
d.confirmed_lastweek = past_week(d.confirmed);
d.deaths_lastweek = past_week(d.deaths);
return d;
})
Insert cell
function past_week(a) {
var new_array = [[a[0][0], 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], curr_sum ])
}
return new_array;
}
Insert cell
data_raw = d3.json("https://raw.githubusercontent.com/hongtaoh/covid19-data/master/output/cntry_stat_owid.json")
Insert cell
Insert cell
Insert cell
Insert cell
import { rangeSlider as rangeSlider } from '@mootari/range-slider'
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more