| Tweet |
Something I've been working on for a while now is using a line chart for just this purpose. The key elements of this are:
- List all of the Stages for the Opportunity - if record types are in use, this should be the Stages applicable to that record type
- Plot a green line and markers for Stages that have been completed (or skipped, if the Opportunity has jumped into the process at an advanced staged
- Plot a red line and markers for Stages still to come
<apex:page standardController="Opportunity" extensions="OpportunityStatusChartController" showheader="false" standardstylesheets="false" sidebar="false">
<head>
<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>
<link rel="stylesheet"
href="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css" />
</head>
<body class="claro">
<apex:form id="frm">
<apex:actionFunction name="reloadWithStages" action="{!reload}" />
<div id="test1" style="width: 100%; height: 150px;"></div>
<apex:outputPanel layout="block" id="vals" style="display:none">
<apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
<apex:inputText value="{!valsText}" required="false" id="back"/>
</apex:outputPanel>
</apex:form>
<apex:outputField value="{!Opportunity.StageName}" rendered="false"/>
<script>
function reload()
{
var ele=document.getElementById('{!$Component.frm.stages}');
var idx=0;
var valText='';
for (idx=0; idx<ele.length; idx++)
{
valText+=ele.options[idx].text + ':';
}
var backele=document.getElementById('{!$Component.frm.back}');
backele.value=valText;
reloadWithStages();
}
<apex:outputPanel layout="none" rendered="{!NOT(loadOnce)}">
dojo.require("dojox.charting.Chart2D");
dojo.require("dojox.charting.axis2d.Default");
dojo.require("dojox.charting.plot2d.Default");
dojo.require("dojox.charting.plot2d.StackedLines");
dojo.require("dojox.charting.plot2d.Columns");
dojo.require("dojox.charting.plot2d.Bars");
dojo.require("dojox.charting.plot2d.ClusteredBars");
dojo.require("dojox.charting.plot2d.StackedBars");
dojo.require("dojox.charting.plot2d.Bubble");
dojo.require("dojox.charting.plot2d.Grid");
dojo.require("dojox.charting.plot2d.Pie");
dojo.require("dojox.charting.themes.PlotKit.green");
dojo.require("dojox.charting.action2d.Highlight");
dojo.require("dojox.charting.action2d.Magnify");
dojo.require("dojox.charting.action2d.MoveSlice");
dojo.require("dojox.charting.action2d.Shake");
dojo.require("dojox.charting.action2d.Tooltip");
dojo.require("dojox.charting.widget.Legend");
dojo.require("dojo.colors");
dojo.require("dojo.fx.easing");
dojo.require("dojo.date.stamp");
dojo.require("dojo.date.locale");
makeCharts = function(){
var myMap={
<apex:repeat value="{!labels}" var="label">
'{!label.idx}':'{!label.text}'<apex:outputText value="," rendered="{!label.idx!=labelCount}"/>
</apex:repeat>
};
var chart1 = new dojox.charting.Chart2D("test1");
chart1.setTheme(dojox.charting.themes.PlotKit.green);
chart1.addPlot("default", {type: "Default", lines: true, markers: true, tension:2});
chart1.addAxis("x",
{
majorTick: {stroke: "black", length: 3},
majorTickStep:1,
minorTicks: false,
microTicks: false,
min: 0,
max: {!labelCount},
rotation:30,
font: "6pt Tahoma",
labels: [
<apex:repeat value="{!labels}" var="label">
{value: {!label.idx}, text:'{!label.text}'}<apex:outputText value="," rendered="{!label.idx!=labelCount}"/>
</apex:repeat>
]
});
chart1.addSeries("cleared",
[
<apex:repeat value="{!doneStageNumbers}" var="stage">
{x: {!stage}, y: 2},
</apex:repeat>
]);
chart1.addSeries("todo",
[
<apex:repeat value="{!todoStageNumbers}" var="stage">
{x: {!stage}, y: 2},
</apex:repeat>
],
{plot: "default", stroke: {color:"#FE2E2E"}}
);
var myMap={
<apex:repeat value="{!tooltips}" var="tooltip">
"{!tooltip.idx}": "{!tooltip.text}",
</apex:repeat>
};
var anim1a = new dojox.charting.action2d.Magnify(chart1, "default");
var anim1b = new dojox.charting.action2d.Tooltip(chart1, "default",
{
text : function(o) {
return (myMap[o.x])
}
});
chart1.render();
};
</apex:outputPanel>
dojo.addOnLoad(
<apex:outputPanel layout="none" rendered="{!loadOnce}">
reload
</apex:outputPanel>
<apex:outputPanel layout="none" rendered="{!NOT(loadOnce)}">
makeCharts
</apex:outputPanel>
);
</script>
</body>
</apex:page>
Essentially this page has two distinct functions - the first to render a hidden form containing an input field for the StageName field, and then submit the values back via javascript, while the second is to actually render the chart based on the values. The completed stages are plotted as a separate series to those that remain, to allow that part of the chart to be rendered in a different colour and markers. I've also thrown in a tooltip on each of my markers so that I can display some help/explanation text about each of the stages.
The controller is quite a simple one:
public class OpportunityStatusChartController
{
public List<Tuple> labels {get; set;}
public List<Tuple> tooltips {get; set;}
public List<Integer> doneStageNumbers {get; set;}
public List<Integer> todoStageNumbers {get; set;}
public Integer labelCount {get; set;}
public Boolean loadOnce {get; set;}
public String valsText {get; set;}
private Opportunity opp;
public OpportunityStatusChartController(ApexPages.StandardController std)
{
opp=(Opportunity) std.getRecord();
loadOnce=true;
}
public PageReference reload()
{
init();
loadOnce=false;
return null;
}
public void init()
{
labels=new List<Tuple>();
tooltips=new List<Tuple>();
doneStageNumbers=new List<Integer>();
todoStageNumbers=new List<Integer>();
labelCount=0;
Boolean done=false;
labels.add(new Tuple(labelCount++, '.'));
for (String val : valsText.split(':'))
{
if (!done)
{
doneStageNumbers.add(labelCount);
}
else
{
todoStageNumbers.add(labelCount);
}
if (val==opp.StageName)
{
done=true;
todoStageNumbers.add(labelCount);
}
labels.add(new Tuple(labelCount, val));
toolTips.add(new Tuple(labelCount, 'Help for ' + val + ' stage'));
labelCount++;
}
labels.add(new Tuple(labelCount, '.'));
}
public class Tuple
{
public Integer idx {get; set;}
public String text {get; set;}
public Tuple(Integer inIdx, String inText)
{
idx=inIdx;
text=inText;
}
}
}
I can then change the record type of the Opportunity, save it and the chart updates accordingly:
You can see a live example of this on my demo site - simply click on one of the Opportunities to see the progress chart.
