Stacked Bargraph of Japan COVID19 Case Data. Data as of 3-25-2020.
Data from: https://covid19japan.com/ https://github.com/reustle/covid19japan#data-sources
As published by the Japan Times: https://www.japantimes.co.jp/liveblogs/news/coronavirus-outbreak-updates/
const margin = {top: 30, right: 30, bottom: 50, left: 70};
const width = 1000 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const x = d3.scaleBand()
.range([0, width])
.padding(0.2)
const y = d3.scaleLinear()
.range([height, 0]);
const yAxis = d3.axisLeft(y)
.ticks(10);
const xAxis = d3.axisBottom(x);
const svg = d3.select("#main").append("svg")
.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
.attr("width", "100%")
.attr("height", "100%")
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const tooltip = d3.select("body")
.append("div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
d3.csv("Japan-Data-3-25-2020.csv").then(data => {
const statuses = [
"Unspecified",
"Hospitalized",
"Recovered",
"Discharged",
"Deceased",
];
let nestedAgeAndStatus = d3.nest()
.key(d => d["Age Bracket"])
.key(d => d["Status"])
.entries(data.map(d => {
d['Age Bracket'] = +d['Age Bracket'];
isNaN(d['Age Bracket']) ? d['Age Bracket'] = "Unspecified" : d['Age Bracket'] = d['Age Bracket'];
return d;
}));
nestedAgeAndStatus = nestedAgeAndStatus.map(raw => {
let groups = raw.values.map(d => {
if (d.key === "") {
d.key = "Unspecified";
}
return d;
}).reduce((prev, next) => {
prev[next.key] = {
key: next.key,
data: next.values,
};
return prev;
}, {});
return {
"AgeRange": raw.key,
"Unspecified": groups["Unspecified"] ? groups["Unspecified"].data.length : 0,
"Hospitalized": groups["Hospitalized"] ? groups["Hospitalized"].data.length : 0,
"Recovered": groups["Recovered"] ? groups["Recovered"].data.length : 0,
"Discharged": groups["Discharged"] ? groups["Discharged"].data.length : 0,
"Deceased": groups["Deceased"] ? groups["Deceased"].data.length : 0,
};
});
const stack = d3.stack()
.keys(statuses)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
const series = stack(nestedAgeAndStatus);
x.domain(nestedAgeAndStatus.sort((a, b) => {
if (a.AgeRange === "Unspecified") return 1; // push "Unspecified" to the back of the array
if (b.AgeRange === "Unspecified") return -1;
if (+a.AgeRange < +b.AgeRange) return -1;
if (+a.AgeRange > +b.AgeRange) return 1;
return 0;
}).map(d => d.AgeRange));
y.domain([0, d3.max(nestedAgeAndStatus.map(d => {
return Object.keys(d).reduce((prev, next) => {
if (next !== "AgeRange") {
prev += d[next];
return prev;
}
return 0;
}, 0);
}))]);
const color = d3.scaleOrdinal()
.domain(series.map(d => { return d.key; }))
.range(["#737373", "#fed976", "#abdda4", "#2b83ba", "#bd0026"])
.unknown("#ccc")
// bars
svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", (d, i) => x(d.data.AgeRange))
.attr("y", d => y(d[1]))
.attr("height", d => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth())
.on("mouseover", d => {
tooltip.html("");
tooltip.append("p").attr("class", "header");
tooltip.append("p").attr("class", "sub-header");
tooltip.append("p").attr("class", "body");
tooltip.select(".header").text(`Age Range: ${d.data.AgeRange}`);
tooltip.select(".sub-header").text(`Status`);
const body = tooltip.select(".body").selectAll('.status')
.data(statuses)
.enter()
.append("div")
.attr("class", "status");
body.append("div")
.attr("class", "color")
.style("background-color", d => color(d));
body.append("div").text(v => `${v}: ${d.data[v]}`);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function(d) {
let { pageX, pageY } = d3.event;
let left = pageX + 10;
let top = pageY - 10;
if (d.data.AgeRange === "90") {
left = pageX - 200;
top = pageY - 80;
} else if (d.data.AgeRange === "Unspecified") {
left = pageX - 250;
top = pageY - 80;
}
return tooltip.style("top", `${top}px`).style("left", `${left}px`);
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
// axes
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height})`)
.call(xAxis)
.append("g")
.attr("class", "label")
.append("text")
.attr("transform", `translate(${width}, 0)`)
.attr("y", 42)
.attr("x", 20)
.text("Age Range");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("g")
.attr("class", "label")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -46)
.attr("x", 10)
.text("Confirmed Cases");
// key
const key = d3.select("#key").selectAll(".entries")
.data(statuses)
.enter()
.append("div")
.attr("class", "entry");
key.append("div")
.attr("class", "color")
.style("background-color", d => color(d));
key.append("div").text(d => d);
});
<!doctype html>
<head>
<meta charset="utf-8">
<title>Japan COVID19</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div id="main"></div>
<div id="key"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="./index.js"></script>
</body>
.axis text {
font-size: 1.2rem;
fill: #333;
}
.axis .label text {
text-anchor: end;
font-size: 1rem;
}
#key, #key .entry {
display: flex;
flex-direction: row;
}
#key {
font-size: 0.8rem;
font-family: sans-serif;
justify-content: space-around;
}
#key .entry .color {
height: 14px;
width: 14px;
margin-right: 3px;
}
#tooltip {
background-color: #f7f7f7;
padding: 3px 12px;
font-size: 1rem;
font-family: sans-serif;
border: 1px solid #bbbbbb;
border-radius: 5px;
box-shadow: 1px 1px 4px #bbbbbb;
}
#tooltip .body .status {
display: flex;
flex-direction: row;
}
#tooltip .body .status .color {
height: 10px;
width: 10px;
margin: auto 3px auto 0;
}
#tooltip p {
font-weight: normal;
font-family: monospace;
margin: 5px 0;
}
#tooltip p.header {
margin-bottom: 10px;
}
#tooltip p.sub-header {
border-bottom: 1px solid;
}
#tooltip p.body {
margin-top: -3px;
}
/* tablet */
@media (min-width: 768px) {
#key, .axis text {
font-size: 1rem;
}
.axis .label text {
font-size: 0.9rem;
}
#tooltip {
font-size: 1.2rem;
}
#key .entry .color {
height: 16px;
width: 16px;
margin-right: 5px;
}
}
/* large desktop */
@media (min-width: 1200px) {
#key {
font-size: 1.2rem;
}
#tooltip {
font-size: 1.4rem;
}
.axis .label text {
font-size: 0.7rem;
}
#key .entry .color {
height: 21px;
width: 21px;
margin-right: 8px;
}
}