block by dhoboy 6775c481bd983024c091120415870330

Japan COVID19 live data

Full Screen

Stacked Bargraph of Japan COVID19 Case Data.

Data pulling live from: https://github.com/reustle/covid19japan-data https://covid19japan.com/

As published by the Japan Times: https://www.japantimes.co.jp/liveblogs/news/coronavirus-outbreak-updates/

index.js

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);
  });
}); 

index.html

<!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>

styles.css

.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;
  }
}