As usual, the Visualforce page pulls in the cross domain Dojo build from Google, and delegates responsibility for the production of the chart to a custom component:
<apex:page > <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" djConfig="parseOnLoad: true, modulePaths: { 'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' } "> </script> <apex:sectionheader title="Dojo Stacked Bar Chart"/> <c:DojoStackedBarChart name1="Inside SLA" series1="1,3,1" colour1="green" click1="http://www.google.com" name2="Escalated" series2="3,8,5" colour2="yellow" click2="http://www.google.com" name3="Breached SLA" series3="4,13,8" colour3="red" click3="http://www.google.com" divId="stackedBars" title="SLA Status" /> </apex:page>each stacked bar in the chart is set up via the series/colour/click/name indexed parameters. I haven't made this a live page, but it would be straightforward to store the series1-3 values in a custom controller and add apex:inputField components that allowed the user to update them. Where this differs from previous posts is the click parameters - this allows the user to click on one of the stacked bars and be taken to another page. I've simply sent the user to www.google.com, but when I've used this technique in a real project the user is taken to a drill down page or report that shows the detail behind that particular stacked bar. The component markup is shown below:
<apex:component > <apex:attribute name="name1" type="String" description="Name of first series (first stack)"/> <apex:attribute name="series1" type="String" description="Comma seperated values for first series"/> <apex:attribute name="colour1" type="String" description="Colour of first series"/> <apex:attribute name="click1" type="String" description="The destination if the user clicks on the first series"/> <apex:attribute name="name2" type="String" description="Name of second series (second stack)"/> <apex:attribute name="series2" type="String" description="Comma seperated values for second series"/> <apex:attribute name="colour2" type="String" description="Colour of second series"/> <apex:attribute name="click2" type="String" description="The destination if the user clicks on the first series"/> <apex:attribute name="name3" type="String" description="Name of third series (third stack)"/> <apex:attribute name="series3" type="String" description="Comma seperated values for first series"/> <apex:attribute name="colour3" type="String" description="Colour of first series"/> <apex:attribute name="click3" type="String" description="The destination if the user clicks on the first series"/> <apex:attribute name="divId" type="String" description="ID of the div to store the chart in"/> <apex:attribute name="title" type="String" description="Title of the chart"/> <script type="text/javascript"> dojo.require("dojox.charting.Chart2D"); dojo.require("dojo.colors"); dojo.require("dojox.charting.themes.Tufte"); dojo.require("dojox.charting.widget.Legend"); makeCharts = function(){ var chart2=new dojox.charting.Chart2D("{!divId}"); // use the Tufte theme as this gives transparent background chart2.setTheme(dojox.charting.themes.Tufte); chart2.addPlot("default", {type: "StackedBars"}); chart2.addSeries("{!name1}", [{!series1}], {fill: "{!colour1}"}); chart2.addSeries("{!name2}", [{!series2}], {fill: "{!colour2}"}); chart2.addSeries("{!name3}", [{!series3}], {fill: "{!colour3}"}); chart2.addAxis("x", {includeZero: true}); chart2.connectToPlot("default", this, function(evt) { if(!evt.shape||(evt.type != 'onclick')){ return; } var link=""; if (evt.run.name=="{!name1}") { link="{!click1}"; } else if (evt.run.name=="{!name2}") { link="{!click2}"; } else if (evt.run.name=="{!name3}") { link="{!click3}"; } if (""!=link) { window.open(link, "_blank"); } } ); chart2.render(); var legend1 = new dojox.charting.widget.Legend({chart: chart2}, "legend1"); }; dojo.addOnLoad(makeCharts); </script> <div id="{!divId}" style="width: 600px; height: 200px;"></div> <div id="legend1"></div> <div style="margin-top: 40px; font-size:14px; font-weight:bold; width: 400px; text-align:center">{!title}</div> </apex:component>Most of this will be familiar to regular readers - create the chart, define the series, add plots and axes etc. The interesting part (for this week at least) is the function that handles the event when the user clicks on a stacked bar.
chart2.connectToPlot("default", this, function(evt) { if(!evt.shape||(evt.type != 'onclick')){ return; } var link=""; if (evt.run.name=="{!name1}") { link="{!click1}"; } else if (evt.run.name=="{!name2}") { link="{!click2}"; } else if (evt.run.name=="{!name3}") { link="{!click3}"; } if (""!=link) { window.open(link, "_blank"); } } );The bar can be identified based on the run.name attribute of the event and is simply matched up with the associated click parameter. The generated chart is shown below:
Just so I am clear in my head on this. SalesForce gives us some powerful and configurable reports, but it's weakness has always been the number of objects you can report against in one pass and also the values you can display. Does this mean that Dojo will free us from this constraint if implemented correctly? Also, does it export to PDF using either the built in renderAs="PDF" and the excellent Conga Composer?
ReplyDelete@Chris Bradshaw - yes, Dojo (and other JavaScript charting libraries) will plot the data that is presented to it, so you would be able to pull this data from multiple objects, and have multiple charts based on different object groups in the same page.
ReplyDeleteThe chart wouldn't appear if the page had the renderAs attribute set to PDF, as the chart is generated by client side JavaScript that rewrites the DOM, which isn't possible when a PDF format document is returned. Printing the page to PDF does retain the chart correctly though, in Google Chrome at least.