October 16, 2009

Jquery, Flot Tutorial (Part 1.5)

1 comment(s)

Sorry for not posting more often. thanks for all the comments. I’ll work on the frequency of my posts!

Flot is indeed a great plugin. There is a lot of things that can be done using it.
Let’s say, I want to create a chart that represent the increase of a given year by month (from january to december). There is several approach but I will use:

  • cfc to get the data from the DB
  • custom axis renderer, for reasons I will explain later


Here is a preview of what we want to achieve.

cfc driven flot charting

All the data are pulled from the database. For you all to try, I used a dsn that is from the default install of CF. And to make it more “sexy”, we’ll add a zest of ajax!
Let’s get started. First, the cfc. Of course you can get the data with whatever server technology you are using.
We are using the cfartgallery datasourse. In that datasource, you have a table name APP.ORDERS. Basically, in this table, there is the orders for the art gallery application. What we want to do is to call a function that will return a query with the monthly order amount for a special year.

<cffunction name="getByDate" output="false" access="public" returntype="query">
<cfargument name="year" type="Numeric" required="false">

<cfset nq = QueryNew("orderdate, total", "VarChar, Integer")>

<cfloop from="1" to="12" index="i">

<cfquery name="q" datasource="cfartgallery">
SELECT Year(ORDERDATE) AS ORDERYEAR, TOTAL
FROM APP.ORDERS
WHERE Month(ORDERDATE) = #i#
AND Year(ORDERDATE) = #year#
</cfquery>

<cfset temp = QueryAddRow(nq)>
<cfset Temp = QuerySetCell(nq, "orderdate", year & '/0' & i)>
<cfset Temp = QuerySetCell(nq, "total", Val(q.total))>

</cfloop>

<cfreturn nq>
</cffunction>

In this quite simple function, we ccreate a new query named nq then loop for 1 to 12 (which are months of the year, from january to december) the we query the database for each one of those months then we append the new query nq with the total and the date with the format “YYYY/M” (no leading 0). Notice that the only argument we are passing to that function is the year. We’ll come to that later. Notice also that the data type for the orderdate is VarChar. We don’t want to receive a date from the cfc, because it might cause some problems later.

Now, we are going to add a new function to our cfc that we are going to call from our cfm page.

<cffunction name="getStructForChart" output="false" access="remote" returntype="any">

<cfargument name="year" type="Numeric" required="false" default="2004">
<cfargument name="yearNumber" type="Numeric" required="false" hint="Number of preceding years">

<cfset myStruct = arraynew(1)>

<cfset data = getByDate(arguments.year)>
<cfset ArrayAppend(myStruct,data)>

<cfif arguments.yearNumber gt 0>
<cfset arguments.yearNumber = arguments.yearNumber - 1>
<cfloop from="1" to="#arguments.yearNumber#" index="y">
<cfset data = getByDate(arguments.year-y)>
<cfset ArrayAppend(myStruct,data)>
</cfloop>
</cfif>

<cfreturn myStruct>

</cffunction>

In this function, we retrieve the year argument and this time, it has a default value. Because of the data in the table, we set it 2004, but of course in your application, you can set it to whatever is fine, for exaple #Year(Now())# for the current year. We also introduce a new argument “yearNumber” which is the number of preeceding year we want to display.

Here we create an array that will be transform to json when we call the cfc using jquery. In this array, we will put the query returned from the getByDate function. If the yearNumber argument is greater than 0, we loop from 1 to the argument yearNumber and we call the function again by passing a new value for the year argument. Finally, we will append the returned query to the array. That’s it for the cfc.

Now, let’s create the javascript function.

var $j = jQuery.noConflict();

$j(document).ready(function(){

//getData();
drawChart();

});

Just to remind you, as I use different javascript libraries, I defined var $j = jQuery.noConflict(); to avoid conflict.

In my previous blog, I specified an array that will be the data store. Let’s define it again.

var dataObject = [];
var plotarea = $j("#placeholder");

Now let’s defined too important variables and a handy array that we will use for our custom axis rendering:

var <cfoutput>#toScript(url.year, "year")#</cfoutput>
var <cfoutput>#toScript(url.yearNumber, "yearNumber")#</cfoutput>

var months = ['jan', 'feb', 'mars', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];

The months array is basically an array of strings, but you could put variables if for example your site is multilingual and using a resource bundle. the too variables get the url var of the year and the yearNumber and pass them to the following ajax call to the cfc:

$j.getJSON(
'http://192.168.0.100:81/model/cfc/service/charting.cfc?wsdl',
{ method : 'getStructForChart', returnformat : 'json', queryformat : 'column', year : year, yearNumber : yearNumber },
function(data) {

A great thing with cfc is that you can call it directly and specify that you want a Json result!!! That is a great gain of time!! You can change the url of your cfc but do not forget the  ?wsdl at the end. That makes the magic! Once we receive the response, let’s populate the dataObject array.

for (var a=0; a<data.length; a++) {

eval("data" + a + " = []");

for (var i=0; i<data[a].ROWCOUNT; i++) {

var myDate = new Date(data[a].DATA.orderdate[i]+'/01');
eval("data" + a).push([myDate.getMonth(),data[a].DATA.total[i]]);
var dataYear = myDate.getFullYear();

}

dataObject.push({label:dataYear,data:eval("data" + a)})

}

Here, we loop through the response (which is an array of objects) and we create a dynamic array (eval(“data” + a + ” = []“);) to push our formated data in. then we push the month of our newly formated date (formated for js using the new Date() function) and the according order amount. And finally we push that dynamic array to our dataObject array. Now the rest is just formating.

var options = {
legend: {
show: true,
margin: 10,
backgroundOpacity: 0.5
},
points: {
show: true,
radius: 6
},
lines: {
show: true
},
grid: {
borderWidth:0,
labelMargin:15,
clickable:true,
hoverable:true,
autoHighlight:true
},
xaxis: {
tickSize: 1,
tickFormatter: function(val,axis) {
return months[val];
}
}
};

There is some changes from my previous post, the radius of the point to make them bigger, no formatting on the vertical axis (yaxis), the points are clickable now, and I have a custom renderer on my horizontal axis (xaxis). That function return the month name defined in the months array for a given value. We’re almost done. Let’s draw the chart and add a function to handle the click on a point:

$j.plot(plotarea, dataObject, options);

$j("#placeholder").bind("plotclick", function (event, pos, item) {
alert('In '+item.series.label+'/'+item.datapoint[0]+', the order amout is '+item.datapoint[1]);
});

In that function, when a button is clicked, three variables are passed to the function. the item variable is, as its name tells, the item values (the horizontal and vertical data).  The item.series.label return the label (here the year of the series). Here is the final code:

&lt;cfparam name="url.year" type="numeric" default="2004"&gt;
&lt;cfparam name="url.yearNumber" type="numeric" default="0"&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;

&lt;title&gt;CFC Driven Chart&lt;/title&gt;
&lt;meta name="description" content="eremiya" /&gt;

&lt;script language="javascript" type="text/javascript" src="jquery/jquery-1.3.2.js"&gt;&lt;/script&gt;
&lt;script language="javascript" type="text/javascript" src="js/jquery/flot/jquery.flot.js"&gt;&lt;/script&gt;

&lt;/head&gt;
&lt;body&gt;

&lt;div id="placeholder" style="width:800px;height:300px"&gt;&lt;/div&gt;

&lt;script id="source" language="javascript" type="text/javascript"&gt;

var $j = jQuery.noConflict();

$j(document).ready(function(){

//getData();
drawChart();

});

function drawChart() {

var dataObject = [];
var plotarea = $j("#placeholder");

var <cfoutput>#toScript(url.year, "year")#</cfoutput>
var <cfoutput>#toScript(url.yearNumber, "yearNumber")#</cfoutput>

var months = ['jan', 'feb', 'mars', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];

$j.getJSON(
'http://192.168.0.100:81/model/cfc/service/charting.cfc?wsdl',
{ method : 'getStructForChart', returnformat : 'json', queryformat : 'column', year : year, yearNumber : yearNumber },
function(data) {
for (var a=0; a<data.length; a++) {

eval("data" + a + " = []");

for (var i=0; i<data[a].ROWCOUNT; i++) {

var myDate = new Date(data[a].DATA.orderdate[i]+'/01');
eval("data" + a).push([myDate.getMonth(),data[a].DATA.total[i]]);
var dataYear = myDate.getFullYear();

}

dataObject.push({label:dataYear,data:eval("data" + a)})

}

var options = {
legend: {
show: true,
margin: 10,
backgroundOpacity: 0.5
},
points: {
show: true,
radius: 6
},
lines: {
show: true
},
grid: {
borderWidth:0,
labelMargin:15,
clickable:true,
hoverable:true,
autoHighlight:true
},
xaxis: {
tickSize: 1,
tickFormatter: function(val,axis) {
return months[val];
}
}
};

$j.plot(plotarea, dataObject, options);

$j("#placeholder").bind("plotclick", function (event, pos, item) {
alert('In '+item.series.label+'/'+item.datapoint[0]+', the order amout is '+item.datapoint[1]);
});
}
);

}
&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

Quite a long post, sorry about that! Please tell me what you think about it! And here are the download files!
Hey, but what if we want to change the number of year using ajax?… “I’ll be back… sooner”

 
Comments
1 comment(s)
Do you have any suggestions? Add your comment. Please don't spam!
  1. [...] my previous post, we created a chart that represent the increase of a given year by month (from january to [...]

Leave a Reply

About Me

Latest posts

Categories

Archives