Published
Edited
Apr 26, 2021
Importers
100 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dms = {
let dimensions = {
width: width,
// height: width * 2,
marginTop: 30,
marginLeft: d3.min([30, width / 30])
};

dimensions.height =
width > 600
? (dataDeathsTotalCumulative.length - 1) * 7 + dimensions.marginTop * 2
: (dataDeathsTotalCumulative.length - 1) * 3.5 + dimensions.marginTop * 2;

dimensions.marginBottom = dimensions.marginTop;
dimensions.marginRight = dimensions.marginLeft;

dimensions.chartWidth =
dimensions.width - dimensions.marginLeft - dimensions.marginRight;
dimensions.chartHeight =
dimensions.height - dimensions.marginTop - dimensions.marginBottom;

return dimensions;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataLive = {
const attached = d3.csvParse(
await FileAttachment("world-daily-historical-combined (2)@1.csv").text()
);

// const attached = await d3.csv(
// "https://inv-covid-data-prod.elections.aws.wapo.pub/world-daily-historical/world-daily-historical-combined.csv"
// );

return (
attached
.filter(d => d.countryIso.length > 0)
// Convert date to the format used in the rest of the notebook
.map(d => {
// Remove columns we don't need
delete d.recovered;
delete d.source;
delete d.confirmed;
delete d.updated;

return {
...d,
deaths: +d.deaths,
date: d3.timeFormat("%m/%d/%y")(d3.timeParse("%Y-%m-%d")(d.date))
};
})
);
}
Insert cell
data = {
const attached = await FileAttachment("backfill.csv").text();

const dataBackfill = d3.csvParse(attached);

const dataMerged = dataBackfill
.concat(dataLive)
.filter(d => d.countryIso.length > 0);

const crosswalkByIso = d3.group(crosswalk, d => d["iso"]);

const dataProcessed = dataMerged.map((d, i, arr) => {
let { country, countryIso, deaths, date } = d;
return {
country: country,
iso: countryIso,
region: crosswalkByIso.get(countryIso)[0]["region"],
date: timeFormatterReverse(timeParser(date)),
deaths: +deaths
};
});

return dataProcessed.sort(
(a, b) =>
d3.ascending(timeParser(a["date"]), timeParser(b["date"])) ||
d3.ascending(a["country"], b["country"])
);
}
Insert cell
Insert cell
dataDeathsRolling = {
const byCountry = d3.groups(data, d => d["iso"]);

const byCountryWithAverages = byCountry.map(elem => {
const allDays = elem[1];

const allDaysWithDaily = [];
for (let i = 0; i < allDays.length; i++) {
if (i > 0) {
allDays[i].deathsDaily =
allDays[i]["deaths"] - allDays[i - 1]["deaths"];
allDaysWithDaily.push(allDays[i]);
} else {
allDays[i].deathsDaily = allDays[i]["deaths"];
allDaysWithDaily.push(allDays[i]);
}
}

const days = 14;
const allDaysWithAverage = [];

for (let i = 0; i < allDays.length; i++) {
let daysTemp;
if (i < days) {
daysTemp = i;
} else {
daysTemp = days;
}

let numbersForAverage = [];
for (let j = 0; j <= daysTemp; j++) {
numbersForAverage.push(d3.max([allDays[i - j]["deathsDaily"], 0]));
}

allDays[i].deathsAvg =
d3.sum(numbersForAverage) / numbersForAverage.length;

allDaysWithAverage.push(allDays[i]);
}

return allDaysWithAverage;
});

return d3
.merge(byCountryWithAverages)
.sort(
(a, b) =>
d3.ascending(timeParser(a["date"]), timeParser(b["date"])) ||
d3.ascending(a["country"], b["country"])
);
}
Insert cell
dataDeathsCumulative = {
return d3.rollup(
dataDeathsRolling,
v => d3.sum(v, d => d.deaths),
d => d.date,
d => (category == "region" ? d["region"] : d["iso"])
);
}
Insert cell
dataDeathsDaily = {
return d3.rollup(
dataDeathsRolling,
v => d3.sum(v, d => d["deathsAvg"]),
d => d["date"],
d => (category == "region" ? d["region"] : d["iso"])
);
}
Insert cell
dataDeathsTotalCumulative = {
const total = d3.rollup(
dataDeathsRolling,
v => d3.sum(v, d => d["deaths"] / 1000),
d => timeFormatter(timeParser(d["date"]))
);
return [["date", "deaths (in thousands)"]].concat(Array.from(total));
}
Insert cell
dataSeries = {
const stack = d3
.stack()
.keys(category == "region" ? regions : codes)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetSilhouette);

const series =
switcher == "daily"
? stack(
[...dataDeathsDaily.values()].map((d, i) => {
const obj = Object.fromEntries(d.entries());
obj.date = [...dataDeathsDaily.keys()][i];
return obj;
})
)
: stack(
[...dataDeathsCumulative.values()].map((d, i) => {
const obj = Object.fromEntries(d.entries());
obj.date = [...dataDeathsCumulative.keys()][i];
return obj;
})
);

return series;
}
Insert cell
Insert cell
x = d3
.scaleLinear()
// .domain(d3.extent(dataSeries))
.domain(switcher == "daily" ? [-8000, 8000] : [-1000000, 1000000])
.range([0, dms.chartWidth])
Insert cell
y = {
let dates = [...dataDeathsCumulative.keys()].map(d => timeParser(d));

return d3
.scaleTime()
.domain([dates[0], dates[dates.length - 1]])
.range([0, dms.chartHeight]);
}
Insert cell
color = d3
.scaleOrdinal()
.domain(category == "region" ? regions : codesHighlight)
.range(colorsTableau)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
area = d3
.area()
.curve(d3.curveBasis)
.y((d, i) => y(timeParser(d.data.date)))
.x0(d => x(d[0]))
.x1(d => x(d[1]))
Insert cell
md`## Adding interactivity`
Insert cell
function hover(event, d) {
const [posX, posY] = d3.pointer(event);

d3.selectAll(".stream").attr("opacity", 0.3);
d3.select(this).attr("opacity", 1);

d3.select(".annotation")
.attr("transform", `translate(0 ${posY})`)
.attr("visibility", "visible")
.selectAll("path")
.attr("d", `M 0 0 H ${dms.chartWidth}`)
.attr("stroke", d3.select(this).attr("fill"));

d3.select(".annotation__date").text(`${timeFormatter(y.invert(posY))}`);
d3.select(".annotation__data").text(
`At least ${commafy(
Math.floor(
dataDeathsDaily.get(timeFormatterReverse(y.invert(posY))).get(d["key"])
)
)} lives lost on this day`
);
d3.select(".annotation__country")
.text(
category == "country"
? crosswalk.find(elem => elem["iso"] == d["key"])["geo"]
: d["key"]
)
.attr("fill", d3.select(this).attr("fill"));

d3.select(".axis").attr("opacity", 0.3);
}
Insert cell
function leave(event) {
d3.selectAll(".stream").attr("opacity", 1);

d3.select(".annotation")
.attr("visibility", "hidden")
.selectAll("path");

d3.select(".axis").attr("opacity", 1);

d3.select(".annotation__date").text("");
d3.select(".annotation__data").text("");
d3.select(".annotation__country").text("");
}
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