Flex Charting: Format Your X And Y Axis
Posted by
Brad Wood
Oct 14, 2008 22:00:00 UTC
Well, once again this is a pretty basic post of information readily available on the Internet. It took me seemingly forever to piece it all together, so I am blogging it to cement it in my mind and hopefully help someone else down the road. This week I have conquered the formatting of my X and Y axis labels for my charts. As usual, the hoops are a little bulky to jump through when you just want to add a very simple bit of formatting, but the flexibility is awesome.My Example chart is a simple line chart showing the trending of monthly sales. The Y axis shows money so it needs to formatted as dollars. Also it is in the thousands of dollars, so to save screen real estate I want to show $600,000 and $600 and then label the axis "Dollars (Thousands)". I plan on showing up to 12 months of data at a time, so I want the X axis ti show an abbreviated form of the month and year: "Jan 2008", "Feb 2008", etc.
Now first of all, it was very tempting to apply some of this formatting in SQL server or ColdFusion where I was more familiar with formatting (The actual implementation of this chart uses a CF webservice for data). I don't think that is the correct approach though. It is the display layer's job to format data, the data and business layer should just serve up the unadulterated goods. Furthermore, formatting tends to lose data (such as in a date) or change the data type (such as adding $ or % to a number).
So to dig in, We'll start with the X-axis. The LineChart will automatically choose defaults for most everything, but we have to at least specify a CategoryAxis with a categoryField. I am using this CategoryAxis as my horizontalAxis. The title attribute of my CategoryAxis is what is displayed under the dates. To control how the dates are output, I want so specify a labelFunction.
[code]<mx:horizontalAxis> <mx:CategoryAxis categoryField="month" title="Month" labelFunction="axisDateLabel" /> </mx:horizontalAxis>[/code]If we browse over to the LiveDocs page for CategoryAxis we will see that the labelFunction needs to have the following signature:
[code]function function_name(categoryValue:Object, previousCategoryValue:Object, axis:CategoryAxis, categoryItem:Object):String { ... } [/code]The previousCategoryValue object is interesting. I'm guessing one could use it to output the incremental change between that category and the previous. So, to format my dates for output I want an instance of the DateFormatter class. For you ColdFusion people, think dateformat() function but reusable and a bit more OO.
[code]<mx:DateFormatter id="dateFormatterAbbr" formatString="MMM YY" />[/code]The formatString is basically a mask that controls how the date is output. MMM gives me the three letter month abbr and YY gives me the two digit year. Now that I have created a custom version of the DateFormatter class, I can reference it by id and call its format() method like so in my label function:
[code]private function axisDateLabel(categoryValue:Object, previousCategoryValue:Object, axis:CategoryAxis, categoryItem:Object):String { return dateFormatterAbbr.format(categoryValue); }[/code]Well, with that out of the day, let's move on to the vertical axis. This attribute of my LineChart will be handed a LinearAxis object which also contains a title attribute as well as a labelFunction:
[code]<mx:verticalAxis> <mx:LinearAxis title="Dollars (Thousands)" labelFunction="vAxisCurrencyLabelNoCentsThousands" /> </mx:verticalAxis>[/code]If we browse over to the LiveDocs page for NumericAxis we will see that the labelFunction needs to have the following signature (LinearAxis inherits the labelFunction property from NumericAxis which it extends):
[code]function function_name(labelValue:Object, previousValue:Object, axis:IAxis):String { ... } [/code]Much like my post on formatting a DataGrid for currency we want an instance of the CurrencyFormatter class to help us out.
[code]<mx:CurrencyFormatter id="currencyFormatterWithCents" precision="2" rounding="nearest" />[/code]Our label function calls the format() method of our CurrencyFormatter class, but not before dividing the value by 1000 since we want our Y axis to show thousands of dollars.
[code]private function vAxisCurrencyLabelNoCentsThousands(labelValue:Number, previousValue:Object, axis:IAxis):String { return currencyFormatterNoCents.format(labelValue/1000); }[/code]My function names may seem kind of long, but I was trying to keep them reusable for other graphs in my app, so I named them generically and descriptively. Here is the finished product:
[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.ArrayList; import mx.charts.chartClasses.IAxis; private var monthly_sales_data:ArrayList = new ArrayList([ {month: "1/1/2008", platinum_sales: 100000, gold_sales: 50000, total_sales: 150000}, {month: "2/1/2008", platinum_sales: 200000, gold_sales: 150000, total_sales: 350000}, {month: "3/1/2008", platinum_sales: 200000, gold_sales: 100000, total_sales: 300000}, {month: "4/1/2008", platinum_sales: 170000, gold_sales: 150000, total_sales: 320000}, {month: "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); } ]]> </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}"> <mx:horizontalAxis> <mx:CategoryAxis categoryField="month" title="Month" labelFunction="axisDateLabel" /> </mx:horizontalAxis> <mx:verticalAxis> <mx:LinearAxis title="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]
John Gag
Very nice and simple. Thanks
Johan Nyberg
Thanks for guiding me in the right direction.
JR Rodriguez
That's for this one.. 'saved me a lot of time going through the documentation :)
Kavita
Thanks for this post. This really helped me a lot...
Kavita
Is there any way in which if the maximum value is in millions, the Y axis can have labelvalue/1000000 and display Dollars (Millions) and if it is in thousands, display in thousands. I tried the following but this does not work. How do you get the maximum value of the Y-Axis series?
private function YAxisLabelInThouMill(labelValue:Number, previousValue:Object, axis:IAxis):String { var MaxVal:Number = Math.max(labelValue); var v_Quotient:Number = MaxVal/1000000; if(v_Quotient > 0) { Bar_LinearAxis.title = "Dollars (Millions)"; return "$" + NumberFormatterNoDecimal.format(labelValue/1000000); } else { Bar_LinearAxis.title = "Dollars (Thousands)"; return "$" + NumberFormatterNoDecimal.format(labelValue/1000); } }
Brad Wood
@Kavita: I would check out the public computedMaximum property of the numericAxis class. The axis object is passed into your labelFunction as "axis"
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/charts/chartClasses/NumericAxis.html?filter_flex=4.1&filter_flashplayer=10.1&filter_air=2#computedMaximum
I haven't tried it yet, but you would probably need to get to the axis title with axis.title, but remember, the label function is called once for each number on the vertical axis so you'd be setting the title over and over.
binu
thanks nice and simple eay understandable