|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
body { |
|
overflow: hidden; |
|
margin: 24px 12px; |
|
font-size: 14px; |
|
font-family: "Futura", "Helvetica Neue", Helvetica, Arial, sans-serif; |
|
} |
|
#container { |
|
display: flex; |
|
} |
|
.section { |
|
display: flex; |
|
flex-direction: column; |
|
margin-right: 10px; |
|
} |
|
.title { |
|
width: 100%; |
|
margin: 12px auto; |
|
text-align: center; |
|
font-size: 18px; |
|
} |
|
.yearLabel { |
|
margin-right: 10px; |
|
color: #666; |
|
} |
|
.beijingYear, .chengduYear, .guangzhouYear, .shanghaiYear, .shenyangYear { |
|
display: flex; |
|
} |
|
#beijing .title { |
|
margin-left: 20px; |
|
} |
|
#key { |
|
display: flex; |
|
min-width: 745px; |
|
max-width: 850px; |
|
justify-content: space-between; |
|
margin: 32px; |
|
} |
|
.keyEntry span { |
|
|
|
margin: 0 0 0 26px; |
|
color: #666; |
|
} |
|
.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 = 3; |
|
var canvasWidth = 160; |
|
var canvasHeight = 34; |
|
|
|
var format = d3.time.format("%x"); |
|
var color = d3.scale.linear() |
|
.domain([18, 45, 91, 141, 191, 291, Infinity]) |
|
.range(["#1a9850", "#d9ef8b", "#fee08b", "#fc8d59", "#d73027", "#a50026", "#a50026"]); |
|
|
|
var colorMap = d3.scale.ordinal() |
|
.range(["#1a9850", "#d9ef8b", "#fee08b", "#fc8d59", "#d73027", "#a50026"]); |
|
|
|
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://gist.githubusercontent.com/dhoboy/499741034a2edb844ba3171aa73298d2/raw/b486c6fbd63747fccc7b492741fc0b775f855d16/" + 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("div").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("div") |
|
.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("div").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("div").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("div").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("div").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" }; |
|
|
|
["good", "moderate", "sensitive", "unhealthy", "very", "hazardous"].forEach(colorMap); |
|
|
|
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 { |
|
console.log(dailyAvg[key][day]); |
|
if (+dailyAvg[key][day] > -1) { |
|
ctx.fillStyle = color(dailyAvg[key][day]); |
|
} else { |
|
ctx.fillStyle = "#e6e6e6"; |
|
} |
|
} |
|
ctx.fillRect(x,y,cellSize,cellSize); |
|
} |
|
|
|
function drawKey(key, ctx) { |
|
ctx.fillStyle = colorMap(key); |
|
ctx.fillRect(0,0,20,20); |
|
} |
|
} |
|
</script> |