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

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.