Function Graphers with Zooming

A recent e-mail from a flashandmath.com reader requested a tutorial on adding mouse-based panning and zooming to a mathematical graph. The specific question was about a "google map" type interface which allowed drag and drop panning and using the mouse for zooming in and out.

Click the screen shot below to open the versatile graphing application that uses a mouse click (in conjunction with depressing an appropriate keyboard key) to manage the user interface for zooming in/out on a point specified by the mouse. We also use simple drag and drop (with some cool easing) to pan the graph with the mouse.

Zoom graph with 
   mouse click

Download

Download the fla file and the accompanying AS3 class files used in this tutorial in the following compressed folder.

Comments

This tutorial brings together ideas from three previous tutorials and how-to's by the Flash & Math team on flashandmath.com site. The first is the custom SimpleGraph class, which we introduced in the Basic tutorial, Using the Custom SimpleGraph Class from flashandmath.com. The SimpleGraph class is behind the function grapher in our examples. The second idea was presented in the how-to: How To Zoom In on a Point in an Image and Pan in AS3 Flash. We showed there how to use the MatrixTransformer AS3 class to zoom on a point in an image. The final idea for this tutorial comes from Dragging with Easing without the Tween Class, where we showed a clever trick for super smooth dragging.

Timeline Code for GraphZoomPan_Click.fla

The grapher presented here uses several of our custom AS3 classes: MathParser, GraphingBoard, SimpleGraph, and others. The classes are included in the zip package. With these classes in place, creating the zoom and pan grapher is easy. Here is the code and comments.

The folder flashandmath must be in the same directory as the current fla file. This folder is included in the downloaded zip file.

import flashandmath.as3.tools.SimpleGraph;

Dimensions of the graphing board are here for easy editability.

var bWidth:Number = 400;

var bHeight:Number = 300;

Establish a variable for the spacing (in the math coordinate system) of labels/tickmarks. This is modified by the method that manages zooming to keep labels from colliding. Initially it should make sense for the function and range of x values in the example preset on the stage.

var labelSpace:Number = 1;

Set the scale change for each time we zoom in. For example, a scale value of 0.8 means we make the graph 80% size on zoom in and 125% size on zoom out.

var scale:Number = 0.8;

Set up a variable that manages the "easing" of the panning motion. See the tutorial Dragging with Easing without the Tween Class for further explanation about different values of this variable.

var responsiveness:Number = 0.25;

Set tab indices for textboxes.

txtXmin.tabIndex = 1;

txtXmax.tabIndex = 2;

txtYmin.tabIndex = 3;

txtYmax.tabIndex = 4;

txtFun.tabIndex = 5;

Hide the zoom cursors that were created and placed on the stage at "authoring" time.

mcIn.visible = false;

mcOut.visible = false;

Create an instance of SimpleGraph. See SimpleGraph tutorial for complete documentation of the class.

var g:SimpleGraph = new SimpleGraph(bWidth,bHeight);

g.x = 10;

g.y = 50;

addChild(g);

Add the cursor movieclips to the SimpleGraph object. Using the addChildToBoard method makes the SimpleGraph masking apply also work to the cursors.

g.addChildToBoard(mcIn);

g.addChildToBoard(mcOut);

Draw the graph after setting up the window & options. The zooming the graph is managed by dynamically changing the values in the on-stage textboxes (and the value of labelSpace) and then calling this method to create the correct graph. Note that setWindow clears all of the graph contents, so all have to be replaced.

function drawGraph():void {

g.setWindow(txtXmin.text, txtXmax.text, txtYmin.text, txtYmax.text);

g.board.drawAxes();

g.board.setTicks(labelSpace,labelSpace,10,10);

g.board.drawTicks();

g.board.setGrid(labelSpace/2,labelSpace/2);

g.board.drawGrid();

g.board.addLabels();

g.graphRectangular(txtFun.text,"x",1,2,0x0000CC);

}

A streamlined version of the previous method for panning. Since panning is tied to the ENTER_FRAME event, it requires rebuilding the graph at the frame rate. Hence we do not draw tickmarks and labels during the pan. When the pan is complete, a call to drawGraph() replaces these details.

function redrawGraph():void {

g.setWindow(txtXmin.text, txtXmax.text, txtYmin.text, txtYmax.text);

g.board.drawAxes();

g.board.drawGrid();

g.graphRectangular(txtFun.text,"x",1,2,0x0000CC);

}

Variables for old and new window limits are global so we can use them for both zoom and pan methods.

var old_xmin:Number, old_xmax:Number, old_ymin:Number, old_ymax:Number;

var new_xmin:Number, new_xmax:Number, new_ymin:Number, new_ymax:Number;

The function zoom below handles the mouse click event. Note again that for Mac users, the ctrlKey refers to the command key.

g.addEventListener(MouseEvent.CLICK, zoom);

 

function zoom(mev:MouseEvent):void {

// If neither or both ctrl&shift keys are held down, then do nothing

if ((!mev.shiftKey)&&(!mev.ctrlKey)) {

return;

}

if ((mev.shiftKey)&&(mev.ctrlKey)) {

return;

}

// The variable center is where the mouse is, converted to math coordinates

var center:Point = new Point(g.board.xfromPix(g.mouseX), g.board.yfromPix(g.mouseY));

// arrRange will get current limits on x and y range directly from the board

var arrRange:Array;

arrRange = g.board.getVarsRanges();

old_xmin = arrRange[0];

old_xmax = arrRange[1];

old_ymin = arrRange[2];

old_ymax = arrRange[3];

// If the ctrl key is depressed, we zoom in.

if (mev.ctrlKey) {

new_xmin = center.x - scale*(center.x - old_xmin);

new_xmax = center.x + scale*(old_xmax - center.x);

new_ymin = center.y - scale*(center.y - old_ymin);

new_ymax = center.y + scale*(old_ymax - center.y);

}

// If the shift key is depressed, we zoom out.

else if (mev.shiftKey) {

new_xmin = center.x - 1/scale*(center.x - old_xmin);

new_xmax = center.x + 1/scale*(old_xmax - center.x);

new_ymin = center.y - 1/scale*(center.y - old_ymin);

new_ymax = center.y + 1/scale*(old_ymax - center.y);

}

// Recompute labelSpace so that space between doubles everytime the x-range doubles

labelSpace = Math.pow(2,Math.floor(Math.log(new_xmax - new_xmin)/Math.log(2))-3);

// Put new range limits in the textboxes on stage and draw resulting graph.

txtXmin.text = new_xmin.toFixed(3);

txtXmax.text = new_xmax.toFixed(3);

txtYmin.text = new_ymin.toFixed(3);

txtYmax.text = new_ymax.toFixed(3);

drawGraph();

}

For panning we need variables for location of the original mouse_down event and the current mouse position.

var old_center:Point = new Point();

var new_center:Point = new Point();

We add listeners for starting/stopping panning. The functions that handle these events are described next.

g.addEventListener(MouseEvent.MOUSE_DOWN, startPan);

stage.addEventListener(MouseEvent.MOUSE_UP, stopPan);

When panning start all relevant information on the graph is captured. All motion of the mouse will be translated relative to these initial values.

function startPan(me:MouseEvent):void {

var arrRange:Array = g.board.getVarsRanges();

old_xmin = arrRange[0];

old_xmax = arrRange[1];

old_ymin = arrRange[2];

old_ymax = arrRange[3];

old_center = new Point(g.mouseX, g.mouseY);

new_center = new Point(g.mouseX, g.mouseY);

//The actual panning is tied to the ENTER_FRAME event in order to get the easing effect described in the tutorial, Easing without the Tween class.

stage.addEventListener(Event.ENTER_FRAME, pan);

}

 

// When panning is stopped, we call drawGraph to draw all the details of the graph.

function stopPan(me:MouseEvent):void {

stage.removeEventListener(Event.ENTER_FRAME, pan);

drawGraph();

}

 

Handle the ENTER_FRAME event while the mouse button is down.

function pan(e:Event):void {

var new_xmin:Number, new_xmax:Number, new_ymin:Number, new_ymax:Number;

var deltax:Number, deltay:Number;

var goodPoint:Point = new Point(g.mouseX, g.mouseY);

The variable goodPoint is initially the mouse coordinates (in pixels) but the following code keeps it from going beyond the boundaries of the SimpleGraph object g.

if (goodPoint.x > bWidth) {

goodPoint.x = bWidth;

}

else if (goodPoint.x < 0) {

goodPoint.x = 0;

}

if (goodPoint.y > bHeight) {

goodPoint.y = bHeight;

}

else if (goodPoint.y < 0) {

goodPoint.y = 0;

}

This is the key in Easing without Tween tutorial. The center of the graphing board moves in the direction of the mouse coordinates (goodPoint, specifically) instead of moving exactly to the mouse coordinates.

new_center.x += responsiveness*(goodPoint.x - new_center.x);

new_center.y += responsiveness*(goodPoint.y - new_center.y);

 

// Find how far the graph needs to be shifted horizontally and vertically.

deltax = g.board.xfromPix(new_center.x) - g.board.xfromPix(old_center.x);

deltay = g.board.yfromPix(old_center.y) - g.board.yfromPix(new_center.y);

 

// Figure out new limits on x and y window limits.

new_xmin = old_xmin - deltax;

new_xmax = old_xmax - deltax;

new_ymin = old_ymin + deltay;

new_ymax = old_ymax + deltay;

Update textboxes on stage and draw the resulting picture. We use the quicker redrawGraph method here to keep up with the ENTER_FRAME event.

txtXmin.text = new_xmin.toFixed(3);

txtXmax.text = new_xmax.toFixed(3);

txtYmin.text = new_ymin.toFixed(3);

txtYmax.text = new_ymax.toFixed(3);

redrawGraph();

}

Our keyboard event handlers look for the ENTER key (which triggers drawing of a new graph), or ctrl/shift keys to display the appropriate cursor.

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);

stage.addEventListener(KeyboardEvent.KEY_UP, keyHandler);

 

function keyHandler(ke:KeyboardEvent):void {

if (ke.keyCode == Keyboard.ENTER) {

drawGraph();

}

mcIn.x = g.mouseX;

mcIn.y = g.mouseY;

mcIn.visible = ke.ctrlKey;

mcOut.x = g.mouseX;

mcOut.y = g.mouseY;

mcOut.visible = ke.shiftKey;

// If either key (ctrl or shift) is depressed, we hide the mouse.

if (ke.ctrlKey || ke.shiftKey) {

Mouse.hide();

} else {

Mouse.show();

}

}

Draw the initial graph:

drawGraph();

 

For basics of mathematical graphing check out the book Flash and Math Applets: Learn by Example by Doug Ensley and Barbara Kaskosz.

Back to Intermediate Tutorials              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.