/* *********************************************************************** paramSpeedArc.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. Speed and arclength are calculated and shown along with the resulting parametric plot. One may enable plotting the accumulation of arclength. The user may drag a slider bar or may "play" the function to show motion. 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, the speed function, and the arclength function. ** the xml Gallery, a feature found in other Kaskoz/Ensley files. ** calculations and graphs for approximate speed and arclength ** accumulation feature ** the option to "play" the parametric function and vectors, a feature found in other Kaskoz/Ensley files. 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 rbmcAccumulate (show/hide accumulation of arclength) rbmcComment (show/hide comments relating arclength, speed) OUTPUT BOXES created in the script (dynamic text boxes) CreditBox (displays program credits) rbAccumulateLabel (dynamic label for the radio button to toggle accumulation) rbCommentLabel (dynamic label for the radio button to toggle comments) 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 ArcMinBox, ArcMaxBox SpeedMinBox, SpeedMaxBox /* ************************************************************************ */ // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// 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 are movie clips created by hand. We adjust their visibility. */ mcXLabel.visible=true; mcYLabel.visible=true; /* set some properties of the input boxes that were created by hand. */ InputBox1.wordWrap=true; InputBox1.borderColor=0x0000FF; InputBox2.wordWrap=true; InputBox2.borderColor=0xFF0000; // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// configure graphing boards ///////////// //////////////////////////////////////////////////////////////////////////// /* Create instances of the custom class GraphingBoard for each board we need. Each is a space for graphing boardXY -- parametric plot {x(t), y(t)} boardArc -- arclength function {t, arclength(t)} boardSpeed -- speed function {t, speed(t)} 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 boardArc: GraphingBoard=new GraphingBoard(boardSize); var boardSpeed: 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; boardArc.ErrorBox.visible = false; boardSpeed.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 = 67; var vSpace:Number = 73; var boardArcx:Number = 43; var boardArcy:Number = 50; boardArc.x=boardArcx; //board11 boardArc.y=boardArcy; boardSpeed.x=boardArcx; //board21 boardSpeed.y=boardArcy + boardSize + vSpace; boardXY.x=boardArcx + boardSize + hSpace; //board12 boardXY.y=boardArcy; boardXY.enableCoordsDisp("x(t)","y(t)"); boardArc.enableCoordsDisp("t","arc(t)"); boardSpeed.enableCoordsDisp("t", "spd(t)"); // colors var yColor:uint = (0xB10101); //dark red var xColor:uint =(0x0000FF); //blue var tColor:uint = (0x02D202); //green var xyColor:uint = (0xBB85E0); //medium purple var arcColor:uint = (0xFF00CC); //bright pink var speedColor:uint = (0xFF6600); // orange // 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 xEdgeXY:Shape = new Shape(); var yEdgeXY:Shape = new Shape(); SetBoardAndEdges(boardXY, xColor, yColor, xEdgeXY, yEdgeXY); var tEdgeArc:Shape = new Shape(); var arcEdgeArc:Shape = new Shape(); SetBoardAndEdges(boardArc, tColor, arcColor, tEdgeArc, arcEdgeArc); var tEdgeSpeed:Shape = new Shape(); var spEdgeSpeed:Shape = new Shape(); SetBoardAndEdges(boardSpeed, tColor, speedColor, tEdgeSpeed, spEdgeSpeed); 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 are NOT enabling the user to draw on the graphing board with the mouse. The method takes the color of the drawing and the thickness as parameters. */ //board.enableUserDraw(0x0000CC,1); /* 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 = boardArcx; hsSlider.y = boardArcy + 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 accumulation feature ///////////// //////////////////////////////////////////////////////////////////////////// /* toggles on/off when the user clicks the radio button. A value of "true" allows accumulation to be shown. */ var isAccumulate: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.) */ rbmcAccumulate.stop(); /* Create shapes to be configured when the radio button is pressed. */ var accumXY:Shape = new Shape(); boardXY.addChild(accumXY); var accumArc:Shape = new Shape(); boardArc.addChild(accumArc); // Labels for the two frames of the radio button var rbAccumulateLabel:TextField=new TextField(); rbAccumulateLabel.type=TextFieldType.DYNAMIC; rbAccumulateLabel.border=false; rbAccumulateLabel.background=false; rbAccumulateLabel.visible=true; rbAccumulateLabel.x = rbmcAccumulate.x + 12; rbAccumulateLabel.y = rbmcAccumulate.y - 10; rbAccumulateLabel.width = 105; rbAccumulateLabel.height = 15; rbAccumulateLabel.text = "full function"; addChild(rbAccumulateLabel); // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// set up commentary feature ///////////// //////////////////////////////////////////////////////////////////////////// /* toggles on/off when the user clicks the radio button. A value of "true" allows comments to be shown. */ var isComment: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. */ rbmcComment.stop(); // - - - - - - - - - - - - - labels for the two frames of the radio buttons var rbCommentLabel:TextField=new TextField(); rbCommentLabel.type=TextFieldType.DYNAMIC; rbCommentLabel.border=false; rbCommentLabel.background=false; rbCommentLabel.visible=true; rbCommentLabel.x = rbmcComment.x + 18; rbCommentLabel.y = rbmcComment.y - 10; rbCommentLabel.width = 85; rbCommentLabel.height = 15; rbCommentLabel.text = "no comments"; addChild(rbCommentLabel); // - - - - - - - - - - - - - dynamic text box for the commentary var CommentBox:TextField=new TextField(); CommentBox.type=TextFieldType.DYNAMIC; CommentBox.border=true; CommentBox.background=true; CommentBox.visible=false; CommentBox.wordWrap=true; //near position of board22 CommentBox.x = boardXY.x; CommentBox.y = boardSpeed.y; CommentBox.width = boardSize; CommentBox.height = boardSize/2; CommentBox.text = ""; addChild(CommentBox); // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// 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 but does not show the speed as well with the "play" feature. Try between 200 and 700. */ var numPoints:int = 200; /* 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; // ranges calculated for arclength and speed var arcMin, arcMax, speedMin, speedMax:Number; /* Store ranges for the graphing boards and slider, allowing padding */ var tMinG, tMaxG:Number; var xMinG, xMaxG:Number; var yMinG, yMaxG:Number; var arcMinG, arcMaxG:Number; var speedMinG, speedMaxG:Number; // arrays that will hold calculated t and function values var tArray:Array=[]; var xArray:Array=[]; var yArray:Array=[]; var arcArray:Array=[]; var speedArray:Array=[]; // arrays that will hold consecutive ordered pairs on the plots var fArrayXY:Array=[]; var fArrayArc:Array=[]; var fArraySpeed: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 arrowPosArc:Array=[]; var arrowPosSpeed:Array=[]; //arrays that will hold calculated commentaries var CommentArray: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 var isPlayOn:Boolean = false; // indicates whether user clicks the "play" button // a graphic to hold a point on the parametric curve at t=tmin var pointXY:Shape = new Shape; var pointArc:Shape = new Shape; // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// begin with 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(); // clear any old items /* 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. */ if (isGoodXY && isTRangeSet) { processFunction(); displayGraph(); } } // - - - - - - - - - - - - - process user clicking an "accumulate" radio button rbmcAccumulate.addEventListener(MouseEvent.CLICK,doAccumulate); function doAccumulate(e:MouseEvent):void { isAccumulate = !isAccumulate; //toggle if(isAccumulate) { rbmcAccumulate.gotoAndStop(2); rbAccumulateLabel.text = "function accumulates"; if(isTRangeSet && isGraphToTrace){ moveAccumulation(); } //do nothing if no functions are set up to graph } else { // isAccumulate is false so remove connections rbmcAccumulate.gotoAndStop(1); rbAccumulateLabel.text = "full function"; resetAccumulation(); } } // - - - - - - - - - - - - - process user clicking a "comment" radio button rbmcComment.addEventListener(MouseEvent.CLICK, doComment); function doComment(e:MouseEvent):void { isComment = !isComment; //toggle if(isComment) { rbmcComment.gotoAndStop(2); rbCommentLabel.text = "commentary"; changeComments(); } else { // isCommentX is false, so remove commentary for x rbmcComment.gotoAndStop(1); rbCommentLabel.text = "no comments"; resetComments(); } } // - - - - - - - - - - - - - process user accessing the "play" feature btnPlay.addEventListener(MouseEvent.CLICK, YesPlay); btnPlay.addEventListener(MouseEvent.MOUSE_DOWN, YesPlay); btnPlay.addEventListener(MouseEvent.MOUSE_UP, NoPlay); btnPlay.addEventListener(MouseEvent.ROLL_OUT, NoPlay); addEventListener(Event.ENTER_FRAME, whenEnterFrame); function YesPlay(e:MouseEvent):void { isPlayOn=true; } function NoPlay(e:MouseEvent):void { isPlayOn=false; } function whenEnterFrame(e:Event):void { if(!isPlayOn || !isGraphToTrace) { return;} if(!isTRangeSet){ resetKnob(); return; } // isPlayOn, isGraphToTrace, and isTRangeSet are all true index = index + 1; if(index > numPoints) {index = 0;} // start play over when at the end of points updateBoards(); } // - - - - - - - - - - - - - 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) 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]; // 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. // - - - - - - - - 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); // - - - - - - - - process speed, arclength; extreme values; comments //Current values of functions var curArc, curSpeed:Number; function arcAndSpeed(ind:int):void { /* calculate the arclength and speed in a backward difference for a particular index. */ var dx1, dy1, temp: Number; dx1 = xArray[ind] - xArray[ind - 1]; dy1 = yArray[ind] - yArray[ind - 1]; curt = tArray[ind]; temp = Math.sqrt(dx1*dx1 + dy1*dy1); curSpeed = temp / tstep; speedArray[ind] = curSpeed; fArraySpeed[ind] = [curt, curSpeed ]; curArc = arcArray[ind - 1] + temp; arcArray[ind] = curArc; fArrayArc[ind]=[curt, curArc]; } arcArray[0] = 0.0; // arclength from a point to itself is zero fArrayArc[0]=[tMin, 0.0 ]; arcMin = 0.0; // set the starting max and min arclength to be the initial value arcMax = 0.0; arcAndSpeed(1); // calculate arclength and speed at index 1 speedArray[0] = speedArray[1]; //define the speed at index=0 to be speed at index=1 fArraySpeed[0] = fArraySpeed[1]; speedMin = speedArray[1]; //set the starting max and min speed to be the initial value speedMax = speedMin; if(curArc > arcMax){ arcMax = curArc }; // update max, min arclength if(curArc < arcMin){ arcMin = curArc }; /* calculate the remaining values for arclength, speed, extreme values */ for(i=2; i <= numPoints; i++){ arcAndSpeed(i); // next arclength and speed if(curArc > arcMax){ arcMax = curArc }; if(curArc < arcMin){ arcMin = curArc }; if(curSpeed > speedMax){ speedMax = curSpeed }; if(curSpeed < speedMin){ speedMin = curSpeed }; } /* - - - - - - - - - - - - - pad graphing window ranges. pad1 - enlarges window generally; pad2 - enlarge for nearly constant functions. Note: It might seem more efficient to use another function repeatedly since we perform the same steps, but in ActionScript we cannot change the values of parameters of type Number (and some other types), and it would not help to pass everything in an array. */ var dif, difSpeed, 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; pad2 = 0; dif = arcMax-arcMin; if(dif < .5) {pad2 = .5}; arcMinG = arcMin - pad1 * dif - pad2; arcMaxG = arcMax + pad1 * dif + pad2; pad2 = 0; dif = speedMax-speedMin; if(dif < .5) {pad2 = .5}; speedMinG = speedMin - pad1 * dif - pad2; speedMaxG = speedMax + pad1 * dif + pad2; difSpeed = dif; // - - - - - - - - - - - - - - calculate commentary var com0, com1, com2, com3:String; // commentary literals com0 = " arclength is always increasing; speed is always positive. "; com1 = " arclength is concave up; speed is increasing. "; com2 = " arclength is concave down; speed is decreasing. "; com3 = " arclength is linear; speed is constant. "; if(difSpeed < 0.5) { for(i = 0; i <= numPoints; i++) { CommentArray[i] = com0 + com3; } } else { CommentArray[1] = com0; CommentArray[0] = com0; for(i = 2; i <= numPoints; i++) { if(speedArray[i] >=speedArray[i-1]) {CommentArray[i] = com0 + com1; } else {CommentArray[i] = com0 + com2;} } } // - - - - - - - - indicate we are ready to roll! index = 0; // reset the index to all parallel arrays isGraphToTrace = true; // everything is calculated and plotted } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// function displayGraph, supporting functions ///////////// //////////////////////////////////////////////////////////////////////////// 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); boardArc.setVarsRanges(tMinG, tMaxG, arcMinG, arcMaxG); boardArc.drawAxes(); arrowPosArc = boardArc.drawGraph(1,2,fArrayArc,arcColor); boardSpeed.setVarsRanges(tMinG, tMaxG, speedMinG, speedMaxG); boardSpeed.drawAxes(); arrowPosSpeed = boardSpeed.drawGraph(1,2,fArraySpeed,speedColor); /* 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); ArcMinBox.text=String(Math.round(arcMinG * roundDigits)/roundDigits); ArcMaxBox.text=String(Math.round(arcMaxG * roundDigits)/roundDigits); SpeedMinBox.text=String(Math.round(speedMinG * roundDigits)/roundDigits); SpeedMaxBox.text=String(Math.round(speedMaxG * roundDigits)/roundDigits); /* Mark the initial position of the accumulation at the point corresponding to t=tMin on the XY graph and on the arclength graph. */ makePt(pointXY,xyColor,arrowPosXY[0][0],arrowPosXY[0][1]); boardXY.addChild(pointXY); makePt(pointArc,arcColor,arrowPosArc[0][0],arrowPosArc[0][1]); boardArc.addChild(pointArc); updateBoards(); } function makePt(thisPoint:Shape,thisColor:uint, xval:Number, yval:Number):void { var pointRadius:int = 4; thisPoint.graphics.clear(); thisPoint.graphics.lineStyle(0, thisColor); thisPoint.graphics.beginFill(thisColor); thisPoint.graphics.drawCircle(0, 0, pointRadius); thisPoint.graphics.endFill(); thisPoint.x=xval; thisPoint.y=yval; } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// update boards, knob, comments ///////////// //////////////////////////////////////////////////////////////////////////// function updateBoards() { if(!isTRangeSet || !isGraphToTrace) { return; } //do nothing if we are not ready moveXY(); moveArc(); moveSpeed(); moveKnob(); if(isComment) { changeComments(); } if(isAccumulate) { moveAccumulation();} } // - - - - - - - - - - - - - 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 moveArc() { var curArrowPos:Array; curArrowPos = arrowPosArc[index]; boardArc.setArrowPos(curArrowPos[0],curArrowPos[1],curArrowPos[2]); boardArc.arrowVisible(true); } function moveSpeed() { var curArrowPos:Array; curArrowPos = arrowPosSpeed[index]; boardSpeed.setArrowPos(curArrowPos[0],curArrowPos[1],curArrowPos[2]); boardSpeed.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(); } // - - - - - - - - - - - - - change comments function changeComments():void { if(!isTRangeSet || !isGraphToTrace) { return; } //do nothing if we would like to comment but // have nothing on which we can comment CommentBox.visible = true; CommentBox.text = CommentArray[index]; } // - - - - - - - - - - - - - change accumulations function moveAccumulation():void { /* Color back over part of the {x(t), y(t)} plot: begin at the point corresponding to tmin and continue through the current index. NOTE: best if a simple curve. no change if it traverses the same portion multiple times. */ accumXY.graphics.clear(); accumXY.graphics.lineStyle(4,arcColor); accumXY.graphics.moveTo(arrowPosXY[0][0],arrowPosXY[0][1]); for(i=1;i<=index;i++) { accumXY.graphics.lineTo(arrowPosXY[i][0],arrowPosXY[i][1]); } /* Color the vertical line segment on the arclength plot corresponding to the current index; this height is the length of the colored portion on the XY plot. */ accumArc.graphics.clear(); accumArc.graphics.lineStyle(4,arcColor); accumArc.graphics.moveTo(arrowPosArc[index][0], arrowPosArc[0][1]); accumArc.graphics.lineTo(arrowPosArc[index][0], arrowPosArc[index][1]); // perhaps show arclength value in dynamic textbox // Note: the length of the arc on boardXY does not appear the same as the height // of the arclength function. } // -------------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////// /////// resetting functions ///////////// //////////////////////////////////////////////////////////////////////////// function resetKnob():void { // Reset knob KnobBox.text=""; hsSlider.setKnobPos(0); } function resetArrays():void { // Reset arrays tArray =[]; xArray =[]; yArray =[]; arcArray =[]; speedArray =[]; arrowPosXY =[]; arrowPosArc =[]; arrowPosSpeed =[]; fArraySpeed =[]; fArrayArc =[]; CommentArray =[]; // 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 { boardXY.cleanBoard(); boardArc.cleanBoard(); boardSpeed.cleanBoard(); boardXY.ErrorBox.visible=false; boardXY.ErrorBox.text=""; boardXY.arrowVisible(false); boardArc.arrowVisible(false); boardSpeed.arrowVisible(false); if(isAccumulate) {resetAccumulation();} if(isComment) {resetComments();} } function resetAccumulation():void { accumXY.graphics.clear(); accumArc.graphics.clear(); } function resetComments():void { CommentBox.visible = false; CommentBox.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(); } /* *********************************************************************** */