Flex Charting: Format Your Data Tips The Same As Your Axes
Posted by
Brad Wood
Oct 14, 2008 22:20:00 UTC
In case you are wondering, "Axes" is the plural form of "Axis" and it is pronounced "Axees" with a long e as in "see". Perhaps you have never pondered that one, but it's been driving me crazy. In this post I wanted to demonstrate a little experiment I did tonight to try and not duplicate as much formatting when it came to my charting data tips. I can already think of a few problems with it, but I wanted to try anyway.This basis of this post is my last post on how to format X and Y Axes for things like dates and money. When you set the showDataTips property of your chart to true, you can hover over a data point and get a little popup containing (minimally) the y value and x value. Optionally, if supplied, you will get the series names. If you gave a displayName property to your axes, they will show up too.
All that is pretty cool, but unfortunately no formatting is applied to your x value and y value-- even if you applied formatting via label functions to your x and y axis. Perhaps it is tolerable to not have a dollar sign in front of your money, but a Date object is obscene in its natural form. In his example, I have cast the dates in my ArrayCollection to Date objects to more closely match what I get out of a datetime column from SQL Server.
I really, really wish you could just flip a switch somewhere to format the x and y values in my data tip but I haven't found a way to do that. The way to control the formatting of your data tips is the dataTipFunction property in my LineChart. If we hop on over to the LiveDocs page on the ChartBase class we will find the signature for the dataTipFunction. (LineChart inherits the dataTipFunction from its super class CartesianChart, which in turn inherits it from its super class ChartBase).
[code]function dataTipFunction(hitData:HitData):String { ... }[/code]As soon as you start using a custom data tip function, all the existing formatting Flex was doing for you is thrown out the window, and you start from scratch. Luckily for us, if we dig out the LineSeries.as file from the Flex 3.1 SDk and scroll to about line 1666 (chill goes down spine) we will find the default formatDataTip function. It goes like this:
[code]private function formatDataTip(hd:HitData):String { var dt:String = ""; var n:String = displayName; if (n && n != "") dt += "<b>" + n + "</b><BR/>"; var xName:String = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).displayName; if (xName != "") dt += "<i>" + xName+ ":</i> "; dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen( LineSeriesItem(hd.chartItem).xValue) + "\n"; var yName:String = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).displayName; if (yName != "") dt += "<i>" + yName + ":</i> "; dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen( LineSeriesItem(hd.chartItem).yValue) + "\n"; return dt; }[/code]So, I said all that to say this. My goal was to create a generic data tip formatting function that would check each axis to see if they had a labelFunction and if so use it to format the x and y value. Just about every graph I have done so far uses some sort of special formatting on one or more axes and frankly I don't want to write a data tip formatting function for every single combination when most of the time I would be perfectly happy for my data tips to be formatted just like the axes. Here's what I came up with today after work:
[code]private function genericDataTipFormatter(hd:HitData):String { var dt:String = ""; var this_series:LineSeries = LineSeries(hd.element); var dataTransform:DataTransform = this_series.dataTransform; var vertical_axis:LinearAxis = LinearAxis(dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS)); var horizontal_axis:CategoryAxis = CategoryAxis(dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS)); var n:String = this_series.displayName; if (n && n != "") dt += "<b>" + n + "</b><BR/>"; var xName:String = horizontal_axis.displayName; if (xName != "") dt += "<i>" + xName+ ":</i> "; var xValue:Object = horizontal_axis.formatForScreen(LineSeriesItem(hd.chartItem).xValue); if(horizontal_axis.labelFunction != null) { dt += horizontal_axis.labelFunction(xValue,{},horizontal_axis,LineSeriesItem(hd.chartItem)) + "\n"; } else { dt += xValue + "\n"; } var yName:String = vertical_axis.displayName; if (yName != "") dt += "<i>" + yName + ":</i> "; var yValue:Object = vertical_axis.formatForScreen(LineSeriesItem(hd.chartItem).yValue); if(vertical_axis.labelFunction != null) { dt += vertical_axis.labelFunction(yValue,{},vertical_axis) + "\n"; } else { dt += yValue + "\n"; } return dt; }[/code]There is a lot of casting that has to happen for it to work. For instance, the series' getAxis method returns an Axis that implements the IAxis interface, but I have to cast it to a LinearAxis or CategoryAxis to reference the labelFunction property. Same with the HitData object that gets passed in. The "element" and "chartItem" property reference to the series and seriesitem object in it, but I have to cast them to LineSeries and LineSeriesItem to be used. Deficiencies I can think of off the top of my head:
- The function is specific to a Line Chart. Even though a column chart is very similar, everything would need to be cast to ColumnChart and ColumnChartSeries to work.
- I have made an assumption that your vertical axis is a LinearAxis
- I have made an assumption that your horizontal axis is a CategoryAxis
- The labelFunction for both LinearAxis and CategoryAxis take the previous value. I'm not passing that in, but since I'm not using it right now I don't care.
[code]<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.charts.chartClasses.IAxis; import mx.charts.HitData; import mx.charts.chartClasses.DataTransform; import mx.charts.chartClasses.CartesianTransform; import mx.charts.series.items.LineSeriesItem; private var monthly_sales_data:ArrayCollection = new ArrayCollection([ {month: new Date("1/1/2008"), platinum_sales: 100000, gold_sales: 50000, total_sales: 150000}, {month: new Date("2/1/2008"), platinum_sales: 200000, gold_sales: 150000, total_sales: 350000}, {month: new Date("3/1/2008"), platinum_sales: 200000, gold_sales: 100000, total_sales: 300000}, {month: new Date("4/1/2008"), platinum_sales: 170000, gold_sales: 150000, total_sales: 320000}, {month: new Date("5/1/2008"), platinum_sales: 300000, gold_sales: 250000, total_sales: 550000}]); private function axisDateLabel(categoryValue:Object, previousCategoryValue:Object, axis:CategoryAxis, categoryItem:Object):String { return dateFormatterAbbr.format(categoryValue); } private function vAxisCurrencyLabelNoCentsThousands(labelValue:Number, previousValue:Object, axis:IAxis):String { return currencyFormatterNoCents.format(labelValue/1000); } private function genericDataTipFormatter(hd:HitData):String { var dt:String = ""; var this_series:LineSeries = LineSeries(hd.element); var dataTransform:DataTransform = this_series.dataTransform; var vertical_axis:LinearAxis = LinearAxis(dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS)); var horizontal_axis:CategoryAxis = CategoryAxis(dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS)); var n:String = this_series.displayName; if (n && n != "") dt += "<b>" + n + "</b><BR/>"; var xName:String = horizontal_axis.displayName; if (xName != "") dt += "<i>" + xName+ ":</i> "; var xValue:Object = horizontal_axis.formatForScreen(LineSeriesItem(hd.chartItem).xValue); if(horizontal_axis.labelFunction != null) { dt += horizontal_axis.labelFunction(xValue,{},horizontal_axis,LineSeriesItem(hd.chartItem)) + "\n"; } else { dt += xValue + "\n"; } var yName:String = vertical_axis.displayName; if (yName != "") dt += "<i>" + yName + ":</i> "; var yValue:Object = vertical_axis.formatForScreen(LineSeriesItem(hd.chartItem).yValue); if(vertical_axis.labelFunction != null) { dt += vertical_axis.labelFunction(yValue,{},vertical_axis) + "\n"; } else { dt += yValue + "\n"; } return dt; } ]]> </mx:Script> <mx:Stroke id="platinum_stroke" color="#C0C0C0" weight="3"/> <mx:Stroke id="total_stroke" color="#000000" weight="3"/> <mx:Stroke id="gold_stroke" color="#EEC900" weight="3"/> <mx:CurrencyFormatter id="currencyFormatterWithCents" precision="2" rounding="nearest" /> <mx:CurrencyFormatter id="currencyFormatterNoCents" precision="0" rounding="nearest" /> <mx:DateFormatter id="dateFormatterAbbr" formatString="MMM YY" /> <mx:Panel title="Sales By Month"> <mx:HBox> <mx:LineChart id="sales_by_month" dataProvider="{monthly_sales_data}" showDataTips="true" dataTipFunction="genericDataTipFormatter"> <mx:horizontalAxis> <mx:CategoryAxis categoryField="month" title="Month" displayName="Month" labelFunction="axisDateLabel" /> </mx:horizontalAxis> <mx:verticalAxis> <mx:LinearAxis title="Dollars (Thousands)" displayName="Dollars (Thousands)" labelFunction="vAxisCurrencyLabelNoCentsThousands" /> </mx:verticalAxis> <mx:series> <mx:LineSeries xField="month" yField="platinum_sales" displayName="Platinum Sales" lineStroke="{platinum_stroke}" /> <mx:LineSeries xField="month" yField="gold_sales" displayName="Gold Sales" lineStroke="{gold_stroke}" /> <mx:LineSeries xField="month" yField="total_sales" displayName="Total Combined" lineStroke="{total_stroke}" /> </mx:series> </mx:LineChart> <mx:Legend dataProvider="{sales_by_month}"/> </mx:HBox> </mx:Panel> </mx:Application> [/code]
Josh
good job
hsTed
Brad Your example helped me tremendously. Thanks!
Clayton Gulick
Very nice example, thanks for posting!
marcelo
very usefull indeed, thanks dude
b.K.
What is up with all the typos,
<mx:Stroke ="platinum_stroke" ?? horizontaAxis ??
loads of them, seems like text get scrambled in your editor or somtehing
Brad Wood
@bk: Sorry about that, my color coder has a concurrency bug. I haven't gotten around to fixing it. Just refresh the page and it should look better. Also, the line breaks aren't very pretty.
http://www.codersrevolution.com/index.cfm/2009/12/3/BlogCFC-Code-Formatting-Not-Thread-Safe-With-Example
Khasim
Excellent Post! Thank you very much! I was looking for this functionality in my project.
manu Krishnan
Excellent.............. thanks:):)