Home  >  

Flex 101: Creating a Performance Comparison Chart

Author photo
January 14, 2010 | | Comments (1)
AddThis Social Bookmark Button

Yet again, I've been inspired by others' creations on the web. In this post, we will walk through the steps to create a custom stock comparison chart similar to the ones used in Google Finance, in Flex using a mx:DataGrid.

In a previous post, I discussed an easy technique that you can use to recreate an interactive line chart, similar to the one used on the Google Finance site. I'm not focusing on that chart again in this post. A different chart caught my eye this time as I was perusing stock information. Below, you can see a screen capture of exactly what caught my attention:

What is interesting in this chart is that it is really based on a table, and it is extremely simplistic and easy to read. The horizontal bars indicate relative performance during the trading day (red indicative of negative performance and green indicating positive), and the black line indicates the zero value (no change).

As I was looking at it, I thought to myself that this would be really easy to recreate in Flex, using DataGrid data visualization techniques that I have discussed previously. Below, you'll find what I came up with:

(note: all of the data values in this are randomly generated)

Now, let's take a look at the code that makes it work. Once you dig in, you will see that it is actually extremely simple. You will find the main.mxml file below.

main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
  xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:qg="quoteGrid.*" 
  layout="absolute" 
  backgroundColor="#FFFFFF"
  backgroundGradientColors="[#FFFFFF,#FFFFFF]"
   viewSourceURL="srcview/index.html">
  
  <mx:Style source="styles.css" />
  
  <mx:Script>
    <![CDATA[
      import mx.controls.dataGridClasses.DataGridColumn;
      import model.Quote;
      import model.DataUtil;
      import mx.collections.ArrayCollection;
      [Bindable]
      private var quotes : ArrayCollection = DataUtil.generateData();
      
      private function priceFormat( item : Quote, col : DataGridColumn ) : String
      {
         return priceFormatter.format( item.price );;
       }
      
      private function percentFormat( item : Quote, col : DataGridColumn ) : String
      {
         return percentFormatter.format( item.percentChange ) + "%";
       }
    ]]>
  </mx:Script>
  
  <mx:CurrencyFormatter id="priceFormatter" />
  <mx:NumberFormatter id="percentFormatter" precision="2" />
  
  <qg:QuoteGrid
    id="dataGrid"
    top="0" left="0"
    bottom="30" right="0"
    dataProvider="{ quotes }"
    showHeaders="false"
    selectable="false"
    >
    
    <qg:columns>
      
      <mx:DataGridColumn width="10"
                 headerText="Symbol"
                 dataField="symbol"/>
      <mx:DataGridColumn width="10"
                 headerText="Price"
                 dataField="price"
                 textAlign="right"
                 labelFunction="priceFormat"/>
      <mx:DataGridColumn width="10"
                 headerText="% Change"
                 dataField="percentChange"
                 textAlign="right"
                 labelFunction="percentFormat"/>
      <mx:DataGridColumn width="20"
                 headerText="Chart"
                 dataField="percentChange"
                 itemRenderer="quoteGrid.ChartItemRenderer"/>
      
    </qg:columns>
    
  </qg:QuoteGrid>
  
  <mx:Button 
    left="0"
    bottom="0"
    label="Generate New Data"
    click="quotes = DataUtil.generateData()"/>
  
</mx:Application>

As you can see, there isn't much to it. There is a data grid with selection disabled and headers hidden, a mx:Button, and some javascript for generating the data and some label functions... but... wait... That isn't a normal mx:DataGrid. We'll get to that in a moment, but first let's examine the data that we are visualizing.

Below you will find the Quote.as file, which is the value object that represents a stock quote. It contains a symbol, a price, and a percent change. The percent change is what we will be visualizing in the fourth column of the data grid instance.

Quote.as

package model
{
   public class Quote
   {
      public var symbol : String;
      public var price : Number;
      public var percentChange : Number;
      
      public function Quote( symbol : String, price : Number, percentChange : Number )
      {
         this.symbol = symbol;
         this.price = price;
         this.percentChange = percentChange;
       }
  
    }
}

Now, back to that data grid. Instead of mx:DataGrid, it is "QuoteGrid", which is a custom class that I created that extends mx:DataGrid. I added the functionality so that when commitProperties is invoked, it will loop over the data in the dataprovider (loop over all Quote instances) and calculate the minimum and maximum percent change values in the collection. We will use these values later when we visualize the data. Below, you can see the QuoteGrid source:

QuoteGrid.as

package quoteGrid
{
   import model.Quote;
   
   import mx.collections.ICollectionView;
   import mx.controls.DataGrid;
 
   public class QuoteGrid extends DataGrid
   {
      private var _minChange : Number = 0;
      private var _maxChange : Number = 0;
      
      public function QuoteGrid()
      {
         super();
       }
      
      public function get minChange() : Number
      {
         return _minChange;
       }
      
      public function get maxChange() : Number
      {
         return _maxChange;
       }
      
      override protected function commitProperties():void
      {
         super.commitProperties();
         _maxChange = 0;
         _minChange = 0;
         for each ( var q : Quote in ICollectionView( dataProvider ) )
         {
            _minChange = Math.min( _minChange, q.percentChange );
            _maxChange = Math.max( _maxChange, q.percentChange );
          }
       }
      
    }
}

Now, how did we get that last column to be visual?

With a custom item renderer, of course. This is a very basic item renderer. We could get significantly more complicated, but this does the job quickly and easily. The ChartItemRenderer uses the minimum and maximum percentChange values in the data provider to calculate the x position of zero, as well as the x position of the current data item's percentChange value. Using these caluclated percentages, we can draw a rectangle indicating the current value relative to zero, and a line representing zero, and we're done! Check out the code below, in particular the updateDisplayList function:

ChartItemRenderer.as

package quoteGrid
{
   import model.Quote;
   
   import mx.controls.DataGrid;
   import mx.controls.listClasses.IListItemRenderer;
   import mx.core.UIComponent;
 
   public class ChartItemRenderer extends UIComponent implements IListItemRenderer
   {
      private var _data : Quote = null;
      
      public function ChartItemRenderer()
      {
         super();
       }
      
      public function get data():Object
      {
         return _data;
       }
      
      public function set data(value:Object):void
      {
         _data = value as Quote;
         invalidateDisplayList();
       }
      
      override protected function updateDisplayList( w:Number, h:Number ):void
      {
         super.updateDisplayList(w,h);
         if ( this.owner is QuoteGrid )
         {
            //spacing above and below rendered content
            var verticalBuffer : int = 3;
            
            //spacing to the left and right of the rendered content
            var horizontalBuffer : int = 3;
            
            var qg : QuoteGrid = this.owner as QuoteGrid;
            var range : Number = (qg.maxChange - qg.minChange);
            
            //width to use for calculations 
            var _width : int = w-(2*horizontalBuffer);
            
            //position of zero (0) in the data range
            var zeroX : int = ((- qg.minChange/range)*_width)+horizontalBuffer;
            
            //X position of the data point 
            var dataX : int = (((_data.percentChange- qg.minChange)/range)*_width)+horizontalBuffer
            
            //calculate the area to draw the bars
            var _x : int = Math.min( dataX, zeroX );
            var _w : int;
            if ( zeroX >= dataX )
              _w = Math.abs(zeroX - dataX)
            else
              _w = dataX - zeroX;
              
            //draw it
            with ( graphics )
            {  
               clear();
               var color : uint = (_data.percentChange > 0) ? 0x00FF00 : 0xFF0000;
               
               lineStyle(1,color); 
               beginFill( color, .25 );
               
               drawRect( _x, verticalBuffer, _w, h-(2*verticalBuffer) );
               
               endFill();
               lineStyle(1,0);
               
               moveTo( zeroX, -2 );
               lineTo( zeroX, h+2 );
              
             } 
          }
       }
      
    }
}

Last, but not least, we can't forget the styles! One thing that makes this so simple to interpret is the use (or lack thereof) of styles. Basically, everything is white. There are no alternating colors, no grid lines, no border... nothing to distract you.

styles.css

/* CSS file */
DataGrid {
    alternatingItemColors: #ffffff, #ffffff;
    horizontalGridLines: false;
    verticalGridLines: false;
    rollOverColor: #dddddd;
    selectionColor: #cccccc;
    dropShadowEnabled: false;
    border-style: none
}

Button {
    cornerRadius: 0;
    themeColor: #ff0000;
}

As you can see, creating custom data visualization is not difficult at all with Flex. You can download the full application source code at:

http://tricedesigns.com/portfolio/stockComparisonChart/srcview/index.html


___________________________________
Andrew Trice
Principal Architect
Cynergy Systems http://www.cynergysystems.com

Read more from Andrew Trice. Andrew Trice's Atom feed

Comments

1 Comments

David Wilhelm said:

Thanks for sharing this - its a great way to represent a large number of datapoints visually, in a non-cluttered or confusing way.

Leave a comment


Type the characters you see in the picture above.


Tag Cloud

iPad

What's your take on the iPad? (Putting aside the Flash/iPad flame war)

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.