Skip to content

Instantly share code, notes, and snippets.

@soloincc
Last active June 20, 2016 15:13
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 soloincc/04aa38a89160fd13226485fcf2059450 to your computer and use it in GitHub Desktop.
Save soloincc/04aa38a89160fd13226485fcf2059450 to your computer and use it in GitHub Desktop.
A standalone script for visualising Kenya's 2016/17 revenue estimates
<!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