Last active
June 20, 2016 15:13
-
-
Save soloincc/04aa38a89160fd13226485fcf2059450 to your computer and use it in GitHub Desktop.
A standalone script for visualising Kenya's 2016/17 revenue estimates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!-- | |
This is a standalone script for visualising Kenya's 2016/17 revenue estimates. | |
The script has been adapted from Iinteractive Donut Charts, http://bl.ocks.org/erichoco/6694616, by http://bl.ocks.org/erichoco | |
Copyright (C) 2016 Wangoru Kihara, soloincc@gmail.com | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
--> | |
<meta charset="utf-8"> | |
<style type="text/css"> | |
body { | |
font-size: 100%; | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
width: 800px; | |
} | |
</style> | |
<body> | |
<div id="donut-charts"></div> | |
<script src="https://code.jquery.com/jquery-1.10.1.min.js"></script> | |
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
$(function() { | |
var donutData = [ | |
{ | |
"Details": "Income Tax from Individuals (P.A.Y.E)", | |
"2014_2015": 279.8, | |
"2015_2016": 309.2, | |
"2016_2017": 359.6 | |
}, | |
{ | |
"Details": "Income Tax from Corporations", | |
"2014_2015": 229.1, | |
"2015_2016": 269.1, | |
"2016_2017": 311.8 | |
}, | |
{ | |
"Details": "Taxes on Property", | |
"2014_2015": 0, | |
"2015_2016": 5.3, | |
"2016_2017": 6.2 | |
}, | |
{ | |
"Details": "VAT", | |
"2014_2015": 259.7, | |
"2015_2016": 300, | |
"2016_2017": 345.6 | |
}, | |
{ | |
"Details": "Excise Receipts", | |
"2014_2015": 118.4, | |
"2015_2016": 139.7, | |
"2016_2017": 171.8 | |
}, | |
{ | |
"Details": "International Trade & Transactions Taxes", | |
"2014_2015": 101, | |
"2015_2016": 109.8, | |
"2016_2017": 126.8 | |
}, | |
{ | |
"Details": "Stamp duty", | |
"2014_2015": 11.5, | |
"2015_2016": 11.6, | |
"2016_2017": 13.1 | |
}, | |
{ | |
"Details": "Petroleum Development Levy", | |
"2014_2015": 1.5, | |
"2015_2016": 1.5, | |
"2016_2017": 1.5 | |
}, | |
{ | |
"Details": "Road Maintenance Levy", | |
"2014_2015": 26.1, | |
"2015_2016": 26.3, | |
"2016_2017": 25.7 | |
}, | |
{ | |
"Details": "Road Maintenance Levy (Annuity Fund)", | |
"2014_2015": 0, | |
"2015_2016": 0, | |
"2016_2017": 12.7 | |
}, | |
{ | |
"Details": "Fees Levied", | |
"2014_2015": 4.4, | |
"2015_2016": 5.6, | |
"2016_2017": 7.5 | |
}, | |
{ | |
"Details": "Social Security Contributions", | |
"2014_2015": 0.1, | |
"2015_2016": 0.1, | |
"2016_2017": 0.1 | |
}, | |
{ | |
"Details": "Property Income", | |
"2014_2015": 15.8, | |
"2015_2016": 23, | |
"2016_2017": 21.7 | |
}, | |
{ | |
"Details": "Fines Penalties and Forfeitures", | |
"2014_2015": 2.5, | |
"2015_2016": 1.4, | |
"2016_2017": 1.6 | |
}, | |
{ | |
"Details": "Other Receipts Not Classified Elsewhere", | |
"2014_2015": 3.6, | |
"2015_2016": 3.4, | |
"2016_2017": 4.5 | |
}, | |
{ | |
"Details": "Administrative Fees and Charges", | |
"2014_2015": 5.2, | |
"2015_2016": 5.4, | |
"2016_2017": 5.9 | |
}, | |
{ | |
"Details": "Sale of Non Financial Assets", | |
"2014_2015": 2.9, | |
"2015_2016": 2.9, | |
"2016_2017": 3 | |
} | |
]; | |
var formattedData = formatData(donutData); | |
var donuts = new DonutCharts(); | |
donuts.create(formattedData); | |
function DonutCharts() { | |
var charts = d3.select('#donut-charts'); | |
var chart_m, chart_r; | |
var color = d3.scale.category20(); | |
charts.append("div") | |
.attr("width", '100%') | |
.attr("text-anchor", "middle") | |
.style("font-size", "18px") | |
.style("text-align", "center") | |
.style("text-decoration", "underline") | |
.text("Tax Revenue Breakdown"); | |
var getCatNames = function(dataset) { | |
var catNames = new Array(); | |
for (var i = 0; i < dataset[0].data.length; i++) { | |
catNames.push(dataset[0].data[i].cat); | |
} | |
return catNames; | |
} | |
var createLegend = function (catNames) { | |
var legends = charts.select('.legend') | |
.selectAll('g') | |
.data(catNames) | |
.enter().append('g') | |
.attr("class", "legendQuant") | |
.attr('transform', function (d, i) { | |
// we want 3 cols of 300px each | |
var top = 20 + Math.floor(i/3)*20; | |
var left = 20 + ((i+1)%3)*270; | |
return 'translate('+ left +', '+ top +')'; | |
}); | |
legends.append('circle') | |
.attr('class', 'legend-icon') | |
.attr('r', 6) | |
.style('fill', function (d, i) { | |
return color(i); | |
}); | |
legends.append('text') | |
.attr('dx', '1em') | |
.attr('dy', '.3em') | |
.text(function (d) { | |
return d; | |
}); | |
} | |
var createCenter = function (pie) { | |
var eventObj = { | |
'mouseover': function (d, i) { | |
d3.select(this) | |
.transition() | |
.attr("r", chart_r * 0.65); | |
}, | |
'mouseout': function (d, i) { | |
d3.select(this) | |
.transition() | |
.duration(500) | |
.ease('bounce') | |
.attr("r", chart_r * 0.6); | |
}, | |
'click': function (d, i) { | |
var paths = charts.selectAll('.clicked'); | |
pathAnim(paths, 0); | |
paths.classed('clicked', false); | |
resetAllCenterText(); | |
} | |
} | |
var donuts = d3.selectAll('.donut'); | |
// The circle displaying total data. | |
donuts.append("svg:circle") | |
.attr("r", chart_r * 0.6) | |
.style("fill", "#E7E7E7") | |
.on(eventObj); | |
donuts.append('text') | |
.attr('class', 'center-txt type') | |
.attr('y', chart_r * -0.16) | |
.attr('text-anchor', 'middle') | |
.style('font-weight', 'bold') | |
.text(function (d, i) { | |
return d.type; | |
}); | |
donuts.append('text') | |
.attr('class', 'center-txt value') | |
.attr('text-anchor', 'middle'); | |
donuts.append('text') | |
.attr('class', 'center-txt percentage') | |
.attr('y', chart_r * 0.16) | |
.attr('text-anchor', 'middle') | |
.style('fill', '#A2A2A2'); | |
} | |
var setCenterText = function (thisDonut) { | |
var sum = d3.sum(thisDonut.selectAll('.clicked').data(), function (d) { | |
return d.data.val; | |
}); | |
thisDonut.select('.value') | |
.text(function (d) { | |
return (sum) ? sum.toFixed(1) + d.unit | |
: d.total.toFixed(1) + d.unit; | |
}); | |
thisDonut.select('.percentage') | |
.text(function (d) { | |
return (sum) ? (sum / d.total * 100).toFixed(2) + '%' | |
: ''; | |
}); | |
} | |
var resetAllCenterText = function () { | |
charts.selectAll('.value') | |
.text(function (d) { | |
return d.total.toFixed(1) + d.unit; | |
}); | |
charts.selectAll('.percentage') | |
.text(''); | |
} | |
var pathAnim = function (path, dir) { | |
switch (dir) { | |
case 0: | |
path.transition() | |
.duration(500) | |
.ease('bounce') | |
.attr('d', d3.svg.arc() | |
.innerRadius(chart_r * 0.7) | |
.outerRadius(chart_r) | |
); | |
break; | |
case 1: | |
path.transition() | |
.attr('d', d3.svg.arc() | |
.innerRadius(chart_r * 0.7) | |
.outerRadius(chart_r * 1.08) | |
); | |
break; | |
} | |
} | |
var updateDonut = function () { | |
var eventObj = { | |
'mouseover': function (d, i, j) { | |
pathAnim(d3.select(this), 1); | |
var thisDonut = charts.select('.type' + j); | |
thisDonut.select('.value').text(function (donut_d) { | |
return d.data.val.toFixed(1) + donut_d.unit; | |
}); | |
thisDonut.select('.percentage').text(function (donut_d) { | |
return (d.data.val / donut_d.total * 100).toFixed(2) + '%'; | |
}); | |
}, | |
'mouseout': function (d, i, j) { | |
var thisPath = d3.select(this); | |
if (!thisPath.classed('clicked')) { | |
pathAnim(thisPath, 0); | |
} | |
var thisDonut = charts.select('.type' + j); | |
setCenterText(thisDonut); | |
}, | |
'click': function (d, i, j) { | |
var thisDonut = charts.select('.type' + j); | |
if (0 === thisDonut.selectAll('.clicked')[0].length) { | |
thisDonut.select('circle').on('click')(); | |
} | |
var thisPath = d3.select(this); | |
var clicked = thisPath.classed('clicked'); | |
pathAnim(thisPath, ~~(!clicked)); | |
thisPath.classed('clicked', !clicked); | |
setCenterText(thisDonut); | |
} | |
}; | |
var pie = d3.layout.pie() | |
.sort(null) | |
.value(function (d) { | |
return d.val; | |
}); | |
var arc = d3.svg.arc() | |
.innerRadius(chart_r * 0.7) | |
.outerRadius(function () { | |
return (d3.select(this).classed('clicked')) ? chart_r * 1.08 | |
: chart_r; | |
}); | |
// Start joining data with paths | |
var paths = charts.selectAll('.donut') | |
.selectAll('path') | |
.data(function (d, i) { | |
return pie(d.data); | |
}); | |
paths | |
.transition() | |
.duration(1000) | |
.attr('d', arc); | |
paths.enter() | |
.append('svg:path') | |
.attr('d', arc) | |
.style('fill', function (d, i) { | |
return color(i); | |
}) | |
.style('stroke', '#FFFFFF') | |
.on(eventObj) | |
paths.exit().remove(); | |
resetAllCenterText(); | |
} | |
this.create = function (dataset) { | |
var $charts = $('#donut-charts'); | |
chart_m = $charts.innerWidth() / dataset.length / 2 * 0.14; | |
chart_r = $charts.innerWidth() / dataset.length / 2 * 0.85; | |
charts.append('svg') | |
.attr('class', 'legend') | |
.attr('width', '100%') | |
.attr('font-size', '0.8em') | |
.attr('height', 150) | |
.attr('transform', 'translate(0, -100)'); | |
var donut = charts.selectAll('.donut') | |
.data(dataset) | |
.enter().append('svg:svg') | |
.attr('width', (chart_r + chart_m) * 2) | |
.attr('height', (chart_r + chart_m) * 2) | |
.append('svg:g') | |
.attr('class', function (d, i) { | |
return 'donut type' + i; | |
}) | |
.attr('transform', 'translate(' + (chart_r + chart_m) + ',' + (chart_r + chart_m) + ')'); | |
createLegend(getCatNames(dataset)); | |
createCenter(); | |
updateDonut(); | |
} | |
this.update = function (dataset) { | |
// Assume no new categ of data enter | |
var donut = charts.selectAll(".donut") | |
.data(dataset); | |
updateDonut(); | |
} | |
} | |
/** | |
* Formats the data received in order to draw the json graphs | |
* | |
* @param {Array} data An array with the dataset | |
* @returns {Array} Returns an array with the formatted data | |
*/ | |
function formatData(data){ | |
var years = {'2014_2015': 'Actuals 2014/2015', '2015_2016': '2015/2016 Proj.', '2016_2017': '2016/2017 Proj.'}; | |
var formattedData = new Array(); | |
$.each(years, function(index, desc){ | |
var curY = {'total': 0, 'unit': ' B', 'type': desc, 'data': []}; | |
for(var i = 0; i < data.length; i++){ | |
var cur = data[i]; | |
var curyd = curY['data'].length; | |
curY['data'][curyd] = {'cat': cur['Details'], 'val': cur[index]}; | |
curY['total'] += cur[index]; | |
} | |
formattedData[formattedData.length] = curY; | |
}); | |
return formattedData; | |
} | |
}); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment