Sending data updates to the graph

Dynamic graphs can update their display whenever the underlying data changes. For instance, you may interrogate a REST service every minute for stock prices and may want the display to reflect the latest data.

There are many ways you may want to update the graph:

  • Update the entire graph with a new data set
  • Add data to the graph and possibly remove the oldest data
  • Update random values

This recipe will demonstrate the first problem, since it can be used to solve all three. However, depending on your situation, you may want to add custom manipulation routines to the graph (see the There's more… section towards the end of this recipe).

Getting ready

This recipe starts from the following document class:

package com.graphing.dataupdate
{
    import flash.display.Sprite;
    import com.graphing.PointGraph;

    public class Recipe2 extends Sprite
    {

        private var _graph:PointGraph;
        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]];
        private var _data2:Array = [[0,30],[50,80],[100,10],[150,160],[200,310],[250,210],[300,410],[350,30],[400,70],[450,260],[500,100],[550,410],[600,510],[650,460],[700,330]];

        public function Recipe2() 
        {
            _graph = new PointGraph();
            _graph.data = _data;
            _graph.graphWidth = 800;
            _graph.graphHeight = 600;
            _graph.graphLeft = -50;
            _graph.graphRight = 750;
            _graph.graphTop = 550;
            _graph.graphBottom = -50;
            _graph.createGraph();
            addChild(_graph);

            _graph.drawHorizontalAxis(0, 0, 700, 50, ["0", "700"]);
            _graph.drawVerticalAxis(0, 0, 500, 50, ["0", "250", "500"]);
        }
    }
}

As you can see it is identical to the previous recipes. We have added a second data set, which is what we want to replace the first one with.

How to do it...

First let's add a button that will trigger the update:

  1. Embed a bitmap in the Recipe2 class:
    [Embed(source = "../../../../lib/badge-circle-direction-right-24-ns.png")]
    private var UpdateButtonBitmap:Class;
  2. And add it to the stage with a click listener:
    var button:BitmapButton = new BitmapButton(new UpdateButtonBitmap());
    button.addEventListener(MouseEvent.CLICK, onClick);
    addChild(button);
  3. Finally we need to implement the listener:
    private function onClick(event:MouseEvent):void {
    _graph.updateData(_data2);
    }

Those are the only changes necessary for the main class. The real work will be done in the PointGraph class.

  1. First we modify the drawPoint method so it returns the point shape:
    public function drawPoint(x:Number, y:Number, color:uint = 0xff9933):Shape
    {
        var transformedLocation:Point = _matrix.transformPoint(new Point(x, y));
        var point:Shape = new Shape(); 
        point.graphics.beginFill( color , 1 );
        point.graphics.drawCircle( 0 , 0 , 3 );
        point.x = transformedLocation.x;
        point.y = transformedLocation.y;
        addChild(point);
        return point;
    }
  2. Next we add a private array that will store all the updateable shapes in the graph, as shown in the following code snippet:
    private var _shapes:Array = [];
  3. Now we can update the method that draws the points so that it also stores them in the _shapes array:
    private function drawDataPoints():void {
        for (var i:int = 0; i < _data.length; i++)
        {
            var point:PointGraphPoint = drawPoint(_data[i][0], _data[i][1]);
            if (_draggable) {
            point.makeDraggable();
            }
           _shapes.push(point);
        }
    }

This is all the infrastructure we need to create the updateData method that we used in the onClick method:

public function updateData(newData:Array):void {
    _data = newData;

    for (var i:int = 0; i < _shapes.length; i++ ) {
        removeChild(_shapes[i]);
    }
    _shapes = [];

    drawDataPoints();
}

How it works...

The changes to the main Recipe2 class are only made to trigger the update. They could be replaced with virtually anything. You could add a timer and other events, based on remote data that is received, and so on.

Most of the code changes that we needed to make were related to keeping track of everything that can change. Basically we need access to it when an update needs to be performed.

We choose to use an array because it is an easy datastructure to manipulate, but you could just as well separate the graph elements that are static and those that are dynamic in two separate sprites.

The options are limitless and should be adapted to your specific problem.

There's more...

The following are a few ways in which you may want to change the recipe to suit your specific situation.

Scrolling data

If you have a historical display and you want to add new data. It's probably interesting to implement a push/pop API to the graph so that only the most recent data is shown. The push method adds a new data point, while the pop method removes the oldest one. Or you could combine both in one function.

You may also want to have the time axis update whenever the data is scrolled through so that it shows the correct time.

Random access

Another extension is to have the possibility to update a single point. For instance, if you have a bar chart of different stock prices, you may only want to update the stock for which the price has changed.

In that case, you may want to use an associative array to store the shapes instead of the simple array that was used here. You can also update the updateData function to take two parameters: the x-coordinate (stock name) that changed and the y-coordinate representing the new value.

Undo

For several reasons, you may want to go back to the state before the graph was changed. For instance, you want to have an undo function or you want to look back through the historical data.

In our current recipe, this can be implemented by creating a two dimensional _shapes array and adding a private "revision" variable. Instead of deleting the old data, we simply add one to the revision and add it do the array.

That way, it is possible to rollback to any revision at any time.

Keep in mind that, if you have many updates, you may want to limit the number of revisions you store, to not run out of memory.

Dispatching and listening for events

If updates come from many different sources in your program, you can organize the change/update infrastructure as an event listener pattern. Your graph listens for a new data-change event that you create. And the various parts dispatch this event.

Creating and dispatching custom events is explained in these blog posts:

http://www.learningactionscript3.com/2007/11/20/dispatching-custom-events/

http://www.learningactionscript3.com/2008/11/11/passing-arguments-with-events/

http://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html

Setting this up will take a little work, but it is a very elegant solution that integrates perfectly with the philosophy of the display list. All objects that are put on the display list already offer many events that one can listen for (keyboard, mouse, and screen changes). Events allow you to program in an asynchronous way, where you don't need to wait for something to happen. Instead, the program will tell you something has happened and execute the relevant code for you. This way of programming will create a responsive user interface that does not freeze when it is waiting for something to happen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.243.32