Odds On Flex

Custom data tips in stacked Flex charts

March 24th, 2009

The number of frustrating decisions in Flex’s charting API is minimal, but high up on my list is a strange decision that prevents developers from accessing information that is frequently desirable for custom data tips in stacked area, bar and column charts. The default data tips for stacked charts display, amongst other things, the total of the values for all the series at a specific point along the x-axis and the percentage the highlighted segment forms of this total, as shown in the following example (source code):
You must have Adobe Flash Player 9 installed to view this application.
If you wanted to customise the data tips and still include information such as the total and percentage values then you would be out of luck. This why I consider the decision to give the necessary information protected scope in the relevant classes in the charting API as strange. Fortunately circumnavigating this issue only requires a little bit of dirty work…

But first, a brief diversion to provide a little background…
The code used to create a stacked column chart generally takes the following form:

<mx:ColumnChart type="stacked">
    <mx:series>
        <mx:ColumnSeries />
        ...
    </mx:series>
</mx:ColumnChart>

This is in fact a shortcut syntax. The ColumnChart automatically wraps the array of series into a ColumnSet with type “stacked”, as it is the ColumnSet class that takes care of the stacking and associated behaviour rather than the ColumnChart class. The same pattern occurs in stacked AreaChart or BarChart. Consequently, the following MXML produces the same end result:

<mx:ColumnChart>
    <mx:series>
        <mx:ColumnSet type="stacked">
            <mx:ColumnSeries />
            ...
        </mx:ColumnSet>
    </mx:series>
</mx:ColumnChart>

When wanting to create more complex combinations of stacking, clustering and overlaying in charts, this can only be achieved by explicitly introducing (and nesting) sets – as in the second code snippet. For further details on this see Adobe’s Stacking Charts article.
Diversion over…

To be able to display customised data tips that include information relating to the stacking in a stacked ColumnChart, BarChart or AreaChart we can take one of two approaches. Both approaches involve extending the ColumnSet, BarSet or AreaSet class (depending on the desired chart type) as it is the protected-scoped negTotalsByPrimaryAxis, posTotalsByPrimaryAxis, stackedMaximum and stackedMinimum properties that contain the interesting information. The first approach is to override the formatDataTip method in the extended class, resulting in something along the following lines:

/**
 * ColumnSet extension to introduce custom data tips.
 */
public class ColumnSet extends mx.charts.series.ColumnSet
{
    /**
     * @inheritDoc
     */
    override protected function formatDataTip(hd:HitData):String
    {
        // build up the custom data tip
        var tip:String = "";
        ...
        return tip;
    }
}

However, I think that this approach is slightly short-sighted because it means that for every customisation of the data tips you would create a new extension. The other, and in my opinion more reusable, approach is to expose the aforementioned properties publicly in extensions to the ColumnSet, BarSet and AreaSet classes. By creating these extensions, the standard approach to customising data tips can be used, i.e. a method with the appropriate signature is passed to the chart’s dataTipFunction property. The classes resulting from this approach would be something along the following lines:

/**
 * ColumnSet extension to expose the stack totals for public 
 * use, e.g. in a data tip function.
 */
public class ColumnSet extends mx.charts.series.ColumnSet
{        
    /**
     * @see StackedSeries.posTotalsByPrimaryAxis
     */
    public function get positiveTotalsByAxis():Dictionary
    {
        return posTotalsByPrimaryAxis;
    }
 
    ...
}

The minor downside to this approach is that the shortcut syntax for achieving stacked series, as used in the first example and explained above, cannot be used. The explicit syntax must be used to ensure that the extended version of the relevant StackedSeries extension is used.

The following example uses the approach presented above to provide customised data tips in the chart. The source code for the example includes the necessary extensions to the ColumnSet, BarSet and AreaSet classes, so feel free to use them.
You must have Adobe Flash Player 9 installed to view this application.

Save Flex chart as image

March 4th, 2009

The ability to allow a user to save a Flex chart, or in fact any Flex UI component, as an image has popped up on my radar several times over the last few years.  Solutions to the problem have generally involved producing a pop-up window with the UI component as an image that the user can then save, either by bouncing the information off a server (James Ward – RIA Cowboy and Flex Cookbook) or interacting with JavaScript (Doug McCune).  However, additions made to the framework in Flex 3 combined with new features of Flash Player 10 have made these cumbersome techniques redundant.  It is now possible to provide this functionality directly from your Flex application in two simple steps.

The first step involves capturing the UI component’s bitmap information.  The Flex 3 API introduced the ImageSnapshot class specifically to simplify this process.  The following line of code is sufficient to capture the image data:

ImageSnapshot.captureImage(myChart);

However, we are able to control the image capturing more precisely by using some of the method’s optional parameters. These allow us to specify the target resolution in dots per inch and the image encoder to use (the Flex 3 API provides a PNGEncoder and a JPEGEncoder).  So, for example, the following line of code would capture a chart as a PNG image at a  resolution of 300dpi:

ImageSnapshot.captureImage(myChart, 300, new PNGEncoder());

Now that we have captured the image data all that remains is the second, and last, step: saving the image data to the user’s file-system. Flash Player 10 introduced a number of changes to its security sandbox, principally the ability to programmatically prompt the user
to save a file to their file-system. This is done using the FileReference class, as shown in the following lines of code:

var file:FileReference = new FileReference();
file.save(image.data, "chart.png");

So, putting the steps together results in a method along the lines of the following code snippet:

/**
 * Attempts to save the chart to the user's file-system.
 */
private function saveChart():void
{
    var image:ImageSnapshot = ImageSnapshot.captureImage(myChart, 300, new PNGEncoder());
    var file:FileReference = new FileReference();
    file.save(image.data, "chart.png");
}

The application below shows this code in action. The values in the data grid can be changed,
with the changes reflected in the chart (just to show that I’m not cheating).
You must have Adobe Flash Player 10 installed to view this application.

The source code is now available.