ActionScript Graphing Cookbook
上QQ阅读APP看书,第一时间看更新

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:

    How to do it...

    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:
    How to do it...

    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.