This post is for others who are using FusionCharts/FusionMaps in their web development (if you're not, this post is PAINfully long and could be deemed a waste of your time to read). FusionCharts/Maps is an amazing product for the price, and does some very very cool stuff when it comes to charting and creating thematic, interactive maps. It does however present some unique challenges when it comes to actually PRINTING these items, due to the fact that every Map and Chart is an embedded Flash object.
I have recently had to tackle this very problem and having come up with what I believe to be a workable (though not totally ideal) solution, thought I'd share it with whoever finds it useful.
(Consider hiring Doug for your Fusioncharts/Fusionmaps project!)
Here's the scenario...
I have a data details page that provides a lot of data tables and such, as well as a dropdown of the available charts. When a chart is selected from the dropdown, I grab the corresponding Chart XML from a Javascript Associative Array and feed it to a newly created FusionChart object which I then render within my target div. That part works beautifully!
some screenshots...

A bit of the data display page the user sees...

An example of the kind of charts FusionCharts produces...
And yet another chart example...
Feel free to play around with the live app. To access the functionality specifically being talked about in this post, you will go to the search page (where you will see a map rendered) and then click on any state to drilldown to that data. The main Print button may not be wired in, but if not, there should be one at the very bottom of the drilldown output that you can click to fire off the process.
Printing this page out is another matter, because the charts being rendered are embedded Flash objects, and most browsers will NOT include an embedded Flash object when in the printed output. What needs to happen then is that, on print, I must convert all of my Flash chart objects to images first and output them within the page to be printed. So far so good, right?
Here comes the first fiery hoop to jump through. The nice folks at Fusioncharts provided a print mechanism that functions like this: The user or some interactive JS executes the rendered chart's 'saveImageAs()' method; this method gathers up all of the rendered chart's bitmap data, packs it all up into nice neat form variables, and submits it to the URL you provided in the Chart XML's "printURL" variable. The target URL must include the needed backend code to take the incoming bitmap data and transform it into JPG binary data. Once you have the JPG as binary, you can actually DO something with it!
example of the JS it takes to create a chart and convert it...
//here's our chart xml in json form...
var jschart = <cfoutput>#serializejson(chartxml)#</cfoutput>;
function makechart(chartid){
var myChart = new FusionCharts("/FusionCharts/pie2D.swf", "drilldownchart", "420", "420", "0", "1");
myChart.setDataXML(jschart[chartid]);
myChart.render("printchart");
}
function FC_Rendered(DOMId){
drilldownchart.saveAsImage();
}
</script>
Doesn't sound like much of an issue, does it? Except for the fact that I have n number of charts that I need to convert to JPG, and the ONLY way to do that is to first render the chart and then call it's 'saveImageAs()' method. Each call of this method changes the current window's location to the new URL, thus forcing the user to leave the page they want to print and then watch as each chart is converted. Now, I personally was able to compromise on the fact that the user would have to watch the charts be converted (it's kind of a cool looking process anyway)
, but I was NOT okay with them being forced to leave their current page. A WHOLE lot of Googling and searches/messages on the FusionCharts forum proved completely unfruitful, and in fact I only found many other people who were facing my exact dilemma and had found no acceptable solution.
I finally came up with what I felt was probably the most elegant solution to be had given the firm constraints of FusionCharts (at least as of version 3.0.5). My solution is built using Coldfusion, with ModelGlue:Unity as the framework, but I do believe that the basic approach can be molded to fit any language/framework; I'll try to keep the walk-through as generic as possible.
Click this image for the full size diagram...
The user arrives at the data detail page and is shown a lot of tables and other data summaries, with a dropdown of available charts. As described above, selecting a chart leverages JS to grab the appropriate set of FusionCharts XML from an associative array and render that chart in the target div. A print button is provided on the page. Clicking the print button submits the following form, all of which is hidden:
Here is an example of the UNserialized chart XML data in a CF structure:
<form name="test" id="test" action="#printchartURL#" method="post" target="_blank">
<input type="hidden" name="endimagenumber" value="#structcount(jsChart)#" />
<input type="hidden" name="chartxml" id="chartxml" value="#urlencodedformat(serializeJSON(jsChart))#" />
<input type="hidden" name="fchartjs" value="#fchartjs#" />
<input type="hidden" name="currentimagenumber" value="1" />
<input type="hidden" name="firstcall" value="true" />
<input type="hidden" name="printbody" value="#urlencodedformat(drilldownhtml)#" />
</form>
</cfoutput>
The submission of this form spawns a new window (target='_blank'), so there's the new window that I wanted for the user. Now, within the new window we begin a dance between the target template and the serverside code/backend that is performing the work. Between the two of them I manage a self-calling loop that:
1. renders a single individual chart;
2. calls the completed chart's 'saveAsImage()' method;
3. reloads the same page again, this time loading up the next chart and repeating steps 1 and 2.
4. when the last chart has been processed, I switch to the second portion of this processing template which is the final output of all printable data AND the charts which have now been converted to JPEGs.
Because I KNOW that anybody who has attempted to do this very thing is going to be interested, let's look at some of the details of how this template/server loop works. We must begin with the hidden form that we submitted from the initial display page.
THE FORM
endimagenumber: this value represents the total number of charts that will need to be rendered, and will serve as a marker to tell us when we are actually finished processing charts within the funky loop to follow. Because I have my chart XML sets contained in both a JS associative array AND a Coldfusion structure within my template, I can get this number by getting the length of either one.
chartXML: take the Coldfusion structure containing all of the chart XML sets, serialize it to a JSON string, then URL encode that string and voila! You have the value of this field. It of course contains the XML we will use to render our charts in the processing template to come.
fchartjs: this is the full path to the FusionCharts.js file. It is needful to include that library within the processing template. This value could be passed in a number of other ways (perhaps already available to all pages in your app as a global variable, etc.)
currentimagenumber: hard coded to '1', this is the starting point of chart processing. In my array/structure, the keys are sequential numbering, so if I have four charts to render, my structure keys will always be 1,2,3, and 4.
firstcall: this boolean flag tells my processing template that this is the first time it has been called..initial kickoff. It's important for me to know this because there are several setup type actions that need to occur initially.
printbody: this hidden field contains the entire, relevant (to be printed) HTML of the display page. In Coldfusion, it's easy to capture this by using the CFSAVECONTENT tags, which is what I did; in other languages I'm sure it's possible as well. I URLencode the content and pass it on to the processing template.
Okay, the only item left regarding the hidden form is the printURL value. Since I'm using Modelglue, it is in the form "index.cfm?event=processCharts".
Submitting the form to this URL will kick off some backend processing that accepts incoming bitmap data and creates JPEG binary data out of it, as well as some "loop maintenance" work (placing certain values into a persistent/session scope as needed, making sure that needed values are present for the actual template that will be loaded after this backend processing has completed, etc.). After the backend processing is finished, the target template is loaded (a .CFM page in my case). This page is essentially divided in half, with the top half performing chart rendering and saveImageAs() calls, and the bottom half outputting printable information and kicking off the 'window.print()' function.
Let's do a pseudo-code type walk through of what happens after the form is submitted...
First, the backend process is called (basically a method invoked on an object). This method receives the incoming bitmap data (completely non existent on the initial call), an assettID value (used for the purpose of keeping our chart JPEG binary data stored in session...we need to know where it is between calls), the current image number, the end image number, and the firstcall boolean flag we mentioned earlier. This method's purpose in life is to:
1. establish the presence of a session substructure to hold onto our JPG binary data if it doesn't already exist;
2. if this is our initial call, grab the 'printBody' value (all of our output, urlencoded) and stuff it into a session variable. We don't want to keep passing such a huge value back and forth, so we'll just hold on to it in session until we know we've completed our last pass. At that point, we'll pass it back to the template.
3. turn the incoming bitmap data into binary data;
4. place the values that will be needed by the template (.CFM page) into the request stream (in the case of ModelGlue, into the Event bucket)
After the backend process is finished, we load up our template (named "PrintChart.cfm" in this case). This template will:
1. grab the values it needs/expects out of the request stream (Viewstate bucket)
2. determine if it needs to be processing a chart. If so, process the chart (render it, call it's saveAsImage()method, which automatically changes the page location to call the SAME url again, only this time with some bitmap data in hand!).
3. if all charts have been processed, output everything for printing. Once we've reached this point, there are no more calls to the print URL and we're done.
Outputting the JPG Binary Data
At the point of output, all of our charts have been converted to JPG binary data, which itself has been base64 encoded and is sitting snug in a structure within our session scope. Here's a snapshot of what the finished JPEG encoded binary data looks like sitting in session:

Now, utilizing this data requires that we jump through ONE more little teeny weeny hoop. We need to create a template whose content type is image/jpeg and use the url to THAT template as the src value for our image tags. Take a gander at this short bit of code that actually performs ALL of the output for our printed page:
<cfloop list="#assettIDlist#" index="i">
Image #i#: <img align="top" height="420" width="420" src="index.cfm?event=getImage&imageid=#i#&assettid=#assettid#" /><br>
</cfloop>
#printbody#
</cfoutput>
Notice that in our final iteration, we are making sure we pass back the AssettID and a list of the individual image IDs. We then loop over those image IDs and pass the current value to the dspShowImage.cfm template. Here is the code for dspShowImage.cfm:
<cfif imageid IS NOT "0">
<cfset item = "imagedata_" & imageid />
<cfset imagecontent = viewstate.getValue(item) />
<cfheader name="Content-Disposition" value="attachment; filename=""FusionCharts.jpg""">
<cfcontent type="image/jpeg" variable="#imagecontent#">
<cfelse>
Image not found
</cfif>
Because we are using Model Glue, there is actually a method that gets executed BEFORE this template renders, whose sole duty is to retrieve (and subsequently delete) the requested jpg binary data and place it into the Request stream. Here is that method (which is part of the FusionMapController.cfc included in the ZIP):
<cfargument name="event" type="any" required="yes" />
<cfset var imageid = arguments.event.getValue("imageID") />
<cfset var assettid = arguments.event.getValue("assettid") />
<cfset arguments.event.setvalue("got called",true) />
<cfif assettid IS NOT ""><!--- we're accessing a collection of images within the printassetts structure --->
<cfif structkeyexists(session.printassetts,assettid)>
<cfif structkeyexists(session.printassetts[assettid],imageid)>
<cfset tmpval = session.printAssetts[assettid][imageid] />
<cfset arguments.event.setValue("imagedata_" & imageid,ToBinary(tmpval)) />
<cfset structdelete(session.printAssetts[assettid],imageid) />
<!--- if we just deleted the last item in this assett collection, remove the whole assett collection from printassetts --->
<cfif structcount(session.printAssetts[assettid]) eq 0>
<cfset structdelete(session.printAssetts,assettid) />
</cfif>
<cfelse>
<cfset arguments.event.setValue("imagedata_" & imageid,"") />
</cfif>
<cfelse>
<cfset arguments.event.setValue("imagedata_" & imageid,"") />
</cfif>
<cfelse><!--- we were called with nothing but an imageid...it must exist within the printAssetts structure --->
<cfif structkeyexists(session.printassetts,imageid)>
<cfset tmpval = session.printAssetts[imageid].image />
<cfset arguments.event.setValue("imagedata_" & imageid,ToBinary(tmpval)) />
<cfset structdelete(session.printAssetts,imageid) />
<cfelse>
<cfset arguments.event.setValue("imagedata_" & imageid,"") />
</cfif>
</cfif>
</cffunction>
DETAILS AND DOWNLOADS
It is a wee bit complicated, I know, but that is the generic walk through of it. Let me now include more visuals/code to be used as a starting point for whoever else has to be able to print out multiple FusionCharts.
Some of those files exposed here for convenience:
The Hidden Form that lives in the initial user display page:
<form name="test" id="test" action="#printchartURL#" method="post" target="_blank">
<input type="hidden" name="endimagenumber" value="#structcount(jsChart)#" />
<input type="hidden" name="chartxml" id="chartxml" value="#urlencodedformat(serializeJSON(jsChart))#" />
<input type="hidden" name="fchartjs" value="#fchartjs#" />
<input type="hidden" name="currentimagenumber" value="1" />
<input type="hidden" name="firstcall" value="true" />
<input type="hidden" name="printbody" value="#urlencodedformat(drilldownhtml)#" />
</form>
</cfoutput>
The printChart.cfm template: click here to view code
The image data processing controller method: The model method that performs the conversion of bitmap data to binary JPG:
<cfargument name="event" required="yes" />
<!--- variables that hold the Flash bitmap metadata... --->
<cfset var width = arguments.event.getValue("width") />
<cfset var height = arguments.event.getValue("height") />
<cfset var data = arguments.event.getValue("data") />
<cfset var bgcolor = arguments.event.getValue("bgcolor") />
<!--- other variables --->
<cfset var assettID = arguments.event.getValue("assettID",createuuid()) />
<cfset var currentimagenumber = arguments.event.getValue("currentimagenumber") />
<cfset var endimagenumber = arguments.event.getValue("endimagenumber") />
<cfset var firstcall = arguments.event.getValue("firstcall",false) />
<cfset var tmpval = "" />
<cfset var retval = "" />
<!--- we were invoked by the saveimageas js function... --->
<cfif not firstcall>
<cfset arguments.event.setValue("currentimagenumber",currentimagenumber + 1) />
<cfif width IS NOT "">
<cfset retval = getFusionMap().makeImage(width=width,height=height,data=data,bgcolor=bgcolor) />
</cfif>
<cfelse><!--- set up this particular print assett collection container --->
<cfset arguments.event.setValue("currentimagenumber",currentimagenumber) />
<cfset session.printAssetts[assettID] = structnew() />
<!--- preserve our chartxml structure (which has been serialized to JSON and then urlencoded) for future calls as well... --->
<cfset session.stChartXML = arguments.event.getValue("chartXML") />
<cfset session.printbody = arguments.event.getValue("printbody") />
</cfif>
<!--- if retval has some data, we must have been invoked by the saveimageas js function --->
<cfif len(retval) gt 25><!--- our item was turned into a binary jpg... --->
<cfset session.printAssetts[assettID][currentimagenumber] = ToBase64(retval) />
</cfif>
<cfif currentimagenumber eq endimagenumber><!--- this was our last iteration. pass back a list of image IDs... --->
<!--- <cfset cookie[assettID] = serializeJSON(session.printAssetts[assettID]) /> --->
<!--- <cfset structdelete(session.printAssetts,assettID) /> --->
<cfset arguments.event.setValue("assettIDlist",structkeylist(session.printAssetts[assettid])) />
<cfset arguments.event.setValue("printbody",session.printbody) />
<cfset structdelete(session,"printbody") />
<cfset structdelete(session,"stChartXML") />
<cfelse>
<cfset arguments.event.setValue("chartXML",session.stChartXML) />
</cfif>
<cfset arguments.event.setValue("assettID",assettID) /><!--- pass this on for future calls... --->
</cffunction>
You are not logged in, so your subscription status for this entry is unknown. You can login or register here.
As both a ModelGlue and FusionCharts user, this post is very handy indeed. The app you link to is very nice too.
