Sunday, January 20, 2008

Dial, Radar and Bubble Charts with ColdFusion MX7

On Friday I noticed Raymond Camden posted a cool tip about how to create gauge charts using ColdFusion 8's built-in webcharts utility. Though he credited Simon Haddan and Christopher Wigginton for the idea.

Yesterday, I received a comment from Mike Kelp. He mentioned that he had been able to do some neat and unusual stuff with webcharts, including spark lines. Thanks to his encouragement I decided to explore the idea a bit further. I started with MX7, because anything it can do CF8 can probably do better. Now, I have not figured out how to do spark lines yet, but give me time ;)

Here are just a few examples of what you can do under MX7. The source code is entirely from Raymond Camden's blog entry. So all credit goes to him, Simon Haddan and Christopher Wigginton. Thanks also go to Mike Kelp for pointing out that the utility is well worth exploring!





What you need to run the examples below

Use the instructions in the documentation to open the webcharts utility. Then create three (3) WCP files for the different chart types. Save all WCP files in the same directory as your .CFM script.
  • Select Dial Chart > Design Tab > File > Save As testDial.wcp
  • Select Radar Chart > Design Tab > File > Save As testRadar.wcp
  • Select Bubble Chart > Design Tab > File > Save As testBubble.wcp

Why the empty cfchart?

The webcharts FAQ's mention that the charting engine is "fully initialized only after at least one chart was produced using CFCHART tag. If your application does not do so, you might want to insert an empty CFCHART tag". In my tests the charts did not display until after I used the empty cfchart. You may not need it.


Dial Chart

<!--- See http://www.webcharts3d.com/website/WebCharts50/cf/faq.jsp --->
<cfchart chartwidth="1" chartheight="1"/>

<!--- Get base server url --->
<cfif len(CGI.HTTPS)>
<cfset baseURL = "https://"& CGI.HTTP_HOST &"/">
<cfelse>
<cfset baseURL = "http://"& CGI.HTTP_HOST &"/">
</cfif>

<!--- Extract the style and sample model from the WCP file --->
<cfset wcp = XMLParse( ExpandPath("./testDial.wcp") )>
<cfset chartStyle = ToString(wcp.project.style.dialChart)>

<!--- create sample chart data --->
<cfsavecontent variable="chartModel"><?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>2000</COL>
<COL>2001</COL>
<COL>2002</COL>
<COL>2003</COL>
<COL>2004</COL>
<ROW col0="120.0" col1="0.0" col2="100.0" col3="180.0" col4="200.0">Sample 0:</ROW>
</XML>
</cfsavecontent>

<!--- initialize chart settings --->
<cfscript>
oMyWebChart = createObject("Java","com.gp.api.jsp.MxServerComponent");
oMyApp = getPageContext().getServletContext();
oSvr = oMyWebChart.getDefaultInstance(oMyApp);
oMyChart2 = oSvr.newImageSpec();
oMyChart2.width = 400;
oMyChart2.height= 300;
oMyChart2.type = "png";
oMyChart2.style = "#chartStyle#";
oMyChart2.model = "#chartModel#";
</cfscript>

<!--- Create html tag set --->
<cfsavecontent variable="chartImgTag">
<cfoutput>#oSvr.getImageTag(oMyChart2, baseURL& "CFIDE/GraphData.cfm?graphCache=wc50&graphID=")#</cfoutput>
</cfsavecontent>

<!--- Good old Webcharts loves to add an extra /Images/ to the URL --->
<cfset chartImgTag = replace(chartImgTag, baseURL &"Images/", baseURL, "All")>

<h2>Dial Chart</h2>
<cfoutput>
#chartimgtag#
</cfoutput>


Radar Chart

<!--- See http://www.webcharts3d.com/website/WebCharts50/cf/faq.jsp --->
<cfchart chartwidth="1" chartheight="1"/>

<!--- Get base server url --->
<cfif len(CGI.HTTPS)>
<cfset baseURL = "https://"& CGI.HTTP_HOST &"/">
<cfelse>
<cfset baseURL = "http://"& CGI.HTTP_HOST &"/">
</cfif>

<!--- Extract the chart style from the WCP file --->
<cfset wcp = XMLParse( ExpandPath("./testRadar.wcp") )>
<cfset chartStyle = ToString(wcp.project.style.radarChart)>

<!--- create sample chart data --->
<cfsavecontent variable="chartModel"><?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>100</COL>
<COL>200</COL>
<COL>300</COL>
<COL>350</COL>
<COL>400</COL>
<ROW col0="100.0" col1="200.0" col2="100.0" col3="180.0" col4="200.0">Sample 0:</ROW>
<ROW col0="150.0" col1="300.0" col2="250.0" col3="230.0" col4="250.0">Sample 1:</ROW>
<ROW col0="200.0" col1="400.0" col2="400.0" col3="280.0" col4="300.0">Sample 2:</ROW>
<ROW col0="250.0" col1="500.0" col2="550.0" col3="330.0" col4="350.0">Sample 3:</ROW>
</XML>
</cfsavecontent>

<!--- initialize chart settings --->
<cfscript>
oMyWebChart = createObject("Java","com.gp.api.jsp.MxServerComponent");
oMyApp = getPageContext().getServletContext();
oSvr = oMyWebChart.getDefaultInstance(oMyApp);
oMyChart2 = oSvr.newImageSpec();
oMyChart2.width = 400;
oMyChart2.height= 300;
oMyChart2.type = "png";
oMyChart2.style = "#chartStyle#";
oMyChart2.model = "#chartModel#";
</cfscript>

<!--- Create html tag set --->
<cfsavecontent variable="chartImgTag">
<cfoutput>#oSvr.getImageTag(oMyChart2, baseURL& "CFIDE/GraphData.cfm?graphCache=wc50&graphID=")#</cfoutput>
</cfsavecontent>

<!--- Good old Webcharts loves to add an extra /Images/ to the URL --->
<cfset chartImgTag = replace(chartImgTag, baseURL &"Images/", baseURL, "All")>

<h2>Radar Chart</h2>
<cfoutput>
#chartimgtag#
</cfoutput>


Bubble Chart

<!--- See http://www.webcharts3d.com/website/WebCharts50/cf/faq.jsp --->
<cfchart chartwidth="1" chartheight="1"/>

<!--- Get base server url --->
<cfif len(CGI.HTTPS)>
<cfset baseURL = "https://"& CGI.HTTP_HOST &"/">
<cfelse>
<cfset baseURL = "http://"& CGI.HTTP_HOST &"/">
</cfif>

<!--- Extract the chart style from the WCP file --->
<cfset wcp = XMLParse( ExpandPath("./testBubble.wcp") )>
<cfset chartStyle = ToString(wcp.project.style.frameChart)>

<!--- create sample chart data --->
<cfsavecontent variable="chartModel"><?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>100</COL>
<COL>200</COL>
<COL>300</COL>
<COL>350</COL>
<COL>400</COL>
<ROW col0="100.0" col1="200.0" col2="100.0" col3="180.0" col4="200.0">Sample 0:</ROW>
<ROW col0="150.0" col1="300.0" col2="250.0" col3="230.0" col4="250.0">Sample 1:</ROW>
<ROW col0="200.0" col1="400.0" col2="400.0" col3="280.0" col4="300.0">Sample 2:</ROW>
<ROW col0="250.0" col1="500.0" col2="550.0" col3="330.0" col4="350.0">Sample 3:</ROW>
</XML>
</cfsavecontent>

<!--- initialize chart settings --->
<cfscript>
oMyWebChart = createObject("Java","com.gp.api.jsp.MxServerComponent");
oMyApp = getPageContext().getServletContext();
oSvr = oMyWebChart.getDefaultInstance(oMyApp);
oMyChart2 = oSvr.newImageSpec();
oMyChart2.width = 400;
oMyChart2.height= 300;
oMyChart2.type = "png";
oMyChart2.style = "#chartStyle#";
oMyChart2.model = "#chartModel#";
</cfscript>

<!--- Create html tag set --->
<cfsavecontent variable="chartImgTag">
<cfoutput>#oSvr.getImageTag(oMyChart2, baseURL& "CFIDE/GraphData.cfm?graphCache=wc50&graphID=")#</cfoutput>
</cfsavecontent>

<!--- Good old Webcharts loves to add an extra /Images/ to the URL --->
<cfset chartImgTag = replace(chartImgTag, baseURL &"Images/", baseURL, "All")>

<h2>Bubble Chart</h2>
<cfoutput>
#chartimgtag#
</cfoutput>

9 comments:

rodell @ delroekid@yahoo.com,  August 31, 2008 at 10:18 PM  

hi,
i have tested the "Dial Chart" code in CFMX7, but i found one problem, the image "PNG" or even the "SWF" (i changed the oMyChart2.type = "swf";) wont display in my IE7 browser. but as i looked into the "\charting\cache", the rendered PNG / SWF was there..

can you help?

cfSearching September 2, 2008 at 11:54 AM  

@rodell,

I assume you verified that a regular cfchart call displays with IE7.

Maybe the paths are incorrect for some reason. Dump the #chartimgtag# variable. What is the output?

rodell @ delroekidATyahoodotcom,  September 10, 2008 at 12:41 AM  

@cfsearching,

hi,

thanks for the idea. i have dumped the #chartimgtag# and found out the it has "https" instead of "http".

:)

Brian Naess January 2, 2009 at 6:15 AM  

Thank you so much for posting this! I was looking everywhere for an easy to follow solution to getting these cool, custom charts into a .cfm page. So, I basically followed what you did for the bubble chart, but what I need is to be able to dynamically supply the x,y, and weights. This is what I tried (I took out brackets and other code-formatting to get this to post), and it plots, but it only plots the first COL, i.e. it puts all the points in the same column. It also doesn't recognize the different weights. The weird thing is if I cfdump the XML that is generated, everything is as I intended. Does this have something to do with when things are rendered?

Here is the code (everything else is as you suggest in the blog and the chart works fine when I hard code in the coordinates):
------------------
cfset x0 = 2.0
cfset x1 = 6.0
cfset y0 = 4.0
cfset y1 = 6.0
cfset weight0 = 300.0
cfset weight1 = 200.0
cfset name0 = "Option 1"
cfset name1 = "Option 2"

!--- create sample chart data ---
cfsavecontent variable="chartModel"
?xml version="1.0" encoding="UTF-8"?
XML type="default"
cfif isdefined("x0")
cfoutput
COL#x0#/COL
end cfoutput
end cfif
cfif isdefined("x1")
cfoutput
COL#x1# end COL
end cfoutput
end cfif
cfif isdefined("x0")
cfoutput
ROW col0="#y0#" #name0# end ROW
ROW col0="#weight0#"
end cfoutput
end cfif
cfif isdefined("x1")
cfoutput
ROW col0="#y1#" #name1# end ROW
ROW col0="#weight1#"
end cfoutput
end cfif
end XML
end cfsavecontent

cfSearching January 2, 2009 at 1:32 PM  

@Brian,

.. but it only plots the first COL

You have two (2) COL values, but only have data for the first COL. So I suspect it is being interpreted differently than you are thinking.

ie:
<!--- output --->
<?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>2.0</COL>
<COL>6.0 </COL>
<ROW col0="4.0"> Option 1 </ROW>
<ROW col0="300.0"/>
<ROW col0="6.0"> Option 2 </ROW>
<ROW col0="200.0"/>
</XML>


<!--- how it is interpreted --->
<?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>2.0</COL>
<COL>6.0</COL>
<ROW col0="2.0" col1="">Option 1</ROW>
<ROW col0="300.0" col1=""/>
<ROW col0="6.0" col1="">Option 2</ROW>
<ROW col0="200.0" col1=""/>
</XML>

..it puts all the points in the same column.

It is probably because of the scaleMax values in the WCP file:

<xAxis type="Scale" scaleMin="0" scaleMax="500" isBucketed="false"/>
<yAxis scaleMin="0" scaleMax="450">
..

Those scales are based on the sample values, which are much larger than what you are using. Since the scale is too large for your values, the points get squished together.

You can either remove the scaleMax values from the WCP file, or try and generate the correct scales dynamically.

Brian Naess January 2, 2009 at 3:44 PM  

It's plotting 2,4 and 2,6 and both weights of 300, though Option 2 says its weight is 200. I actually change the axis min's and max's to be from 0 to 12 in the style sheet, which is what I need.

Like I said, everything works and looks fine when I hard code in the values. And, if I cfdump the data XML, it says it should be plotting 2,4 and 6,6 with the right weights.

As I understand it, COL is what would be displayed across the x-axis, and for each row tag, the col0 is the y value.

Thanks!

cfSearching January 2, 2009 at 7:07 PM  

@Brian,

I think your generated XML is incorrect. At least for the results you are trying to produce.

.. And, if I cfdump the data XML, it says it should be plotting 2,4 and 6,6

No, it says it should be plotting 2,4 and 2,6 because both of the y and weight values are in col0, where x = 2. (See the xml in my last post above).

If you want it to plot 2,4 and 6,6 then the second set of values should be in col1 (ie where x = 6).

<?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>2.0</COL>
<COL>6.0</COL>
<ROW col0="4.0" col1="">Option 1</ROW>
<ROW col0="300.0" col1=""/>
<ROW col0="" col1="6.0">Option 2</ROW>
<ROW col0="" col1="200.0"/>
</XML>


Paste your current XML into webcharts. It will give you a better idea of how it is interpreting your data and what your XML should look like.

It's plotting 2,4 and 2,6 and both weights of 300, though Option 2 says its weight is 200.

I believe the weights are relative to other points in the _same_ series. So even though two points may have the same weight, they may not always be the same size if they belong to a different series.

Take this example. The first two points in both series have the same weight (300), but in the final chart the bubble sizes are different because the weights are measured against other points in the same series.

<?xml version="1.0" encoding="UTF-8"?>
<XML type="default">
<COL>2.0</COL>
<COL>6.0</COL>
<ROW col0="4.0" col1="4.0">Option 1</ROW>
<ROW col0="300.0" col1="150.0"/>
<ROW col0="6.0" col1="6.0">Option 2</ROW>
<ROW col0="300.0" col1="600.0"/>
</XML>

Brian Naess January 3, 2009 at 7:00 PM  

Thanks for clearing up my confusion with the x,y coordinates. Your suggestion definitely worked, and now that I see it, it makes sense.

And, I think you are correct about the plotting of the weights. This is extremely unfortunate!! I absolutely need different sized bubbles based on weight.

Do you have any suggestions about a workaround? Do you think that I can plot a small-sized (100) circle under the real point that would get covered up by the real value?

Thanks so much for answering all these questions!!

cfSearching January 4, 2009 at 6:29 PM  

@Brian,

No problem. I had a quick look around and did not see any properties that allow you to adjust this behavior. I am not saying there is not one, but nothing is jumping out at me. Other than that, I am not coming up with any clean work-arounds that do not have negative side effects.

It is possible that webcharts just does not support that feature. So you may need to look into a different chart engine. At least for this type of chart. I know it is possible to adjust the weights in a Flex chart, so I am sure it is possible with other charting products as well.

Here is the same example in CF/Flex. The difference is both 300 points are the same size. (Watch the line wrapping).

http://livedocs.adobe.com/flex/3/html/charts_types_04.html

<cfimport prefix="cfmxml" taglib="/WEB-INF/lib/cf-bootstrap-for-flex.jar">
<!-- charts/BubbleRelativeSize.mxml -->
<cfmxml:mxml>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var s1:ArrayCollection = new ArrayCollection( [
{"x": 2, "y": 4, "r":300 },
{"x": 6, "y": 4, "r":150 }]);

[Bindable]
private var s2:ArrayCollection = new ArrayCollection( [
{"x": 2, "y": 6, "r":300 },
{"x": 5, "y": 6, "r":600 }]);

]]>
</mx:Script>

<mx:Panel title="Bubble Chart (Bubbles relative to other series)">
<mx:BubbleChart id="myChart"
showDataTips="true"
>
<mx:series>
<mx:BubbleSeries
dataProvider="{s1}"
displayName="series1"
xField="x"
yField="y"
radiusField="r"
/>
<mx:BubbleSeries
dataProvider="{s2}"
displayName="series2"
xField="x"
yField="y"
radiusField="r"
/>
</mx:series>
</mx:BubbleChart>
<mx:Legend dataProvider="{myChart}"/>
</mx:Panel>
</mx:Application>
</cfmxml:mxml>

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep