block by dhoboy 19d5d6dda01690e314e9a14a3cc3118e

Beijing Air Quality 2

Full Screen

Beijing Air Quality Data recorded at the US Embassy in Beijing over the past 8 years and fed into Mike’s Calendar block.

Data is from US State Department and is not fully validated or verified.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 18px sans-serif;
  shape-rendering: crispEdges;
}

.day {
  fill: #f0f0f0;
  stroke: #ccc;
}

.month {
  fill: none;
  stroke: #000;
  stroke-width: 2px;
}

.tooltip {
  background-color: #fff;
  padding: 0px 10px;
  font-family: sans-serif;
  width: 800px;
}

.tooltip_body {
  margin: 5px 0;
  font-size: 18px;
}

h5 {
  font-size: 18px;
  margin-left: 10px;
}
</style>
<body>
<h5>Concentration of Particluate Matter less than 2.5 micrometers in diameter</h5>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
    height = 136,
    cellSize = 17,
    margin = 14;

var format = d3.time.format("%x");

var color = d3.scale.threshold()
    .domain([51, 101, 151, 201, 301])
    .range(["#1a9850", "#fee08b", "#f46d43", "#d73027", "#a50026", "#67001f"]);

var tooltip = d3.select("body")
    .append("div")
    .attr("class", "tooltip")
    .style("position", "absolute")
    .style("z-index", "10")
    .style("visibility", "hidden");

var svg = d3.select("body").selectAll("svg")
    .data(d3.range(2008, 2016))
  .enter().append("svg")
    .attr("width", width)
    .attr("height", height + margin)
  .append("g")
    .attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")");

svg.append("text")
    .attr("transform", "translate(-6," + cellSize * 3.5 + ")rotate(-90)")
    .style("text-anchor", "middle")
    .text(function(d) { return d; });

var rect = svg.selectAll(".day")
    .data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
  .enter().append("rect")
    .attr("class", "day")
    .attr("width", cellSize)
    .attr("height", cellSize)
    .attr("x", function(d) { return d3.time.weekOfYear(d) * cellSize; })
    .attr("y", function(d) { return d.getDay() * cellSize; })
    .datum(format);

svg.selectAll(".month")
    .data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
  .enter().append("path")
    .attr("class", "month")
    .attr("d", monthPath);

var data = {}, remaining = 8; // parallel loading csv code from here: https://groups.google.com/forum/#!msg/d3-js/3Y9VHkOOdCM/YnmOPopWUxQJ
d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2008_HourlyPM2.5_created20140325.csv", function(data_2008) {
  data.yr_2008 = data_2008;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2009_HourlyPM25_created20140709.csv", function(data_2009) {
  data.yr_2009 = data_2009;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2010_HourlyPM25_created20140709.csv", function(data_2010) {
  data.yr_2010 = data_2010;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2011_HourlyPM25_created20140709.csv", function(data_2011) {
  data.yr_2011 = data_2011;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2012_HourlyPM2.5_created20140325.csv", function(data_2012) {
  data.yr_2012 = data_2012;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2013_HourlyPM2.5_created20140325.csv", function(data_2013) {
  data.yr_2013 = data_2013;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2014_HourlyPM25_created20150203.csv", function(data_2014) {
  data.yr_2014 = data_2014;
  if (!--remaining) draw();
})

d3.csv("/d/4c8f05129838a63ec90930f8c46262f0/Beijing_2015_HourlyPM25_created20160201.csv", function(data_2015) {
  data.yr_2015 = data_2015;
  if (!--remaining) draw();
})

function draw() {
  data = d3.entries(data);

  data.forEach(function(dataset) {
    dataset.value = dataset.value.filter(function(d) { // only want valid readings
      return d["QC Name"] == "Valid" && d["Value"] != "-999";
    });
  });

  var flatData = d3.merge(data.map(function(d) { return d.value; }));

  var dailyAvg = d3.nest()
    .key(function(d) {
      return format(new Date(d.Year, d.Month - 1, d.Day));
    })
    .rollup(function(hourlyReadings) {
      if (hourlyReadings.length != 24) {
        return "N/A";
      }
      return d3.sum(hourlyReadings, function(d) { return +d.Value; }) / 24
    })
    .map(flatData)

  var key = { "#1a9850": "Good", "#fee08b": "Moderate", "#f46d43": "Unhealthy for sensitive groups",
              "#d73027": "Unhealthy", "#a50026": "Very Unhealthy", "#67001f": "Hazardous" };

  var calBounds = { "cal2008": { top: 150 }, "cal2009": { top: 255 }, "cal2010" : { top: 409 },
                    "cal2011": { top: 563 }, "cal2012": { top: 718 }, "cal2013" : { top: 870 },
                    "cal2014" : { top: 1024 }, "cal2015": { top: 1179 } };

  var calTooltipTop = { "cal2008": 60, "cal2009": 217, "cal2010": 372, "cal2011": 525,
                        "cal2012": 679, "cal2013": 833, "cal2014": 987, "cal2015": 1142 };

  d3.keys(calBounds).forEach(function(cal) {
    calBounds[cal].bottom = calBounds[cal].top + 114;
  });

  rect
    .style("fill", function(d) { // d here is the date sring for each day
      if (dailyAvg[d] && dailyAvg[d] !== "N/A") {
        return color(dailyAvg[d]);
      }
    })
    .on("mouseover", function(d) {
      tooltip.html("");
      tooltip.append("pre").attr("class", "tooltip_body");
      tooltip.select(".tooltip_body")
        .text(
           "Date: " + d + "\t Reading: " + (dailyAvg[d] && dailyAvg[d] !== "N/A" ? d3.round(dailyAvg[d], 2) + " µg/cu PM2.5  \t" + "Air Quality: " + key[color(dailyAvg[d])] : "No Data")
        );
       return tooltip.style("visibility", "visible");
    })
    .on("mousemove", function() {
      var y = d3.event.pageY;
      var currentCal = "";

      d3.keys(calBounds).forEach(function(cal) {
        if (y >= calBounds[cal].top && y <= calBounds[cal].bottom) {
          currentCal = cal;
        }
      });
      return tooltip.style("top", calTooltipTop[currentCal] + "px").style("left", "45px");
    })
}

function monthPath(t0) {
  var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
      d0 = t0.getDay(), w0 = d3.time.weekOfYear(t0),
      d1 = t1.getDay(), w1 = d3.time.weekOfYear(t1);

  return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
      + "H" + w0 * cellSize + "V" + 7 * cellSize
      + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
      + "H" + (w1 + 1) * cellSize + "V" + 0
      + "H" + (w0 + 1) * cellSize + "Z";
}

</script>