Skip to content

Instantly share code, notes, and snippets.

@dhoboy
Last active April 3, 2020 07:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhoboy/6775c481bd983024c091120415870330 to your computer and use it in GitHub Desktop.
Save dhoboy/6775c481bd983024c091120415870330 to your computer and use it in GitHub Desktop.
Japan COVID19 live data
<!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>
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");
const latest = d3.text("https://raw.githubusercontent.com/reustle/covid19japan-data/master/docs/patient_data/latest.json");
latest.then(fileName => {
d3.json(`https://raw.githubusercontent.com/reustle/covid19japan-data/master/docs/patient_data/${fileName}`).then(data => {
const statuses = [
"Unspecified",
"Hospitalized",
"Recovered",
"Discharged",
"Deceased",
];
let nestedAgeAndStatus = d3.nest()
.key(d => d.ageBracket)
.key(d => d.patientStatus)
.entries(data.map(d => {
d.ageBracket = +d.ageBracket;
if (!d.ageBracket || isNaN(d.ageBracket) || d.ageBracket === -1) {
d.ageBracket = "Unspecified"
}
if (!d.patientStatus) {
d.patientStatus = "Unspecified";
}
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");
if (d.data.AgeRange !== "Unspecified") {
tooltip.select(".header").text(`Age Range: ${d.data.AgeRange}-${+d.data.AgeRange + 9}`);
} else {
tooltip.select(".header").text(`Age Range: Unspecified`);
}
tooltip.select(".sub-header").text(`Total Cases: ${d.data.Unspecified + d.data.Hospitalized + d.data.Recovered + d.data.Discharged + d.data.Deceased}`);
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);
});
});
.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;
font-weight: bold;
}
#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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment