Air Quality Data for Beijing, Chengdu, Guangzhou, Shanghai, and Shenyang recorded at U.S. Embassies in each city. Mike's Calendar view for each year rendered to Canvas. Thanks to Syntagmatic for his help with rendering to Canvas.
Data is from US State Department and is not fully validated or verified.
xxxxxxxxxx
<meta charset="utf-8">
<style>
body {
margin-left: 20px;
}
#container {
display: flex;
}
.section {
display: flex;
flex-direction: column;
margin-right: 20px;
}
.title {
width: 100%;
margin: 10px auto;
text-align: center;
}
.yearLabel {
margin-top: 6px;
margin-right: 12px;
}
.beijingYear, .chengduYear, .guangzhouYear, .shanghaiYear, .shenyangYear {
display: flex;
}
#key {
display: flex;
justify-content: space-between;
padding-right: 4%;
}
.keyEntry span {
margin-left: 29px;
font-family: monospace;
}
.keyEntry canvas {
position: absolute;
margin-top: -1px;
}
</style>
<body>
<div id="container">
<div id="beijing" class="section"></div>
<div id="chengdu" class="section"></div>
<div id="guangzhou" class="section"></div>
<div id="shanghai" class="section"></div>
<div id="shenyang" class="section"></div>
</div>
<div id="key"></div>
<script src="https://d3js.org/d3.v3.js"></script>
<script>
var cellSize = 4;
var canvasWidth = 250;
var canvasHeight = 50;
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 cities = ['beijing', 'chengdu', 'guangzhou', 'shanghai', 'shenyang'];
var data = {};
var remaining = 30;
var datasets = ["beijing2008", "beijing2009", "beijing2010", "beijing2011", "beijing2012",
"beijing2013", "beijing2014", "beijing2015", "beijing2016", "chengdu2012",
"chengdu2013", "chengdu2014", "chengdu2015", "chengdu2016", "guangzhou2011",
"guangzhou2012", "guangzhou2013", "guangzhou2014", "guangzhou2015", "guangzhou2016", "shanghai2011", "shanghai2012", "shanghai2013", "shanghai2014", "shanghai2015", "shanghai2016", "shenyang2013", "shenyang2014", "shenyang2015", "shenyang2016"];
(function loadData() {
datasets.forEach(function(dataset) {
d3.csv("https://dhoboy.github.io/china-air-files/" + dataset + ".csv", function(err, d) {
if (!err) {
data[dataset] = d;
--remaining;
}
if (!remaining) draw();
});
});
})();
function draw() {
// turn into array to filter out invalid readings
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";
});
});
// turn back into object for daily averages
data = data.reduce(function(prev, next) {
prev[next.key] = next.value;
return prev;
}, {});
var dailyAvg = {};
cities.forEach(function(city) {
d3.range(2008,2017).forEach(function(year) {
if (data[city + year]) {
dailyAvg[city + year] = 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(data[city + year])
} else {
dailyAvg[city + year] = "No Data";
}
});
});
d3.select("div#beijing").append("pre").attr("class", "title").text("beijing");
var beijingDivs = d3.select("div#beijing").selectAll(".beijingYear")
.data(d3.keys(dailyAvg).filter(function(d) { return /beijing/.test(d)}))
.enter()
.append("div")
.attr("class", "beijingYear");
beijingDivs.append("pre")
.attr("class", "yearLabel")
.text(function(d) {
return /\d{4}/.exec(d)[0];
})
beijingDivs.append("canvas")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.attr("id", function(d) { return d; });
d3.select("div#chengdu").append("pre").attr("class", "title").text("chengdu");
var chengduDivs = d3.select("div#chengdu").selectAll(".chengduYear")
.data(d3.keys(dailyAvg).filter(function(d) { return /chengdu/.test(d)}))
.enter()
.append("div")
.attr("class", "chengduYear")
.append("canvas")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.attr("id", function(d) { return d; });
d3.select("div#guangzhou").append("pre").attr("class", "title").text("guangzhou");
var guangzhouDivs = d3.select("div#guangzhou").selectAll(".guangzhouYear")
.data(d3.keys(dailyAvg).filter(function(d) { return /guangzhou/.test(d)}))
.enter()
.append("div")
.attr("class", "guangzhouYear")
.append("canvas")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.attr("id", function(d) { return d; });
d3.select("div#shanghai").append("pre").attr("class", "title").attr("class", "title").text("shanghai");
var shanghaiDivs = d3.select("div#shanghai").selectAll(".shanghaiYear")
.data(d3.keys(dailyAvg).filter(function(d) { return /shanghai/.test(d)}))
.enter()
.append("div")
.attr("class", "shanghaiYear")
.append("canvas")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.attr("id", function(d) { return d; });
d3.select("div#shenyang").append("pre").attr("class", "title").text("shenyang");
var shenyangDivs = d3.select("div#shenyang").selectAll(".shenyangYear")
.data(d3.keys(dailyAvg).filter(function(d) { return /shenyang/.test(d)}))
.enter()
.append("div")
.attr("class", "shenyangYear")
.append("canvas")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.attr("id", function(d) { return d; });
var categoryMap = { "good": "good", "moderate": "moderate", "sensitive": "unhealthy for sensitive groups", "unhealthy": "unhealthy", "very": "very unhealthy", "hazardous": "hazardous" };
var colorMap = { "good": "#1a9850", "moderate": "#fee08b", "sensitive": "#f46d43",
"unhealthy": "#d73027", "very": "#a50026", "hazardous": "#67001f"};
var keyEntries = d3.select("#key").selectAll(".keyEntry")
.data(["good", "moderate", "sensitive", "unhealthy", "very", "hazardous"])
.enter()
.append("div")
.attr("class", "keyEntry");
keyEntries.append("canvas")
.attr("width", 20)
.attr("height", 20)
.attr("id", function(d) { return d; });
keyEntries.append("span")
.text(function(d) { return categoryMap[d]; });
var keyCtx = {};
d3.keys(categoryMap).forEach(function(k) {
keyCtx[k] = document.getElementById(k).getContext("2d");
drawKey(k, keyCtx[k]);
});
var ctx = {};
d3.keys(dailyAvg).forEach(function(k) {
ctx[k] = document.getElementById(k).getContext("2d");
});
cities.forEach(function(city) {
d3.range(2008,2017).forEach(function(year) {
var monthsInYear = d3.time.months((new Date(year, 0, 1)), new Date(year+1, 0, 1));
var daysInYear = d3.time.days((new Date(year, 0, 1)), new Date(year+1, 0, 1));
daysInYear.forEach(function(day) {
drawDay((d3.time.weekOfYear(day)*cellSize), (day.getDay() * cellSize), city + year, format(day), ctx[city + year]);
});
});
});
function drawDay(x, y, key, day, ctx) {
if (dailyAvg[key] == "No Data") {
ctx.fillStyle = "#ffffff";
} else {
if (color(+dailyAvg[key][day])) {
ctx.fillStyle = color(dailyAvg[key][day]);
} else {
ctx.fillStyle = "#f0f0f0";
}
}
ctx.fillRect(x,y,cellSize,cellSize);
}
function drawKey(key, ctx) {
ctx.fillStyle = colorMap[key];
ctx.fillRect(0,0,20,20);
}
}
</script>
Modified http://d3js.org/d3.v3.js to a secure url
https://d3js.org/d3.v3.js