----------------------------------------------------------
Introduction to bullet chart structure
One of the first D3.js examples I ever came across (back when Protovis was the thing to use) was one with bullet charts (or bullet graphs).It struck me straight away as an elegant way to represent data by providing direct information and context.
The Bullet Graph Design specification was laid down by Stephen Frew as part of his work with Perceptual Edge.
Using his specification we can break down the components of the chart as follows.
Quantitative scale:
A scale that is an analogue of the scale on the x axis of a two dimensional xy graph.
A scale that is an analogue of the scale on the x axis of a two dimensional xy graph.
Performance measure:
The primary data being displayed. In this case the frequency of operation of a CPU.
The primary data being displayed. In this case the frequency of operation of a CPU.
Comparative marker:
A reference symbol designating a measurement such as the previous day's high value (or similar).
A reference symbol designating a measurement such as the previous day's high value (or similar).
Qualitative ranges:
These represent ranges such as low medium and high or bad, satisfactory and good. Ideally there would be no fewer than two and no more than 5 of these (for the purposes of readability).
These represent ranges such as low medium and high or bad, satisfactory and good. Ideally there would be no fewer than two and no more than 5 of these (for the purposes of readability).
Understanding the specification for the chart is useful, because it's also reflected in the way that the data for the chart is structured.
For instance, If we take the current example, the data can be presented (in JSON) as follows;
[{
"title":"CPU 1 Load",
"subtitle":"GHz",
"ranges":[1500,2250,3000],
"measures":[2200],
"markers":[2500]
}
]
Here we an see all the components for the chart laid out and it's these values that we will load into our D3 script to display.
D3.js code for bullet charts
We'll move through the explanation of the code in a similar process to the other examples in the book. Where there are areas that we have covered before, I will gloss over some details on the understanding that you will have already seen them explained in an earlier section (most likely the basic line graph example).Here is the full code;
<!DOCTYPE html> <meta charset="utf-8"> <style> body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; margin: auto; padding-top: 40px; position: relative; width: 800px; } button { position: absolute; right: 40px; top: 10px; } .bullet { font: 10px sans-serif; } .bullet .marker { stroke: #000; stroke-width: 2px; } .bullet .tick line { stroke: #666; stroke-width: .5px; } .bullet .range.s0 { fill: #eee; } .bullet .range.s1 { fill: #ddd; } .bullet .range.s2 { fill: #ccc; } .bullet .measure.s0 { fill: steelblue; } .bullet .title { font-size: 14px; font-weight: bold; } .bullet .subtitle { fill: #999; } </style> <button>Update</button> <script type="text/javascript" src="d3/d3.v3.js"></script> <script src="js/bullet.js"></script> <script> var margin = {top: 5, right: 40, bottom: 20, left: 120}, width = 800 - margin.left - margin.right, height = 50 - margin.top - margin.bottom; var chart = d3.bullet() .width(width) .height(height); d3.json("data/cpu1.json", function(error, data) { 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; }); d3.selectAll("button").on("click", function() { svg.datum(randomize).call(chart.duration(1000)); }); }); function randomize(d) { if (!d.randomizer) d.randomizer = randomizer(d); d.markers = d.markers.map(d.randomizer); d.measures = d.measures.map(d.randomizer); return d; } function randomizer(d) { var k = d3.max(d.ranges) * .2; return function(d) { return Math.max(0, d + k * (Math.random() - .5)); }; } </script> </body>
This code is a derivative of one of Mike Bostock's blocks here. You can download it (and a data set with two bullet chart groups in it) from https://gist.github.com/d3noob/5886992. You can view an online version here.
It will become clearer in the process of going through the code below, but as a teaser, it is worth noting that while the code that we will modify is as presented above, we are employing a separate script `bullet.js` to enable the charts.
The first block of our code is the start of the file and sets up our HTML.
<!DOCTYPE html> <meta charset="utf-8"> <style>
body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; margin: auto; padding-top: 40px; position: relative; width: 800px; } button { position: absolute; right: 40px; top: 10px; } .bullet { font: 10px sans-serif; } .bullet .marker { stroke: #000; stroke-width: 2px; } .bullet .tick line { stroke: #666; stroke-width: .5px; } .bullet .range.s0 { fill: #eee; } .bullet .range.s1 { fill: #ddd; } .bullet .range.s2 { fill: #ccc; } .bullet .measure.s0 { fill: steelblue; } .bullet .title { font-size: 14px; font-weight: bold; } .bullet .subtitle { fill: #999; }
The first line `.bullet { font: 10px sans-serif; }` sets the font size.
The second line sets the colour and width of the symbol marker. So if we were to change it to...
.bullet .marker { stroke: red; stroke-width: 10px; }
The next three lines set the colours for the fill of the qualitative ranges.
.bullet .range.s0 { fill: #eee; } .bullet .range.s1 { fill: #ddd; } .bullet .range.s2 { fill: #ccc; }
The next line designates the colour for the value being measured.
.bullet .measure.s0 { fill: steelblue; }
The final two lines lay out the styling for the label.
The next block of code loads the JavaScript files.
</style> <button>Update</button> <script type="text/javascript" src="d3/d3.v3.js"></script> <script src="js/bullet.js"></script> <script>
Then we get into the JavaScript. The first thing we do is define the size of the area that we'll be working in.
var margin = {top: 5, right: 40, bottom: 20, left: 120}, width = 800 - margin.left - margin.right, height = 50 - margin.top - margin.bottom;
var chart = d3.bullet() .width(width) .height(height);
Then we load our JSON data with our values that we want to display.
d3.json("data/cpu1.json", function(error, data) {
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);
We use our `.select` and `.selectAll` statements to designate where the chart will go (d3.select(`"body").selectAll("svg")`) and then load the data as `data` (`.data(data)`).
We add in a svg element (`.enter().append("svg")`) and assign the styling from our css section (`.attr("class", "bullet")`).
Then we set the size of the svg container for an individual bullet chart using `.attr("width", width + margin.left + margin.right)` and `.attr("height", height + margin.top + margin.bottom)`.
We then group all the elements that make up each individual bullet chart with `.append("g")` before placing the group in the right place with `.attr("transform", "translate(" + margin.left + "," + margin.top + ")")`.
The we wave the magic wand and call the `chart` function with `.call(chart);` which will take all the information from our data file ( like the `ranges`, `measures` and `markers` values) and use the `bullet.js` script to create a chart.
The reason I made the comment about the process looking like magic is that the vast majority of the heavy lifting is done by the `bullet.js` file. Because it's abstracted away from the immediate code that we're writing, it looks simplistic, but like all good things, there needs to be a lot of complexity to make a process look simple.
We then add the titles.
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; });
Then we append the `title` and `subtitle` data (from our JSON file) to our chart with a modicum of styling and placement.
Then we add a button and functions which do the job of applying random data to our variables every time it's pressed.
d3.selectAll("button").on("click", function() { svg.datum(randomize).call(chart.duration(1000)); }); }); function randomize(d) { if (!d.randomizer) d.randomizer = randomizer(d); d.markers = d.markers.map(d.randomizer); d.measures = d.measures.map(d.randomizer); return d; } function randomizer(d) { var k = d3.max(d.ranges) * .2; return function(d) { return Math.max(0, d + k * (Math.random() - .5)); }; }
I'm not going to delve into the working of the randomize function, because it exists simply to demonstrate the dynamic nature of the chart and not really how the chart is drawn.
However, I will be going through a process later to ensure that we can update the data and the chart automatically which will hopefully be more orientated to practical applications.
That's it! Now we'll go through how you can use the data to change aspects of the chart and what part's of the code need to be adjusted to work with those changes.
The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).
Is this plugin supports with bootstrap. If no is there any other way to achieve it with bootstrap?
ReplyDeleteThis plugin supports d3.js. However, you can use d3.js with Bootstrap, so you can set up a page with bootstrap and then use d3.js and bullet charts inside that. there is a sequence of articles explaining how to use bootstrap and d3.js in the book here https://leanpub.com/D3-Tips-and-Tricks (it's free). Check ths info out and see how that sets you up. Good luck
DeleteHi, I want to use a bullet graph in the context of Not-to-Exceed Targets. My problem is that a I have temperature data that can be positive or ngative values and I do not know how to manage Negative values. Is there are any implementation or tip that you can suggest to help me?
ReplyDeleteI haven't tried that myself, but I'd make a start with https://groups.google.com/forum/#!topic/d3-js/Zk1Vb2mvPrw or perhaps http://stackoverflow.com/questions/10127402/bar-chart-with-negative-values for inspiration
DeleteHi, I am trying to implement bullet chart as a node with in indented tree. It is not working. Any inputs please advise.
ReplyDeleteGolly, that's an interesting project. I haven't seen anyone attempt that before (although you should do some thorough googling of course). I'm sorry, but the best advice I could provide would be to get the simplest example of a bullet chart and the simplest example of the type of tree diagram you are trying to incorporate and to further reduce them wherever possible so that when you bring them together, it is as clear as possible. Good luck
DeleteHi can we set x axis as per our wish
ReplyDeleteThis may be problematic. The bullet.js code defines the x axis, so you would need to edit that code. Totally possible, but a little bit more hard core than most people would attempt.
DeleteDo you have anything explaining the bullet.js? It's a huge file that adds lots of functionality so I'd like to understand what's in it, sorry for being a noob :)
ReplyDeleteHi Jeremy, there is more information in the book (download a copy here https://leanpub.com/D3-Tips-and-Tricks or read online here https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-bullet-charts). However, I don't explain the bullet.js file, just some of my observations on how it can be used. I have yet to update the v4 book with the bullet chart info, but I see that there is a v4 port already in play here https://github.com/GordonSmith/d3-bullet.
DeleteGood luck.
Hello i am having a problem trying to add JSON objects i made in the same page instead of having a separate JSON file. How do i use this object?
ReplyDeleteCheck out the 'Getting the data' section in the book. There is a possibility that it might be something simple that is foiling you. If possible use a `console.log(data);` or similar statement to see how the data is getting ingested into your code. failing that try to reduce your code (and your data) as much as possible to see if you can determine the problem.
Delete