Fun With Canvas: Create a Bar Graphing Plugin, Part 1
In this two-part series, we’ll combine the versatile canvas element with the robust jQuery library to create a bar graphing plugin. In this first part, we are going to code the core logic of the plugin as a standalone version.
Today, we are going to create a bar graphing plugin. Not an ordinary plugin, mind you. We’ll show some jQuery love to the canvas element to create a very robust plugin.
In this two-part article, we will start from the beginning by implementing the logic of the plugin as a standalone script, refactoring it into a plugin and then finally adding all the additional eye candy on top of the plugin code. In this first part, we are going to deal solely with implementing the core logic.
Need an example before we get started? Here you go!
Different graphs created with supplying different settings to our plugin
Satisfied? Interested yet? Let’s start.
Functionality
Our plugin needs to accomplish some basic things whilst not doing some other things. Let me elucidate:
- As usual, we are going to utilize only the canvas element and JavaScript. No images of any kind, no broken CSS techniques, no prerendering. Plain old (or is it new?) canvas element along with some jQuery to lighten our workload.
- With respect to the data source, we are going to pull all of the data directly from a standard table. No arrays to pass on the plugin at startup. This way the user can just put all the data in a table and then invoke our plugin. Plus, it is much more accessible.
- No special markup for the table acting as the data source and definitely no special classes names for the data cells. We are going to utilize only the ID of the table and pull all our data from there.
- No flimsy text overlay for rendering the labels and such on the graph. It is not only highly tedious but the rendered text isn’t part of the graph when it is saved. We are going to use the fillText and strokeText as defined by the WHATWG specs.
Dependencies
As we are delving into the world of cutting-edge, still not fully specified, technology, we do have some dependencies. For the canvas element to work, most modern browsers are sufficient. But since we make use of the new text rendering API, we need newer builds. Browsers utilizing the Webkit engine r433xx and above or the Gecko engine 1.9.1 and above should be excellent platforms for the plugin. I recommend grabbing a nightly build of Chromium or Firefox.
Before We Start
I’d like to mention that our plugin is purely for learning purposes. This plugin is in no way meant to replace other full-fledged graphing plugins like Flot, Plotr and such. Also the code is going to be as verbose as possible. You could write far, far more efficient code but for the sake of learning, everything is going to be as uncomplicated as possible. Feel free to refactor it in favor of efficiency in your production code.
The HTML Markup
<!DOCTYPE html> <html lang="en-GB"> <head> <title>OMG WTF HAX</title> </head> <body> <table width="200" border="0" id="data"> <tr> <th>Year</th> <th>Sales</th> </tr> <tr> <td>2009</td> <td>130</td> </tr> <tr> <td>2008</td> <td>200</td> </tr> <tr> <td>2007</td> <td>145</td> </tr> <tr> <td>2006</td> <td>140</td> </tr> <tr> <td>2005</td> <td>210</td> </tr> <tr> <td>2004</td> <td>250</td> </tr> <tr> <td>2003</td> <td>170</td> </tr> <tr> <td>2002</td> <td>215</td> </tr> <tr> <td>2001</td> <td>115</td> </tr> <tr> <td>2000</td> <td>135</td> </tr> <tr> <td>1999</td> <td>110</td> </tr> <tr> <td>1998</td> <td>180</td> </tr> <tr> <td>1997</td> <td>105</td> </tr> </table> <canvas id="graph" width="550" height="220"></canvas> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="mocha.js"></script> </body> </html>
Nothing special about the markup. I’ll do a quick overview anyway.
- We begin by including the requisite doctype. Since we are using the canvas element, we use the appropriate one for HTML 5.
- The data source table is then defined. Do notice that no special markup is being described or new classes being defined and assigned inside its members.
- A canvas element is defined and then assigned an ID to later reference it to. This specific canvas element will only be here for the standalone version. In the plugin version, the canvas element and its attributes will be injected dynamically into the DOM and then manipulated as needed. For progressive enhancement this way works out a lot better.
- Finally, we include the jQuery library and our custom script. As Jeffrey has mentioned time and again, including scripts at the end of the document is always a good idea.
The Canvas Grid
Before we start the Javascript, let me explain the canvas coordinate system. The top-left corner acts as the origin i.e. (0, 0). Points are then measured with respect to the origin with x increasing along the right and y increasing along the left. For the mathematically inclined, we are effectively working in the 4th quadrant except that we take the absolute value of y instead of its negative value. If you have worked with graphics in other languages you should be at home here.
The canvas co-ordinate system
The Rectangle Rendering Routine
Canvas’ rectangle rendering routine will be used extensively through out the article to render the bars, the grid and some other elements. With that in mind, let’s take a short look at those routines.
Of the three routines available, we will be using the fillRect and strokeRect methods. The fillRect method actually fills the rendered rectangle while the strokeRect method only strokes the rectangles. Other than that, both the methods take the same parameters.
- x – The x coordinate of the point from where to start drawing.
- y – The y coordinate with respect to the origin.
- width – Defines the width of the rectangle to be drawn.
- height – Defines the height of the rectangle.
The Javascript Magic
As always, I highly recommend you to download the source code and have it on the side for reference. It’s easier to look at the big picture and parse each function one by one than look at each function individually and then create the big picture in your mind.
Variable Declaration
var barSpacing = 20, barWidth = 20, cvHeight = 220, numYlabels = 8, xOffset = 20, gWidth=550, gHeight=200; var maxVal, gValues = [], xLabels = [], yLabels = []; var cv, ctx;
Graph variables
- xLabels – An array which holds the value of the labels of the X axis.
- yLabels – Same as above except that it contains the values of the Y axis labels.
- gValues – Array which holds all the graph data we pull off the data source.
- cv – Variable to point towards the canvas element.
- ctx – Variable to refer to the context of the canvas element.
Graph Option Variables
These variables hold hard coded values to aid us in positioning and layout of the graph and the individual bars.
- barSpacing – Defines the spacing between individual bars.
- barWidth – Defines the width of each individual bar.
- cvHeight – Defines the height of the canvas element. Hard coded since we created the canvas element beforehand. Plugin version varies in this functionality.
- numYlabels – Defines the number of labels to be drawn in the Y axis.
- xOffset – Defines the space between the beginning of the canvas element and the actual graph. This space is utilized for drawing the labels of the Y axis.
- gWidth, gHeight – Hard coded values holding the dimension of the actual rendering space of the graph itself.
How each variable controls the appearance of the graph
Grabbing the Values
With jQuery’s strong selector engine it becomes very easy for us to get the data we need. Here we have a number of ways to access the necessary elements. Let me explain a few below:
$("tr").children("td:odd").each(function(){ //code here });
The simplest way to access the necessary rows. Looks for a tr element and then accesses every other td element. Fails miserably when you have more than one table on your page.
$("#data").find("td:odd").each(function(){ //code here });
A much more straight forward way. We pass in the ID of the table and then access every other row.
$("#data tr td:odd").each(function(){ //code here });
Same as above except that we just use CSS style selector syntax.
$("#data tr td:nth-child(2)").each(function(){ //code here });
The version we are going to use today. This way is a lot better if we need to grab data from a different row or, if needed, multiple rows.
The final version looks like so:
function grabValues () { // Access the required table cell, extract and add its value to the values array. $("#data tr td:nth-child(2)").each(function(){ gValues.push($(this).text()); }); // Access the required table cell, extract and add its value to the xLabels array. $("#data tr td:nth-child(1)").each(function(){ xLabels.push($(this).text()); }); }
Nothing complicated here. We use the snippet of code mentioned above to add the value of the table cell to the gValues array. Next, we do the same except that we access the first table cell in order to extract the requisite label for the x axis. We’ve encapsulated the data extraction logic to its own function for code reusability and readability.
Canvas Initialization
function initCanvas () { // Try to access the canvas element and throw an error if it isn't available cv = $("#graph").get(0); if (!cv) { return; } // Try to get a 2D context for the canvas and throw an error if unable to ctx = cv.getContext('2d'); if (!ctx) { return; } }
Routine canvas initialization. First we try to access the canvas element itself. We throw an error if unable to. Next up, we try to obtain a reference to the 2d rendering context through the getContext method and throw an error if we’re unable to do so.
Utility Functions
Before we step into the actual rendering of the graph itself, we need to look at a number of utility functions which aid us greatly in the process. Each of them are tiny by themselves, but will be used extensively throughout our code.
Determining the Maximum Value
function maxValues (arr) { maxVal=0; for(i=0; i<arr.length; i++) { if (maxVal<parseInt(arr[i])) { maxVal=parseInt(arr[i]); } } maxVal*= 1.1; }
A small function which iterates through the passed array and updates the maxVal variable. Do note that we inflate the maximum value by 10% for special purposes. If the maximum value is left as it is, then the bar representing the topmost value will touch the edge of the canvas element which we do not want. With that in mind, a 10% increment is issued.
Normalizing the Value
function scale (param) { return Math.round((param/maxVal)*gHeight); }
A small function to normalize the extracted value with respect to the height of the canvas element. This function is used extensively in other functions and directly in our code to express the value as a function of the height of the canvas. Takes a single parameter.
Returning the X Coordinate
function x (param) { return (param*barWidth)+((param+1)*barSpacing)+xOffset; }
Returns the x ordinate to the fillRect to aid us in the positioning of each individual bar. I’ll explain this a bit more in detail when it is used.
Returning the Y Coordinate
function y (param) { return gHeight - scale (param) ; }
Returns the y ordinate to the fillRect method to aid us in the positioning of each individual bar. More explanations a bit later.
Returning the Width
function width () { return barWidth; }
Returns the width of each individual bar.
Returning the height
function height (param) { return scale(param); }
Returns the height of the bar to be drawn. Uses the scale function to normalize the value and then returns it to the caller.
Drawing the X Axis Labels
function drawXlabels () { ctx.save(); ctx.font = "10px 'arial'"; ctx.fillStyle = "#000"; for(index=0; index<gValues.length; index++) { ctx.fillText(xLabels[index], x(index), gHeight+17); } ctx.restore(); }
A simple function to render the labels of the x axis. We first save the current state of the canvas including all the rendering settings so that anything we do inside the functions never leaks out. Then we set the size and font of the labels. Next, we iterate through the xLabels array and call the fillText method each time to render the label. We use the x function to aid us in the positioning of the labels.
Drawing the Y Axis Labels
function drawYlabels() { ctx.save(); for(index=0; index<numYlabels; index++) { yLabels.push(Math.round(maxVal/numYlabels*(index+1))); ctx.fillStyle = "#000"; ctx.fillText(yLabels[index], xOffset, y(yLabels[index])+10); } ctx.fillText("0", xOffset, gHeight+7); ctx.restore(); }
A slightly more verbose function. We first save the current state of the canvas and then proceed. Next we divide maxVal’s value into n elements where the variable numYlabels dictates n. These values are then added to the yLabels array. Now, as shown above, the fillText method is called to draw the individual labels with the y function aiding us in the positioning of each individual label.
We render a zero at the bottom of the canvas to finish drawing the Y labels.
Drawing the Graph
function drawGraph () { for(index=0; index<gValues.length; index++) { ctx.save(); ctx.fillStyle = "#B7B7B7"; ctx.fillRect( x(index), y(gValues[index]), width(), height(gValues[index])); ctx.restore(); } }
The function which draws the actual bars in the bar graph. Iterates through the gValues array and renders each individual bar. We use the fillRect method to draw the bars. As explained above, the method takes four parameters, each of which is taken care of by our utility functions. The current index of the loop is passed to our functions as parameters along with the actual value held in the array, as needed.
The x function returns the x co-ordinate of the bar. Each time, it is incremented by the value of the sum of barWidth and barSpacing variables.
The y function calculates the difference between the height of the canvas element and the normalized data and returns it. I know this sounds a bit topsy turvy, but this is due to the fact that the y values on the canvas grid increase on moving down while in our graph the y values increase on moving up. Thus, we have to do a little work to make it function the way we wish.
The width function returns the width of the individual bars themselves.
The height function just returns the normalized value which is going to be used as the height of the bar to be drawn.
Summary
In this first part, we’ve implemented the base logic of our plug-in as a standalone version with bare bones looks and features. We reviewed the canvas coordinate system, the rectangle rendering methods, some nifty data extraction using jQuery’s innate awesomeness [Have I mentioned how much I like jQuery?], looked at how the labels are drawn, and finally looked at the logic behind the rendering of the graph itself.
At the end of this article, the output should look like so.
Next Up!
Our current implementation is rather lacking really. It looks bland, can’t create multiple graphs on the same page, and let’s face it, is rather spartan on the features front. We are going to tackle all of that next week. In the next article we will:
- Refactor our code towards making it a full-fledged jQuery plugin.
- Add some eye candy.
- Include some nifty little features.
Questions? Criticisms? Praises? Feel free to hit the comments. Thanks for reading and, when you’re ready, move on to part two!
- Follow us on Twitter, or subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.