Select Table or Map and drag slider for Year/Quarter. Press Play for animation. Float over map to see regional figures. Raw data is here.
v0.6. Maps modified from Wikimedia Commons under CC BY-SA 3.0 license
Select Table or Map and drag slider for Year/Quarter. Press Play for animation. Float over map to see regional figures. Raw data is here.
v0.6. Maps modified from Wikimedia Commons under CC BY-SA 3.0 license
<!DOCTYPE html> | |
<html><head> | |
<meta content="text/html; charset=UTF-8" http-equiv="content-type"/> | |
<link rel="stylesheet" type="text/css" href="main.css"> | |
</head> | |
<body> | |
<div id="chart"></div> | |
<div id="ctrls"> | |
<div id="readme"/></div> | |
<strong>Display</strong> | |
<form name="buttons" action=""> | |
<input type="radio" id="radio1" name="charttype" onclick="chartType(0)" checked>Table<br> | |
<input type="radio" id="radio2" name="charttype" onclick="chartType(1)">Map<br> | |
</form> | |
<br><br> | |
<input id="slider" type="range" min="0" max="10" oninput="sliderInput()"> | |
<br> | |
<label id="period">period</label> | |
<br><br> | |
<button id="play" type="button" onclick="play()">Play</button> | |
</div> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="//labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script> | |
<script src="//cdn.rawgit.com/showdownjs/showdown/1.3.0/dist/showdown.min.js"></script> | |
<script src="/pinsterdev/raw/a2fcc47fae884342e190/util.js"></script> | |
<script src="https://api.github.com/gists/0f82dc8380acfd77299724369184a01d?callback=gistDataLoaded"></script> | |
<script src="main.js"></script> | |
</body> | |
</html> |
<div id="tabular"> | |
<div id="tableft"> | |
<table> | |
<thead> | |
<tr> | |
<th class="col1" width="90">County/City</th> | |
<th width="55">€/sq.ft.</th> | |
<th width="55">YoY chg</th> | |
</tr> | |
</thead> | |
<tbody/> | |
</table> | |
</div> | |
<div id="tabright"> | |
<table> | |
<thead> | |
<tr> | |
<th class="col1" width="90">Post code</th> | |
<th width="55">€/sq.ft.</th> | |
<th width="55">YoY chg</th> | |
</tr> | |
</thead> | |
<tbody/> | |
</table> | |
<br><br><br> | |
<table> | |
<thead> | |
<tr> | |
<th class="col1" width="90">Region</th> | |
<th width="55">€/sq.ft.</th> | |
<th width="55">YoY chg</th> | |
</tr> | |
</thead> | |
<tbody/> | |
</table> | |
</div> | |
</div> |
/* main layouts */ | |
body { | |
font: 12px sans-serif; | |
width: 1000; | |
} | |
#chart { | |
width: 60%; | |
float: left; | |
} | |
#ctrls { | |
width: 40%; | |
float: left; | |
} | |
#period { | |
font-size: 36px; | |
} | |
#tableft, #tabright { | |
width: 260px; | |
float:left;" | |
} | |
/* data table */ | |
#tabular { | |
width: 540; | |
margin:20px auto; | |
font-size: 12px; | |
} | |
#tabular table { | |
border-collapse: collapse; | |
table-layout:fixed; | |
} | |
#tabular td { | |
border-bottom: 1px solid lightgray; | |
} | |
#tabular th { | |
border-bottom: 2px solid lightgray; | |
} | |
#tabular td, th { | |
text-align: right; | |
} | |
#tabular td.col1, th.col1 { | |
text-align: left; | |
} | |
#tabular td.neg { | |
color: red; | |
} | |
#tabular td.pos { | |
color: green; | |
} | |
#tabular table tr:nth-child(odd) td{ | |
background-color: #f0f0f0; | |
} | |
/* SVG transforms */ | |
svg { | |
width: 640px; | |
height: 640px; | |
} | |
#layer1 { | |
display: inline; | |
transform: scale(1) translate(0px,1200px); | |
} | |
/* irl map defaults */ | |
.region { | |
fill: #008000; | |
stroke: #000000; | |
stroke-width: 2; | |
} | |
.region:hover { | |
fill: green !important; | |
} | |
.ni { | |
fill: #a0a0a0; | |
} | |
/* tooltip */ | |
.d3-tip { | |
line-height: 1; | |
padding: 12px; | |
background: rgba(0, 0, 0, 0.8); | |
color: #fff; | |
border-radius: 2px; | |
} | |
.d3-tip .h { | |
float: left; | |
white-space: nowrap; | |
} | |
.d3-tip .v { | |
float: left; | |
font-weight: bold; | |
} | |
.d3-tip .vm { | |
font-weight: bold; | |
color: #ff8080; | |
} | |
.d3-tip .vp { | |
font-weight: bold; | |
color: lightgreen; | |
} | |
/* Creates a small triangle extender for the tooltip */ | |
.d3-tip:after { | |
box-sizing: border-box; | |
display: inline; | |
font-size: 10px; | |
width: 100%; | |
line-height: 1; | |
color: rgba(0, 0, 0, 0.8); | |
content: "\25BC"; | |
position: absolute; | |
text-align: center; | |
} | |
/* Style northward tooltips differently */ | |
.d3-tip.n:after { | |
margin: -1px 0 0 0; | |
top: 100%; | |
left: 0; | |
} |
var | |
isFirefox = (typeof InstallTrigger !== 'undefined'), | |
tip = d3.tip().attr('class','d3-tip').offset(tipOffset).html(tipHtml), | |
tipData = null, savedChoice = -1, playing = false, mapSelector = "path.region", | |
elemMap, elemTable, xdata, ydata, mapData, tblData, colNames, rowName, qDub, qOth, | |
selectedPeriod, timer; | |
loadData(); | |
/** | |
* Load a number of resources in parallel. | |
*/ | |
function loadData() { | |
readme(document.getElementById("readme")); // async load readme | |
var loaded = new Object(), remaining = 3; | |
var load = function(d3Function, urlToLoad, resultName) { | |
d3Function(urlToLoad, function(error,d) { | |
if (error) throw error; | |
loaded[resultName] = d; | |
if (!--remaining) { | |
loadChart(loaded); | |
} | |
}); | |
}; | |
load(d3.text, "irlmap.svg", "map"); | |
load(d3.text, "ireland_table.txt", "table"); | |
load(d3.text, "sortedregion.csv", "regions"); | |
} | |
/** | |
* Munge data and views after loading. | |
* @param loaded async loaded data | |
*/ | |
function loadChart(loaded) { | |
// Munge loaded data into usable form | |
mungeData(); | |
// Create SVG map elements and data table | |
(elemMap = document.createElement("div")).innerHTML = loaded.map; | |
(elemTable = document.createElement("div")).innerHTML = loaded.table; | |
// Create empty rows in the table view. | |
var regionRows = d3.csv.parseRows(loaded.regions) | |
var rows = d3.select(elemTable).selectAll("tbody").data(regionRows) | |
.selectAll("tr").data(function(d) { return d;}) | |
.enter().append("tr").attr("id", function(d,i) {return d;}); | |
// 3 columns per row | |
rows.insert("td").classed("col1", true); | |
rows.insert("td"); | |
rows.insert("td"); | |
// munge data for map and set up tooltip | |
mapData = d3.select(elemMap).selectAll(mapSelector)[0].map(function(d,j) { | |
var id = d.getAttribute("id"); | |
return (id in xdata)? xdata[id] : {id: id, missing : true}; | |
}); | |
d3.select(elemMap).select("svg g").call(tip); | |
d3.select(elemMap).selectAll(mapSelector).data(mapData) | |
.on('mouseover', tip.show) | |
.on('mouseout', tip.hide); | |
// set up slider based on number of periods | |
var last = (typeof parseQueryString().p == 'undefined')? | |
colNames.length - 1 : parseQueryString().p; | |
d3.select("#slider").attr("max", colNames.length - 1).property("value", last); | |
selectedPeriod = colNames[last]; | |
d3.select("#period").text(selectedPeriod); | |
// default to table | |
var ct = parseParamWithDefault('d',['table','map']); | |
document.buttons.charttype[ct].checked = true; | |
chartType(ct); | |
} | |
function sliderInput() { | |
selectedPeriod = colNames[d3.select("#slider").property("value")]; | |
d3.select("#period").text(selectedPeriod); | |
displayChart(); | |
} | |
function play() { | |
if (playing) { | |
playing = false; | |
d3.select("#play").text("Play"); | |
clearInterval(timer); | |
} else { | |
playing = true; | |
d3.select("#play").text("Stop"); | |
timer = setInterval(playStep, 750); | |
} | |
} | |
function playStep() { | |
selectedPeriod = colNames[d3.select("#slider").property("value")]; | |
var slider = d3.select("#slider"); | |
var period = +slider.property("value"); | |
period = (period < (colNames.length - 1))? period + 1 : 0; | |
slider.property("value", period); | |
sliderInput(); | |
} | |
function chartType(choice) { | |
if (choice == savedChoice) { | |
return; | |
} | |
savedChoice = choice; | |
d3.selectAll("#chart>*").remove(); | |
var choices = [elemTable, elemMap] | |
d3.select("#chart").append(function(d) {return choices[choice];}); | |
displayChart(); | |
} | |
function displayChart() { | |
switch (savedChoice) { | |
case 0: displayTable(); break; | |
case 1: displayMap(); break; | |
} | |
} | |
function displayMap() { | |
d3.select(elemMap).selectAll(mapSelector).data(mapData) | |
.attr("style", function(d) { | |
return d.missing? "fill:#a0a0a0;" : d.quantize(d[selectedPeriod]); | |
}); | |
document.querySelector(".d3-tip").innerHTML = tipHtml(tipData); | |
displayLegend(); | |
} | |
function displayLegend() { | |
var numLeg = 4; | |
var svg = d3.select(elemMap).select("svg"); | |
var qclass = ".legendo", legx = 2000, legt = "Ireland"; | |
[qOth, qDub].forEach(function(quant) { | |
var dom = quant.domain(), step = ((dom[1]-dom[0])/numLeg)-0.1; | |
var legData = d3.range(dom[0],dom[1], step); | |
legData.push(legt); | |
legData.reverse(); | |
var legend = svg.selectAll(".legend").data(legData) | |
.enter().append("g").attr("class", qclass).attr("transform", | |
function(d, i) { | |
return "translate(5," + (i * 40 + 400) + ")"; | |
}); | |
var sq = 36; | |
legend.append("rect").attr("x", legx).attr("width", sq).attr( | |
"height", sq).attr("style", function(d,i) {return i>0? quant(d) : "fill:#ffffff;"}); | |
legend.append("text").attr("x", legx - 6).attr("y", 9).attr("dy", | |
"0.5em").style({"text-anchor": "end", "font-size": "36px"}).text(function(d,i) { | |
return i>0?'€'+d3.round(d,0) : d; | |
}); | |
qclass = ".legendd"; | |
legx = 2200; | |
legt = "Dublin"; | |
}); | |
} | |
function displayTable() { | |
d3.select(elemTable).selectAll("tr[id]").each(function(d, i){ | |
var yoy = ydata[d][selectedPeriod]; | |
d3.selectAll(this.childNodes) | |
.text(function(d, i) { | |
switch (i) { | |
case 0: return d; | |
case 1: return xdata[d][selectedPeriod]; | |
case 2: return yoy; | |
} | |
}) | |
.classed("neg",function(d, i) {return i == 2 && yoy.startsWith('-');}) | |
.classed("pos",function(d, i) {return i == 2 && !yoy.startsWith('-');}); | |
}); | |
} | |
/** | |
* Html content for tooltip | |
* @param d data associated with moused-over svg element | |
* @returns html string | |
*/ | |
function tipHtml(d) { | |
if (typeof(d) == 'undefined' || d === null) return; | |
tipData = d; // cache | |
if (d.missing) return d.id; // name only | |
var val = d[selectedPeriod]; | |
var yoy = ydata[d.id][selectedPeriod]; | |
var cls = yoy.charAt(0) != '-' ? "vp" : "vm"; // css for +/- colour | |
var s = "<div><div class='h'>#per#: <br>€/sq.ft: <br>YoY: </div><div " + | |
"class='v'>#id#<br>#val#<br><span class='#cls#'>#yoy#</span></div>"; | |
return s.replace("#per#", selectedPeriod).replace("#id#", d.id) | |
.replace("#val#", val).replace("#cls#", cls).replace("#yoy#", yoy); | |
} | |
/** | |
* Workaround for a bug in Firefox whereby it ignores SVG scaling. | |
* Scaling adjustment is calculated and returned as an offset for | |
* displaying the d3 tooltip. | |
* @returns {Array} of vertical and horizontal offset | |
*/ | |
function tipOffset() { | |
if (!isFirefox) return [0, 0]; | |
var tbbox = this.getBBox(); | |
var matrix = this.getScreenCTM(); | |
var scale = 1.0; // TODO: hardcoded, must match CSS | |
var pt = { | |
// only works for default 'north' tooltip direction | |
x: tbbox.x + (tbbox.width / 2) + matrix.e, | |
y: tbbox.y + matrix.f | |
}; | |
return [pt.y * (scale - 1), pt.x * (scale - 1)]; | |
} | |
function mungeData() { | |
var sheet = extractGistData(gistData["MyhomeData.csv"].content); | |
colNames = sheet.colNames.filter(function (d) { | |
return d.indexOf(" Q") > -1; | |
}); | |
rowName = sheet.rowName; | |
xdata = new Object(); | |
ydata = new Object(); | |
var yoy = d3.format("+.0f"); // formatter for yoy | |
var allValuesDub = [], allValuesOth = []; | |
sheet.data.forEach(function(d) { | |
var arr = d[rowName].startsWith("dublin-")? allValuesDub : allValuesOth; | |
for (i = 0; i < colNames.length; i++) { | |
arr.push(+d[colNames[i]]); | |
} | |
}); | |
qDub = getQuantizeFunc(d3.min(allValuesDub),d3.max(allValuesDub), false); | |
qOth = getQuantizeFunc(d3.min(allValuesOth),d3.max(allValuesOth), true); | |
sheet.data.forEach(function(d) { | |
var id = d[rowName]; | |
xdata[id]= {id : id, quantize : id.startsWith("dublin-")? qDub : qOth, missing: false}; | |
ydata[id]= {id : id}; | |
for (i = 0; i < colNames.length; i++) { | |
var c = colNames[i]; | |
xdata[id][c] = d[c]; | |
// calculate YoY for years after first | |
ydata[id][c] = (i < 4) ? "" : yoy(d[c] / d[colNames[i - 4]] * 100 - 100) + "%"; | |
} | |
}); | |
} | |
/** | |
* Quantize given range into white-red or white-blue color gradient | |
* @param min range minimum | |
* @param max range maximum | |
* @param redblue true/false for blue or red gradient | |
* @returns color gradient quantize function | |
*/ | |
function getQuantizeFunc(min, max, redblue) { | |
var hex1 = d3.format("0x"); | |
var hex = function(n) { | |
var s = hex1(255-n); | |
return s.length == 1? "0" + s : s; | |
}; | |
return d3.scale.quantize() | |
.domain([min, max]) | |
.range(d3.range(255).map(function(i) { | |
return redblue? "fill:#ff" + hex(i) + hex(i)+";" : | |
"fill:#" + hex(i) + hex(i)+"ff;"; | |
})); | |
} | |
carlow,cavan,clare,cork,cork-city,cork-west,donegal,dublin,galway,galway-city,kerry,kildare,kilkenny,laois,leitrim,limerick,limerick-city,longford,louth,mayo,meath,monaghan,offaly,roscommon,sligo,tipperary,waterford,westmeath,wexford,wicklow | |
dublin-1,dublin-2,dublin-3,dublin-4,dublin-5,dublin-6,dublin-6W,dublin-7,dublin-8,dublin-9,dublin-10,dublin-11,dublin-12,dublin-13,dublin-14,dublin-15,dublin-16,dublin-17,dublin-18,dublin-20,dublin-22,dublin-24 | |
dublin-county,dublin-north,dublin-south,dublin-west |
Looking good so far. I mightn't be able to contribute anything of value but I'll enjoy watching as it comes together.
That's the bad weather area of the country. Also not covered by myhome.ie.