|
<!DOCTYPE html> |
|
<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="http://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("http://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> |