Skip to content

Instantly share code, notes, and snippets.

@pinsterdev
Last active June 19, 2020 12:11
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 pinsterdev/321e576f55e056a3fe246cf0bdb8a3cf to your computer and use it in GitHub Desktop.
Save pinsterdev/321e576f55e056a3fe246cf0bdb8a3cf to your computer and use it in GitHub Desktop.

Central Bank of Ireland: Mortgage Arrears.

Select category, statistic and display item. Raw data here. Source data here and here (v0.4)

<html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<br><br>
<div id="chart" style="float: left;"></div>
<div style="float: left; margin: 250px 0px 0px 40px;">
<div>
<strong>Category</strong>
<form name="selC" action="">
<input type="radio" id="radio01" name="selC" onclick="selCat(0)" checked>Private Dwelling House<br>
<input type="radio" id="radio02" name="selC" onclick="selCat(1)">Buy-to-Let<br>
</form>
<br>
<strong>Statistic</strong>
<form name="selS" action="">
<input type="radio" id="radio11" name="selS" onclick="selStat(0)" checked>Mortgage Arrears by Age<br>
<input type="radio" id="radio12" name="selS" onclick="selStat(1)">Restructured Mortgages<br>
</form>
<br>
<strong>Display</strong>
<form name="selD" action="">
<input type="radio" id="radio21" name="selD" onclick="selDisp(0)" checked>Number of Mortgages<br>
<input type="radio" id="radio21" name="selD" onclick="selDisp(1)" checked>Percent of Mortgages<br>
<input type="radio" id="radio22" name="selD" onclick="selDisp(2)">Total Mortgage Balance<br>
<input type="radio" id="radio23" name="selD" onclick="selDisp(3)">Total Arrears Amount<br>
</form>
</div>
</div>
<br>
<div style="clear: both"></div>
<div id="readme"></div>
<script src="//d3js.org/d3.v3.min.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>
body {
font: 12px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
line.axis2 {
display: none;
}
path.line {
fill: none;
stroke-width: 3;
}
.tick line{
stroke: lightgrey;
opacity: 0.4;
}
#readme {
width: 960px;
}
#chart {
width: 960px;
height: 500px;
}
readme(document.getElementById("readme"));
var chartElem = d3.select("body #chart");
var yAxisLabel = ["Number of Mortgages", "Percent of Mortgages",
"Total Mortgage Balance (€'000)", "Total Arrears Amount (€'000)"];
var ages0 = ["lt90","lt180","gt180"];
var ageNames0 = ["<90 days","<180 days",">180 days"];
var ages = ["lt90","lt180","lt360","lt720","gt720"];
var ageNames = ["<90 days","<180 days","<360 days","<720 days",">720 days"];
var ageAll = ["lt90","lt180","gt180","lt360","lt720","gt720","rs","rsna"];
var disp = ["num","num","bal","val"];
var margin = {
top : 20,
right : 80,
bottom : 60,
left : 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(d3.time.years, 1)
.tickFormat(d3.time.format("%Y"))
.tickSize(-height, 0, 0);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5)
.tickFormat(d3.format("s"))
.tickSize(-width, 0, 0);
var color = d3.scale.category10();
var sheet1 = extractGistData(gistData["CB_Arrears_PDH.csv"].content);
var sheet2 = extractGistData(gistData["CB_Arrears_BTL.csv"].content);
var allData = [mungeData(sheet1), mungeData(sheet2)];
var selectedCat = parseParamWithDefault('c',['pdh','btl']);
document.selC.selC[selectedCat].checked = true;
var selectedStat = parseParamWithDefault('s',['arrears','restructured']);
document.selS.selS[selectedStat].checked = true;
var selectedDisp = parseParamWithDefault('d',['number','percent', 'balance', 'arrears']);
document.selD.selD[selectedDisp].checked = true;
doSvg();
/**
* Selection changed - remove and redo graph
*/
function selCat(c) {
selectedCat = c;
d3.select("svg").remove();
doSvg();
}
function selStat(s) {
selectedStat = s;
d3.select("svg").remove();
doSvg();
}
function selDisp(d) {
selectedDisp = d;
d3.select("svg").remove();
doSvg();
}
function doSvg() {
var data = allData[selectedCat];
var agedisp = ages.map(function (d) {
return d + disp[selectedDisp];
});
color.domain(ageAll);
// Adds the svg canvas
var svg = chartElem.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Define the area
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var stack = d3.layout.stack()
.values(function(d) { return d.values; });
var breakIndex = (selectedStat == 1) ?
data.findIndex(function (d) {return d.rsnum > 0;}) :
data.findIndex(function (d) {return d.lt360num > 0;});
var agecats0 = (selectedStat == 1) ? [] : stack(ages0.map(function(col, i) {
return {
name: col,
age: ageNames0[i],
values: data.slice(0, breakIndex+1).map(function(d) {
var col0 = col + disp[selectedDisp];
return {date: d.date, y:
(selectedDisp == 1)? d[col0] / d.trmnum * 100 : d[col0]
};
})
};
}));
var arr1 = (selectedStat == 1) ? ["rs"] : ages;
var arr2 = (selectedStat == 1) ? ["Restructured, in arrears"] : ageNames;
var agecats = (selectedStat == 0 || selectedDisp == 3) ?
stack(arr1.map(function(col, i) {
return {
name: col,
age: arr2[i],
values: data.slice(breakIndex, data.length).map(function(d) {
var col0 = col + disp[selectedDisp];
return {date: d.date, y:
(selectedDisp == 1)? d[col0] / d.trmnum * 100 : d[col0]
};
})
};
}))
: stack(["rs", "rsna"].map(function(col, i) {
return {
name: col,
age: ["Restructured, in arrears","Restructured, not in arrears"][i],
values: data.slice(breakIndex, data.length).map(function(d) {
var col0 = col + disp[selectedDisp];
var ocol = "rsna" + disp[selectedDisp];
var val = (i == 0) ? d[col0] - d[ocol] : d[col0];
return {date: d.date, y:
(selectedDisp == 1)? val / d.trmnum * 100 : val
};
})
};
}));
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date;}));
var max0 = (selectedStat == 1)? 0 :
d3.max(agecats0[agecats0.length - 1].values, function(d) {return d.y + d.y0});
var max1 = d3.max(agecats[agecats.length - 1].values, function(d) {return d.y + d.y0});
y.domain([0, max0 > max1? max0 : max1]);
var agecat0 = svg.selectAll(".agecat0")
.data(agecats0)
.enter().append("g")
.attr("class", "agecat0");
agecat0.append("path")
.attr("class", "area")
.attr("d", function(d) { return area(d.values); })
.style({fill: function(d) { return color(d.name); }, stroke: function(d) { return color(d.name); }});
agecat0.append("text")
.datum(function(d) { return {age: d.age, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.y0 + d.value.y / 2) + ")"; })
.attr("x", -60)
.attr("dy", ".35em")
.text(function(d) { return d.age; });
var agecat = svg.selectAll(".agecat")
.data(agecats)
.enter().append("g")
.attr("class", "agecat");
agecat.append("path")
.attr("class", "area")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d) { return color(d.name); });
agecat.append("text")
.datum(function(d) { return {age: d.age, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.y0 + d.value.y / 2) + ")"; })
.attr("x", selectedStat == 0? 4 : -160)
.attr("dy", ".35em")
.text(function(d) { return d.age; });
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("dx", "0em")
.attr("dy", "1.15em")
.attr("transform", "rotate(-0)" );
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis).append("text")
.attr("transform", "rotate(-90)").attr("y", 6).attr("dy",
".71em").style("text-anchor", "end").text(yAxisLabel[selectedDisp]);
}
function mungeData(sheet) {
// Parse the date / time
var parseDate = d3.time.format("%Y%m%d").parse;
var data = sheet.data;
data.forEach(function (d) {
var month = (d.month.length < 2)? "0"+d.month : d.month;
d.date = parseDate(d.year + month + "01");
sheet.colNames.forEach(function (c) {
d[c] = +d[c];
});
});
return data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment