Home >
In this post, we will walk through the steps to create an interactive chart with a Google-Finance-esque interaction.
Before we get to far into the details, let's take a look at the final product. The top chart is a line chart showing a subset of data from the greater data set. The chart on the bottom is a representation of the entire data set. You can adjust the slider on the bottom to change the granularity and scope of the data represented in the top line chart.
The data in this example is the National 30-Year Conventional Mortgage Rate, obtained from http://research.stlouisfed.org/fred2/series/MORTG?cid=114, and obtained in CSV format.
The first thing to do is load data into the application. In this case, an HTTPService is being used to load the CSV content.
<mx:HTTPService
id="httpService"
url="data/MORTG.csv"
result="onHTTPResult(event)"
fault="onHTTPFault(event)"
resultFormat="text"/>
In the result handler, we've got a basic routine to parse the CSV file based on string characters. The string value is parsed into individual rows based on the newline character, and the values are broken apart into individual values by parsing each row token based on a comma (",").
private function onHTTPResult( event : ResultEvent ) : void
{
var result : String = event.result.toString();
var rows : Array = result.split( "\n" );
var index : int = 0;
for each (var row : String in rows)
{
var tokens : Array = row.split( "," );
if ( tokens.length > 1 )
{
var point : DataPoint = new DataPoint( StringUtil.trim( tokens[0] ), parseFloat( tokens[1] ), index );
fullDataCollection.addItem( point );
index ++;
}
}
slider.maximum = fullDataCollection.length;
slider.values = [fullDataCollection.length-150,fullDataCollection.length];
filteredCollection.filterFunction = filterTopChart;
filteredCollection.source = fullDataCollection.source;
}
The parsed results populate 2 data collections, "fullDataCollection" and "filteredCollection". The filtered collection will be used to populate the top, larger chart, and the full collection will be used to populate the smaller, full range chart component.
In this case, I decided to keep the filter simple. Each data point gets assigned an index value (assigned when parsing data), and the filtered collection gets filtered based on the index.
private function filterTopChart(item:DataPoint):Boolean
{
return (slider.values[0] <= item.index && item.index <= slider.values[1]);
}
Now, Let's take a look at the chart components. They're actually just plain-old Flex Charts, with very little customization. In both cases, the styles applied to the axes have been customized, but everything else is completely stock, out-of-the-box functionality.
<mx:LineChart id="linechart1" left="10" right="10" top="10" bottom="50" dataProvider="{ filteredCollection }">
<mx:series>
<mx:LineSeries displayName="Series 1" yField="value" />
</mx:series>
<mx:horizontalAxisRenderers>
<mx:AxisRenderer
tickLength="0"
tickPlacement="0"
canDropLabels="true"
showLabels="false">
<mx:axis>
<mx:LinearAxis />
</mx:axis>
</mx:AxisRenderer>
</mx:horizontalAxisRenderers>
</mx:LineChart>
<mx:LineChart id="linechart0"
left="10" bottom="10"
dataProvider="{ fullDataCollection }"
gutterBottom="0" gutterLeft="0" gutterRight="0" gutterTop="0" right="10" height="40">
<mx:series>
<mx:LineSeries yField="value" />
</mx:series>
<mx:horizontalAxisRenderers>
<mx:AxisRenderer
tickLength="0"
tickPlacement="0"
canDropLabels="true"
showLabels="false">
<mx:axis>
<mx:LinearAxis />
</mx:axis>
</mx:AxisRenderer>
</mx:horizontalAxisRenderers>
<mx:verticalAxisRenderers>
<mx:AxisRenderer
tickLength="0"
tickPlacement="0"
canDropLabels="true"
showLabels="false">
<mx:axis>
<mx:CategoryAxis />
</mx:axis>
</mx:AxisRenderer>
</mx:verticalAxisRenderers>
</mx:LineChart>
The filtering slider/overlay over the bottom chart is a little more complicated, but again, not that hard when you break it down. Over top of the smaller chart, a HSlider instance (from the flexlib project) is overlaid. This is not the default slider in Flex. This version is customized to allow you to have linked slider thumbs.
<controls:HSlider
id="slider"
left="10" right="10" bottom="10" height="40"
minimum="0" maximum="1"
trackHighlightSkin="skins.CustomSliderThumbHighlightSkin"
allowThumbOverlap="false"
liveDragging="true"
showTrackHighlight="true"
thumbCount="2"
snapInterval="1"
change="filteredCollection.refresh();"
trackSkin="mx.skins.ProgrammaticSkin"
/>
You'll also notice above that the slider thumb highlight has a custom skin applied to it. This custom skin extends the SliderThumbNoGripHighlightSkin and changes the object's measured size, and changes the updateDisplayList method to show the rectangular overlay, over top of the full data chart.
package skins
{
import flexlib.skins.SliderThumbNoGripHighlightSkin;
public class CustomSliderThumbHighlightSkin extends SliderThumbNoGripHighlightSkin
{
override protected function updateDisplayList(w:Number, h:Number):void
{
with (graphics)
{
clear();
lineStyle( 1, 0x666666 );
beginFill( 0x999999, .25 );
drawRect( 0,-5,w,h );
}
}
override public function get measuredWidth():Number
{
return 40;
}
override public function get measuredHeight():Number
{
return 40;
}
}
}
...and it's as simple as that.
You can view and download the full source code at:
http://tricedesigns.com/portfolio/interactivechart/srcview/index.html
___________________________________
Andrew Trice
Principal Architect
Cynergy Systems
http://www.cynergysystems.com




Facebook Application Development
This is a pretty cool example of how simple a "complex" interaction can be to implement in Flex. One problem with the example though... as you drag the viewable "window" on the bottom chart, the max value on the Y axis changes. This really needs to stay fixed so that you don't have to reorient yourself to what the graph is actually displaying.
Hi, can you please post the DataPoint class as well? Thanks!
This is a great example. Thanks!
However, there is an error in the main.mxml file line 121.
1046: Type was not found or was not a compile-time constrant:HSlider.
Pavlo, this file is in the full source link at the bottom of the entry.
Tom, you need to get that control from the flexlib open source project, the link is above.
Thanks,
Andrew Trice
It's finally working.
Thank you so much, Andrew!