Skip to content

Instantly share code, notes, and snippets.

@dhoboy
Last active April 18, 2016 06:17
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/f9b212b40aa52386802621b53fc3fdab to your computer and use it in GitHub Desktop.
Save dhoboy/f9b212b40aa52386802621b53fc3fdab to your computer and use it in GitHub Desktop.
Beijing Air Quality 3

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.

This example has a different mouseover interaction than Beijing Air Quality 2

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
shape-rendering: crispEdges;
width: 1375px;
}
.day {
fill: #f0f0f0;
stroke: #ccc;
}
.month {
fill: none;
stroke: #000;
stroke-width: 2px;
}
.yearLabel {
font-size: 18px;
}
.tooltip {
width: 350px;
background-color: #f7f7f7;
padding: 3px 12px;
font-family: sans-serif;
border: 1px solid #bbbbbb;
box-shadow: 1px 1px 4px #bbbbbb;
}
.info {
font-size: 12px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: #a6cee3;
stroke-width: 2px;
stroke-linecap: round;
}
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>
/* namespacing */
var m = { l: {top: 10, right: 25, bottom: 35, left: 35}, c: 14 }; // margin
var c = { width: 960, height: 136, cellSize: 17, format: d3.time.format("%x") }; // calendars
var l = { width: 350 - m.l.left - m.l.right, height: 200 - m.l.top - m.l.bottom }; // linegraph
var color = d3.scale.threshold()
.domain([51, 101, 151, 201, 301])
.range(["#1a9850", "#fee08b", "#f46d43", "#d73027", "#a50026", "#67001f"]);
var getYear = d3.time.format("%Y"); // getYear(new Date("datestring")) => 2008, etc
var getMonth = d3.time.format("%m");
var getDay = d3.time.format("%d");
l.x = d3.scale.linear() // x is hours in the day
.domain([0, 23])
.range([0, l.width]);
l.y = d3.scale.linear() // y is value
.domain([0, 1000])
.range([l.height, 0]);
l.line = d3.svg.line()
.x(function(d) { return l.x(d.hour) })
.y(function(d) { return l.y(d.value) })
.interpolate("linear");
l.xAxis = d3.svg.axis()
.scale(l.x)
.orient("bottom")
.ticks(24);
l.yAxis = d3.svg.axis()
.scale(l.y)
.orient("left")
.ticks(10);
var calendars = d3.select("body").selectAll("svg")
.data(d3.range(2008, 2016))
.enter().append("svg")
.attr("width", c.width)
.attr("height", c.height + m.c)
.append("g")
.attr("transform", "translate(" + ((c.width - c.cellSize * 53) / 2) + "," + (c.height - c.cellSize * 7 - 1) + ")");
calendars.append("text")
.attr("transform", "translate(-6," + c.cellSize * 3.5 + ")rotate(-90)")
.attr("class", "yearLabel")
.style("text-anchor", "middle")
.text(function(d) { return d; });
var rect = calendars.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", c.cellSize)
.attr("height", c.cellSize)
.attr("x", function(d) { return d3.time.weekOfYear(d) * c.cellSize; })
.attr("y", function(d) { return d.getDay() * c.cellSize; })
.datum(c.format);
calendars.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 tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
var tooltipInfo = tooltip.append("pre")
.attr("class", "info");
var tooltipGraph = tooltip.append("svg")
.attr("width", l.width + m.l.right + m.l.left)
.attr("height", l.height + m.l.top + m.l.bottom)
.append("g")
.attr("transform", "translate(" + m.l.left + "," + m.l.top + ")");
tooltipGraph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + l.height + ")")
.call(l.xAxis)
.append("text")
.attr("transform", "translate(312, 32)")
.style("text-anchor", "end")
.text("Hour of the day");
tooltipGraph.append("g")
.attr("class", "y axis")
.call(l.yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("PM2.5");
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 c.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: 100 }, "cal2015": { top: 1165 } };
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) {
tooltipInfo.text("")
var reading = d3.round(dailyAvg[d], 2) + " µg/cu PM2.5" + "\nAir Quality: " + key[color(dailyAvg[d])]; // valid 24 hour average
if (dailyAvg[d] == "N/A") { // incomplete reading for the day, still can graph something on the hour by hour breakdown
reading = "Data Incomplete\nAir Quality: Data Incomplete";
}
if (!dailyAvg[d]) {
reading = "No Data\nAir Quality: No Data";
}
tooltipInfo.text(
"Date: " + d + "\nReading: " + reading
);
var day = new Date(d);
var yearKey = "yr_" + getYear(day);
var dayData = data.filter(function(d) {
return d.key == yearKey;
});
dayData = dayData[0].value.filter(function(d) {
var date = new Date(d["Date (LST)"].split(" ")[0]);
return (getDay(date) == getDay(day)-1 && getMonth(date) == getMonth(day));
}).map(function(d) {
return { hour: +d.Hour, value: +d.Value };
});
tooltip.select(".line").remove();
tooltipGraph.append("path")
.datum(dayData)
.attr("class", "line")
.attr("d", l.line);
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;
}
});
var top = y - 150;
if (currentCal == "cal2008") {
top = 60;
}
if (currentCal == "cal2015") {
top = 1005;
}
return tooltip.style("top", top + "px").style("left", d3.event.pageX + 40 + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden")
})
calendars.selectAll(".month").on("mouseover", function() {
return tooltip.style("visibility", "visible")
})
}
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) * c.cellSize + "," + d0 * c.cellSize
+ "H" + w0 * c.cellSize + "V" + 7 * c.cellSize
+ "H" + w1 * c.cellSize + "V" + (d1 + 1) * c.cellSize
+ "H" + (w1 + 1) * c.cellSize + "V" + 0
+ "H" + (w0 + 1) * c.cellSize + "Z";
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment