/* *********************************************************************** paramFull.fla Last modified: APR 2008 P R O G R A M D E S C R I P T I O N The program allows the user to examine parametric functions, either user- defined or loaded from a "gallery" of examples. Component functions are shown along with the resulting parametric plot. The user may drag a slider bar to show motion on the function. The user may enable commentaries on x and/or on y, such as "x(t) increases so the parametric plot moves to the right". The user may enable visual x and/or y connectors to emphasize the relationship between the components and the parametric plot. R E Q U I R E D F I L E S folder bkde ParamGallery1.xml in the same folder as this file A C K N O W L E D G E M E N T S This work is the result of modifications by Therese Shelton of Southwestern University (shelton@southwestern.edu) of work by Barbara Kaskosz, University of Rhode Island (bkaskosz@math.uri.edu) and Douglas E. Ensley, Shippensburg University(deensl@ship.edu). Kaskosz and Ensley created the original template and classes. In June 2007 Shelton began work within the MAA PREP Flash Workshop. The original work and the workshop were supported in part by the National Science Foundation under the grant DUE-0535327. Modifications and adaptations were made to several Kaskosz/Ensley files, primarily on param_basic_as3, fun_graph_as3_template2, and GraphingBoard.as. Shelton added ** multiple graphing boards. ** color-coding output, labels, and graphing board edges for each variable. ** the calculation of largest and smallest x(t) and y(t) values, allowing the automatic calculation of x and y ranges. ** multiple boards to show the relationship between the parametric graph and its components. ** the xml Gallery, a feature found in other Kaskoz/Ensley files. ** connectors to illustrate connections between the parametric curve and the components ** comments on motion Some code was reorganized or rewritten. N O T E ** If a graphing window does not include a zero value, an axis will not show. ** For now assume user does nothing out of sequence, such as changing x(t) and not hitting the GRAPH button before using the slider bar. S T A G E I T E M S (names must be used exactly in the program) INPUT BOXES created on the stage (dynamic text boxes) TminEnter, TmaxEnter (input values for graphing) XminEnter, YminEnter YminEnter, YmaxEnter InputBox1 (formula for x(t) ) InputBox2 (formula for y(t) ) BUTTONS butSyntax (mouse over to see a box of instructions regarding proper syntax for user input) butGraph (click to graph the user changes) butReset (click to reset everything to the original example) galleryBtn (click to load in another example from the xml file) btnPlay (animate or "play" the function and, if set to show, accumulation) RAIO BUTTONS (actually movie clips) (from Kaskosz/Ensley files) frame 1 no black dot; frame 2 with black dot. user clicks to toggle functionality rbmcConnectX (show/hide connectors: horizontal on {t,x(t)} and vertical on {x(t), y(t)} ) rbmcConnectY (show/hide connectors: vertical on {t,y(t)}, vertical on {x(t), y(t)} ) rbmcCommentX (show/hide comments relating increase/decrease of x(t) to right/left on {x(t), y(t)} ) rbmcCommentY (show/hide comments relating increase/decrease of y(t) to up/down on {x(t), y(t)} ) OUTPUT BOXES created in the script (dynamic text boxes) CreditBox (displays program credits) rbConnectLabelX (dynamic label for the radio button to toggle x-connectors) rbConnectLabelY (dynamic label for the radio button to toggle y-connectors) rbCommentLabelX (dynamic label for the radio button to toggle comments on x(t) ) rbCommentLabelY (dynamic label for the radio button to toggle comments on y(t) ) OUTPUT BOXES created on the stage (dynamic text boxes) KnobBox (displays the current t-value for the slider bar) TminBox, TmaxBox (output values for graphing windows and sliders) XminBox, XmaxBox YminBox, YmaxBox /* ************************************************************************ */ // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// set up utilities, parsers //////////////////// //////////////////////////////////////////////////////////////////////////// // import items we need import bkde.as3.utilities.*; import bkde.as3.parsers.*; import bkde.as3.boards.*; /* Create an instance of the custom class MathParser and store it in a variable called "procFun" whose datatype is MathParser. The constructor takes one parameter: an array of strings giving the names of variables allowed. In our case, it is just one variable t. (If there were more, the order in which we list variables is important. The values of the variables will be passed to the evaluator method of MathParser in the same order, e.g.: procFun.doEval([4]); ) */ var procFun:MathParser = new MathParser(["t"]); /* Create an instance of the custom class RangeParser. The user must enter a t range in the appropriate input boxes. User input will be processed and checked for validity. If the user enters their own function, they must enter t ranges. If desired, the user may enter x and y ranges; if missing, the program will calculate x and y ranges. Numerical values are allowed as well as expressions involving pi, e.g.: 2*pi, pi/4. */ var procRange:RangeParser = new RangeParser(); /* MathParser's "doCompile" method returns a datatype CompiledObject (defined by one of the classes in the package). Any instance of CompiledObject has three instance propetries: CompiledObjectInstance.PolishArray which represents a mathematical formula in a form suitable for evaluation, CompiledObjectInstance.errorMes which contains a string with an error message should a mistake in syntax be found. Finally, CompiledObjectInstance.errorStatus which is 1 if an error is detected and 0 otherwise. Create a variable to store the results of compiling the user's formulas for the component functions x(t), y(t) . */ var compObj1:CompiledObject; var compObj2:CompiledObject; /* The methods "parseRangeFour" and "parseRangeTwo" of the package RangeParser return a datatype RangeObject (defined by one of the classes in the package). Any instance of RangeObject has three propetries: (1) RangeObjectInstance.Values which is an array containing numerical values for xMin, xMax, yMin, yMax, (or for tMin and tMax), (2) RangeObjectInstance.errorMes which contains an error message should a mistake in range be found, and, (3) RangeObjectInstance.errorStatus which is 1 if an error is detected and 0 otherwise. Create a variable to store the results of compiling the user's formulas for the component functions x(t), y(t) . */ var oRange2:RangeObject; var oRange4:RangeObject; // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// some stage settings //////////////////// //////////////////////////////////////////////////////////////////////////// // rectangular coordinates var sCoordsXY:String="xy"; /* prevent our applet from being rescaled in the Flash Player since many pixel calculations are involved. */ stage.scaleMode="noScale"; /* Labels for the coordinate input boxes. The labels are movie clips created by hand. We adjust their visibility. */ mcXLabel.visible=true; mcYLabel.visible=true; /* The input boxes were created by hand, but we can set some of their properties programmatically. */ InputBox1.wordWrap=true; InputBox1.borderColor=0x0000FF; InputBox2.wordWrap=true; InputBox2.borderColor=0xFF0000; // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// configure graphing boards ///////////// //////////////////////////////////////////////////////////////////////////// /* Create instances of the custom class GraphingBoard: boardXY - {x(t), y(t)} parametric plot boardTX - {t, x(t)} component plot boardTY - {t, y(t)} component plot The custom class GraphingBoard is a visual class (which inherits from the Sprite class) that will be responsible for all the dynamic drawing, displaying an error message should the user make a mistake etc. It will also draw a square in the main movie in which its graph will reside. Its parameter is the size of the square, in pixels. Color and other properties of the "board" variables could be adjusted via methods of the class. We use the default of white board, black border. */ var boardSize:Number = 180; var boardXY:GraphingBoard = new GraphingBoard(boardSize); var boardTY:GraphingBoard = new GraphingBoard(boardSize); var boardTX:GraphingBoard = new GraphingBoard(boardSize); /* Notes on ErrorBox of the GraphingBoard class. You can control the appearance and the position of the error text field with the following method: instance.setErrorBoxSizeAndPos(w:Number,h:Number,xpos:Number, ypos:Number): void Parameters: width and height (in pixels) of error text field, and its x and y position relative to the instance of GraphingBoard. Default: The text field is positioned over the upper half of the graphing board created by the instance. We use the default settings. We will only use boardXY for error messages. */ boardXY.ErrorBox.visible = false; boardXY.ErrorBox.wordWrap = true; boardTY.ErrorBox.visible = false; boardTX.ErrorBox.visible = false; /* As GraphingBoard extends Sprite, we can use properties of the Sprite class to adjust the position of the "board" variables. The x,y positions are measured in pixels from the upper left corner of the parent Display Object Container, in this case from the upper left corner of the Stage. The x coordinate increases to the right, y when going down. The boards are arranged in a rectangular grid. */ var hSpace:Number = 50; var vSpace:Number = 73; var board11x:Number = 7; var board11y:Number = 50; boardTY.x=board11x; //board11 boardTY.y=board11y; boardTX.x=board11x; //board21 boardTX.y=board11y + boardSize + vSpace; boardXY.x=board11x + boardSize + hSpace; //board12 boardXY.y=board11y; boardXY.enableCoordsDisp("x(t)","y(t)"); boardTY.enableCoordsDisp("t","y(t)"); boardTX.enableCoordsDisp("t", "x(t)"); // colors var yColor:uint = (0xB10101); //dark red var xColor:uint = (0x0000FF); //blue var tColor:uint = (0x02D202); //green var xyColor:uint = (0xBB85E0); //medium purple // the size of an arrow for curve tracing var nArrowSize:Number = 7; /* mark edge of each board with the color for that variable; set up boards, arrows, and edges to display*/ var tEdgeTY:Shape = new Shape(); var yEdgeTY:Shape = new Shape(); SetBoardAndEdges(boardTY, tColor, yColor, tEdgeTY, yEdgeTY); var tEdgeTX:Shape = new Shape(); var xEdgeTX:Shape = new Shape(); SetBoardAndEdges(boardTX, tColor, xColor, tEdgeTX, xEdgeTX); var xEdgeXY:Shape = new Shape(); var yEdgeXY:Shape = new Shape(); SetBoardAndEdges(boardXY, xColor, yColor, xEdgeXY, yEdgeXY); function SetBoardAndEdges(b:GraphingBoard, horizColor:uint, vertColor:uint, horizSh:Shape, vertSh:Shape):void { /* Add each board to the Display List as a child of the main MovieCip, i.e. our swf file, which is a Display Object Container. This allows the boards to be rendered on screen. */ addChild(b); b.setArrowSize(nArrowSize); /* Set the style of the tracing cursor to "arrow" for the "board" variables. We need "arrow" rather than "cross" to obtain rotation information. */ b.setTraceStyle("arrow"); /* We will be graphing only one curve at a time, so we set maximum number of graphs for the "board" variables at 1; not really necessary since default is 3. Must not exceed 3. */ b.setMaxNumGraphs(1); // create colored edges vertSh.graphics.lineStyle(2,vertColor); vertSh.graphics.moveTo(b.x-2, b.y); vertSh.graphics.lineTo(b.x-2, b.y + boardSize); horizSh.graphics.lineStyle(2,horizColor); horizSh.graphics.moveTo(b.x, b.y + boardSize + 2); horizSh.graphics.lineTo(b.x + boardSize, b.y + boardSize + 2); addChild(horizSh); addChild(vertSh); } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// set up slider ///////////// //////////////////////////////////////////////////////////////////////////// /* Create an instance of a custom class HorizontalSlider. The constructor takes two parameters: the length of a slider in pixels, (set to be the same width as the x-y board and to align on the left side with the x-y board), and the style of the knob. Two styles are available: "triangle" and "rectangle". We add our instance, hsSlider, to the Display List and adjust its position. */ var hsSlider:HorizontalSlider=new HorizontalSlider(boardSize,"triangle"); addChild(hsSlider); hsSlider.x=board11x; hsSlider.y=board11y + 2*boardSize + vSpace + 20; //The length of our slider. var sliderLen:Number=hsSlider.getSliderLen(); /* The HorizonatalSlider class has many methods that allow us to customize the appearance of the slider. We use default values except for the color of the knob which we want to match the color of the parameter. */ hsSlider.changeKnobColor(tColor); // Set the initial position of the slider's knob at the far left, clear knob label. resetKnob(); // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// set up connector feature ///////////// //////////////////////////////////////////////////////////////////////////// /* Each toggles on/off when the user clicks the appropriate radio button. A value of "true" allows connectors to be shown. */ var isConnectX:Boolean = false; var isConnectY:Boolean = false; /* radio button movie clips (from Kaskosz/Ensley files) frame 1 no black dot; frame 2 with black dot. Use stop() to avoid flashing (looping between frames.) */ rbmcConnectX.stop(); rbmcConnectY.stop(); // - - - - - - - - - - - - - connectors will be line segments var xBarOnTX:Shape = new Shape(); var xBarOnXY:Shape = new Shape(); var yBarOnTY:Shape = new Shape(); var yBarOnXY:Shape = new Shape(); boardTX.addChild(xBarOnTX); boardXY.addChild(xBarOnXY); boardTY.addChild(yBarOnTY); boardXY.addChild(yBarOnXY); // - - - - - - - - - - - - - labels for the two frames of the radio buttons var rbConnectLabelX:TextField=new TextField(); var rbConnectLabelY:TextField=new TextField(); rbConnectLabelX.type=TextFieldType.DYNAMIC; rbConnectLabelX.border=false; rbConnectLabelX.background=false; rbConnectLabelX.visible=true; rbConnectLabelX.x = rbmcConnectX.x + 12; rbConnectLabelX.y = rbmcConnectX.y - 10; rbConnectLabelX.width = 85; rbConnectLabelX.height = 15; rbConnectLabelX.text = "no x connector"; addChild(rbConnectLabelX); rbConnectLabelY.type=TextFieldType.DYNAMIC; rbConnectLabelY.border=false; rbConnectLabelY.background=false; rbConnectLabelY.visible=true; rbConnectLabelY.x = rbmcConnectY.x + 12; rbConnectLabelY.y = rbmcConnectY.y - 10; rbConnectLabelY.width = 85; rbConnectLabelY.height = 15; rbConnectLabelY.text = "no y connector"; addChild(rbConnectLabelY); // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// set up commentary feature ///////////// //////////////////////////////////////////////////////////////////////////// /* Each toggles on/off when the user clicks the appropriate radio button. A value of "true" allows connectors to be shown. */ var isCommentX:Boolean = false; var isCommentY:Boolean = false; /* radio button movie clips (from Kaskosz/Ensley files) frame 1 no black dot; frame 2 with black dot. Use stop() to avoid looping between frames. */ rbmcCommentX.stop(); rbmcCommentY.stop(); // - - - - - - - - - - - - - labels for the two frames of the radio buttons var rbCommentLabelX:TextField=new TextField(); var rbCommentLabelY:TextField=new TextField(); rbCommentLabelX.type=TextFieldType.DYNAMIC; rbCommentLabelX.border=false; rbCommentLabelX.background=false; rbCommentLabelX.visible=true; rbCommentLabelX.x = rbmcCommentX.x + 12; rbCommentLabelX.y = rbmcCommentX.y - 10; rbCommentLabelX.width = 85; rbCommentLabelX.height = 15; rbCommentLabelX.text = "no x comments"; rbCommentLabelY.type=TextFieldType.DYNAMIC; rbCommentLabelY.border=false; rbCommentLabelY.background=false; rbCommentLabelY.visible=true; rbCommentLabelY.x = rbmcCommentY.x + 12; rbCommentLabelY.y = rbmcCommentY.y - 10; rbCommentLabelY.width = 85; rbCommentLabelY.height = 15; rbCommentLabelY.text = "no y comments"; addChild(rbCommentLabelX); addChild(rbCommentLabelY); // - - - - - - - - - - - - - dynamic text box for the commentary var CommentBoxX:TextField=new TextField(); var CommentBoxY:TextField=new TextField(); CommentBoxX.type=TextFieldType.DYNAMIC; CommentBoxX.border=true; CommentBoxX.background=true; CommentBoxX.visible=false; CommentBoxX.wordWrap=true; CommentBoxY.type=TextFieldType.DYNAMIC; CommentBoxY.border=true; CommentBoxY.background=true; CommentBoxY.visible=false; CommentBoxY.wordWrap=true; //near position of board22 CommentBoxX.x = board11x + boardSize + hSpace; CommentBoxX.y = board11y + boardSize + vSpace; CommentBoxX.width = boardSize + hSpace; CommentBoxX.height = boardSize/2 - 5; CommentBoxX.text = ""; CommentBoxY.x = CommentBoxX.x; CommentBoxY.y = CommentBoxX.y + CommentBoxX.height + 5; CommentBoxY.width = boardSize + hSpace; CommentBoxY.height = boardSize/2 - 5; CommentBoxY.text = ""; addChild(CommentBoxX); addChild(CommentBoxY); // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// define global graphing variables ///////////// //////////////////////////////////////////////////////////////////////////// // Many variables are global since they are used by several functions. /* Set the number of points used to create the graph of each curve entered by the user. A larger number yields smoother graphing and tracing. Try between 200 and 700. */ var numPoints:int = 500; /* Set number of decimals to display with this power of 10. (100 for 2 decimals) */ var roundDigits:int = 100; var i:int; // A counter variable for loops. var index:int; // index into parallel arrays. var tstep:Number; // distance between t-values for consecutive points /* Store the ranges for t, x, and y. The user must define the t-range. The user may define the ranges for x and y, or the program calculates them. */ var tMin, tMax:Number; var xMin, xMax:Number; var yMin, yMax:Number; /* Store ranges for the graphing boards and slider, allowing padding. */ var tMinG, tMaxG:Number; var xMinG, xMaxG:Number; var yMinG, yMaxG:Number; //arrays that will hold calculated t and function values var tArray:Array=[]; var xArray:Array=[]; var yArray:Array=[]; // arrays that will hold consecutive ordered pairs on the plots var fArrayXY:Array=[]; var fArrayTY:Array=[]; var fArrayTX:Array=[]; /* Arrays which will store positions of the x and y coordinates in pixels as well as rotation values for the tracing arrows. */ var arrowPosXY:Array=[]; var arrowPosTY:Array=[]; var arrowPosTX:Array=[]; //arrays that will hold commentaries var xCommentArray:Array=[]; var yCommentArray:Array=[]; var isTRangeSet:Boolean = false; // indicates whether user set the t-range var isGoodXY:Boolean = false; // indicates whether user entered good x(t), y(t) var isGraphToTrace:Boolean = false; // indicates presence of a curve to trace // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// begin with a graph; process user entries, clicks ///////////// //////////////////////////////////////////////////////////////////////////// showOriginal(); // begin with a pre-set curve function showOriginal():void { /* Initial entries in the coordinates input boxes; that is, formulas for x(t) and y(t). */ InputBox1.text="3*cos(t)"; InputBox2.text="sin(t)"; // Initial entries for t, x, and y ranges input boxes. XminEnter.text="-3.75"; XmaxEnter.text="3.75"; YminEnter.text="-3.75"; YmaxEnter.text="3.75"; TminEnter.text="-0.25*pi"; TmaxEnter.text="2*pi"; graphCurve(); } // - - - - - - - - - - - - - process user clicking "reset" button butReset.addEventListener(MouseEvent.CLICK,doReset); function doReset(e:MouseEvent):void { showOriginal(); } // - - - - - - - - - - - - - process user clicking "graph" button butGraph.addEventListener(MouseEvent.CLICK,doGraph); function doGraph(e:MouseEvent):void { graphCurve(); } function graphCurve():void { clearSettings(); /* grab function definitions and graphing ranges, parse and check for errors. */ processInput(); /* If there are no errors in t, x(t), or y(t), then calculate all the values and display plots. Otherwise do nothing. */ if (isGoodXY && isTRangeSet) { processFunction(); displayGraph(); } } // - - - - - - - - - - - - - process user clicking a "connect" radio button rbmcConnectX.addEventListener(MouseEvent.CLICK,doConnectX); rbmcConnectY.addEventListener(MouseEvent.CLICK,doConnectY); function doConnectX(e:MouseEvent):void { isConnectX = !isConnectX; //toggle if(isConnectX) { rbmcConnectX.gotoAndStop(2); rbConnectLabelX.text = "x is connected"; moveXConnectors(); } else { // isConnectX is false so remove connections rbmcConnectX.gotoAndStop(1); rbConnectLabelX.text = "no x connector"; resetXConnectors(); } } function doConnectY(e:MouseEvent):void { isConnectY = !isConnectY; //toggle if(isConnectY) { rbmcConnectY.gotoAndStop(2); rbConnectLabelY.text = "y is connected"; moveYConnectors(); } else { // isConnectY is false so remove connections rbmcConnectY.gotoAndStop(1); rbConnectLabelY.text = "no y connector"; resetYConnectors(); } } // - - - - - - - - - - - - - process user clicking a "comment" radio button rbmcCommentX.addEventListener(MouseEvent.CLICK,doCommentX); rbmcCommentY.addEventListener(MouseEvent.CLICK,doCommentY); function doCommentX(e:MouseEvent):void { isCommentX = !isCommentX; //toggle if(isCommentX) { rbmcCommentX.gotoAndStop(2); rbCommentLabelX.text = "x commentary"; CommentBoxX.visible=true; changeXComment(); } else { // isCommentX is false, so remove commentary for x rbmcCommentX.gotoAndStop(1); rbCommentLabelX.text = "no x comments"; resetXComment(); } } function doCommentY(e:MouseEvent):void { isCommentY = !isCommentY; //toggle if(isCommentY) { rbmcCommentY.gotoAndStop(2); rbCommentLabelY.text = "y commentary"; CommentBoxY.visible=true; changeYComment(); } else { // isConnectY is false, so remove commentary for y rbmcCommentY.gotoAndStop(1); rbCommentLabelY.text = "no y comments"; resetYComment(); } } // - - - - - - - - - - - - - process user moving slider knob stage.addEventListener(MouseEvent.MOUSE_MOVE, handleSliderMove); function handleSliderMove(e:MouseEvent):void { if(!hsSlider.isPressed || !isGraphToTrace){ return; } if(!isTRangeSet) { resetKnob(); return; } /* hsSlider.isPressed, isGraphToTrace, and isTRangeSet are all true. Get the knob position from the slider in pixels; translate it to a t-value, and find the corresponding index into all the arrays. */ index = tToIndex( tFromSlider ( hsSlider.getKnobPos() ) ); updateBoards(); //activate all changes e.updateAfterEvent(); } // other user clicks (syntax, credits, instructions, etc) are processed elsewhere // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// function processInput ///////////// //////////////////////////////////////////////////////////////////////////// function processInput():void { // store the user's input for the t-range values, x(t), and y(t). var stMin:String; var stMax:String; var sFunction1:String = ""; var sFunction2:String = ""; /* Store the user's formulas for the x-coordinate in sFunction1, and the y-coordinate in sFunction2. */ sFunction1 = InputBox1.text; sFunction2 = InputBox2.text; /* Check if the user entered formulas for both coordinates. If not, an error message is sent to the boardXY.ErrorBox and the function quits. */ if(sFunction1.length == 0) { boardXY.ErrorBox.visible = true; boardXY.ErrorBox.text = "Enter formula for x(t) coordinates."; return; } if(sFunction2.length == 0) { boardXY.ErrorBox.visible = true; boardXY.ErrorBox.text = "Enter formula for y(t) coordinates."; return; } /* Retrieve the user's entries in t range boxes and parse them. If an error is found an error message is displayed and the function quits. Otherwise, the t range is set. */ stMin = TminEnter.text; stMax = TmaxEnter.text; oRange2 = procRange.parseRangeTwo(stMin, stMax); if(oRange2.errorStatus == 1){ boardXY.ErrorBox.visible = true; boardXY.ErrorBox.text = "Error in t-range."; return; } tMin = oRange2.Values[0]; tMax = oRange2.Values[1]; isTRangeSet = true; /* Evoke our MathParser "doCompile" method to compile the formula for the first and the second coordinates entered by the user. If an error is found during compiling, a message is sent to boardXY.ErrorBox and the function quits. */ compObj1 = procFun.doCompile(sFunction1); if(compObj1.errorStatus == 1){ boardXY.ErrorBox.visible = true; boardXY.ErrorBox.text = "Error in x(t). "+compObj1.errorMes; return; } compObj2 = procFun.doCompile(sFunction2); if(compObj2.errorStatus == 1){ boardXY.ErrorBox.visible = true; boardXY.ErrorBox.text = "Error in y(t). "+compObj2.errorMes; return; } isGoodXY = true; // have good x(t), y(t) /* Retrieve the user's entries in x and y range boxes and parse them. If no error is found, the x and y ranges are set. If an error is found or anything is left blank, we wait until we process the functions and calculate values. */ oRange4= procRange.parseRangeFour(XminEnter.text, XmaxEnter.text, YminEnter.text, YmaxEnter.text); if(oRange4.errorStatus == 0) { xMin = oRange4.Values[0]; xMax = oRange4.Values[1]; yMin = oRange4.Values[2]; yMax = oRange4.Values[3]; } /* at this point, we have good values for tMin, tMax and good formulas for x(t), and y(t). If the user entered them, we also have max and min values for x and y. */ } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// function processFunction ///////////// //////////////////////////////////////////////////////////////////////////// function processFunction():void { /* This section is executed if we have good entries for tMin, tMax, x(t), and y(t). If the user entered them, we also have max and min values for x and y. */ tstep = (tMax - tMin)/numPoints; /* We pad the t-interval by 5% on either end. */ tMinG = tMin - 0.05 * (tMax - tMin); tMaxG = tMax + 0.05 * (tMax - tMin); // - - - - - - - - - - - - - fill t, x, y arrays and find extreme values /* We calculate the max and min values of x(t) and y(t) using xMinCalc, etc. */ var yMaxCalc, yMinCalc, xMaxCalc, xMinCalc:Number; // begin with the first value as both the largest and smallest. xMaxCalc = procFun.doEval(compObj1.PolishArray,[tMin]); xMinCalc = xMaxCalc; yMaxCalc = procFun.doEval(compObj2.PolishArray,[tMin]); yMinCalc = yMaxCalc; // Current values of x and y as t increases. var curt, curx, cury:Number; for(i=0; i <= numPoints; i++){ curt = tMin+tstep*i; tArray[i] = curt; /* Calculate values of x(t) and y(t). The values are obtained by applying "doEval" method of MathParser to the PolishArray of the compiled first and then the second coordinate with the current value of t. */ curx = procFun.doEval(compObj1.PolishArray,[curt]); cury = procFun.doEval(compObj2.PolishArray,[curt]); xArray[i] = curx; yArray[i] = cury; fArrayXY[i] = [curx, cury]; fArrayTY[i] = [curt, cury]; fArrayTX[i] = [curt, curx]; // Update our calculations for the biggest and smallest x and y values so far if(cury > yMaxCalc) { yMaxCalc = cury }; if(curx > xMaxCalc) { xMaxCalc = curx }; if(cury < yMinCalc) { yMinCalc = cury }; if(curx < xMinCalc) { xMinCalc = curx }; } // Now xMinCalc, xMaxCalc, yMinCalc, yMaxCalc are the calculated max/min from the x and y arrays. //- - - - - - - - - - - - - calculate comments var dx, dy:Number; var xcom1, xcom2:String; xcom1 = " x(t) is increasing, so the parametric function is moving to the right. "; xcom2 = " x(t) is decreasing, so the parametric function is moving to the left. "; var ycom1, ycom2:String; ycom1 = " y(t) is increasing, so the parametric function is moving up. "; ycom2 = " y(t) is decreasing, so the parametric function is moving down. "; /* Calculate x and y commentaries. */ for(i=1; i <= numPoints; i++){ dx = xArray[i] - xArray[i-1]; if( dx < 0 ) {xCommentArray[i] = xcom2; } else {xCommentArray[i] = xcom1;} dy = yArray[i] - yArray[i-1]; if( dy < 0 ) {yCommentArray[i] = ycom2; } else {yCommentArray[i] = ycom1;} } xCommentArray[0] = xCommentArray[1]; yCommentArray[0] = yCommentArray[1]; // - - - - - - - - - - - - - check some window settings /* If the user left something blank or otherwise made an entry error for the max/min of x, y, then we use the calculated maxs and minx. */ if(oRange4.errorStatus == 1) { xMin = xMinCalc; yMin = yMinCalc; xMax = xMaxCalc; yMax = yMaxCalc; oRange4.errorStatus = 0; // update the text in the x and y "Enter" boxes XmaxEnter.text=String(Math.round(xMax*roundDigits)/roundDigits); XminEnter.text=String(Math.round(xMin*roundDigits)/roundDigits); YmaxEnter.text=String(Math.round(yMax*roundDigits)/roundDigits); YminEnter.text=String(Math.round(yMin*roundDigits)/roundDigits); } /* Whether we have user-entered or calculated values, we set the max and min of x and y to graph. We will pad these values after they are used for further calculations. */ xMinG = Math.min(xMin, xMinCalc); yMinG = Math.min(yMin, yMinCalc); xMaxG = Math.max(xMax, xMaxCalc); yMaxG = Math.max(yMax, yMaxCalc); /* - - - - - - - - - - - - - pad graphing window ranges. pad1 - enlarges window generally; pad2 - enlarge for nearly constant functions. */ var dif, pad1, pad2:Number; pad1 = 0.125; pad2 = 0; dif = xMaxG-xMinG; if(dif < .5) {pad2 = .5}; xMinG = xMinG - pad1 * dif - pad2; xMaxG = xMaxG + pad1 * dif + pad2; pad2 = 0; dif = yMaxG-yMinG; if(dif < .5) {pad2 = .5}; yMinG = yMinG - pad1 * dif - pad2; yMaxG = yMaxG + pad1 * dif + pad2; // - - - - - - - - indicate we are ready to roll! index = 0; // reset the index to all parallel arrays isGraphToTrace = true; // everything is calculated and plotted } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// function displayGraph ///////////// //////////////////////////////////////////////////////////////////////////// function displayGraph() { /* This section is executed if we have all possible graphing windows, function values, and vectors defined. */ /* For each board, assign window settings and draw axes. Note: if a variable's range is all positive or all negative, then that axis will not show. The method "drawGraph" of GraphingBoard draws the curve and stores info for the tracing arrow in an array: positions (in pixels) for the x and y coordinates, and, if the cursor type were set to ARROW, as we did, the rotation. Method parameters: (1) graph number; must not exceed maximum number of graphs (boardXY.setMaxNumGraphs(1);), (2) graph thickness, (3) array of numPoints to be joined by lineal elements; (4) graph color. */ boardXY.setVarsRanges(xMinG,xMaxG,yMinG,yMaxG); boardXY.drawAxes(); arrowPosXY = boardXY.drawGraph(1,3,fArrayXY,xyColor); boardTY.setVarsRanges(tMinG,tMaxG,yMinG,yMaxG); boardTY.drawAxes(); arrowPosTY = boardTY.drawGraph(1, 2, fArrayTY, yColor); boardTX.setVarsRanges(tMinG,tMaxG,xMinG,xMaxG); boardTX.drawAxes(); arrowPosTX = boardTX.drawGraph(1, 2, fArrayTX, xColor); /* Show values in the range display boxes. */ TminBox.text=String(Math.round(tMinG * roundDigits) / roundDigits); TmaxBox.text=String(Math.round(tMaxG * roundDigits) / roundDigits); XminBox.text=String(Math.round(xMinG * roundDigits) / roundDigits); XmaxBox.text=String(Math.round(xMaxG * roundDigits) / roundDigits); YminBox.text=String(Math.round(yMinG * roundDigits) / roundDigits); YmaxBox.text=String(Math.round(yMaxG * roundDigits) / roundDigits); updateBoards(); } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// update boards, knob, comments ///////////// //////////////////////////////////////////////////////////////////////////// function updateBoards() { if(!isTRangeSet || !isGraphToTrace) { return; } //do nothing if we are not ready moveXY(); moveTY(); moveTX(); moveKnob(); if(isConnectX) { moveXConnectors(); } if(isConnectY) { moveYConnectors(); } if(isCommentX) { changeXComment(); } if(isCommentY) { changeYComment(); } } // - - - - - - - - - - - - - move arrows on the boards function moveXY() { var curArrowPos:Array; curArrowPos = arrowPosXY[index]; boardXY.setArrowPos(curArrowPos[0],curArrowPos[1],curArrowPos[2]); boardXY.arrowVisible(true); } function moveTY() { var curArrowPosTY:Array; curArrowPosTY = arrowPosTY[index]; boardTY.setArrowPos(curArrowPosTY[0],curArrowPosTY[1],curArrowPosTY[2]); boardTY.arrowVisible(true); } function moveTX() { var curArrowPosTX:Array; curArrowPosTX = arrowPosTX[index]; boardTX.setArrowPos(curArrowPosTX[0],curArrowPosTX[1],curArrowPosTX[2]); boardTX.arrowVisible(true); } // - - - - - - - - - - - - - move knob function moveKnob():void { // Reset knob to the position corresponding to current index if(isTRangeSet) { var curt:Number; curt = tArray[index]; KnobBox.text=String(Math.round(curt * roundDigits)/roundDigits); hsSlider.setKnobPos(tToSlider(curt)); } else resetKnob(); } // - - - - - - - - - - - - - move connectors on the boards /* To move a connector on a board: clear the old graphics; set line thickness, color, opacity; obtain the vertical and horizontal positions of the arrow on that board; use the pixel-conversion method built into GraphingBoard for the min and max. For horizontal, move -to the left and line-to the right. (For vertical, move-to the bottom and line-to the top.) */ function moveXConnectors():void { if(!isTRangeSet || !isGraphToTrace || !isConnectX) { return; } //do nothing unless we can and we want to var curArrowPos:Array = []; // show horizontal bar on boardTX xBarOnTX.graphics.clear(); xBarOnTX.graphics.lineStyle(1,xColor); xBarOnTX.graphics.beginFill(xColor, 0.7); curArrowPos= arrowPosTX[index]; xBarOnTX.graphics.moveTo( boardTX.xtoPix(tMinG), curArrowPos[1]); xBarOnTX.graphics.lineTo( boardTX.xtoPix(tMaxG), curArrowPos[1]); xBarOnTX.graphics.endFill(); /* show vertical bar on boardXY. Since axes do not always show, make line go across the board. */ xBarOnXY.graphics.clear(); xBarOnXY.graphics.lineStyle(1,xColor); // set line thickness, color, opacity xBarOnXY.graphics.beginFill(xColor, 0.7); /* use the pixel-conversion method built into GraphingBoard for yMinG and yMaxG */ curArrowPos = arrowPosXY[index]; xBarOnXY.graphics.moveTo(curArrowPos[0], boardXY.ytoPix(yMinG)); xBarOnXY.graphics.lineTo(curArrowPos[0], boardXY.ytoPix(yMaxG)); xBarOnXY.graphics.endFill(); } function moveYConnectors():void { if(!isTRangeSet || !isGraphToTrace || !isConnectY) { return; } //do nothing unless we can and we want to var curArrowPos:Array = []; // show horizontal bar on boardTY yBarOnTY.graphics.clear(); yBarOnTY.graphics.lineStyle(1,yColor); // set line thickness, color, opacity yBarOnTY.graphics.beginFill(yColor, 0.7); /* use the pixel-conversion method built into GraphingBoard for tMinG and tMaxG */ curArrowPos = arrowPosTY[index]; yBarOnTY.graphics.moveTo( boardTY.xtoPix(tMinG), curArrowPos[1]); yBarOnTY.graphics.lineTo( boardTY.xtoPix(tMaxG), curArrowPos[1]); yBarOnTY.graphics.endFill(); // show horizontal bar on boardXY yBarOnXY.graphics.clear(); yBarOnXY.graphics.lineStyle(1,yColor); // set line thickness, color, opacity yBarOnXY.graphics.beginFill(yColor, 0.7); /* use the pixel-conversion method built into GraphingBoard for xMinG and xMaxG */ curArrowPos = arrowPosXY[index]; yBarOnXY.graphics.moveTo(boardXY.xtoPix(xMinG), curArrowPos[1]); yBarOnXY.graphics.lineTo(boardXY.xtoPix(xMaxG), curArrowPos[1]); yBarOnXY.graphics.endFill(); } // - - - - - - - - - - - - - change comments about the boards function changeXComment():void { if(!isTRangeSet || !isGraphToTrace || !isCommentX) { return; } //do nothing unless we can and we want to CommentBoxX.visible = true; CommentBoxX.text = xCommentArray[index]; } function changeYComment():void { if(!isTRangeSet || !isGraphToTrace || !isCommentY) { return; } //do nothing unless we can and we want to CommentBoxY.visible = true; CommentBoxY.text = yCommentArray[index]; } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// resetting functions ///////////// //////////////////////////////////////////////////////////////////////////// function resetKnob():void { // Reset knob KnobBox.text=""; hsSlider.setKnobPos(0); } function resetArrays():void { // Reset arrays tArray =[]; xArray =[]; yArray =[]; dxdtArray =[]; dydtArray =[]; arrowPosXY =[]; arrowPosTY =[]; arrowPosTX =[]; fArrayXY =[]; fArrayTY =[]; fArrayTX =[]; xCommentArray =[]; xCommentArray =[]; xyCommentArrayX =[]; xyCommentArrayY =[]; // reset related indicators to false isTRangeSet = false; // no tMin, tMax isGoodXY = false; // no x(t), y(t) formulas isGraphToTrace = false; // no calculated functions } function resetBoards():void { // Reset boards boardXY.cleanBoard(); boardTY.cleanBoard(); boardTX.cleanBoard(); boardXY.ErrorBox.visible=false; boardXY.ErrorBox.text=""; boardXY.arrowVisible(false); boardTY.arrowVisible(false); boardTX.arrowVisible(false); if(isConnectX) { resetXConnectors(); } if(isConnectY) { resetYConnectors(); } if(isCommentX) { resetXComment(); } if(isCommentY) { resetYComment(); } } function resetXConnectors():void { // Reset x-connections xBarOnTX.graphics.clear(); xBarOnXY.graphics.clear(); } function resetYConnectors():void { // Reset y-connections yBarOnTY.graphics.clear(); yBarOnXY.graphics.clear(); } function resetXComment():void { CommentBoxX.visible=false; CommentBoxX.text = ""; } function resetYComment():void { CommentBoxY.visible=false; CommentBoxY.text = ""; } function clearSettings():void { resetBoards(); resetArrays(); resetKnob(); } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// translations between t, ///////////// /////// slider position, and parallel arrays ///////////// //////////////////////////////////////////////////////////////////////////// /* We padded values for t. Slider represents interval (tMinG, tMaxG). (tVal - tMinG)/(tMaxG - tMinG) = (sliderPix)/(sliderLen) */ /* translates the pixel position of slider's knob to corresponding t. */ function tFromSlider(sliderPix:Number):Number { return ( tMinG + (sliderPix/sliderLen)*(tMaxG - tMinG) ); } /* translates t to corresponding pixel position of slider's knob. */ function tToSlider(tVal:Number):Number { return (tVal - tMinG)/(tMaxG - tMinG) * sliderLen; } /* Find the index for the value in tArray closest to tVal. */ function tToIndex(tVal:Number):int { if(tVal < tArray[0]) {return 0;} if(tVal > tArray[numPoints-1]) {return numPoints;} for(i=0; i < numPoints; i++){ if(tVal >= tArray[i] && tVal < tArray[i+1]) { return i; } } } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// syntax ///////////// //////////////////////////////////////////////////////////////////////////// /* The movie clip Instructions was created by hand. It is stored in the Library. Under the Linkage menu item (the menu that can be accessed through the little icon in the upper right corner of the Library window) the clip was set for export to ActionScript with the identifier Instructions. Instructions is a subclass of the Movie clip class. We create an instance of Instructions, store it in the variable mcSyntax, and add it to the Display List. */ var mcSyntax:Instructions=new Instructions(); addChild(mcSyntax); /* Set initial visibilty to false and give an initial position. */ mcSyntax.visible=false; mcSyntax.x=18; mcSyntax.y=30; /* When the user mouses over the SYNTAX button, the intruction movie clip appears. When the mouse rolls out, the clip disappears. */ butSyntax.addEventListener(MouseEvent.MOUSE_OVER,showSyntax); function showSyntax(e:MouseEvent):void { mcSyntax.visible=true; } butSyntax.addEventListener(MouseEvent.MOUSE_OUT,hideSyntax); function hideSyntax(e:MouseEvent):void { mcSyntax.visible=false; } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// credits ///////////// //////////////////////////////////////////////////////////////////////////// var CreditBox:TextField=new TextField(); SetUpCredits(); btnCredits.addEventListener(MouseEvent.MOUSE_OVER, showCredits); btnCredits.addEventListener(MouseEvent.MOUSE_OUT,hideCredits); function SetUpCredits():void { CreditBox.type=TextFieldType.DYNAMIC; CreditBox.border=true; CreditBox.background=true; CreditBox.wordWrap=true; CreditBox.visible=false; CreditBox.mouseEnabled=false; CreditBox.width=300; CreditBox.height=200; CreditBox.x=15; CreditBox.y=15; addChild(CreditBox); var words:String; words = " "; words += "Final product by Therese Shelton (Southwestern University). "; words += "Based on programs and tutorials by Doug Ensley (Shippensburg University) "; words += "and Barbara Kaskosz (University of Rhode Island). "; words += "Their work was supported in part by the "; words += "National Science Foundation under the grant DUE-0535327. "; words += "Shelton participated in the Summer 2007 Flash Workshop given by Kaskosz and Ensley. "; words += "See www.flashandmath.com. "; words += " "; CreditBox.text= words; } function hideCredits(e:MouseEvent):void { CreditBox.visible=false; } function showCredits(evt:MouseEvent):void { CreditBox.visible=true; } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// Handle XML ///////////// //////////////////////////////////////////////////////////////////////////// var arrxFunc:Array; var arryFunc:Array; var arrtMin:Array; var arrtMax:Array; var arrxMin:Array; var arrxMax:Array; var arryMin:Array; var arryMax:Array; var xmlString:URLRequest = new URLRequest("ParamGallery1.xml"); var xmlLoader:URLLoader = new URLLoader(xmlString); xmlLoader.addEventListener("complete", initData); function initData(evt:Event):void { var plotDataXML:XML = XML(xmlLoader.data); var numFuncs:Number = plotDataXML.plot.length(); arrxFunc = new Array(numFuncs); arryFunc = new Array(numFuncs); arrtMin = new Array(numFuncs); arrtMax = new Array(numFuncs); arrxMin = new Array(numFuncs); arrxMax = new Array(numFuncs); arryMin = new Array(numFuncs); arryMax = new Array(numFuncs); for (var i=0; i < numFuncs; i++) { arrxFunc[i] = plotDataXML.plot[i].xfunc.toString(); arryFunc[i] = plotDataXML.plot[i].yfunc.toString(); arrtMin[i] = plotDataXML.plot[i].tRange.@min.toString(); arrtMax[i] = plotDataXML.plot[i].tRange.@max.toString(); arrxMin[i] = plotDataXML.plot[i].xRange.@min.toString(); arrxMax[i] = plotDataXML.plot[i].xRange.@max.toString(); arryMin[i] = plotDataXML.plot[i].yRange.@min.toString(); arryMax[i] = plotDataXML.plot[i].yRange.@max.toString(); } } var exampleIndex:Number = 0; butGallery.addEventListener(MouseEvent.CLICK, nextExample); function nextExample(evt:MouseEvent):void { InputBox1.text = arrxFunc[exampleIndex]; InputBox2.text = arryFunc[exampleIndex]; TminEnter.text=arrtMin[exampleIndex]; TmaxEnter.text=arrtMax[exampleIndex]; XminEnter.text = arrxMin[exampleIndex]; XmaxEnter.text = arrxMax[exampleIndex]; YminEnter.text = arryMin[exampleIndex]; YmaxEnter.text = arryMax[exampleIndex]; exampleIndex = (exampleIndex + 1) % arrxFunc.length; graphCurve(); } /* *********************************************************************** */