A couple of years ago, I created a control that took chart data and used the Google Chart API to create a custom chart image inside Salesforce. That worked nicely, but I was happy to see that Salesforce finally added charting elements to visualforce in Winter '12.
Once I was added to the charting pilot, I spent some time exploring the new elements. Generally, it is a good start, but there are several features I would like to see (like customizable tooltips), and there are a few bugs that need to be cleaned up (for example, long labels are chopped when displayed in slice tooltips).
When I finished exploring and started creating some of these charts for a customer, I realized that most of the code behind for gathering and displaying chart data is almost identical. I like modular code, so I saw this as an opportunity to create a wrapper control that can take a few parameters and create the chart for you without having to write new apex/soql.
This code sample creates a pie chart when you give it the following parameters:
- Field - The field name (standard or custom) that is grouped into pie slices.
- Object - The object (standard or custom) that includes the above field.
- Conditions - A SOQL string that contains conditions for the query (if needed).
- Title - A title to display above the chart.
- Height - The height of the chart area.
- Width - The width of the chart area.
This control takes care of the null group (no value set), empty dataset, and allows you to add a good range of conditions. Other than that, it is pretty simple. Once set up though, it is easy to add some more bells and whistles. For example, in a later version, I added the ability to reference lookup fields for labels, and the ability to use the data with different chart types.
Step one:
Create an apex class Chart.cls to hold the code for the visualforce components:
public with sharing class Chart {
// constructor
public Chart() { }
// set up properties to get the values from the calling vf page
public string fld { get; set; }
public string obj { get; set; }
public string conditions { get; set; }
public string title { get; set; }
public string width { get; set; }
public string height { get; set; }
// class to hold data for the display
public class ChartData {
public String label { get; set; }
public Integer value { get; set; }
public ChartData(String label, Integer value) {
this.label = label;
this.value = value;
}
}
private List Counts;
public List getCounts() {
if (Counts == null) {
if (fld !=null && obj != null) {
Counts = new List();
// construct the query for the results. add escape chars to conditions
string strQuary = 'SELECT Count(Id), ' + fld + ' FROM ' + obj;
if (conditions != null && conditions.length() > 0)
strQuary += ' WHERE ' + conditions.replaceall('\'', '\\\'');
strQuary += ' GROUP BY ' + fld;
AggregateResult[] groupedResults = Database.query(strQuary);
if (groupedResults == null || groupedResults.IsEmpty())
// no results, just create a dummy chart
Counts.add(new ChartData('No Data', 1));
else
for (AggregateResult ar : groupedResults)
// construct a list of the counts
if (string.valueof(ar.get(fld)) == null)
// capture the count for no value
Counts.add(new ChartData('No Value', integer.valueof(ar.get('expr0'))));
else
Counts.add(new ChartData(string.valueof(ar.get(fld)), integer.valueof(ar.get('expr0'))));
}
}
return Counts;
}
}
Step two:
Create a component Chart.component:
<apex:component controller="Chart">
<apex:attribute name="Field" type="String" required="true" assignTo="{!fld}"
description="The field name (standard or custom) that is grouped into pie slices." />
<apex:attribute name="Object" type="String" required="true" assignTo="{!obj}"
description="The object (standard or custom) that includes the above field." />
<apex:attribute name="LookupField" type="String" required="false" assignTo="{!lookupFld}"
description="If the DataField is a lookup, this is the field holds the values that the lookup resolve to." />
<apex:attribute name="LookupObject" type="String" required="false" assignTo="{!lookupObj}"
description="If the Field is a lookup, this is the object that LookupField is in." />
<apex:attribute name="Conditions" type="String" required="false" assignTo="{!conditions}"
description="A SOQL string that contains conditions for the query (if needed)." />
<apex:attribute name="Title" type="String" required="false" assignTo="{!title}"
description="A title to display above the chart." />
<apex:attribute name="height" type="String" required="false" assignTo="{!height}" default="220"
description="The height of the chart area." />
<apex:attribute name="width" type="String" required="false" assignTo="{!width}" default="300"
description="The width of the chart area." />
<span style="font-size: 14px; font-weight: bold;">{!Title}</span><br/>
<apex:chart height="{!height}" width="{!width}" data="{!Counts}">
<apex:pieSeries dataField="value" labelField="label"/>
<apex:legend position="right"/>
</apex:chart>
</apex:component>
Step three:
Include the component in your visualforce page like that:
<c:Chart Field="Status__c" Object="CustomObj__c" Conditions="IsArchived__c = False" title="My Status" />