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:
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).
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.
First let's add a button that will trigger the update:
Recipe2
class:[Embed(source = "../../../../lib/badge-circle-direction-right-24-ns.png")] private var UpdateButtonBitmap:Class;
var button:BitmapButton = new BitmapButton(new UpdateButtonBitmap()); button.addEventListener(MouseEvent.CLICK, onClick); addChild(button);
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.
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; }
private var _shapes:Array = [];
_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(); }
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.
The following are a few ways in which you may want to change the recipe to suit your specific situation.
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.
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.
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.
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.
3.149.243.32