Everyone likes pictures, they tell a thousand words. Now you can augment your Apache FlexJS applications with charts. The FlexJS chart package provides a handful of basic charts: column, bar, line, and pie. This article describes the chart package API and how it works.
As with most things in FlexJS, you describe your chart using MXML and provide it data through a model. You can also modify the chart at runtime using ActionScript (you can also create charts with ActionScript if you prefer). All of the chart types are based on List, but use a different set of itemRenderers as well as a different container to hold the itemRenderers. Chart layouts provide the actual "chart" feature of moving and sizing the itemRenderers to make the chart.
Charts display their data in the form of series. For example, you can have a column chart that compares revenue from years 2013 and 2014; that would be a chart with two series. Each series has its own itemRenderers that displays a data point for that series. For instance, one itemRenderer might display the revenue from April 2013 as a blue bar and another itemRenderer might display the revenue from April 2014 with an orange bar.
You can see working examples of FlexJS charts by following these links: FlexJS SWF Charts and FlexJS JavaScript Chart.
ColumnChart
To display a column chart (which has vertical bars, BarChart has horizontal bars), you begin with the <js:ColumnChart> MXML tag, such as:
<js:ColumnChart x="20" y="20" width="400" height="200">
</js:ColumnChart>
Data
Next you connect the chart to some data using a FlexJS ConstantBinding bead:
<js:ColumnChart x="20" y="20" width="400" height="200">
<js:beads>
<js:ConstantBinding sourceID="applicationModel" sourcePropertyName="productList" destinationPropertyName="dataProvider" />
</js:beads>
</js:ColumnChart>
The ConstantBinding bead connects the application model's productList property to the ColumnChart's dataProvider property. The dataProvider might be an ArrayCollection object items where each item has a revenue property for each year.
Series
To actually display the values, a set of ColumnSeries are added to the chart using the ColumnChart's series property:
<js:ColumnChart x="20" y="20" width="400" height="200">
<js:beads>
<basic:ConstantBinding sourceID="applicationModel" sourcePropertyName="productList" destinationPropertyName="dataProvider" />
</js:beads>
<js:series>
<js:ColumnSeries yField="sales2013">
<js:itemRenderer> ... </js:itemRenderer>
</js:ColumnSeries>
<js:ColumnSeries yField="sales2014">
<js:itemRenderer> ... </js:itemRenderer>
</js:ColumnSeries>
</js:series>
</js:ColumnChart>
itemRenderers
The ColumnSeries element defines which field in the data it is using for its display. An itemRenderer is also provided to the series. FlexJS has (at the moment) a handful of itemRenderers and the one most appropriate for a ColumnSeries is the BoxItemRenderer which simply draws a filled rectangle. Here is how to specify the itemRenderer for each ColumnSeries:
<fx:Component>
<js:BoxItemRenderer>
<js:fill>
<js:SolidColor color="#FF964D" />
</js:fill>
</js:BoxItemRenderer>
</fx:Component>
The itemRenderer is specified in-line using the <fx:Component> notation which declares a class or factory to be used to create the instances of the itemRenderer. BoxItemRenderer has a fill property that takes a SolidColor object which is specified here as being filled with an orange color.
Full Chart
Put together, the final ColumnSeries MXML specification looks like this:
<js:ColumnChart x="20" y="20" width="400" height="200">
<js:beads>
<js:ConstantBinding sourceID="applicationModel" sourcePropertyName="productList" destinationPropertyName="dataProvider" />
</js:beads>
<js:series>
<js:ColumnSeries yField="sales2013">
<js:itemRenderer>
<fx:Component>
<js:BoxItemRenderer>
<js:fill>
<js:SolidColor color="#FF964D" />
</js:fill>
</js:BoxItemRenderer>
</fx:Component>
</js:itemRenderer>
</js:ColumnSeries>
<js:ColumnSeries yField="sales2014">
<js:itemRenderer>
<fx:Component>
<js:BoxItemRenderer>
<js:fill>
<js:SolidColor color="#964DFF" />
</js:fill>
</js:BoxItemRenderer>
</fx:Component>
</js:itemRenderer>
</js:ColumnSeries>
</js:series>
</js:ColumnChart>
When compiled and run, this MXML produces a column chart with two series, each one side-by-side per month.
Axes
While a chart is fine, most charts are accompanied by a set of axes to help you understand the data being presented. FlexJS has two types of axes: one for linear (numeric) data and one for categorized data. The ColumnChart above uses both types: the horizontal axis per-unit data (monthly in this case) while the vertical axis is displaying the revenue values.
Axes are added to a chart as FlexJS beads (just after the ConstantBinding bead in the <js:beads> section).
<js:HorizontalCategoryAxisBead categoryField="month" />
<js:VerticalLinearAxisBead valueField="sales2013" />
At the moment the charts can have only one vertical and one horizontal axis, so you need to choose a data field from the dataProvider to use for each axis.
Other Axis Types
In addition to the HorizontalCategoryAxisBead and VerticalLinearAxisBead there are VerticalCategoryAxisBead and HorizontalLinearAxisBead.
All of the axis beads have stroke (line) properties to set the appearance of their axis lines and tick marks. For example, to set the axis line to red and tick marks green:
<js:HorizontalCategoryAxisBead categoryField="month">
<js:axisStroke>
<js:SolidColorStroke color="#FF0000" weight="2" />
</js:axisStroke>
<js:tickStroke>
<js:SolidColorStroke color="#00FF00" weight="2" />
</basic>tickStroke>
</js:HorizontalCategoryAxisBead>
Without specifying the axisStroke and tickStroke, the axis parts will be drawn with a thin black line.
Chart Types
In addition to ColumnChart, FlexJS has the following chart types and their series and itemRenderers:
Chart Type | Series | Suggest itemRenderer |
---|---|---|
ColumnChart | ColumnSeries | BoxItemRenderer |
BarChart | BarSeries | BoxItemRenderer |
StackedColumnChart | ColumnSeries | BoxItemRenderer |
StackedBarChart | ColumnSeries | BoxItemRenderer |
PieChart | PieSeries note 1 | WedgeItemRenderer |
LineChart | LineSeries | BoxItemRenderer and LineSegmentItemRenderer note 2 |
#note1 At this time a PieChart supports only 1 PieSeries
#note2 A LineSeries has two renderers: the itemRenderer is used to draw a figure at the point (such as a small square box). The lineSegmentItemRenderer is used to connect the points with a single line. Both renderers are optional although you should include at least one of them to see anything.
If you compare the component (strand) code for each chart type, you will see they are almost identical and are very thin. Each chart is extends on ChartBase which provides the common series property. The actual "chart" graphics are handled by the chart layouts, covered next. The reason for the different chart classes is to make it easier to assign styles and default beads via CSS.
Chart Layouts and Axes
For the most part, all of the charts are the same. Charts are lists and follow the same pattern, using an itemRenderer mapper to generate the itemRenderers for the chart elements and using a layout to size and position them. The work of building a chart falls onto the layout.
The ColumnChart uses a ColumnChartLayout bead which determines where and how the bars appear. The StackedColumnChart uses a StackedColumnChartLayout. All of the chart layouts extend ChartBaseLayout which provides common properties, such as identifying an horizontal or vertical axis beads that might be used.
An important part of the chart is the chart's drawing parent, which is a ChartDataGroup (List uses NonVirtualDataGroup). The ChartDataGroup provides a way for a chart layout to locate itemRenderers based on series as well as position.
A FlexJS chart layout follows this basic pattern:
- Identify the axes being used to determine how much space they occupy.
- Determine minimums and maximums of the values to be plotted.
- Identify an itemRenderer (created earlier by the itemRenderer data mapper bead) to use for each point, size and position it based on the chart data values and scaled to the chart's visible size.
- Send a notification that the layout is complete.
Once the layoutComplete notification has been sent, the chart's axes, if it has them, go to work and size and position themselves along the chart's edges. They also calculate their tick marks, lines, and set their labels.
Chart optimization
The original FlexJS Chart package consists of many layers of elements. As an exercise, a set of optimized, or flattened, structure was created using a variation of the original FlexJS Chart components. Once that work was completed, a test was set up to see if there were any improvements; the charts below tell the story.
The FlexJS Chart components follow the same pattern as List: a dataProvider holds the information to be presented, a factory bead generates instances of the itemRenderers that will draw the visual representation of the beads, and a layout places those itemRenderers into the correct locations. Following this pattern, the outer-most element is the chart itself (a DIV in JavaScript) with nested DIV elements for the axes and one for the chart graphics. Each itemRenderer is a component (DIV) and within that is the graphics for the presentation (an SVG plus a RECT for a BoxItemRenderer). Within each axis there is a SPAN for the tick labels and an SVG+PATH for the axis line and each label.
Optimization
The optimized version flattens this out by using no DIV elements within the chart area and using a single SVG for each axis and one for the chart graphics area. The presentation is delivered by having the itemRenderers create the RECT elements right in the chart's SVG. Similarly, the axis uses SVG TEXT elements for the labels and a PATH for the line and tick marks. The result is a much flatter display structure. When run in browsers that have any SVG optimization, the results show that the optimized charts perform better. This optimization also works for Flash because layers of Sprites are no longer created.
The optimization of the charts is possible because the charts use the FlexJS core.graphics package and, specifically, the GraphicsContainer component. The core.graphics package has both an ActionScript and a JavaScript version. The ActionScript version uses Sprite and Surface while the JavaScript version uses SVG. When the BoxItemRenderer needs to display a rectangle, it uses the core.graphics.Rect component. In ActionScript, this component creates a Sprite and then uses the Flash Player drawing API to make the rectangle. In JavaScript, the core.graphics.Rect component creates an SVG and adds a RECT element to it.
The optimized chart components can be found in the FlexJS charts.optimized package and consist of replacement itemRenderers, a replacement for the ChartAxisGroup, and a replacement for the ChartDataGroup. The itemRenderers are fairly straightforward: instead of creating a core.graphics.Rect element, the itemRenderers access their dataGroup (which extends core.graphics.GraphicsContainer) and uses the drawRect() function. This does not create any new elements in ActionScript and only adds a RECT element to the dataGroup's SVG base in JavaScript.
The optimized ChartDataGroup extends core.graphics.GraphicsContainer which has drawing functions but also fits into the FlexJS component framework. The optimized ChartDataGroup is specified for a in CSS and simply replaces the standard ChartDataGroup. This is required as the optimized itemRenderers expect to be able to use the drawing functions found in GraphicsContainer.
The optimized ChartAxisGroup is similar to the optimized ChartDataGroup in which it replaces the creation of sub-components with the use of the GraphicsContainer drawing functions. For example, rather than creating a Label component for each tick mark, the optimized ChartAxisGroup uses the drawText() function of GraphicsContainer.
The end result is that the optimized charts only replace itemRenderers, ChartAxisGroup, and the ChartDataGroup; the chart layouts and axis beads are untouched.
Performance
The charts below (created with FlexJS Chart package) show the difference between the optimized (orange and sky) and regular (red and blue) chart components; shorter bars are faster. The performance data were gather from two FlexJS LineCharts of a sine wave: one with optimized components and one without. The charts used BoxItemRenderers for each point plotted along with a LineSegmentItemRenderer to display the line connecting the points. The vertical axis is the average of ten runs in milliseconds.
source: flex-asjs/examples/ChartExample using TestModel and SpeedResultsView.
The results show that the best performance comes from the optimized chart components with the difference most notable in the JavaScript version; the Flash versions out performed the JavaScript versions every time. Another factor to consider is that Flash is better at handling larger data sets. Extrapolating the performance data, you can see what the increasing the size of the data increases the time on JavaScript significantly over Flash. In fact, running charts using ten thousand points often caused the browsers to either stop or pause and notify the user that the script was taking too long. Some pseudo-threading techniques could help the perceived performance, but overall, Flash is better at handling large data.
It is yet to be seen if the non-optimized chart components actually offer any advantage over the non-optimized versions; if that turns out to be true, it may not be worth while to keep the non-optimized versions.