Yesterday night I took at jqPlot, a jQuery plugin for charting. I'm a big fan of charting, especially ColdFusion's built-in charting, but I wanted to take a look at jqPlot more deeply ever since I played with it a bit within the CFAM project. For the most part, jqPlot works well (although I had a few issues I'll describe below), and if a JavaScript rendered chart solution is ok for you then you definitely want to take a look. Just remember that this is completely client side solution. You won't emailing these charts or embedding them in a presentation anytime soon.

I'd begin by taking a look at the jqPlot examples. These give a good idea of what the plugin is capable of. Then you want to download the bits, extract, and start playing. If you look at the usage example, you can see that the plugin is rather simple to use. You include your jQuery, the plugin, and a CSS sheet. If you ever used jQuery UI then this should be very familiar to you. You then select a div (and note it is important that the div have a defined width and height) and then you create an instance of jqPlot within that div. The code makes it look deceptively simple:

$.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);

However this never worked for me. The third argument to the constructor is an options object. Apparently this is optional but for me, nothing worked until I actually passed in an object, even a blank one. Here is a complete working example of the chart.

<html>

<head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script type="text/javascript" src="jqplot/jquery.jqplot.js"></script> <link rel="stylesheet" type="text/css" href="jqplot/jquery.jqplot.css" /> <script> $(document).ready(function() {

$.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]], {}); }) </script>

</head> <body>

<h2>Test jqPlot</h2>

<div id="chartdiv" style="width:400px;height:400px"></div>

</body> </html>

You can see an example of this here: http://www.coldfusionjedi.com/demos/dec172010/jqplot/test1.cfm The default rendering works but there are approximately one million or so ways to configure the chart.

One critical thing you don't want to miss is the format of the data. This confused me at first because visually, [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]], doesn't exactly scream out "Readability." What that is, in English, is an array of series of points. So the top level array contains series inside it. A simple chart has one series. A series is made up of data points. So for the line graph 1,2 means x position 1, y position 2.

So how about a ColdFusion example? I began by performing a simple query against the cfartgallery datasource.

<cfquery name="getsales" datasource="cfartgallery"> select sum(total) as sales, month(orderdate) as month from orders group by month(orderdate) </cfquery>

I then needed to translate this query into the format jqPlot desires:

<!--- core data ---> <cfset data = []> <!--- one series ---> <cfset data[1] = []> <cfloop query="getsales"> <cfset arrayAppend(data[1], [month,sales])> </cfloop> <cfset serializedData = serializeJSON(data)> <cfset serializedData = replace(serializedData, """", "", "all")>

For the most part this should make sense. But why am I doing the replacement at the end? ColdFusion 901 wraps all the data in quotes. It's easy to remove them at once, but I bet jqPlot could just do parseInt on em and be fine. Anyway, I then output it like so:

$(document).ready(function() { <cfoutput> $.jqplot('chartdiv', #serializedData#, { title:'Sales by Month', axes:{ xaxis:{min:1, max:12,numberTicks:12, tickOptions:{formatString:'%s'} }, yaxis:{min:0} } } ); </cfoutput> })

Note I've used a few options here. I added a title and specified the xaxis should go from 1 to 12. I also formatted the ticks. You can see an example of this here: http://www.coldfusionjedi.com/demos/dec172010/jqplot/test2a.cfm It works, but how about using a bar chart? That's better for this type of data. Switching to a bar chart involves a bit more work. Here is the complete template. You can see two main changes. First - we include plugins to support the bar chart. Secondly we use a new renderer for the xaxis.

<cfquery name="getsales" datasource="cfartgallery"> select sum(total) as sales, month(orderdate) as month from orders group by month(orderdate) </cfquery> <!--- core data ---> <cfset data = []> <!--- one series ---> <cfset data[1] = []> <cfloop query="getsales"> <cfset arrayAppend(data[1], sales)> </cfloop> <cfset serializedData = serializeJSON(data)> <cfset serializedData = replace(serializedData, """", "", "all")>

<html>

<head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script type="text/javascript" src="jqplot/jquery.jqplot.js"></script> <script type="text/javascript" src="jqplot/plugins/jqplot.categoryAxisRenderer.js"></script> <script type="text/javascript" src="jqplot/plugins/jqplot.barRenderer.js"></script>

<link rel="stylesheet" type="text/css" href="jqplot/jquery.jqplot.css" /> <script> $(document).ready(function() { <cfoutput> $.jqplot('chartdiv', #serializedData#, { title:'Sales by Month', series:[ {renderer:$.jqplot.BarRenderer, rendererOptions:{barPadding: 0, barMargin: 3}} ], axes:{ xaxis:{renderer:$.jqplot.CategoryAxisRenderer}, yaxis:{min:0} } } ); </cfoutput> }) </script>

</head> <body>

<h2>Test jqPlot</h2>

<div id="chartdiv" style="width:400px;height:400px"></div>

</body> </html>

You can see this here: http://www.coldfusionjedi.com/demos/dec172010/jqplot/test2.cfm. Looking nicer, right? Not quite as simple, but I think once you get the chart to your liking, then your home free. So how about one more example? Imagine for a moment that our chart is a bit overwhelming for the sales staff. They can't quite handle seeing 12 bars at once. It overwhelms them and confuses them. How about we give them a nice way to control how much data is shown at once? In my final example, I've added the jQuery Slider control. I'm using the option with 2 controls so that you can specify an exact portion of the year. Here is the code. Notice how I look for changes to the slider and then fire off XHR requests to get my data.

<html>

<head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script type="text/javascript" src="jqueryui/jquery-ui-1.8.7.custom.min.js"></script> <script type="text/javascript" src="jqplot/jquery.jqplot.js"></script> <script type="text/javascript" src="jqplot/plugins/jqplot.categoryAxisRenderer.js"></script> <script type="text/javascript" src="jqplot/plugins/jqplot.barRenderer.js"></script>

<link rel="stylesheet" type="text/css" href="jqplot/jquery.jqplot.css" /> <link rel="stylesheet" type="text/css" href="jqueryui/css/ui-lightness/jquery-ui-1.8.7.custom.css" /> <script> function getData() { var min = $("#monthbegin").text(); var max = $("#monthend").text();

$.post("data.cfc?method=getdata&returnformat=json", {"minmonth":min,"maxmonth":max}, function(res,code) { //remove the quotes res = res.replace(/"/g,""); //create the proper data var data = []; data[0] = eval(res);

var options = { title:'Sales by Month', series:[ {renderer:$.jqplot.BarRenderer, rendererOptions:{barPadding: 0, barMargin: 3}} ], axes:{ xaxis:{renderer:$.jqplot.CategoryAxisRenderer}, yaxis:{min:0} } }; $("#chartdiv").html(""); $.jqplot('chartdiv', data, options);

}); }

$(document).ready(function() {

getData();

$( "#slider-range" ).slider({ range: true, min: 1, max: 12, values: [ 1, 12 ], slide: function( event, ui ) { $("#monthbegin").text(ui.values[0]); $("#monthend").text(ui.values[1]); getData(); } });

}) </script>

</head> <body>

<h2>Test jqPlot</h2>

<b>Show data from month <span id="monthbegin">1</span> to <span id="monthend">12</span></b>

<div id="slider-range" style="width:400px"></div>

<div id="chartdiv" style="width:400px;height:400px"></div>

</body> </html>

The CFC behind the scenes really isn't relevant, but here it is if you want to see it:

component {

remote array function getdata(numeric minmonth=1, numeric maxmonth=12) {

var q = new com.adobe.coldfusion.query(); q.setDatasource("cfartgallery"); q.setSQL("select sum(total) as sales, month(orderdate) as month from orders where month(orderdate) >= :min and month(orderdate) <= :max group by month(orderdate)"); q.addParam(name="min",value="#arguments.minmonth#",cfsqltype="cf_sql_integer"); q.addParam(name="max",value="#arguments.maxmonth#",cfsqltype="cf_sql_integer"); var results = q.execute().getResult(); var result = []; for(var i=1; i<= results.recordCount; i++) { arrayAppend(result, [results.month[i], results.sales[i]]); } return result; }

}

And finally, you can play with this demo by clicking the big ass demo button below:

p.s. Quick final note folks. When I posted my demos, I forgot to include the IE fix. jqPlots DOES support IE. Just be sure to add this one line:

<!--[if IE]><script language="javascript" type="text/javascript" src="jqplot/excanvas.js"></script><![endif]-->