Just before Dreamforce, Dave Carroll and I hosted the Winter ’13 developer preview webinar talking about what’s new to the platform. One of the major features that became generally available was Visualforce Charting. Visualforce Charting is an easy way to create customized business charts, based on data sets you create directly from SOQL queries or by building the data set in JavaScript. Sandeep Bhanot posted an article a while back going over how to build layered line and bar charts with a controller, and in this article I am going to go over new chart types and techniques.

Visualforce charts are rendered client-side using JavaScript. This allows charts to be animated and, on top of that, chart data can load and reload asynchronously which can make the page feel more responsive. In this article, I want to highlight a few of the new chart types that were recently released, and demonstrate some of the advanced rendering capabilities.

The Gauge Chart

You can build this data set similarly to how you would build a data set for a pie chart. The main difference is that there is only one list element to build the complete chart. In the example below, I am summarizing the total amount of opportunities closed this month related to an account.

public class GaugeChartController {
    public String acctId {get;set;}

    public GaugeChartController(ApexPages.StandardController controller){
        acctId = controller.getRecord().Id;
    }

    public List<gaugeData> getData() {
    	  Integer TotalOpptys = 0;
    	  Integer TotalAmount = 0;
          Integer thisMonth = date.Today().month();

          AggregateResult ClosedWonOpptys = [select SUM(Amount) totalRevenue, CALENDAR_MONTH(CloseDate) theMonth, COUNT(Name) numOpps
                                   from Opportunity
                                   where AccountId =: acctId
                                   and StageName = 'Closed Won'
                                   and CALENDAR_MONTH(CloseDate) =: thisMonth
                                   GROUP BY CALENDAR_MONTH(CloseDate) LIMIT 1];

          List<gaugeData> data = new List<gaugeData>();
          data.add(new gaugeData(Integer.valueOf(ClosedWonOpptys.get('numOpps')) + ' Opptys', Integer.valueOf(ClosedWonOpptys.get('totalRevenue'))));
          return data;
     }

    public class gaugeData {
        public String name { get; set; }
        public Integer size { get; set; }

        public gaugeData(String name, Integer data) {
            this.name = name;
            this.size = data;
        }
    }
}

The structure for building this chart is almost identical to what we’ve seen already with the previously existing chart types. To populate the chart with data, you need to build a list with an inner class (aka wrapper class). One thing to note, your wrapper class must have a ‘name’ field if you want to display a tooltip on hover. Once your data set is constructed, you can output it on your Visualforce page  with a few simple components:

 <apex:page standardController="Account" extensions="GaugeChartController">
    <apex:chart name="MyChart" height="300" width="450" animate="true" data="{!data}">
        <apex:axis type="Gauge" position="gauge" title="Closed Won Opportunities"  minimum="0" maximum="30000" steps="10"/>
        <apex:gaugeSeries dataField="size" donut="50" colorSet="#78c953,#ddd"/>
    </apex:chart>
 </apex:page>

The chart to the left is what the output looks like initially. If you try to use CSS to manipulate the width and/or height frame, you will get to watch the frame dynamically continue to cut off your text. Thankfully the frame problem can be alleviated using a little bit of JavaScript.

First give your chart a name. This makes the component recognizable as a JavaScript object for additional configurations or dynamic operations. Note this name must be unique across all chart components, and if the encompassing top-level component (<apex:page> or <apex:component>) is namespaced, the chart name will be prefixed with it (ie. MyNamespace.MyChart).

In my page above I named the chart “MyChart.” I was able to manipulate all of the axes by using a simple on() method call. The code below is the snippet that I put into my page, and the picture below that displays the new results.

     <script>
        MyChart.on('beforeconfig', function(config) {
            config.axes[0].margin=-10;
        });
    </script>

 

The Radar Chart

The radar chart is a unique chart to build. In order to create this data set, you will need to create a list of maps rather than using a wrapper class. For my example, I am plotting customer satisfaction ratings related to an account. Each of these ratings are stored as number fields to plot on a circle. In Winter ’13 you can now query field sets, so I have put all of the rating fields from my account into a field set (named RadarSet) to build out my chart.

One thing to note, you don’t have to use a field set to build this  chart. I used a field set because I thought it would be more elegant to dynamically query for and generate chart data. You could build your own hardcoded SOQL query if you wanted, but by using field sets you can easily change the fields in the query without having to change the code, or you could also take this code and make another radar chart quickly off of a different field set.

public class RadarDemo{
    public List<Map<Object,Object>> data = new List<Map<Object,Object>>();
    public String acctId {get;set;}

    public RadarDemo(ApexPages.StandardController controller){
        acctId = controller.getRecord().Id ;
    }

    public List<Schema.FieldSetMember> getFields() {
        return SObjectType.Account.FieldSets.RadarSet.getFields();
    }

    public List<Map<Object,Object>> getData() {
        String query = 'SELECT ';
        List<String> fieldNames = new List<String>();

        for(Schema.FieldSetMember f : getFields()){
            query += f.getFieldPath() + ', ';
            fieldNames.add(f.getFieldPath());
        }
        query += 'Id, Name FROM Account where Id=\'' + acctId + '\' LIMIT 1';

        SObject myFieldResults = Database.Query(query);
        Schema.DescribeSObjectResult R = myFieldResults.getSObjectType().getDescribe();
        Map<String, Schema.SObjectField> fieldMap = R.fields.getmap();

        //creates a map of labels and api names
        Map<String,String> labelNameMap = new Map<String,String>();
        for(String key : fieldMap.keySet()){
             labelNameMap.put(fieldMap.get(key).getDescribe().getName(), fieldMap.get(key).getDescribe().getlabel());
        }

        //creates a map of labels and values
        for(String f : fieldNames){
            String fieldLabel = labelNameMap.get(f);
            String fieldValue = String.valueOf(myFieldResults.get(f));

            Map<Object, Object> m = new Map<Object,Object>();
            m.put('field', fieldLabel);
            m.put('value', fieldValue);
            data.add(m);
        }

        return data;
    }
}

To explain the code above, I have broke down the order of operations in my class as follows:

  • Build a dynamic SOQL statement using fields from my RadarSet field set (lines 9-21)
  • Save all of the field set API names to a list for reference to use with the describe maps (line 19)
  • Generate describe information based off of query results (lines 23-25)
  • Create a map of labels and API names (lines 28-31)
  • Use field set list to map labels to values returned from the query (line 30)
  • Store these to a map of objects for each data point, and add that to the overall map which will populate the chart on the page (lines 34 to 42)

The Visualforce page is again pretty trivial. In addition, some of my field labels were a little long and were cut off by the frame again. The JavaScript hack above didn’t do the trick, but there is a static ID associated with the frame that cuts off the chart, so I was able to apply sizing to that explicitly so that the chart would render properly.

<apex:page controller="RadarDemo">
	<style>
	     #vfext4-ext-gen1026 {
		width:800px !important;
	     }
	</style>  

        <apex:chart name="myChart" height="600" width="650" legend="false" data="{!data}">
             <apex:legend position="left" />
             <apex:axis type="Radial" position="radial"/>
             <apex:radarSeries title="Customer Satisfaction" xField="field" yField="value" tips="true" opacity="0.4"/>
        </apex:chart>
</apex:page>

The Scatter Chart with JavaScript Remoting

The final graph I’m going to go over is the scatter series chart. In addition to feeding data directly into the chart from a standard getter method in your Apex class, you can provide the component with the name of a JavaScript function that generates the data. The actual JavaScript function is defined in or linked from your Visualforce page and has the opportunity to manipulate the results before passing it to your chart, or to perform other user interface or page updates.

In my example I am querying for all opportunities related to a campaign and plotting them on a graph with the x-axis displaying the expected revenue value and the y-axis displaying the actual amount. By default it will display all opportunities, but I am using an actionFunction on a selectList to rerender the chart to display records of a specific lead source.

First, let’s take a look at my JavaScript function on my Visualforce page constructing the chart data.

    function getRemoteData(callback) {
        ScatterChartController.getRemoteScatterData(function(result, event) {
            var sourceType = $('[id*="leadSource"]').val();
            var newResultList = new Array();
            var index = 0;

            if(event.status && result && result.constructor === Array) {
               	for(i = 0; i < result.length; i++){
                    if(result[i].type == sourceType ){
                        newResultList[index] = result[i];
	           	index++;
	            } else if (sourceType == 'All') {
           		newResultList[index] = result[i];
                        index++;
                    }
           	}
                callback(newResultList);
            }
        });
    }

In my function I first call a remote action, a method inside my Apex class, called getRemoteScatterData. After I check that the result returned from the remote action is valid, I loop through the results to either save everything if ‘All’ is selected or only the opportunities with the selected lead source. I used a jQuery selector to grab the selected value because Visualforce appends extra characters to the id of the selectList.

By default my selectList shows everything, but you could easily add in more JavaScript functionality like adding a show/hide to only show the chart after a value is selected. There is a lot of flexibility with how you want the chart to render in here.

My remote action (displayed below) is very simple in comparison. It runs the query on all opportunities related to the campaign and then saves the result to a list using my wrapper class. The values being sent to my wrapper class are stored to variables ‘name’, ‘type’, ‘expected’, and ‘amount’ respectively. This is why in my JavaScript function I can reference result[i].type.

    @RemoteAction
    public static List getRemoteScatterData() {
        List data = new List();
        List opps = [select Name, Id, Amount, ExpectedRevenue, LeadSource from Opportunity where CampaignId =: campId];
        for(Opportunity opp : opps){
       		data.add(new scatterData(opp.Name, opp.LeadSource,Integer.valueOf(opp.ExpectedRevenue), Integer.valueOf(opp.Amount)));
        }
        return data;
    }

Back on my Visualforce page, I have two main sections other than the JavaScript that I want to breakdown. First, we have the chart. This pulls in the ‘newResultList’ returned from the callback in my getRemoteData function. The component does some behind-the-scenes magic on the back end, so for proper syntax you only need to reference the name of the function you are calling. Map the associated attributes with the appropriate variables in your wrapper class, and add an outputPanel around the chart for advanced rendering.

    <apex:outputPanel id="mychartpanel">
        <apex:chart height="530" width="700" animate="true" id="myChart" data="getRemoteData">
        <apex:scatterSeries xField="expected" yField="amount" markerType="circle" markerSize="3" markerFill="#551A8B" />
        <apex:axis type="Numeric" position="bottom" fields="expected"  title="Expected Revenue" grid="true" maximum="50000" minimum="0">
            <apex:chartLabel />
        </apex:axis>
        <apex:axis type="Numeric" position="left" fields="amount"  title="Actual Amount" grid="true" maximum="50000" minimum="0">
            <apex:chartLabel />
        </apex:axis>
        </apex:chart>
    </apex:outputPanel>

In order to rerender this chart, I created an actionFunction that gets called onChange of the list value. I had to do this because there is no reRender attribute on the selectList tag, but the actionFunction action is a simple method in my class (public PageReference NoOp()) that does nothing except return null. When the NoOp method finishes, the chart will rerender and call the JavaScript function again using the new value in the list to sort the chart points.

    <apex:actionFunction action="{!getNoOp}" name="getMyRemoteData" rerender="mychartpanel" />
    <apex:selectList id="leadSource" onchange="getMyRemoteData();" value="{!source}" multiselect="false" size="1"  >
        <apex:selectOptions value="{!sources}" />
    </apex:selectList>

Try it yourself!

Visualforce charting enables you to quickly generate rich, animated charts, without having to use a 3rd party system for the meat of it. There are definitely some kinks in the process right now being that it just went GA in Winter ’13, but I’m sure we’ll be seeing updates and enhancements with upcoming releases in the future.

I have done a few examples here, and there are more examples elsewhere on developer.force.com, but there’s no better way to learn than to try it out yourself. In addition to the examples I have put here, I have also uploaded a full sample pack on GitHub including a complete Apex controller and Visualforce page for each chart type. Take a look at those examples, and feel free to reach out to me via twitter if you have any questions.

tagged , , , , , , , , , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • Anonymous

    Thanks Samntha for sharing nice information regarding Charts….

    • http://twitter.com/samantha_ready Samantha Ready

      Thanks for reading :)

      • Ram

        Hi Samantha,

        Is there any support for rendering charts as PDF? I just get a blank area for the chart whenever rendering in PDF.

        Thanks,
        Ram

        • http://twitter.com/samantha_ready Samantha Ready

          Hi Ram. I do not believe there is currently support for rendering a chart in a PDF being that they are so JavaScript heavy.

          • Ram

            Thanks, Samantha. I’ve been using image charts for PDF rendering. BTW, very informative article – much appreciated!

  • http://twitter.com/forceguru Ankit Arora

    I tried this long back (when we need to request to enable this feature) and got stuck with re-rendering issue. So it would be interesting to see what we can do with this now.

    http://www.salesforce.com/us/developer/docs/pages/Content/pages_charting_limitations.htm

    • http://twitter.com/samantha_ready Samantha Ready

      Yes, rerendering is a new feature as of the Winter ’13 release. I definitely encourage you to check it out!

      • http://twitter.com/forceguru Ankit Arora

        Super awesome!

  • http://twitter.com/sandeeptrick Sandeep

    We tried VF charting when it was released as a beta version, however we noticed Vf charting does not support decimals and rounds the decimals which did not help in out chart which had values ranging betweeen 1 and 2.

    • http://twitter.com/samantha_ready Samantha Ready

      Keep a look out for decimal support in the upcoming releases; I noticed that limitation as well. If all of your values are between 1 and 2, and you are looking to use VF charts, would it be feasible to graph all points multiplied by 10? Just thinking out loud…

  • http://twitter.com/zachelrath Zach McElrath

    Thanks Samantha! With the support for chart manipulation with JavaScript, we’re strongly considering using Visualforce Charting for our upcoming ‘chart’ component in our product Skuid (http://www.skoodat.com/skuid). Thanks for going into such depth with this!

    • http://twitter.com/samantha_ready Samantha Ready

      Thanks Zach for the read :) Glad to hear it looks like a good fit!

  • Jodi Randle

    Wow! This is awesome! A truly remarkable idea! Great for winter! Thanks for sharing this very informative post!

    -RDJacobs.com

  • http://twitter.com/abhinavguptas Abhinav Gupta

    Good post, using native charts is really simple and good from data binding standpoint.

  • Ashok kumar

    Thanks for very nice post :) Do you know how can one use scatter chart and clicking on point should open the record page?

  • Ravi Krishan

    I tried Gauge chart (copied the code). But It shows me following error
    Error: Unknown component apex:gaugeSeries
    It works with apex:pieSeries

    I am using API version 26.0
    What can be the issue..!

  • Ravi Krishan

    Resolved ..!! Issue was that Page version was 23.0 and i was trying to make it 26.0 by using attribute apiVersion on page

  • Eric Howard

    Great Article. Thanks. I have one question. How would you go about having multiple data series? Specifically, I am looking to something with the radar chart. For Example, you have customer satisfaction data for 2013 and 2014 and you want to see them both on the same chart.