Ask a Jedi: Getting the legend from CFCHART

Brijesh asks:

Dear Jedi. In my reporting application, I am using tab based reports, one tab shows the graph and the other needs to show the legend, this is beacuse I am limited with the space of the POD in which the graphs need to be shown (280 x 280 px), is it possible to show the graph in one tab and legend in another? how do I just get the legend to show it separately?

I began by looking at the chart editor. While I could add things to my chart (like a table of data), as far as I could see there was no way to not display the chart unless I completely switched the chart type. Even though I could change the chart to just a table, there was no way to switch it to just the label. That wasn't terribly surprising since this is probably not something commonly requested. I can definitely see why Brijesh needed this functionality though so I decided to hack it up a bit.

To begin, I create a simple query of data and a chart to render it:

<cfset data = queryNew("name,value")> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Apples")> <cfset querySetCell(data, "value", 50)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Bananas")> <cfset querySetCell(data, "value", 40)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Cherries")> <cfset querySetCell(data, "value", 72)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Donuts")> <cfset querySetCell(data, "value", 59)> <cfchart chartheight="300" chartwidth="300" title="Test Chart" showlegend="true"> <cfchartseries type="pie" query="data" itemcolumn="name" valuecolumn="value" /> </cfchart>

Which renders this fairly typical chart:

So the first step is to get rid of the legend. That takes all of two seconds - just change the showlegend value to false. Now for the next step. My reader mentioned using pods but for now I'm going to use ColdFusion's built in tab support. I normally use jQuery UI for this but for a quick demo, this works great:

<cflayout type="tab" width="320" height="320"> <cflayoutarea title="Chart"> <cfchart chartheight="300" chartwidth="300" showLegend="false"> <cfchartseries type="pie" query="data" itemcolumn="name" valuecolumn="value"/> </cfchart> </cflayoutarea> <cflayoutarea title="Legend"> My Legend </cflayoutarea> </cflayout>

Obviously the legend is a work in progress, but as you can see I've got my chart in one tab and my legend in the other. However, this runs into another problem. If you switch back and forth a few times, you eventually get this:

Luckily there is a workaround for this. Brijesh ran into this as well and used the same solution (for the most part) that I'm going to demo here. CFCHART allows us to extract the bits generated and save it to the file system. Obviously this means you need to think about how long you save the bits, but for now, I'm just generating the chart once:

<cfif not fileExists(expandPath("./cfchart.swf"))> <cfchart chartheight="400" chartwidth="400" showLegend="false" name="data"> <cfchartseries type="pie" query="data" itemcolumn="name" valuecolumn="value"/> </cfchart> <cffile action="write" file="#expandPath('./cfchart.swf')#" output="#data#"> </cfif> <cflayout type="tab" width="320" height="320"> <cflayoutarea title="Chart"> <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" width="300" height="300" id="mymoviename"> <param name="movie" value="cfchart.swf" /> <param name="quality" value="high" /> <param name="bgcolor" value="#ffffff" /> <embed src="cfchart.swf" quality="high" bgcolor="#ffffff" width="300" height="300" name="mymoviename" align="" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"> </embed> </object> </cflayoutarea> (Continued...)

This code snippet begins by seeing if I have file, cfchart.swf, in my current directory. If not, I create the chart and write it to the file system. I modified the tab then to use old school html tags to embed the swf. (Btw - who else really hates all the code you have to use to embed Flash? Thank goodness for swfObject.)

So at this point I've got my chart rendering in a tab. Now I need to worry about the legend. As I said, there is no way (that I know of), to ask cfchart to just generate the legend. Therefore we need to do it ourselves. The main problem that we have is figuring out what colors refer to what data. Luckily cfchart lets us specify a color list to apply to our data. So my solution simply creates a hard coded list of colors and remembers what data label uses what color.

<cfset coreColorList = "red,blue,green,yellow,orange,silver,lime,navy,olive"> <cfset colorList = ""> <cfset legend = []> <cfloop query="data"> <cfif currentRow lte listLen(coreColorList)> <cfset thisColor = listGetAt(coreColorList, currentRow)> <cfelse> <cfset thisColor = listGetAt(coreColorList, currentRow mod listLen(coreColorList))> </cfif> <cfset legend[currentRow] = { color=thisColor, label=name }> <cfset colorList = listAppend(colorList, thisColor)> </cfloop>

At the end of this block we have two variables. A list, colorList, that represents the colors I'll use for my chart. (Please don't use my colors. I can barely dress myself. You should see have seen what I wore out yesterday.) The second variable, legend, is an array of structs where I store the label and the color used. Now for the final bit - generating the legend. I chose to use a simple table where the left side was a cell for the color and the right side was the actual legend.

<cflayoutarea title="Legend"> <table style="height:90%"> <cfloop index="l" array="#legend#"> <cfoutput> <tr> <td style="background-color:#l.color#;width:30px">  </td> <td>#l.label#</td> </tr> </cfoutput> </cfloop> </table> </cflayoutarea>

And here is the final result, starting with the chart:

And now the legend:

So not the prettiest solution, but workable. Here is the complete template.

<cfset data = queryNew("name,value")> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Apples")> <cfset querySetCell(data, "value", 50)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Bananas")> <cfset querySetCell(data, "value", 40)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Cherries")> <cfset querySetCell(data, "value", 72)> <cfset queryAddRow(data)> <cfset querySetCell(data, "name", "Donuts")> <cfset querySetCell(data, "value", 59)> <cfset coreColorList = "red,blue,green,yellow,orange,silver,lime,navy,olive"> <cfset colorList = ""> <cfset legend = []> <cfloop query="data"> <cfif currentRow lte listLen(coreColorList)> <cfset thisColor = listGetAt(coreColorList, currentRow)> <cfelse> <cfset thisColor = listGetAt(coreColorList, currentRow mod listLen(coreColorList))> </cfif> <cfset legend[currentRow] = { color=thisColor, label=name }> <cfset colorList = listAppend(colorList, thisColor)> </cfloop> <cfif not fileExists(expandPath("./cfchart.swf"))> <cfchart chartheight="400" chartwidth="400" showLegend="false" name="data"> <cfchartseries type="pie" query="data" itemcolumn="name" valuecolumn="value" colorList="#colorList#"/> </cfchart> <cffile action="write" file="#expandPath('./cfchart.swf')#" output="#data#"> </cfif> <cflayout type="tab" width="320" height="320"> <cflayoutarea title="Chart"> <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" width="300" height="300" id="mymoviename"> <param name="movie" value="cfchart.swf" /> <param name="quality" value="high" /> <param name="bgcolor" value="#ffffff" /> <embed src="cfchart.swf" quality="high" bgcolor="#ffffff" width="300" height="300" name="mymoviename" align="" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"> </embed> </object> </cflayoutarea> <cflayoutarea title="Legend"> <table style="height:90%"> <cfloop index="l" array="#legend#"> <cfoutput> <tr> <td style="background-color:#l.color#;width:30px">  </td> <td>#l.label#</td> </tr> </cfoutput> </cfloop> </table> </cflayoutarea> </cflayout>

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Comments