Skip to content

Instantly share code, notes, and snippets.

@CDEdata
Last active September 5, 2016 08:01
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 CDEdata/e432ac81c9ae8f7565266755d47f8dbc to your computer and use it in GitHub Desktop.
Save CDEdata/e432ac81c9ae8f7565266755d47f8dbc to your computer and use it in GitHub Desktop.
Quality of Investment (Compliance)

This visualization is based on Mike Bostock example "Bullet Charts" http://bl.ocks.org/mbostock/4061961

  1. First make sure that brackets is installed otherwise, download http://brackets.io / install and start Brackets
  2. Create a new file, copy paste the index.html from below and save it as index.html
    1. Create a new file, copy paste the bullet.js from below and save it as bullet.js
  3. Create another new file, copy paste the bullets.json from below and save it as bullets.json in the same folder as you saved index.html and bullet.js before Adjust the data in bullets.json for other projects

For this visualisation you need three files

  1. index.html
  2. bullet.js
  3. bullets.json

index.html here you style the bar charts and include the bullet.js (where the visualisation is coded) as well as the bullets.json (where the data is stored)

bullet.js this file includes the coding for the chart. In case you want the bar chart to start from the right side, you have to change that here. Furthermore, you will find all the coding for transitions from on data set to another. I left this coding just in case we use it later.

bullets.json The format of the .json is as follows: "title":"Surveying & Approval" add here the title of the indicator "subtitle":"" add here the subtitle of the indicator "ranges":[50,100]
"measures":[100] add here the score of the indicator "markers":[50,100]} add here the possible percentage ticks that the specific indicator can take

(function() {
// Chart design based on the recommendations of Stephen Few. Implementation
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
// http://projects.instantcognition.com/protovis/bulletchart/
d3.bullet = function() {
var orient = "left", // TODO top & bottom
reverse = false, //false means that the bar chart starts on the left side, true lets the bar chart start on the right side
duration = 0,
ranges = bulletRanges,
markers = bulletMarkers,
measures = bulletMeasures,
width = 380,
height = 30,
tickFormat = null;
// For each small multiple…
function bullet(g) {
g.each(function(d, i) {
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
markerz = markers.call(this, d, i).slice().sort(d3.descending),
measurez = measures.call(this, d, i).slice().sort(d3.descending),
g = d3.select(this);
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
// Derive width-scales from the x-scales.
var w0 = bulletWidth(x0),
w1 = bulletWidth(x1);
// Update the range rects.
var range = g.selectAll("rect.range")
.data(rangez);
range.enter().append("rect")
.attr("class", function(d, i) { return "range s" + i; })
.attr("width", w0)
.attr("height", height)
.attr("x", reverse ? x0 : 0)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
range.transition()
.duration(duration)
.attr("x", reverse ? x1 : 0)
.attr("width", w1)
.attr("height", height);
// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);
measure.enter().append("rect")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);
// Update the marker lines.
var marker = g.selectAll("line.marker")
.data(markerz);
marker.enter().append("line")
.attr("class", "marker")
.attr("x1", x0)
.attr("x2", x0)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6)
.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1);
marker.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6);
// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);
// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(x1.ticks(8), function(d) {
return this.textContent || format(d);
});
// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("g")
.attr("class", "tick")
.attr("transform", bulletTranslate(x0))
.style("opacity", 1e-6);
tickEnter.append("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickEnter.append("text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", height * 7 / 6)
.text(format);
// Transition the entering ticks to the new scale, x1.
tickEnter.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
tickUpdate.select("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickUpdate.select("text")
.attr("y", height * 7 / 6);
// Transition the exiting ticks to the new scale, x1.
tick.exit().transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1e-6)
.remove();
});
d3.timer.flush();
}
// left, right, top, bottom
bullet.orient = function(x) {
if (!arguments.length) return orient;
orient = x;
reverse = orient == "right" || orient == "bottom";
return bullet;
};
// ranges (bad, satisfactory, good)
bullet.ranges = function(x) {
if (!arguments.length) return ranges;
ranges = x;
return bullet;
};
// markers (previous, goal)
bullet.markers = function(x) {
if (!arguments.length) return markers;
markers = x;
return bullet;
};
// measures (actual, forecast)
bullet.measures = function(x) {
if (!arguments.length) return measures;
measures = x;
return bullet;
};
bullet.width = function(x) {
if (!arguments.length) return width;
width = x;
return bullet;
};
bullet.height = function(x) {
if (!arguments.length) return height;
height = x;
return bullet;
};
bullet.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return bullet;
};
bullet.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return bullet;
};
return bullet;
};
function bulletRanges(d) {
return d.ranges;
}
function bulletMarkers(d) {
return d.markers;
}
function bulletMeasures(d) {
return d.measures;
}
function bulletTranslate(x) {
return function(d) {
return "translate(" + x(d) + ",0)";
};
}
function bulletWidth(x) {
var x0 = x(0);
return function(d) {
return Math.abs(x(d) - x0);
};
}
})();
[
{"title":"Surveying & Approval","subtitle":"","ranges":[100],"measures":[100],"markers":[50,100]},
{"title":"Concession boundary","subtitle":"","ranges":[100],"measures":[100],"markers":[50,100]},
{"title":"Contract","subtitle":"other legal violation","ranges":[100],"measures":[50],"markers":[50,100]},
{"title":"Village consent","subtitle":"grievance mechanism","ranges":[100],"measures":[54],"markers":[6,12,18,24,30,36,42,48,54,60,66,72,78,84,90,100]},
{"title":"Project progress","subtitle":"","ranges":[100],"measures":[25],"markers":[25,50,75,100]},
{"title":"Project reporting","subtitle":"","ranges":[100],"measures":[0],"markers":[50,100]}
]
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*define here the layout and style of the bar charts*/
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
padding-top: 40px;
position: relative;
width: 900px;
}
.bullet { font: 10px sans-serif; }
.bullet .marker { stroke: #000; stroke-width: 1px; }
.bullet .tick line { stroke: #666; stroke-width: .5px; }
.bullet .range.s0 { fill: #ddd; }
.bullet .range.s1 { fill: #ddd; }
.bullet .range.s2 { fill: #ccc; }
.bullet .measure.s0 { fill: #FFD34A; } /*this is the color of the bar chart*/
.bullet .measure.s1 { fill: steelblue; }
.bullet .title { font-size: 14px; font-weight: bold; } /*font style of title e.g. "Contract"*/
.bullet .subtitle { fill: #999; } /*font style of subtitle e.g. "other legal violation"*/
</style>
<!--Here we load the javascript libraries needed for the visualisation. The first is the general d3 javasript library.
The second one is the specific javascript for the bullet charts.-->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="bullet.js"></script>
<script>
var margin = {top: 5, right: 40, bottom: 20, left: 200},
width = 600 - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
var chart = d3.bullet()
.width(width)
.height(height);
d3.json("bullets.json", function(error, data) { //here we link our data to the chart
if (error) throw error;
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
title.append("text")
.attr("class", "subtitle")
.attr("dy", "1em")
.text(function(d) { return d.subtitle; });
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment