Fun With Canvas: Create a Bar Graphing Plugin, Part 1

Fun With Canvas: Create a Bar Graphing Plugin, Part 1

Tutorial Details
  • Language: JavaScript
  • Difficulty: Intermediate

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!


Siddharth is Siddharth on Codecanyon
Add Comment

Discussion 57 Comments

  1. g_alex_stef says:

    great tut man, i ll give it a try

  2. Michael says:

    wow, really nice. perfect timing, need it for my next project ;)

  3. Milan says:

    Very nice, quite useful for a couple sites I know.

  4. bill says:

    Nice but use Flash for this instead. Sadly the majority of web surfers (e.g. IE 6 & 7) can’t view this without hacks so why bother?

    • Emil Hajric says:

      Bill, you do have a point. But, I hate Flash on a site more than I hate hacking IE code!

    • Aaron says:

      All depends on your target audience. Maybe its to a point where we developers have to abandon these old broken browsers

    • Andrei says:

      I can’t view flash on my iPhone.

    • Mark Sinkinson says:

      The whole point Bill is that this is the future of web development and there will come a time when we have to start ignoring idiots who use crappy old browsers

    • Jarrod says:

      What a pointless comment Bill. Libraries such as flot (mentioned in another post by Noel Tiangco) use excanvas to achieve compatibility for IE browsers that don’t yet support canvas, so would it also be possible here, but that isn’t the point of this article.

      This is not a comprehensive graphing plugin. It is a tutorial (and quite a good one at that) on how to use canvas and jquery, with the outcome of creating a graph and creating a reusable jquery plugin.

  5. Morten Najbjerg says:

    Great and useful article. I’m looking forward to part two…

  6. Noel Tiangco says:

    great tutorial. if other readers are looking for a graphing tool now, check out http://code.google.com/p/flot/

  7. rishteria says:

    So useful article, nice for share it ;)

  8. DynamicGuru says:

    Hmmm…. I may say nice tut but i didnt get the canvas element, is it some new kinda element???
    Anyway thnx for the tut

  9. ctx.fillText doesn’t work with some versions of FF3, you have to have a fix for it

    For example, for the drawXLabels function, instead of just having:

    ctx.fillText(xLabels[index], x(index), gHeight+17);

    You have to use this:

    if(ctx.fillText) {
    ctx.fillText(xLabels[index], x(index), gHeight+17);
    } else if(ctx.mozDrawText) {
    ctx.translate(x(index), gHeight+17);
    ctx.mozDrawText(xLabels[index]); // for FF3-
    ctx.translate( (0-x(index)), (0-(gHeight+17)) );
    }

    Just to cover in case of fail, otherwise, you’ll get error messages that ctx.fillText doesn’t exist.

    It’s dumb, but unfortunate.

  10. adam says:

    the source doesn’t work. javascript error in IE8

  11. very nice tuts keep up the good work dudes!

  12. I something new for me! Excellent tut.

  13. Neil says:

    wow, such a lot of work for a static graph, that doesn’t render in Firefox!

    I prefer using Flash – send a XML document generated by SQL to render an interactive Graph

    • Siddharth says:
      Author

      This is more of a fun learning exercise than a real world project actually. For actual projects, I highly recommend using SVG rather than Flash.

  14. Patrik Ilola says:

    Very nice! Actually need it for my current project. Right now I’m using flash to render the graphs, but… I don’t like it ;)

  15. paradox says:

    Very cool !

    Thanks

  16. Akshay Sura says:

    This is why Microsoft is adopting jQuery and integrating it into Visual Studio.

  17. Moksha says:

    nice thanks for sharing

  18. astroot says:

    The Google Chart API is a great tool for making a quick graph.

  19. sarmen says:

    thanks for sharring. i noticed that the demo file doesnt work in firefox 3, internet explorer 8, and partly displays someting in google chrome. what browser are you using?

  20. Very nice tutorial! I’m looking forward to the sequel. There’s only one problem for me and that is the Y axis labels, I don’t like this line of code yLabels.push(Math.round(maxVal/numYlabels*(index+1)));
    I would use:
    yLabels.push(Math.round(maxVal/numYlabels)*(index+1));
    so the difference between each value is the same but what if the values in the chart are below 1?

    It is even better if the numbers are multiples of 2, 5 or 10 in which case a more complex function is needed.

  21. booyah says:

    Nettuts is for NUTS.

  22. samuel pushpak says:

    wonderful!! very useful.

    mr.siddharth.. can you please give your email id.

  23. wow, what an awesome tutorial. I can’t wait for the coming one next week!

  24. Jozko says:

    Amazing… very good tutorial. I’ll implement this plugin on one of my sites. Thanx.

  25. Wow, sure that nettuts reads minds, as we are about to do a web site that would benefit from this. Nice to see some international developers stepping up :)

    Great tutorial thank you Siddharth

  26. wit says:

    Amazing… very good tutorial. I’ll implement this plugin on one of my sites. Thank you very much

  27. phoenixfly says:

    ultra nice tutorial.. this is what I looking for months.. and fnally I found it here. good work brother! thx a bunch ^_^

  28. Phil D says:

    Cool. I usually use ChartGo to create a graph online and then save the image. But when I need to use Jquery, this tutorial is perfect.

  29. Yariv says:

    Great work!!
    Is there a way to Draw the text Vertically?

  30. Will says:

    So I’ve searched a few times on here and I can’t find part 2. Has that been written yet?

  31. ITALO COSTA says:

    PT-BR-> Olá, como faço para salvar a imagem gerada no
    EN-US-> Hi, How do I do to save img generate in

    Obrigado
    Thankyou

  32. edp says:

    nice tuts
    thanks

  33. OMG WTF HAX

    I loled …

    • Siddharth says:
      Author

      I try. I put something funny in each article so someone may get a chuckle if they read carefully instead of skimming.

      Thanks for reading carefully. :)

  34. Shane says:

    This is interesting stuff. I dabbled with SVG for graphs a few years ago, and got some good results.

    Flash would have some advantages, sure, but it’s a developer’s job to assess when Flash would be appropriate, and when it wouldn’t. Sometimes it is, sometimes it isn’t.

  35. Marko Jozic says:

    demo isn’t working in Opera

  36. Really enjoyed the article; learned a lot. Didn’t work at all in the latest version of Flock browser, but then what does.

  37. Very nice, quite useful.

    And I think Flash would have some advantages.

    Thanks.

  38. Nice tutorial about HTML5 feature. I think we can make it better by defining all the configuration in one object like:

    var conf = {
    barSpacing: 20,
    barWidth: 20,
    cvHeight: 220,
    numYlabels: 8,
    xOffset: 20,
    gWidth: 550,
    gHeight: 200;
    }

  39. ON&E BLOG says:

    I really need this tool…. can i use this tool for commercial use….?

  40. Great tutorial. I think it is a high time I start studying this canvas thing. Thanks man

  41. carlo says:

    this is not compatible with mozilla firefox.. if all value is 0 table is appear and the chart is disappear..please reply tnx

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.