Home Programming ActionScript Graphing Cookbook

ActionScript Graphing Cookbook

books-svg-icon Book
eBook $28.99 $19.99
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $28.99 $19.99
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Getting Started with Graph Drawing
About this book
"A picture is worth a thousand words" has never been more true than when representing large sets of data. Bar charts, heat maps, cartograms, and many more have become important tools in applications and presentations to quickly give insight into complicated issues.The "ActionScript Graphing Cookbook" shows you how to add your own charts to any ActionScript program. The recipes give step-by-step instructions on how to process the input data, how to create various types of charts and how to make them interactive for even more user engagement.Starting with basic ActionScript knowledge, you will learn how to develop many different types of charts.First learn how to import your data, from Excel, web services and more. Next process the data and make it ready for graphical display. Pick one of the many graph options available as the book guides you through ActionScript's drawing functions. And when you're ready for it, branch out into 3D display.The recipes in the "ActionScript Graphing Cookbook" will gradually introduce you into the world of visualization.
Publication date:
November 2012
Publisher
Packt
Pages
288
ISBN
9781849693202

 

Chapter 1. Getting Started with Graph Drawing

In this chapter, we will cover:

  • Drawing in two dimensions

  • Building point charts

  • Creating a line graph based on a function

  • Adding labels and axes

  • Graphing a spreadsheet

  • Area charts

  • Multiple area charts

  • Styling a graph

  • Adding legends

  • Using Flex for charts

 

Introduction


In this chapter, we will see a number of recipes that go into the very basics of drawing in ActionScript. The recipes will explain the coordinate system, so that you know where you are drawing. A quick overview of the ActionScript DisplayList object and other ActionScript drawing functions is given.

A final recipe shows how Flex can be used to generate many standard graphs with virtually no actual programming. Flex is not the focus of the recipes, but the book wouldn't be complete without at least some coverage of the framework and its functions related to graph drawing.

Although we do not expect you to be an ActionScript expert, some basic knowledge will help you in quickly understanding and applying the recipes presented. Also, we will not go into the details of the tools used.

 

Drawing in two dimensions


This recipe goes into the very basics of what it takes to draw in two dimensions. There's a little math in there to explain the coordinate system and transformations. It's not the most interesting read, but it is very important that you understand this for all the following recipes.

We also extensively use the ActionScript 3.0 display list concept. We will explain the principles, but if you've never heard of it, you may want to read up on it in other ActionScript tutorials or books. A few of these are mentioned in the See also section of this recipe.

Getting ready

All recipes in this book are completely editor-agnostic; you can use them in any ActionScript editor you like. The samples are provided for the free FlashDevelop IDE available at http://www.flashdevelop.org/ , because it is completely free and of a very high quality.

If you want to follow along with FlashDevelop, now is the time to download and install it. Make sure you also configure the Flex SDK, if FlashDevelop does not do this for you. The Flex SDK is the part that will be responsible for compiling and executing your programs. More information can be found on the FlashDevelop wiki found at http://www.flashdevelop.org/wikidocs/index.php?title=Configuration#Configuring_the_Flex_SDK.

In FlashDevelop, create a new AS3 project. You can create one for each chapter, or re-use the same one for most of the recipes. Most recipes will require you to set the document class. This is done by right-clicking on the class in the project view and picking set document class from the options.

If you are using Flash Professional, we will not use the timeline that you may be used to. Information on setting up the document class can be found in the following blog article: http://active.tutsplus.com/tutorials/actionscript/quick-tip-how-to-use-a-document-class-in-flash/.

How to do it...

The first three steps will need to be performed for virtually all the recipes in this book, so we won't repeat them, unless they differ:

  1. Create a new class (in this case it's called Recipe1).

  2. Have it extend flash.display.Sprite.

  3. Set it as the Document Class.

    You should have the following piece of code:

    package  
    {
      import flash.display.Sprite;
      public class Recipe1 extends Sprite
      {
        public function Recipe1()
        {
        }
      }
    }

    Tip

    Downloading the example code

    You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

  4. Now run the code. You should get a blank screen with no warnings or errors.

  5. In order to draw a nice looking point, we will actually draw a small circle. The following piece of code creates a shape, draws a circle, moves the center point, and displays it:

    package  
    {
      import flash.display.Shape;
      import flash.display.Sprite;
    
      public class Recipe1 extends Sprite 
      {
        public function Recipe1() 
        {
                var point:Shape = new Shape( ); 
                point.graphics.beginFill( 0xff9933 , 1 );
                point.graphics.drawCircle( 0 , 0 , 3 );
                point.x = 20;                                 
                point.y = 20;
                addChild( point ); 
        }
      }
    }
  6. To more easily draw points inside graph coordinates of our choosing, we can introduce graph:Sprite, which will perform a transformation on all points we draw inside it:

    package  
    {
      import flash.display.Shape;
      import flash.display.Sprite;
      import flash.geom.Matrix;
    
      public class Recipe1 extends Sprite 
      {
        public function Recipe1() 
        {
          var graph:Sprite = new Sprite();
          graph.x = 20;
          graph.y = 20;
          addChild(graph);
    
          var point:Shape = new Shape(); 
          point.graphics.beginFill( 0xff9933 , 1 );
          point.graphics.drawCircle( 0 , 0 , 3 );
          point.x = 0;
          point.y = 0;
          graph.addChild(point);
        }
      }
    }

    The visual result is exactly the same as the previous piece of code, but now the point is at the coordinates (0,0).

  7. To obtain the transformation presented in the How it works... section of this recipe, we just need to apply the right transformation to the graph sprite:

    package  
    {
      import flash.display.Shape;
      import flash.display.Sprite;
      import flash.geom.Matrix;
    
      public class Recipe1 extends Sprite 
      {
        public function Recipe1() 
        {
          var graph:Sprite = new Sprite();
          graph.x = 400;
          graph.y = 300;
          graph.scaleY = -1;
          addChild(graph);
    
          var point:Shape = new Shape(); 
          point.graphics.beginFill( 0xff9933 , 1 );
          point.graphics.drawCircle( 0 , 0 , 3 );
          point.x = 0;
          point.y = 0;
          graph.addChild(point);
        }
      }
    }

How it works...

ActionScript 3.0 uses a concept called a display list. A display list is a collection of visual elements. The most important display list is your main class. This is the list that will be displayed (that is the reason why it extends Sprite).

This list is organized as a tree. It has a root node, to which various display objects are attached. Some of these display objects can also contain more display objects, hence the tree structure.

For instance, if you add a Sprite child to your main class, it will be displayed. If you add yet another Sprite child to that child, it too will be drawn on the screen.

However this isn't the end of the story. You can apply transformations to objects in the display list. A transformation can be scaling, rotating, and moving, or all at once. If you apply a transformation to one object, this transformation will also apply to all its children. This is how we displayed a point at (0,0) that was in the center of the screen.

When drawing a point, we do this by adding a shape to the display list:

  1. First a shape is created, which will hold all data related to our point (location, color, size).

  2. The ActionScript Graphics class is used to draw a solid circle into the shape.

  3. Next we change the location of the shape.

    Note

    We could also draw the circle at the position, but because the (0,0) coordinates of the circle sprite are at the top-left corner, this would not place the point in the center of the shape. This will complicate any transformation on the shape as we'll see later on. That's because the pivot point for scaling and rotating is the (0,0) coordinate.

  4. Finally we add the point object to the display list. Without this last line, the point would not be visible.

To understand the transformation that takes place, you should take note of two different coordinate systems that are used:

  1. Screen coordinates: If you used the default FlashDevelop settings, you will have a screen that goes from x = 0, y = 0 in the upper-left corner to x = 800, y = 600 in the bottom-right corner.

    This means that the X-axis of the coordinate system goes from the left of the screen to the right. The Y-axis goes from the top, down to the bottom.

    Usually the coordinates are given as a pair, so x = 0, y = 0 is written as (0,0). The following diagram demonstrates the screen coordinates:

  2. Most graphs however are not drawn in screen coordinates. For instance, if you want to plot a function, you may want to put the (0,0) coordinates in the center of the screen. This might look something like the following screenshot. The graph coordinates are in blue; we show only the direction and origin for the graph axes:

    So if you want to draw a graph point at (0,0), you would in effect have to draw it on the screen coordinates (400,300).

    To create such a transformation, we first translate the (0,0) point to the center of the screen. And second, the Y-axis is scaled by -1. This inverts the Y direction (up is down, and down is up).

In the next recipe, we will see a transformation matrix. This matrix is a mathematical shorthand notation for a coordinate transformation.

There's more...

Although this recipe seems simple; there's much you can learn from it. Let's look at some more features.

Graphics drawing

We've only covered the very basics of drawing with the Graphics class. Further recipes will show more details. But if you can't wait, try experimenting with the color and the size. The ActionScript 3.0 reference pages have the information you need: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Graphics.html.

Coordinate systems and transformations

A great exercise is to experiment with the transformations. Try to obtain various results. If you modify the scaleX and scaleY variables, you can zoom in or out of the graph. You can even rotate your system.

Or if you're into maths, you can directly manipulate the transformation matrix of the graph sprite. Again, the ActionScript 3.0 reference guide will help you out: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/geom/Matrix.html.

See also

If you want to read up on the display list, most of the ActionScript 3.0 fundamental books have a dedicated chapter about it. For instance, Learning ActionScript 3 or Essential ActionScript 3 (both published by O'Reilly) are good references.

If you want to read up on coordinate systems and transformations, you can find the best coverage in any vector maths book. Wikipedia is also a very informative source: http://en.wikipedia.org/wiki/Coordinate_system .

 

Building point charts


This recipe builds on the previous one. The previous recipe placed all the codes into one big method. This is fine for the first experiment, but when things get more complicated, you'll lose track of what goes where.

This recipe brings structure into the code and shows how to graph multiple points without repeating yourself.

Getting ready

Make sure you at least glanced over the previous recipe. You'll need it here.

Create a new, blank Recipe2 class, set it as the document class, and copy and paste the code inside the Recipe1 constructor placed inside it. To be sure, run the class and verify that everything looks the same as in the previous recipe.

How to do it...

We will tackle the generalization of the code in a few steps. If you take a look at the code, you'll notice that there are actually two important blocks: one that creates the graph shape and one that draws a point.

  1. In the first step we will extract these blocks into two methods:

    package  
    {
      import flash.display.Shape;
      import flash.display.Sprite;
      public class Recipe2 extends Sprite
      {
        private var graph:Sprite;
        
        public function Recipe2() 
        {
          createGraph();
          drawPoint();
        }
    
        private function createGraph():void
        {
          graph = new Sprite();
          graph.x = 400;
          graph.y = 300;
          graph.scaleY = -1;
          addChild(graph);
        }
    
        private function drawPoint():void
        {
          var point:Shape = new Shape(); 
          point.graphics.beginFill( 0xff9933 , 1 );
          point.graphics.drawCircle( 0 , 0 , 3 );
          point.x = 0;
          point.y = 0;
          graph.addChild(point);
        }
      }
    
    }

    The end result should still be exactly the same.

  2. The coordinates of the current graph are chosen for this one example; it would be much better to parameterize this. As parameters, we'll include the top-left and bottom-right coordinates we'd like to have:

    createGraph(-400, 300, 400, -300);

    Note

    See the images of the Drawing in two dimensions recipe in this chapter for the coordinate system that we are using.

    To make sure the method works with all possible choices of coordinates, we need to do some calculations in the createGraph method:

    • Translating the graph to the new center point (keeping in mind the scale).

    • Scale the graph to the right size. This can be done by comparing the current width and height against the values we want. The current width and height are fixed when you enter your project properties. But you can also obtain them from the stage object.

    • Lastly, if the coordinates are reversed, we must mirror the image.

    All these calculations together form this reusable method:

    private function createGraph(left:Number, top:Number, right:Number, bottom:int):void
        {
          var width:Number = Math.abs(right - left);
          var height:Number = Math.abs(bottom - top);
          var scaleWidth:Number = stage.stageWidth / width;
          var scaleHeight:Number = stage.stageHeight / height;
          var flipX:Boolean = (left > right);
          var flipY:Boolean = (top > bottom);
    
          graph = new Sprite();
          graph.x = scaleWidth * Math.abs(left);
          graph.y = scaleHeight * Math.abs(top);
          graph.scaleX = (flipX ? -1 : 1) * scaleWidth;
          graph.scaleY = (flipY ? -1 : 1) * scaleHeight;
          addChild(graph);
      }
  3. Now that we've got the hard part out of the way, let's make the drawPoint method a little more reusable. For now, we want to set all the points to the same style and size, so the only parameters to the method are the location.

    The resulting code is as follows:

    package  
    {
      import flash.display.Shape;
      import flash.display.Sprite;
    
      public class Recipe2 extends Sprite
      {
        private var graph:Sprite;
        public function Recipe2() 
        {
          createGraph(-400, 300, 400, -300);
          drawPoint(0, 0);
          drawPoint(20, 20);
          drawPoint(-40,-40);
        }
    
        private function createGraph(
    left:Number, top:Number, right:Number, bottom:Number):void
        {
          var width:Number = Math.abs(right - left);
          var height:Number = Math.abs(bottom - top);
          var scaleWidth:Number = stage.stageWidth / width;
          var scaleHeight:Number = stage.stageHeight / height;
          var flipX:Boolean = (left > right);
          var flipY:Boolean = (top > bottom);
    
          graph = new Sprite();
          graph.x = scaleWidth * Math.abs(left);
          graph.y = scaleHeight * Math.abs(top);
          graph.scaleX = (flipX ? -1 : 1) * scaleWidth;
          graph.scaleY = (flipY ? -1 : 1) * scaleHeight;
          addChild(graph);
        }
    
        private function drawPoint(x:Number, y:Number):void
        {
          var point:Shape = new Shape(); 
          point.graphics.beginFill( 0xff9933 , 1 );
          point.graphics.drawCircle( 0 , 0 , 3 );
          point.x = x;
          point.y = y;
          graph.addChild(point);
        }
      }
    }

    This code also shows that the method can be reused to draw several different points. If everything went well, you should see three points along a diagonal, with the bottom-left point being a little farther away from the others.

How it works...

To see how the transformation actually works, add the following line to the end of the graph constructor:

trace(graph.transform.matrix);

Note

If you have issues viewing the trace statements output, it may be due to the Flash player you are using or the FlashDevelop configuration. The FlashDevelop wiki has troubleshooting instructions right here: http://www.flashdevelop.org/wikidocs/index.php?title=AS3:Debugging.

This trace statement will display the transformation matrix in use. If you still remember your math, you may find this other way of looking at the process enlightening (see http://en.wikipedia.org/wiki/Transformation_matrix for an in-depth discussion on the topic).

The createGraph method sets up the following transformations:

  • It calculates the required scaling based on the target width and height.

  • The flipX and flipY variables are used to calculate whether the target coordinate system is mirrored or not. For instance, in this example, the Y- axis of the screen coordinates points down (0 is at the top and positive numbers are at the bottom), while the coordinate system in which we draw the graph works the other way around. This means we need to mirror the Y coordinates.

The drawPoint method draws a new point and can be called repeatedly to draw multiple points.

It creates a new shape for every point. This is not strictly necessary and might even cause a degraded performance when drawing many points. In the coming chapters, we'll see some uses for this approach, but if you have issues with performance, you can directly draw on the Recipe2 sprite and not instantiate new shapes for every point.

There's more...

Again, this basic program is very powerful. And with a few changes, it allows for lots of experimentation.

Coordinate system

Try to change the parameters of the createGraph method and see if you can predict the results.

Typically, scientific graphs will have their origin (0,0) in the center of the graph. Most business-type charts will have their origin somewhere in the bottom-left corner. All of these can be easily achieved by changing the parameters of the drawGraph method.

Scaling woes

If you change the coordinate system, you may notice that the point scales with the coordinate system. You can fix this by scaling back the point to the original size. Or you can transform all the operations, instead of the shape (refer to the next recipe for the solution).

Adding more parameters

If you like multiple points with different colors, then why not add another parameter to the drawPoint method.

See also

This recipe closely relates to many of the others in this chapter. So if something isn't clear, go back to the previous one, or skip ahead and come back to it later.

 

Creating a line graph based on a function


Now that we have our coordinate system and we know how to draw points, it's time to look a little further. This recipe introduces some other Graphics functions that will allow you to draw lines.

This recipe uses one of the most typical line graphs, namely the display of a function.

Getting ready

Before starting this recipe, we will go through one final iteration of our code in order to have a proper object-oriented structure that we can easily and quickly extend in this and later recipes.

Start by creating a Recipe3 class, set it as document class, and have it extend Sprite.

Next create a Graph class (in a new file, with name Graph.as) where we will centralize all graph drawing methods:

package  
{
  import flash.display.Shape;
  import flash.display.Sprite;
  import flash.events.Event;

  public class Graph extends Sprite
  {
    private var left:Number;
    private var top:Number;
    private var right:Number;
    private var bottom:Number;

    private var matrix:Matrix;

    public function Graph(left:Number, top:Number, right:Number, bottom:Number) 
    {
      this.left   = left;
      this.top    = top;
      this.right  = right;
      this.bottom = bottom;

      addEventListener(Event.ADDED_TO_STAGE, createGraph);
    }
    
    private function createGraph(event:Event):void
    {
      removeEventListener(Event.ADDED_TO_STAGE, createGraph);
      
      var width:Number = Math.abs(right - left);
      var height:Number = Math.abs(bottom - top);
      var scaleWidth:Number = stage.stageWidth / width;
      var scaleHeight:Number = stage.stageHeight / height;
      var flipX:Boolean = (left > right);
      var flipY:Boolean = (top > bottom);

      matrix = new Matrix(
        (flipX ? -1 : 1) * scaleWidth,
        0,
        0,
        (flipY ? -1 : 1) * scaleHeight,
        scaleWidth * Math.abs(left),
        scaleHeight * Math.abs(top)
      );
    }

    public function drawPoint(x:Number, y:Number):void
    {
      var transformedLocation:Point = matrix.transformPoint(new Point(x, y));
      var point:Shape = new Shape(); 
            point.graphics.beginFill( 0xff9933 , 1 );
            point.graphics.drawCircle( 0 , 0 , 3 );
      point.x = transformedLocation.x;
      point.y = transformedLocation.y;
            addChild(point);
    }
  }
}

You'll notice two major changes from the previous recipe:

  1. Due to the way the display list works, we don't have access to the stage until the graph is attached to it. This means we have to create the graph after the ADDED_TO_STAGE event is fired. This detail is not important for the rest of the recipe, so if you don't understand it at this point, don't worry.

  2. Instead of transforming the graph shape (via its x, y, scaleX, and scaleY parameters), we transform the coordinates of the point. The result is that the circle is always as we expected and is not scaled with your coordinates system (see the Scaling woes section in the previous recipe).

How to do it...

  1. We now start with the same program as the Building point charts recipe, but using the new Graph class:

    package  
    {
      import flash.display.Sprite;
    
      public class Recipe3 extends Sprite
      {
        private var graph:Graph;
    
        public function Recipe3() 
        {
          graph = new Graph( -400, 300, 400, -300);
          addChild(graph);
    
          graph.drawPoint(0, 0);
          graph.drawPoint(20, 20);
          graph.drawPoint(-40,-40);
        }
    
      }
    }

    If everything went right, you should see, once again, the exact same result. Only now, our main program (Recipe3) only shows what we want to do (that is, to create a graph and draw points). How those actions are performed, is written inside the Graph class.

  2. Now to create this recipe. We need a way to draw lines. Add the following method to the Graph class:

    public function drawLine(x1:Number, y1:Number, x2:Number, y2:Number):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
    
      graphics.lineStyle(2, 0x000000);
      graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
    }
  3. Now let's do something useful with this. Say you want to draw the sine function in the range starting from –π/2 and ending at π/2. The result will go from -1 to 1. So we use (-π/2, 1) and (π/2) as the upper-left and lower-right corners of our graph.

    But a sine is not a linear function, so we can't just draw one line from (–π/2, -1) to (π/2, 1). What we will do, is approximate the sine curve by splitting it into smaller parts and drawing a number of shorter lines.

    Take a look at the following code:

    package  
    {
      import flash.display.Sprite;
      import flash.geom.Point;
    
      public class Recipe3 extends Sprite
      {
        private var graph:Graph;
    
        public function Recipe3() 
       {
          graph = new Graph( -Math.PI/2, 1, Math.PI/2, -1);
          addChild(graph);
    
          var i:Number = -Math.PI/2;
          var step:Number = 0.1;
          var previousPoint:Point = new Point(i, Math.sin(i));
          for (i += step; i <= Math.PI/2; i += step)
          {
            var point:Point = new Point(i, Math.sin(i));
            graph.drawLine(previousPoint.x, previousPoint.y , point.x, point.y);
            previousPoint = point;
          }
        }
    
      }
    }

    The result is the sine curve, as shown in the following screenshot:

    Because we used many small lines (the program uses π / 0.1 or 31 pieces), the segments are hardly distinguishable.

  4. Change the step variable to 1 and you'll immediately see the separate lines, as shown in the following screenshot:

    You can still see the sine curve, but it's clear that the three segments don't have a very good approximation.

How it works...

To draw a line, we use the Graphics methods as follows:

  • We calculate the correct position of the beginning and the end points of the line

  • The ActionScript Graphics methods are used to set a line style and draw the line

You can try this out with any data, not just a function. Replace the drawPoint method calls in Recipe3 with the following piece of code:

      graph.drawLine(0, 0, 20, 40);

You should see a short line appear. Once we can draw a line, drawing a function just builds on top of that:

  • To change the way the transformation is handled, instead of scaling the entire shape, we just scale the coordinates. The result is that the graphics element, such as circles and line widths, don't scale with the coordinate system.

  • To approximate the actual sine curve, we take samples and fill in the holes with a straight line. This is linear interpolation and if it's done right, it's hardly distinguishable from the real thing.

There's more...

If function drawing is the main aim of your program, there are quite a few ways this code can be extended and improved.

Improving the Graph class

Until now, our two graph methods (drawPoint and drawLine) have taken number arguments. A better interface would probably have point classes as arguments. Why not try this refactoring yourself? It will give you more insight into the code. The result will be used in the next recipes.

Using points to draw functions

There are many ways to draw a function. One thing you may have thought about using is the drawPoint method, just placing many points. You can quite easily adapt the program to do this. However, you'll notice you need quite a lot more points to obtain the same result.

Curves

The ActionScript Graphics class can also draw so called Bézier curves with the curveTo method. You'll only need two well-chosen curves to get a very convincing result. This is not for the faint of heart, but you can find code that should get you started at these sites:

http://www.cartogrammar.com/blog/actionscript-curves-update/

http://gskinner.com/blog/archives/2008/05/drawing_curved_.html

See also

If you want to read up on linear interpolation, a good starting point is Wikipedia http://en.wikipedia.org/wiki/Linear_interpolation or your old, math books.

The sine function is also well explained on Wikipedia:

http://en.wikipedia.org/wiki/Sine.

 

Adding labels and axes


Before diving into some more graph types, it's about time we started adding axes and scales. If we don't have those, there really is no way to properly interpret our nice charts.

In this recipe we will add a horizontal and vertical axis to the sine graph we created in the previous recipe. We will also add markers and labels.

Getting ready

This recipe continues to expand the Graph class presented in the previous recipe. So if you haven't written that yet, copy it from the provided sources.

Next, create a Recipe4 class, just as we did in the previous recipes.

How to do it...

To create axes, we will add two methods to the Graph class. They are as follows:

  • public function drawVerticalAxis(x:Number, y1:Number, y2:Number)

  • public function drawHorizontalAxis(y: Number, x1:Number, x2:Number)

Calling the methods will draw a straight line from (x,y1) to (x,y2) and (x1,y) to (x2,y), respectively.

  1. For our first implementation we can simply reuse the drawLine method.

    Add the following methods to the Graph class:

        public function drawVerticalAxis(x:Number, y1:Number, y2:Number)
        {
          drawLine(x, y1, x, y2);
        }
    
        public function drawHorizontalAxis(y: Number, x1:Number, x2:Number)
        {
          drawLine(x1, y, x2, y);
        }
  2. Now you can try them out in the Recipe4 class:

    package  
    {
      import flash.display.Sprite;
      import flash.geom.Point;
    
      public class Recipe4 extends Sprite
      {
        private var graph:Graph;
    
        public function Recipe4() 
        {
          graph = new Graph( -Math.PI/2, 1, Math.PI/2, -1);
          addChild(graph);
    
          graph.drawVerticalAxis(0, 1, -1);
          graph.drawHorizontalAxis(0, -Math.PI / 2, Math.PI /2);
    
          var i:Number = -Math.PI/2;
          var step:Number = 0.1;
          var previousPoint:Point = new Point(i,Math.sin(i));
          for (i += step; i <= Math.PI/2; i += step)
          {
            var point:Point = new Point(i, Math.sin(i));
            graph.drawLine(previousPoint.x, previousPoint.y ,point.x, point.y);
            previousPoint = point;
          }
        }
    
      }
    }

    The result should be the same graph as before, but now with two lines forming an axis.

    A typical axis has at least two features that make it actually useful:

    i. Regularly spaced tick marks.

    ii. Labels associated with those marks.

  3. We will first draw the marks. In order to do this, we first create a separate drawAxisLine method. This will also allow you to style the axes differently than the actual graph.

    A first revision of the drawHorizontalAxis method looks like the following code:

    public function drawHorizontalAxis(y: Number, x1:Number, x2:Number):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y));
      drawAxisLine(transformedLocation1, transformedLocation2);
    }
    
    private function drawAxisLine(p1: Point, p2: Point):void
    {
      var line:Shape = new Shape();
      line.graphics.lineStyle(1, 0x000000);
      line.graphics.moveTo(p1.x, p1.y);
      line.graphics.lineTo(p2.x, p2.y);
      addChild(line);
    }
  4. Now we add marks. This uses a stepping algorithm very similar to the one we used for drawing a function. First we calculate the mark position on the axis and next a short line is drawn, perpendicular to the axis:

    public function drawHorizontalAxis(y: Number, x1:Number, x2:Number):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y));
      drawAxisLine(transformedLocation1, transformedLocation2);
    
      var step:Number    = 50;
      for (var markX:Number = transformedLocation1.x; 
        markX <= transformedLocation2.x; 
        markX += step) 
      {
        var markPoint1:Point = new Point(markX, transformedLocation1.y);
        var markPoint2:Point = new Point(markX, transformedLocation1.y + 10);
        drawAxisLine(markPoint1, markPoint2);
    }
    }
  5. And finally we add labels. To display a label we use the ActionScript TextField class. This class has a tremendous amount of parameters that allow you to customize how the text is displayed. We will look at a few parameters in the later recipes, but for now, we use the standard formatting.

    We don't want to clutter the display too much, so we only add a text label every four markers:

    public function drawHorizontalAxis(y: Number, x1:Number, x2:Number):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y));
      drawAxisLine(transformedLocation1, transformedLocation2);
    
      var labels:Array   = ["-PI/2","-PI/4","0","PI/4","PI/2"];
      var step:Number = 50;
      var labelCount:int = 0;
      for (var markX:Number = transformedLocation1.x; 
        markX <= transformedLocation2.x; 
        markX += step) 
      {
        var markPoint1:Point = new Point(markX, transformedLocation1.y);
        var markPoint2:Point = new Point(markX, transformedLocation1.y + 10);
        drawAxisLine(markPoint1, markPoint2);
    
        if (int(markX) % 200 == 0)
        {
          //place a label every 4 markers
          var textField:TextField = new TextField();
          textField.text = labels[labelCount++];
          textField.x = markX;
          textField.y = transformedLocation1.y + 12;
          addChild(textField);
        }
      }
    }
  6. Run the program again, and you'll see a horizontal axis with values.

How it works...

This recipe is probably one of the least spectacular ones. It just builds on top of ActionScript methods for drawing lines and displaying text. There's a lot of mechanical work involved in getting everything in the right location, but nothing complicated.

The best way to understand the recipe is to play with the various parameters:

  • Increase and decrease the marker step size

  • Change the size of the markers

  • Change the number of labels (make sure you have enough label values in the labels array; if not, you'll end up receiving error messages)

There's more...

Now let's talk about some other options, or possibly some pieces of general information that are relevant to this task.

Vertical axis

Only the code for the horizontal axis was shown. The code for the vertical axis is, of course, very similar and a great exercise.

Screen versus graph coordinates

All the axes drawing code was written in screen coordinates. This simplified a few things, but it could also make it more complex to correctly place the marks. A good exercise is to rewrite the code using graph coordinates.

Error checking and adding parameters

We haven't really bothered ourselves with adding error checking to the parameters (what if the first value of the axis coordinate is larger than the second?). We also haven't yet made the code more generic. You can add parameters for the number of marks you want to be shown on the labels.

 

Graphing a spreadsheet


Graphing functions is nice, but visualizing a spreadsheet is probably the most popular use case for graphs. Having a visual representation of tabular data can result into insights previously hidden behind the numbers.

In this recipe, we will structure the code to use data from a fixed array in the ActionScript program. In the next chapter, we'll go into all kinds of ways to get the data into that array.

Getting ready

Create a new document class called Recipe5 and have it extend Sprite. Also make sure to copy the updated Graph class from the provided source package. It has enhanced and more flexible axes methods (if you implemented any of the items mentioned in the There's more... section of the Adding labels and axes recipe in this chapter, you may have already created a very similar one).

This time we'll focus on only positive numeric data, so we'll put the origin (0,0) of the graph in the bottom-left corner:

package  
{
  import flash.display.Sprite;

    private const MAX_X:int  = 700;
    private const MAX_Y:int  = 500;
    private const BORDER:int = 50;
    private const TICK:int   = 50;

  public class Recipe5 extends Sprite 
  {
    private var graph:Graph;

    public function Recipe5() 
    {
      graph = new Graph( -BORDER, MAX_Y + BORDER, MAX_X + BORDER, -BORDER);
      addChild(graph);
      graph.drawHorizontalAxis(0, 0, MAX_X, TICK, ["0", MAX_X]);
      graph.drawVerticalAxis(0, 0, MAX_Y, TICK, ["0", MAX_Y/2, MAX_Y]);
    }
  }
}

If you run this program, you should see both axes run from the bottom-left corner and there should be two labels on the horizontal axis and three on the vertical axis.

How to do it...

For now, we'll just show point charts. Feel free to convert this into a line chart (as shown in the Creating a line graph based on a function recipe in this chapter) and in later recipes you'll learn how to draw many different types of charts.

If we have the following table:

We want to draw two points: (10,40) and (20,60).

Storing data is easiest in a two dimensional array. In the next chapter, we'll go over many different ways of storing data (such as in files and on the Internet). The array mimics how you represent this data in a program such as MS Excel:

private var graph2d:Array = [[0,20],[50,70],[100,0],[150,150],[200,300],[250,200],
[300,400],[350,20],[400,60],[450,250],[500,90],[550,400],
[600,500],[650,450],[700,320]];

All the points are grouped together. Each entry in the array is one (x , y) coordinate on the graph.

The code to draw this dataset is just a simple loop:

for (i = 0; i < graph2d.length; i++)
{
  graph.drawPoint(graph2d[i][0], graph2d[i][1], 0x3399ff);
}

This structure does have one problem: it's not easy to manipulate the data. You may want to change the data, for instance, if this was connected to live web statistics that are updated every minute (we'll see more on that in the next chapter). Say you want to change the point (150,150) to (150,200). How would you do that? You need to loop over the array and find the correct entry and change it.

However, searching this way in a large array can become slow quite quickly. If the array is always sorted, like in the preceding example, you could implement your own version of a search algorithm to make it quicker. But then you'd need to create your own insertion algorithm to make sure the data remains in order.

No matter how you solve this, if you need to manipulate the data a lot, this is not a good data structure.

See the There's more... section of this recipe for other solutions to these problems.

How it works...

The data we want to display is a simple data mapping from one value to the another. Imagine an Excel spreadsheet with two columns. One column holds the x-axis values while the other holds the y-axis value. Two numbers on the same row present one (x , y) point.

That is why a two-dimensional array is one of the best ways to represent data: it's an easy structure to program and it's easy to read. However, it is hard to manipulate. If you have a fixed data set, this is the structure you want.

There's more...

There are an endless number of possible ways to represent data. Depending on your data source or your specific data set, you may want to look into other options. In the following section, we present the two most popular ones, each with its own advantages and disadvantages. Ultimately, it is possible to combine most advantages into one structure, but it will require additional development.

Two arrays

The easiest solution to store data is in two arrays, one for each column in the spreadsheet:

private var graph1x:Array = 
[0, 50, 100, 150, 200, 250, 300, 350, 400, 
450, 500, 550, 600, 650, 700];
private var graph1y:Array = 
[20, 70, 0, 150, 300, 200, 400, 20, 60, 250, 90, 400, 500, 450, 320];

And you can draw the graph with a simple loop:

for (var i:int = 0; i < graph1x.length; i++)
{
  graph.drawPoint(graph1x[i], graph1y[i], 0xff9933);
}

This works effectively and is completely understandable. There is a major drawback to this approach that is already clear in this simple example.

There's no easy way to quickly verify that you have the same number of elements in both arrays. You can write a test in your code, but if you want to edit the data, it's hard to see which value belongs to which.

For instance, the 400 x-value maps to 60 on the y-axis. That isn't readily apparent from the code. If you forget to add a y-value, you run the risk of breaking your entire program.

Associative array

If inserting and updating data is important, you may want to look into a third solution: the object or associative array.

private var graphObject:Object = { 0:20,50:70,100:0,150:150,200:300,250:200,
300:400,350:20,400:60,450:250,500:90,550:400,
600:500,650:450,700:320 };

Although the notation looks similar to the previous one, the data structure that is created internally is quite different. Drawing it is a little more complicated and requires the usage of the for-in loop (again we shift the points to demonstrate the difference):

for (var s:String in graphObject)
{
  graph.drawPoint(Number(s) + 8, graphObject[s], 0xff3399);
}

Because the x coordinate is stored as an object property, it is stored as a string. Before we can draw it, we need to convert it back to a number.

This data structure is perfect for manipulation. Instead of having to search through it to find the item we want to change, we just write the following:

graphObject[150] = 200;

However, there is one very major disadvantage of the associate array and the for-in loop: you should not rely, in any way, on the order in which elements are looped over. So there's no guarantee that the loop will first draw (0,20) and then (50,70), and so on.

Order will become important if you want to draw more complicated charts than the point chart shown. In that case, you need to first convert the object to an array and then sort the array.

Vectors

If you like to work in a more object-oriented manner, and to avoid some of the pitfalls of arrays and increase performance, you may want to look into vectors.

Although the code that you need to write tends to be much more verbose than arrays and objects, they offer a very high level of type safety and have many convenience functions.

For instance, you can store points in the Point objects:

var point:Point = new Point(0,20);

And store these points in a point vector:

var graphVector:Vector.<Point> = new Vector.<Point>();
graphVector.push(point);

Associative array to two-dimensional array conversion

If you intend to manipulate your data inside your ActionScript program you will probably want to write at least a conversion function from the associate array to the ordered two-dimensional arrays. Potentially, you'll also need to do this the other way around.

What you need to do is:

  • Use a for-in loop to construct a two-dimensional array

  • Use the Array.sort method and the custom order function to sort the array

See also

If you're not confident with manipulating (associative) arrays, it's best to look at one of the ActionScript fundamentals books. Both data structures are vital to understanding most of the recipes in this book.

 

Area charts


In the previous recipes we've learned:

  • Transforming the data so that it shows up at the right location on the screen

  • Using the correct data structure so it's easy to display it

In the remaining recipes in this chapter, we will look into ActionScript's drawing methods and show you a few ways to customize the display.

This recipe looks at drawing area charts. They resemble a function chart, but the area between the chart line and the x-axis is filled. They are ideal to represent volumes.

Getting ready

Create a Recipe6 document class and set up the graph as in the previous recipe. We will use the two-dimensional data set because we will need an ordered data set:

package  
{
  import flash.display.Sprite;

  public class Recipe6 extends Sprite
  {
    private var graph:Graph;
    private var data:Array = [[0, 20], [50, 70], [100, 0], [150, 150], [200, 300], [250, 200], [300, 400], [350, 20], [400, 60], [450, 250], [500, 90], [550, 400], [600, 500], [650, 450], [700, 320]];
    public function Recipe6() 
    {
      graph = new Graph( -50, 550, 750, -50);
      addChild(graph);
      graph.drawHorizontalAxis(0, 0, 700, 50, ["0", "700"]);
      graph.drawVerticalAxis(0, 0, 500, 50, ["0","250","500"]);
    }

  }
}

How to do it...

  1. Add the following code to the Recipe6 constructor:

    for (var i:Number = 1; i < data.length; i++)
    {
      graph.drawLine(data[i-1][0], data[i-1][1], data[i][0], data[i][1]);
    }

    Running the program at this point will yield the data set from the previous recipe, but now connected with a line.

  2. In the Graph class, we create a copy of the drawLine method and name it drawArea. In the main program, we now replace the drawLine call. The result should still be the same. But now we change the code so that the area is filled:

    public function drawArea(x1:Number, y1:Number, x2:Number, y2:Number):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
      var transformedOrigin:Point    = matrix.transformPoint(new Point(0, 0));
    
      var area:Shape = new Shape();
      area.graphics.beginFill(0xff9933);
      area.graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      area.graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
      area.graphics.lineTo(transformedLocation2.x, transformedOrigin.y);
      area.graphics.lineTo(transformedLocation1.x, transformedOrigin.y);
      area.graphics.endFill();
      addChild(area);
    }

    If you run the program now, you should see an orange area chart. Everything below the line is now nicely filled with the orange color.

How it works...

This recipe is created in two steps: first, display a line chart and next, fill the void beneath the line to obtain an area.

Because drawing a line requires two points, we start counting at 1 instead of 0. In the loop, we draw a line between the previous point and the current one.

There are two other things to note from the code:

  • We calculate the transformed origin. Actually, we only need the 0 y-coordinate, but it's easier to just use the matrix for this calculation.

  • Instead of drawing a line between two points, we create a "fill" between four points: the two that were used for the line and the two on the x-axis (with y = 0).

There's more...

As with previous recipes, there's a lot that can be customized. Most of these will be discussed in some of the next recipes, but there's nothing keeping you from starting to experiment.

Having the color as a parameter

Right now, the fill color is fixed. You can make this a variable by using it as an argument to the drawArea method.

The fill style

If you check out the ActionScript documentation, you'll find that there are two more ways of creating fills: the beginGradientFill and beginBitmapFill methods. They are a bit more complicated, but can be used to obtain dramatic effects.

See also

The ActionScript 3.0 reference has a lot more detail on creating and drawing filled shapes. It can be found at the following URL:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Graphics.html

 

Multiple area charts


In this recipe, we're going to visualize multiple sets of data on one chart.

Getting ready

As usual, create a Recipe7 document class. We'll start with the code from the previous recipe. Only now, we've slightly changed the data set to demonstrate some of the items covered better. There's also an added set of data. We've chosen to store it in the same array for simplicity, but there are other ways of course.

The following is the starting class:

package  
{
  import flash.display.Sprite;

  public class Recipe7 extends Sprite
  {
    private var graph:Graph;
    private var data:Array = [[0, 20, 50], [50, 70, 40], [100, 0, 100], 
[150, 150, 150], [200, 300, 200], [250, 200, 170], 
[300, 170, 160], [350, 20, 120], [400, 60, 80], 
[450, 250, 150], [500, 90, 20], [550, 50, 40], 
[600, 110, 90], [650, 150, 150], [700, 320, 200]];

    public function Recipe7() 
    {
      graph = new Graph( -50, 550, 750, -50);
      addChild(graph);
      graph.drawHorizontalAxis(0, 0, 700, 50, ["0", "700"]);
      graph.drawVerticalAxis(0, 0, 500, 50, ["0", "250", "500"]);

      for (var i:Number = 1; i < data.length; i++)
      {
        graph.drawArea(data[i-1][0], data[i-1][1], data[i][0], data[i][1]);
      }

    }

  }

}

How to do it...

There are two ways to show multiple area charts. You can stack the charts on top of each other or you can make them transparent and have them overlaid.

We'll cover the transparent overlay first, because it's the easiest.

  1. Rewrite the drawArea method as follows:

    public function drawArea(x1:Number, y1:Number, x2:Number, y2:Number, 
    colour:uint = 0xff9933, alpha:Number = 1):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
      var transformedOrigin:Point    = matrix.transformPoint(new Point(0, 0));
    
      var area:Shape = new Shape();
      area.graphics.beginFill(colour, alpha);
      area.graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      area.graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
      area.graphics.lineTo(transformedLocation2.x, transformedOrigin.y);
      area.graphics.lineTo(transformedLocation1.x, transformedOrigin.y);
      area.graphics.endFill();
      addChild(area);
    }
  2. If we now rewrite the main loop, we can draw multiple area charts with transparency:

    for (var i:Number = 1; i < data.length; i++)
    {
      graph.drawArea(data[i - 1][0], data[i - 1][1], data[i][0], data[i][1], 
    0xff9933, 0.5);
      graph.drawArea(data[i - 1][0], data[i - 1][2], data[i][0], data[i][2],
    0x3399ff, 0.5);
    }

    Creating stacked charts is a little more complicated. Once again, we start by expanding the drawArea method. We add two more parameters that define the bottom y-coordinates of the area. In the previous graphs, they've always been zero, so we leave that in as the default:

    public function drawArea(x1:Number, y1:Number, x2:Number, y2:Number, 
    colour:uint = 0xff9933, alpha:Number = 1, 
    y3:Number = 0, y4:Number = 0):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
      var transformedLocation3:Point = matrix.transformPoint(new Point(x1, y3));
      var transformedLocation4:Point = matrix.transformPoint(new Point(x2, y4));
    
      var area:Shape = new Shape();
      area.graphics.beginFill(colour, alpha);
      area.graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      area.graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
      area.graphics.lineTo(transformedLocation4.x, transformedLocation4.y);
      area.graphics.lineTo(transformedLocation3.x, transformedLocation3.y);
      area.graphics.endFill();
      addChild(area);
    }

    Use the following code:

    for (var i:Number = 1; i < data.length; i++)
    {
      graph.drawArea(data[i - 1][0], data[i - 1][1], data[i][0], data[i][1]);
      graph.drawArea(data[i - 1][0], data[i - 1][1] + data[i - 1][2], 
       data[i][0], data[i][1] + data[i][2], 
       0x3399ff, 1,
               data[i - 1][1], data[i][1]);
    }

How it works...

Areas are drawn by moving from one point to the other, either clockwise or counter-clockwise. We rushed over this important point in the previous recipe, but it is important in this one.

Because the areas are a little more complex, you need to be extra careful to get the order right. Otherwise you'll run into some strange phenomena.

When overlaying multiple charts, we use the alpha property of the fills. The alpha value controls the transparency of the fill. By making the fill translucent, we can see both areas behind each other.

When stacking multiple charts, we need to use the sum of both coordinates to properly place the different data sets. The bottom coordinates of the second data set are given by the first area chart, while the top coordinates are the sum of two data sets.

There's more...

We've seen the basics of drawing multiple data sets on one chart. In later chapters, there will be many more ways of displaying this information. In the meantime, here are a few ways to expand this recipe.

Improving the interface

The drawArea method's parameters can be improved upon. In particular if you want to draw a third or fourth set of data points, this is going to become unwieldy.

One option is to accept arrays of y-coordinates.

Styling the fill

As with the previous recipe, there are many options to style the fill. We'll look into a few in the next recipe.

 

Styling a graph


Until now, we've used the most basic way of drawing points, lines, and areas. However, ActionScript's Graphic class offers a wealth of different options to make your charts look more attractive.

In this recipe, we will give a short primer of some of the tools at your fingertips. We won't be able to cover them all, but this should help you on your way.

Getting ready

We start by having a graph that shows all three types of charts we've discussed:

package  
{
  import flash.display.Sprite;

  public class Recipe8 extends Sprite
  {
    private var graph:Graph;
    private var data:Array = [[0, 20], [50, 70], [100, 0], [150, 150], [200, 300], [250, 200], [300, 400], [350, 20], [400, 60], [450, 250], [500, 90], [550, 400], [600, 500], [650, 450], [700, 320]];

    public function Recipe8() 
    {
      graph = new Graph( -50, 550, 750, -50);
      addChild(graph);
      graph.drawHorizontalAxis(0, 0, 700, 50, ["0", "700"]);
      graph.drawVerticalAxis(0, 0, 500, 50, ["0", "250", "500"]);

      graph.drawPoint(data[0][0], data[0][1] + 50);
      for (var i:Number = 1; i < data.length; i++)
      {
        graph.drawArea(data[i - 1][0], data[i - 1][1], data[i][0], data[i][1]);
        graph.drawLine(data[i - 1][0], data[i - 1][1] + 25, data[i][0], data[i][1] + 25);
        graph.drawPoint(data[i][0], data[i][1] + 50);
      }

    }

  }

}

Notice that we have shifted the y-coordinate of the different charts, so that it's clear which one is which. If you run this program you should see an area chart, 25 pixels higher a line graph, and the another 25 pixels higher a point chart.

How to do it...

  1. Let's look at points first and replace the points with images.

    For this recipe, we will use the freely available SweetiePlus icons, available at http://sublink.ca/icons/sweetieplus/. Copy any one of the icons you would like to the lib folder of your project. For instance, the heart icon: heart-16-ns.png.

    If you open the folder in FlashDevelop, you should see the file appear. Place the cursor in the Recipe8 class file, just above the graph's var definition.

    Now right-click on the image and pick generate embed code. This will embed the image into your program and is the easiest and best way to embed small images like this.

    Note

    If you use some other software, embedding images might be a little different: In Flash Builder you can use the [Embed] metadata tag directly. Refer to: http://www.adobe.com/devnet/flash/articles/embed_metadata.html.

    In Flash Professional, you can also add the resource to the stage and give it an instance name to address it directly without the need for an [Embed] tag.

    Just below the embed code, you now need to connect that embedded image to a class name. It looks like the following code:

    …
    public class Recipe8 extends Sprite
    {
      [Embed(source = "../lib/heart-16-ns.png")]
      private var HeartClass:Class;
    
      private var graph:Graph;

    We can now add a new drawBitmapPoint method to the Graph class:

    public function drawBitmapPoint(x:Number, y:Number, BitmapClass:Class):void
    {
      var transformedLocation:Point = matrix.transformPoint(new Point(x, y));
    
      var bitmapPoint: Bitmap = new BitmapClass();
      bitmapPoint.x = transformedLocation.x - bitmapPoint.width / 2;
      bitmapPoint.y = transformedLocation.y - bitmapPoint.height / 2;
      addChild(bitmapPoint);
    }
  2. Next we will look at gradients. These allow you to fill an area or a line with a gradually changing color. The complete description of how to apply, position, and create gradients is fairly complicated and beyond the scope of this recipe.

    However, we will explain one example, the drawing of a gradient-filled area:

    public function drawGradientArea(x1:Number, y1:Number, x2:Number, y2:Number, 
                    y3:Number = 0, y4:Number = 0):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
      var transformedLocation3:Point = matrix.transformPoint(new Point(x1, y3));
      var transformedLocation4:Point = matrix.transformPoint(new Point(x2, y4));
    
      var area:Shape = new Shape();
    
      var gradType:String = GradientType.LINEAR;
      var colors:Array    = [0xff9933, 0x9933ff];
      var alphas:Array    = [1, 1];
      var ratios:Array    = [100, 255];
      var matrix:Matrix   = new Matrix();
      matrix.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2);
    
      area.graphics.beginGradientFill(gradType, colors, alphas, ratios, matrix);
      area.graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      area.graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
      area.graphics.lineTo(transformedLocation4.x, transformedLocation4.y);
      area.graphics.lineTo(transformedLocation3.x, transformedLocation3.y);
      area.graphics.endFill();
      addChild(area);
    }
  3. You can also apply gradients to lines, but for the final example, we'll look at applying bitmaps to lines:

    public function drawBitmapLine(x1:Number, y1:Number, x2:Number, y2:Number, 
        BitmapClass:Class):void
    {
      var transformedLocation1:Point = matrix.transformPoint(new Point(x1, y1));
      var transformedLocation2:Point = matrix.transformPoint(new Point(x2, y2));
    
      var line:Shape = new Shape();
      line.graphics.lineStyle(16, 0x000000);
      var bitmap:Bitmap = new BitmapClass();
      line.graphics.lineBitmapStyle(bitmap.bitmapData);
      line.graphics.moveTo(transformedLocation1.x, transformedLocation1.y);
      line.graphics.lineTo(transformedLocation2.x, transformedLocation2.y);
      addChild(line);
    }

How it works...

ActionScript's Graphic class offers a rich set of drawing primitives. This allows you to create virtually any vector graphic you like. Some of the concepts will feel natural, while others can take a while to properly grasp. It's worth learning the ins and outs of the Graphics class because a well-placed gradient or bitmap can really spice up any graph.

As in all other drawing methods we've seen in this chapter, first the coordinates are transformed.

When drawing a bitmap point, the embedded resource class is instantiated into a Bitmap class. This is the class that will display the image.

Next we use some simple math to place the bitmap at the center of the coordinates. In ActionScript, the bitmap's (x , y) coordinates reflect the upper-left corner. So if we want to place the center of the bitmap at our coordinates, we need to subtract half of the width and height.

As usual, the final step is adding the bitmap to the graph sprite.

Drawing gradients requires extra work. To draw an area that is filled with a gradient, we use the beginGradientFill method. It takes the following parameters:

Optionally, you can also add a matrix that transforms the gradient. This will allow you to correctly place the gradient. In the case of this example, we stretch the gradient over the full screen and rotate it by 90 degrees.

When drawing bitmap fills for lines, there are a few points worth noting:

  • Line bitmaps and gradients are applied to the actual drawn line. This means you need both the lineStyle and lineBitmapStyle methods. You can't take out the first one or you would not see anything drawn.

  • The lineBitmapStyle method takes a BitmapData class as an argument. The difference between this and the Bitmap class, is that bitmap is the actual representation on the screen, while BitmapData is just the bits that are needed to draw the bitmap. Hence BitmapData does not have an x or y coordinate.

If you want to change the exact placement of the bitmaps, the lineBitmapStyle method takes an optional Matrix as an argument. This works similar to all the other matrix operations we've seen. Getting this exactly right isn't easy, so you may need to do some experimentation.

There's more...

We've only covered the very tip of the iceberg that is the Graphics class.

Transformation

As with most visual elements in ActionScript, bitmaps and gradients can be translated, rotated, made translucent, and much more. It's worth experimenting a little to get to know what's possible.

Gradient lines and points, bitmap areas

We've only shown three examples. However you can combine any style with any type of graph. Feel free to extend the existing Graph class with whatever you need for your graphs.

See also

Most ActionScript books have good coverage of the Graphics class. But there are also a few that go into much more detail.

Although it can be a bit hard to get into, the live docs also provide a fairly in-depth overview of the features. This is available at: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Graphics.html .

 

Adding legends


Without a little explanation, any graph will quickly become incomprehensible. Especially when showing multiple sets of data, it is important to properly distinguish between the two. A legend can do just that.

Getting ready

For the Recipe9 class, we start from the overlapping multiple area charts class. So copy that class and rename it to Recipe9 to follow along.

How to do it...

  1. The legend related display methods will receive their own class. We start by creating a new Legend class that extends Sprite. Optionally, you can add a title:

    package  
    {
        import flash.display.Sprite;
        import flash.text.TextField;
        import flash.text.TextFieldAutoSize;
    
        public class Legend extends Sprite
        {
          private var lineHeight:Number = 20;
          private var lines:int = 0;
          
          public function Legend(title:String = null) 
          {
             if (title != null) {
                    var titleField:TextField = new TextField();
                    titleField.text = title;
                    titleField.autoSize = TextFieldAutoSize.LEFT;
                    addChild(titleField);
                    lines++;
                }
            }
            public function addKey(key:String, color:uint, alpha:Number):void
            {
                // see further
            }
      }
    }
  2. In the main program (Recipe9), you can add the legend to the display with the following code:

    var legend:Legend = new Legend("Legend title");
    legend.x = 600;
    legend.y = 20;
    legend.addKey("First series", 0xff9933, 0.5);
    legend.addKey("More data points", 0x3399ff, 0.5);
    addChild(legend);
  3. As you can see, we've already added methods to add the display of actual keys. In typical graph fashion, we want to show a small square containing the color of that data set with some text next to it to name the data set.

    The code isn't overly complicated and uses the drawRect method from the Graphics class and the plain TextField class we've seen before:

    public function addKey(key:String, color:uint, alpha:Number):void
    {
      var keySample:Shape = new Shape();
      keySample.graphics.lineStyle(1);
      keySample.graphics.beginFill(color, alpha);
      keySample.graphics.drawRect(0, lines * lineHeight, 15, 15);
      keySample.graphics.endFill();
      addChild(keySample);
    
      var keyField:TextField = new TextField();
      keyField.text = key;
      keyField.x = 20;
      keyField.y = lines * lineHeight;
      keyField.autoSize = TextFieldAutoSize.LEFT;
      addChild(keyField);
    
      lines++;
    }

    If you run the program now, you'll notice one thing still missing: a nice box around our legend to distinguish it from the actual graph.

  4. The only tricky thing is that it needs to be updated dynamically when a key is added. So we store it in an instance variable:

        private var box:Shape;
  5. Next we create the method to update the box:

         private function updateBox():void 
        {
          if (box != null) {
            removeChild(box);
          }
          box = new Shape();
          box.graphics.lineStyle(1);
          box.graphics.drawRect(-5, -5, width+6, height+6);
          addChild(box);
        }
  6. Now we should execute the updateBox method every time the legend changes. So the final step is to add it to the end of the constructor and the addKey methods.

How it works...

Just like with the Graph class, we use the Legend class to hold all of the separate graphical elements of the legend. That way we can just work from the (0,0) origin and not worry about the exact location where the legend will be placed.

Since this would require its own chapter, we won't be going into the details of styling and customizing the textField class. The only option that we use the autoSize property. It will make sure that the size of the text field fits the text and isn't just left at the default 100x100. This guarantees that the sprite's size will be exactly the size of the text and allows us to easily draw a nice fitting box around the entire legend display.

The line counter is responsible for making sure we can place each individual legend key at the right distance.

You may have noticed that we draw the legend in screen coordinates, not graph coordinates. In many cases, it's easier to place it in the correct location that way. Although if you want to fix the legend in relation to the graph (for instance, always in the bottom, at the center) you may want to think about putting it inside the Graph class and use the transformed coordinates. In that case, the Legend class would probably be a child of the Graph class.

The legend is a sprite, which means you can use the legend like any other one. You can resize it, move it, and even rotate it if you want (you may need to use embedded fonts on the text fields to perform some of those operations).

There's more...

With this recipe, we've only scratched the surface of what you can do with legends.

TextField customization

In this recipe, we've used the very basics of the textField class. However, the textField class is one of the most versatile ActionScript classes available. It offers so many options that ActionScript reference books need an entire chapter or two to cover it.

So if you want to change the text display, start with the live docs for textField and textFormat and go from there.

A background

In this recipe, we've kept the legend transparent. It is perfectly possible to add a background color to it. To obtain this, extends the updateBox method so it also draws a fill (see the previous recipe). One thing to keep in mind: you need to make sure that the box is drawn behind the keys and title and not on top.

Research the addChildAt method to find the solution for this issue: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObjectContainer.html#addChildAt%28%29.

See also

More information on customizing textField can be found in the Adobe live docs:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html.

The textFormat class is the main way to change fonts, sizes, and many more options:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextFormat.html.

 

Using Flex for charts


No book on ActionScript graphing would be complete without at least mentioning Flex. The Flex software development kit also contains an extensive API for drawing different types of graphs.

The techniques we've discussed in the previous recipes are much more powerful, but depending on the case, a small Flex component might be enough.

Getting ready

Since we need access to the Flex library, we'll need to set up our FlashDevelop workspace differently. The easiest way is to create a new project. When asked for the type of project, pick Flex 3 project (instead of AS3 Project, which we use throughout the other parts of this book).

If you browse through the files in the src directory of your project, you will now find a Main.mxml file instead of the previous Main.as file. This is the file that describes your user interface. Instead of programmatically adding all the sprites and shapes to your interface, you will add them to this file.

How to do it...

  1. Open Main.mxml. All the code presented here should be added inside the mx:Application tags.

  2. First let's start with the data set definition:

      <mx:Script><![CDATA[
        import mx.collections.ArrayCollection;
        [Bindable]
        public var dataSet:ArrayCollection = new ArrayCollection([
          {x:0,   y1:20,  y2:50},
          {x:50,  y1:70,  y2:40},
          {x:100, y1:0,   y2:100},
          {x:150, y1:150, y2:150},
          {x:200, y1:300, y2:200},
          {x:250, y1:200, y2:170},
          {x:300, y1:170, y2:160},
          {x:350, y1:20,  y2:120},
          {x:400, y1:60,  y2:80},
          {x:450, y1:250, y2:150},
          {x:500, y1:90, y2:20},
          {x:550, y1:50, y2:40},
          {x:600, y1:110, y2:90},
          {x:650, y1:150, y2:150},
          {x:700, y1:320, y2:200}
        ]);
        ]]></mx:Script>
  3. This is the same set we used in previous recipes. Now we add the actual chart and legend:

      <mx:Panel title="Our first Flex chart">
        <mx:AreaChart id="areaChart" showDataTips="true" dataProvider="{dataSet}">
          <mx:horizontalAxis>
            <mx:CategoryAxis
              dataProvider="{dataSet}"
              categoryField="x"
            />
          </mx:horizontalAxis>
          <mx:series>
            <mx:AreaSeries 
              yField="y1" 
              displayName="First series">
              <mx:areaFill>
                <mx:SolidColor color="0xff9933" alpha="0.5" />
              </mx:areaFill>
            </mx:AreaSeries>
            <mx:AreaSeries 
              yField="y2" 
              displayName="More data points">
              <mx:areaFill>
                <mx:SolidColor color="0x3399ff" alpha="0.5" />
              </mx:areaFill>
            </mx:AreaSeries>
          </mx:series>
        </mx:AreaChart>
        <mx:Legend dataProvider="{areaChart}"/>
      </mx:Panel>

How it works...

Flex has a few new data structures; in this recipe we use the ArrayCollection data structure. This is a specially defined data structure that makes it easy to manage chart data. In fact, if you wanted to include the Flex library in your project, you could use the class for your own graph drawing.

Now let's look at the structure as a display list: the root element is a Panel class. This is a simple wrapper class to which you can add a title.

Inside this panel, there are two visual elements: an area chart and a legend. The legend is the easiest of the two explain; we use the default settings and let it read its data from the chart. It's all automatically added and filled.

The area chart is a little more involved:

  • The dataProvider attribute links this chart to the data we previously defined.

  • The showDataTips attribute is enabled to show you how quickly you can get some pretty fancy charts with Flex. If this is enabled, you can hover over the data points with your mouse, and you'll see a pop up with more details.

  • The horizontal axis is mapped to the x element inside the data set. Note that scaling of the axes happens automatically (but it can be overridden if required).

  • Finally we add two data series, both of which we map to the data set we defined previously. We also apply the same fill that was used in the previous recipes.

If you run the program, you will see a graph similar to the one presented in the multiple area charts recipe.

The difference is the way we defined the chart. With Flex you describe how the chart will look and let Flex do the work for you. In plain ActionScript, you have full control, but you have to do all the hard work.

The choice will depend on the application and most likely also on personal preferences.

There's more...

The Flex charting API is immense and with the recipe above, and we haven't even scratched the surface. If you think this style of development is for you, here are a few more things you can do.

Flex without MXML

If you like Flex, but don't like MXML, there's a way to program Flex with virtually no XML. Since Flex wasn't built for this purpose, you'll find fairly little documentation explaining this.

If this is something you want to try out, start with this StackOverflow question:

http://stackoverflow.com/questions/141288/possible-to-use-flex-framework-components-without-using-mxml.

ActionScript in Flex

Everything inside the mx:Script tags is pure ActionScript code. If you don't like the ArrayCollection data structure, you can always write your own bit of ActionScript that maps from any data structure to the one Flex expects.

See also

If you like to get into Flex, there are numerous books. Also, the online documentation is very complete. For charting, your best starting point is available here:

http://livedocs.adobe.com/flex/3/html/help.html?content=charts_intro_7.html.

Latest Reviews (1 reviews total)
ActionScript Graphing Cookbook
Unlock this book and the full library FREE for 7 days
Start now