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):
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.


email: godds@scottlogic.co.uk
I can’t thank you enough for this! It was driving me nuts. This is by far the best solution I’ve seen and will work with my application.
G8, but how can we generate the stacked chart more than 3 stacks and one line chat. Is it possible?
You can stack as many series as you want; just add more series as children of the ColumnSet. To add a line to the chart, simply add a LineSeries as a sibling of the ColumnSet. For more information on the basics of stacking, see Stacking Charts. For more information on combining different series types, see Using Multiple Data Series.
hey ..
thanks for the cool solution for this issue … when i trying to see the source[View Source] getting .. Error 404 – Not Found..
Can u tell how i can c the source
- RTY
^I m alive ?
The source code is available here.
This alternative worked for me:
on the <mx:ColumnChart line
dataTipFunction="formatTooltip"
then
private function formatTooltip(theObject:Object):String {
return theObject.element.yField + "” + theObject.item[theObject.element.yField];
} }
Graham, Thanks a for the solution.
Hi Graham,
Thanks for posting this fix. However, when I try to implement, I get an error to the tune of “ype Coercion failed: cannot convert ColumnSet@568a479 to mx.charts.chartClasses.IStackable2.”
Any thoughts on what I’m doing wrong?
Thanks for your post!
Daniel
Hi Daniel,
I would have to see the code for how you are setting up your sets/series to be able to give more information. However, the error message suggests that you may be trying to stack sets, which I believe cannot be done. I’d recommend comparing your implementation to the example source code to see if there are any glaring differences that may suggest where the problem lies, or alternatively the Adobe documentation about stacking charts may clarify how you can and can’t use sets to stack and cluster in charts.
Hope this helps,
Graham
Hi Graham
Was using your solution for a while (thanks for that), but figured a new approach that needs no extended classes, just the code below:
private function myDataTipFunction(hitData:HitData):String
{
var itemsDictionary:Dictionary = new Dictionary();
var total:Number = 0;
//hitData.item holds name value pair for each dataProvider row
for(var property:Object in hitData.item){
itemsDictionary[property] = hitData.item[property];
}
// Have to get instance of hitData.element (a ColumnSeries) to
// access element.series.stacker.series which holds the yField values e.g. toys.
// Somehow hitData.element.series.stacker is not accessible directly.
var series:ColumnSeries = ColumnSeries(hitData.element);
for( var key:Object in itemsDictionary){
for( var i:int = 0; i < series.stacker.series.length; i++){
if(series.stacker.series[i].yField == key.toString()){
total += itemsDictionary[key];
}
}
}
// You've now got your total value!
Regards
Brian
[...] code is based on an example on Graham Odds blog which solves the same problem in a different and slightly longer way (ive been using it for a [...]
Hi Brian,
Thanks for that code – looks like a really good solution if you want to avoid custom classes. The only very minor issue I can see is that you are building up the totals value every time the function is called. This could cause a small performance hit in extreme cases but in general it will be absolutely fine.
It’s nice to see you’re using the C#/Flex combination. I’d have thought most C# developers were shifting towards Silverlight these days (like my collegues Colin and Gergely).
Graham
Hi Graham
Theres actually quite a lot of .Net/Flex/Air dev going on (integration tools like WebOrb and FluorineFx and are quite big). Mostly I think its because people were’nt too confident about Silverlight when it came out first, although its probably improved a lot by now.
Thanks Graham. This not only helps me with the tooltips, but served as a good introduction to classes and namespaces.
Graham,
Thanks so much for your work. I shopped the net looking for the solution to this problem. Yours is far and away the most elegant. I have a problem im sure is a simple solution however I cant see it. Im trying to incorporate a line series with a columnSet. Columns work fine but the lineseries throws an error which im sure lies with your statement above.
“You can stack as many series as you want; just add more series as children of the ColumnSet. To add a line to the chart, simply add a LineSeries as a sibling of the ColumnSet”.
I dont understand “sibling”.
Can you explain how I add a lineseries?
Your help is greatly appreciated!
thanks in advance
Geoff
USA
Heres my partial code block.
private function myDataTipFunction(hitData:HitData):String
{
var series:ColumnSeries = ColumnSeries(hitData.element);
var item:ColumnSeriesItem = ColumnSeriesItem(hitData.chartItem);
var quarter:Object = xAxis.formatForScreen(item.xValue);
var value:Number = Number(item.yValue) – Number(item.minValue);
var myComment:String = hitData.item.comment;//comments
return “” + series.displayName + “\n” +
“——————\n” +
“” + quarter + “\n” +
“——————\n” +
“Value:“+value+”\n” +
“——————\n”+
“Comments:\t\t\n” + myComment;
}
]]>
Hi Geoff,
What I mean by sibling is that the LineSeries will have to be alongside the ColumnSet rather than within it. However, I believe this is not your problem. The custom data tip function is set at the chart level rather than the series level, so it applies to all series in the chart. Therefore, the line of code where you cast to a ColumnSeries will throw an error when the data tip function is invoked as a result of mousing over the LineSeries. You need to change the data tip function to check the type of the element, i.e. something along the lines of:
I hope this helps.
Regards,
Graham
Graham,
You rock! Using the if else in the hitData is what I was trying however I am rather new to all this and could not work out the hitData.element bit. Its late so tomorrow I will implement your direction and see what happens.
Thanks much for your help.
Geoff
Graham,
Outstanding job! This code popped right in and works great. Thank you so much for this valuable information. I doubt, but I hope I can help you out in some small way in the future.
Thanks much
Geoff
USA
goflashman@yahoo.com
Graham,
is it possible that I can add multiple line series to this chart with these lines tied to a single axis renderer placed on the right side, ranging from 0-600 lets say.
Thanks,
Geoff
Geoff, yes, this is relatively straightforward to achieve. See Adobe’s using multiple axes and positioning chart axes help articles.
Regards,
Graham
Hi Graham,
I tried these approaches. Im simply trying to tie lineseries “two” to the axis with label {v4}?
This is what I have:
Thank you for the submit, i loved reading it. I do not agree with it all however it was a great publish.