// James Brink, 11/30/2021 let can1; let can2; let mode = 0; // start in rectangular modemotion let s2; // span for showing mode; let f = ["", "", "", ""]; //, "", "", ""]; let fDefined = []; // the function i is defined let noFDefined; let specialDefined = false; // true if a special like x=, o=, L, ... defined let fLabel = []; let fInput = []; let x, y, t; let minX, maxX, minY, maxY, minT, maxT; // used before checking equalSpace let miniX, maxiX, miniY, maxiY; // in case equalSpace true let xAxisLoc, yAxisLoc; let xVals = []; let yVals = []; let fun, funP, funPP, funPPP; // function and its derivatives let maxIterations; // global for sake of check function let xMaxV = []; let xMinV = []; let yMaxV = []; let yMinV = []; let yMaxVals; let yMinVals; let resultArea; let xMinLabel, xMaxLabel, yMinLabel, yMaxLabel; let xMinInput, xMaxInput, yMinInput, yMaxInput; let tMinLabel, tMinInput, tMaxLabel, tMaxInput; let hashSpacingX, hashSpacingY; let startingXInp; let startingX2Inp; let showFP; // check box for showing f' let showFPP; // check box for showing f'' let equalSpace; // check box for Equal Spacing let allowLoop; // check box for Allow motion let zoomInBtn; // enlarge picture around last iteration let zoomOutBtn; // zoom out let zoomRatio = 5; // multiplier for zoom let moveBackBtn; // let radiansRadio, degreesRadio; //// used for javascript buttons let modeAngle = "radians"; // The value of RADIANS let numPtsLabel, numPtsInput; // number of evaluation pts let showExampleDataBtn; let loopCnt = 0; let p5left; let p5right; let big; // enlarged canvas let valuesLab = ["a", "b", "c", "d", "e", "s"]; let a = 0, b = 0, c = 0, d = 0, e = 0, s; let theValues = [0, 0, 0, 0, 0, 0]; let valDisplay; let colors = ["red", "blue", "green", "maroon", "magenta", "navy"]; // Number of colors determines NUM_F_INPUT let numF = colors.length; let useColor = []; // used to allow specifying color with words let valLab = []; let valInp = []; let numDiv = 200; let numPts; let numPtsOffset = .05 * numDiv; // 2 * numPtsOffset + numDiv = numPts let startAt; // count at which iterations start let errorMsg = ""; // error message shown in the canvas let functionError = false; let minMaxError = false; let valueError = false; let numberEvalsError = false; let startingXError = false; let methodCode; let method = 1; // 0 secant, 1 Newton, 2 modified, 3 accelerated, 4 cubic let iterateBtn; let xOld, fValOld, lastFVal; // for secant method let lastDeltaX; // for modified Newton's let deltaX; // for modified Newton's let lastX; // for modified Newton's let fVal; let useG = false; // true if we are using g(x); const locOffset = 19; const ERROR = "ERROR"; const RECT_MODE = 0; const POLAR_MODE = 1; const PARA_MODE = 2; const REALY_BIG = 1.2345E100; const NUM_F_INPUT = colors.length; // FUNCTION_LABn format = [Label for mode 0, label for mode 1, label for mode2] const FUNCTION_LAB0 = ['
f(x) = ']; const FUNCTION_LAB1 = ['
' + "f '(x) = "]; const FUNCTION_LAB2 = ['
' + "f ''(x) = "]; const FUNCTION_LAB3 = ['
' + "tangent "]; const FUNCTION_LAB4 = ['
' + "v line "]; const FUNCTION_LAB5 = ['
' + "            "]; const FUNCTION_LAB = [FUNCTION_LAB0, FUNCTION_LAB1, FUNCTION_LAB2, FUNCTION_LAB3, FUNCTION_LAB4]; const T_MIN_LAB = ['', '
Minimum θ  ', '
Maximum t  ']; const T_MAX_LAB = ['', '
Maximum θ  ', '
Maximum t  ']; const T_MIN_VAL = [0, 0, 0]; const T_MAX_VAL = [1, 'TWO_PI', 10]; const SPECIAL_SYMBOLS = ['^a', '^b', '^c', '^d', '^e']; const MODE_COLORS = ["Crimson", "black"]; // for examples // for enlargement let enlarge = false; let enlargeRatio = 1.5; let enlargeCheckBox; let enlargeSpan; // label for enlargeSlider let enlargeSlider; // Examples: let example = []; let exampleRadio; // radio buttons for the examples // not used let examplesRadio = [] const EXAMPLE_NAME = 23; // location of the name function setup() { p5left = document.getElementById("p5-left"); // used with radio p5right = document.getElementById("p5-right"); can1 = createCanvasClass(430, 430); // must be square can1.doubleClicked(dblClick); can1.parent(p5left); can1.textAlign("center"); can1.mouseClicked(clearErrorMsg); can2 = createCanvasClass(430 * enlargeRatio, 430 * enlargeRatio); big = document.getElementById("big"); can2.parent(big); can2.textAlign("center"); can2.mouseClicked(clearErrorMsg); can2.style("display: none"); spacer = createDiv(" "); spacer.style("display: none"); numPts = Math.floor(1.1 * numDiv); resultArea = document.getElementById("area"); resultArea.value = ""; let radio; // start left hand info display below the graph let s0 = createSpan("
Values"); s0.parent(p5left); for (i = 0; i < valuesLab.length; i++) { if (i == 3 ) { // start a new line let s1 = createSpan("
         " + "  "); s1.parent(p5left); } valLab[i] = createSpan(" " + valuesLab[i] + " ") valLab[i].parent(p5left); valInp[i] = createInput("", "text"); valInp[i].parent(p5left); valInp[i].size(109); valInp[i].changed(valueChanged); } // Handle s input in a special way valInp[5].input(sInput); // create slider for s sleft = createSpan("
         " + "          " + " Adjust value of s (0 - 100)     "); sleft.parent(p5left); sValueSlider = createSlider(0, 100, 50); sValueSlider.parent(p5left); sValueSlider.changed(sSliderChanged); // create display for values for a ... e s1a = createSpan("
"); s1a.parent(p5left); valDisplay = createSpan(""); valDisplay.parent(p5left); // start right hand info display // display function labels and inputs let s3 = createSpan("
To change a function, value, mins and max, type" + '
the desired value and then press "Enter".'); s3.parent(p5right); for (i = 0; i < NUM_F_INPUT; i++) { myFill(colors[i]); fLabel[i] = createSpan(""); // create label functionLab(i, mode); // provide an initial label fLabel[i].parent(p5right); fInput[i] = createInput(f[i]); fInput[i].parent(p5right); if (i == 0) { fInput[i].changed(inputF0); } else { fInput[i].changed(inputF); } fInput[i].size(260); } // showing f' and f'' showFP = createCheckbox("Show f ' ", false); showFP.parent(p5right); showFP.changed(somethingChanged); showFPP = createCheckbox("Show f '' ", false); showFPP.parent(p5right); showFPP.changed(somethingChanged); // values for solving startingXLab = createSpan("
Starting x "); startingXLab.parent(p5right); startingXInp = createInput(""); //******** startingXInp.parent(p5right); startingXInp.size(50); startingX2Lab = createSpan("
2nd starting x for secant method "); startingX2Lab.parent(p5right); startingX2Inp = createInput(""); //******** startingX2Inp.parent(p5right); startingX2Inp.size(50); maxIterationsLab = createSpan("
Maximum iterations "); maxIterationsLab.parent(p5right); maxIterationsInp = createInput("50"); maxIterationsInp.parent(p5right); maxIterationsInp.size(50); // solve buttons let s3a = createSpan("
"); s3a.parent(p5right); let secantSolveBtn = createButton("Solve (Secant Method)" + " (2nd starting x required)"); secantSolveBtn.parent(p5right); secantSolveBtn.mousePressed(secantSolve); let s3b = createSpan("
"); s3b.parent(p5right); let newtonSolveBtn = createButton("Solve (Newton Method)"); newtonSolveBtn.parent(p5right); newtonSolveBtn.mousePressed(newtonSolve); let s3bb = createSpan("
"); s3bb.parent(p5right); let approxSolveBtn = createButton( "Solve (Newton's with approximate derivatives)" + "
(f' not required)"); approxSolveBtn.parent(p5right); approxSolveBtn.mousePressed(approxSolve); let s3c = createSpan("
"); s3c.parent(p5right); let modSolveBtn = createButton("Solve (Modified Newton's Method) (f '' required)"); modSolveBtn.parent(p5right); modSolveBtn.mousePressed(modifiedNewtonSolve); let s3d = createSpan("
"); s3d.parent(p5right); let accelSolveBtn = createButton("Solve (Accelerated Newton's Method)"); accelSolveBtn.parent(p5right); accelSolveBtn.mousePressed(acceleratedNewtonSolve); let s3e = createSpan("
"); s3e.parent(p5right); let gFunctionBtn = createButton("Solve (using g(x) = f(x)/f '(x))" + " (f '' required)"); gFunctionBtn.parent(p5right); gFunctionBtn.mousePressed(useGNewton); let s3ee = createSpan("
"); s3ee.parent(p5right); let gApproxBtn = createButton("Solve (using g(x) = f(x)/f '(x))" + " (f '' not required.)"); gApproxBtn.parent(p5right); gApproxBtn.mousePressed(useGApprox); let s3f = createSpan("
"); s3f.parent(p5right); let cubicSolveBtn = createButton("Solve (cubic Method) (values a, b, c, d required)"); cubicSolveBtn.parent(p5right); cubicSolveBtn.mousePressed(cubicSolve); let s3g = createSpan("
"); s3g.parent(p5right); iterateBtn = createButton("Continue iterating"); iterateBtn.parent(p5right); iterateBtn.mousePressed(iterate); iterateBtn.hide(); // create a help link let s4 = createSpan('

' + 'Help with solver app and its functions.
'); s4.parent(p5right); // The polar division contains inputs for max and mins of both theta and t // It is displayed only in the polar and parametric modes polarDiv = createDiv(); polarDiv.parent(p5right); polarDiv.style("display: none"); tMinLabel = createSpan(T_MIN_LAB[mode]); tMinLabel.parent(polarDiv); tMinInput = createInput(T_MIN_VAL[mode], "text"); tMinInput.parent(polarDiv); tMinInput.changed(checkMinMax); tMaxLabel = createSpan(T_MAX_LAB[mode]) tMaxLabel.parent(polarDiv); tMaxInput = createInput(T_MAX_VAL[mode], "text"); tMaxInput.parent(polarDiv); tMaxInput.changed(checkMinMax); // create input for max and mins for x and y xMinLabel = createSpan("
Minimum x  "); xMinLabel.parent(p5right); xMinInput = createInput("-5", "text"); xMinInput.parent(p5right); xMinInput.changed(checkMinMax); xMaxLabel = createSpan("
Maximum x "); xMaxLabel.parent(p5right); xMaxInput = createInput("5", "text"); xMaxInput.parent(p5right); xMaxInput.changed(checkMinMax); instLabel = createSpan("
The minimum and maximum y values will" + " be
calculated automatically if you leave them blank."); instLabel.parent(p5right); yMinLabel = createSpan("
Minimum y  "); yMinLabel.parent(p5right); yMinInput = createInput("", "text"); yMinInput.parent(p5right); yMinInput.changed(checkMinMax); yMaxLabel = createSpan("
Maximum y "); yMaxLabel.parent(p5right); yMaxInput = createInput("", "text"); yMaxInput.parent(p5right); yMaxInput.changed(checkMinMax); // zoom buttons let s4a = createSpan("
Doubleclick to set center of graph.
"); s4a.parent(p5right); zoomInBtn = createButton("Zoom in"); zoomInBtn.parent(p5right); zoomInBtn.mousePressed(zoomIn); zoomOutBtn = createButton("Zoom out"); zoomOutBtn.parent(p5right); zoomOutBtn.mousePressed(zoomOut); // create Equal spacing, Allow motion and Radians/degrees inputs equalSpace = createCheckbox("Equal spacing ", false); equalSpace.parent(p5right); equalSpace.changed(equalSpaceChanged); let s5 = createSpan("
"); s5.parent(p5right); allowLoop = createCheckbox("Allow motion", false); allowLoop.parent(p5right); allowLoop.changed(allowLoopChanged); enlargeCheckBox = createCheckbox("Show enlarged canvas", false); enlargeCheckBox.parent(p5right); enlargeCheckBox.changed(enlargedChanged); enlargeSpan = createSpan("Enlargement ratio (" + (round(10 * enlargeRatio)/10) + ")  "); enlargeSpan.parent(p5right); enlargeSlider = createSlider(430, 1000, 645); enlargeSlider.parent(enlargeSpan); enlargeSlider.changed(enlargeChanged); let s5a = createSpan("

"); s5a.parent(p5right); radio = HTMLforRadioButton("angles", "RADIANS", "getRadioValueAngle()", true); radiansRadio = displayHTMLElements(radio, p5right, "inline-block"); radio = HTMLforRadioButton("angles", "DEGREES", "getRadioValueAngle()"); degreesRadio = displayHTMLElements(radio, p5right, "inline-block"); // create number of evaluations inputs numPtsLabel = createSpan("
Number of evaluation points "); numPtsLabel.parent(p5right); numPtsInput = createInput(200, "text"); numPtsInput.parent(p5right); numPtsInput.size(87); numPtsInput.changed(numPtsChanged); // create buttons for displaying and reading examples let s6 = createSpan("

"); s6.parent(p5right); showExampleDataBtn = createButton("Show current setup"); showExampleDataBtn.parent(p5right); showExampleDataBtn.mousePressed(showExampleData); saveExampleBtn = createButton("Save as a temp example"); saveExampleBtn.parent(p5right); saveExampleBtn.mousePressed(saveExample); // create button for saving graph as an image let s7 = createSpan("

"); s7.parent(p5right); saveImageBtn = createButton("Save as an image file"); saveImageBtn.parent(p5right); saveImageBtn.mousePressed(saveImage); // initialize arrays xVals and yVals which hold points to be plotted for (let i = 0; i < NUM_F_INPUT; i++) { xVals[i] = []; yVals[i] = []; } // some initial values (temporary) fInput[0].value("x ** 3 - 2*x**2 + x"); //******** Default problem fInput[1].value("3 * x ** 2 - 4*x + 1"); //******** fInput[2].value("6 * x - 4"); //******** xMinInput.value(-1.5);//******** xMaxInput.value(2.5);//******** yMinInput.value(-2);//******** yMaxInput.value(3);//******** startingXInp.value(2);//******** // some additional initialization inputF(); valueChanged(); checkMinMax(); frameRate(20); // finally, initialize the examples setupExamples(); displayExamples(); } // setup function draw() { // set background color for plot area myBackground("lightblue"); // warning if no function defined and hence no graph can be drawn if (noFDefined) { // Has there been an error? if (errorMsg != "") { displayError(); } else { if (mode != 1) { myText("You need to define a function.", can1.width/2, can1.height/2); } } noLoop(); return; } if (errorMsg != "") { displayError(); return; } // *** for each function determine points to be plotted *** for (let i = 0; i < NUM_F_INPUT; i++) { //for each function // initials values, mins, and maxs xVals[i] = []; yVals[i] = []; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); if (i == 1 && !showFP.checked()) { continue; } if (i == 2 && !showFPP.checked()) { continue; } // determine points to be ploted if (!fDefined[i]) {continue;} specialDefined = false; if (f[i] != "") { let firstChar = f[i].charAt(0).toLowerCase(); let first2Char = f[i].substring(0, 2).toLowerCase(); if (first2Char == "x=") { gVerticalLine(i); } else if (first2Char == "o=") { // ray gTheta(i); } else if (first2Char == "r=") { // circle gCircle(i); } else if (firstChar == "l") { // line segment gLineSegment(i); } else if (firstChar == "q") { // quadratic gQuad(i); } else if (firstChar == "p") { // point gPoint(i); } else if (firstChar == "w") { //word (text, label) gWord(i); } else { evalRectangular(i); } } } // end of evaluation of points loop // start determining the min and max of x xMaxVals = max(xMaxV); xMinVals = min(xMinV); yMaxVals = max(yMaxV); yMinVals = min(yMinV); if (abs(xMaxVals) == Infinity || abs(xMinVals) == Infinity || abs(yMaxVals) == Infinity || abs(yMinVals) == Infinity) { if (!specialDefined) { // If special defined, an attempt at a special such x= // but no the max and mins were not set so no graph can be drawn. // If there was an error, it should already registered. // The following should only happen if no values are valid points // or no values calculated because the only function is illegal let s = "The values cannot be calculated. Possible division by 0" + " or invalid \nsquare root." + " Or only function is illegal or incomplete."; if (mode == 2) { s += "Or both f(t) and g(t) must be specified."; } setErrorMsg(s); } } else { // process anything that has been defined // check to see if max and min are too close or the same) if (abs(xMaxVals - xMinVals) < .01 * xMaxVals) { if (xMaxVals > 0) { xMaxVals = 1.1 * xMaxVals; xMinVals = 0; } else if (xMaxVals == 0) { xMaxVals = 1; xMinVals = -1; } else { xMaxVals = 0; xMinVals = 1.1 * xMinVals; } } if (yMaxVals == yMinVals) { if (yMaxVals > 0) { yMaxVals = 1.1 * yMaxVals; yMinVals = 0; } else if (yMaxVals == 0) { yMaxVals = 0.1; yMinVals = -0.1; } else { yMaxVals = 0; yMinVals = 1.1 * yMinVals; } } // determine maxX, minX, maxY and minY if (maxX == "") { maxX = ceil(xMaxVals); } if (minX == "") { minX = floor(xMinVals); } if (maxY == "") { maxY = ceil(yMaxVals); } if (minY == "") { minY = floor(yMinVals); } // check for equal spacing setting maxiX, miniX, maxiY, miniY maxiX = maxX; miniX = minX; maxiY = maxY; miniY = minY; if (equalSpace.checked()) { if (maxX - minX > maxY - minY) { let dif = maxX - minX - maxY + minY ; maxiY += dif/2; miniY -= dif/2; } else { let dif = maxY - minY - maxX + minX; maxiX += dif/2; miniX -= dif/2; } } // locate and draw axis and hashmarks locateAxes(); myStroke("black"); myLine(0, xAxisLoc, can1.width, xAxisLoc); myLine(yAxisLoc, 0, yAxisLoc, can1.height); myFill("blue"); xHashMarks(); yHashMarks(); myNoStroke(); // lastPx and lastPy are used to detect vertical asymptotes let lastPx, lastPy; let px, py; // *** start drawing for each function *** for (i = 0; i < NUM_F_INPUT; i++) { //for each function if (i == 1 && !showFP.checked()) { continue; } if (i == 2 && !showFPP.checked()) { continue; } // first check for vertical lines if (!fDefined[i]) { continue; } if (mode == PARA_MODE) { myStroke(colors[floor(i/2)]); } else { myStroke(colors[i]); } lastPy = NaN; if (xVals[i][0] == "vLine") { // check for line let xx = xVals[i][1]; let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset); let yHigh, yLow; if (yVals[i][0] >= 1) { yLow = yVals[i][1]; } else { yLow = miniY - 10; } if (yVals[i][0] >= 2) { yHigh = yVals[i][2]; } else { yHigh = maxiY + 10; } let py1 = map(yLow, miniY, maxiY, can1.height - locOffset, locOffset); let py2 = map(yHigh, miniY, maxiY, can1.height - locOffset, locOffset); myLine(px, py1, px, py2); // then for rays through the origin. The endpoints have been determined // also check for lineSegments } else if (xVals[i][0] == "oLine" || xVals[i][0] == "l") { for (let j = 0; j < 2 * yVals[i][0]; j = j+2) { let px1 = map(xVals[i][j + 1], miniX, maxiX, locOffset, can1.width - locOffset); let py1 = map(yVals[i][j + 1], miniY, maxiY, can1.height - locOffset, locOffset); let px2 = map(xVals[i][j + 2], miniX, maxiX, locOffset, can1.width - locOffset); let py2 = map(yVals[i][j + 2], miniY, maxiY, can1.height - locOffset, locOffset); myLine(px1, py1, px2, py2); } // check for circular curves } else if (xVals[i][0] == "r") { lastPx = xVals[i][1]; lastPy = yVals[i][1] for (let j = 1; j < xVals[i].length; j++) { let xx = xVals[i][j]; let yy = yVals[i][j]; let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset); let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset); if (j > 1) { myLine(px, py, lastPx, lastPy); } lastPx = px; lastPy = py; } // check for points } else if (xVals[i][0] == "p") { for (let j = 0; j < yVals[i][0]; j++) { let xx = xVals[i][j+1]; let yy = yVals[i][j+1]; let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset); let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset); myStrokeWeight(5); myPoint(px, py); myStrokeWeight(1); } // check for words } else if (xVals[i][0]== "w") { for (let j = 0; j < yVals[i][0]; j++) { let xx = xVals[i][2*j+1]; let yy = yVals[i][2*j+1]; let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset); let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset); myNoStroke(); myFill(useColor[i]); myText(yVals[i][2*j + 2], px, py); } // having taken care of special cases, plot normal curves } else { // normal curves for (let ix = 0; ix < yVals[i].length; ix++) { if (mode == PARA_MODE && (i == 1 || i == 3 || i == 5)) {continue} // the first time in this loop is just to initialize lastPx and lastPy if (!isNaN(yVals[i][ix])) { let xx = xVals[i][ix]; let yy = yVals[i][ix]; if (isNaN(xx) || isNaN(yy) || abs(xx) == Infinity || abs(yy) == Infinity) { lastPy = NaN; } else { let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset); let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset); if (!isNaN(lastPy) && abs(py - lastPy) <= .3 * can1.height && py * lastPy >= 0) { myLine(px, py, lastPx, lastPy); } else { lastPy = NaN; } lastPx = px; lastPy = py; } } } } } } // Some function was defined and processed // Has there been an error? if (errorMsg != "") { displayError(); } // check to see if we are looping myText(loopCnt, 30, 15); if (loopCnt > 3 && !allowLoop.checked()) { noLoop(); loopCnt = 0; } else { ++loopCnt; } } // draw function myRedraw() { if (fInput[3].value() == "L ") { fInput[3].value(""); } if (fInput[4].value() == "L ") { fInput[4].value(""); } if (fInput[5].value() == "q") { fInput[5].value(""); } inputF(); redraw(); } // myRedraw function secantSolve(restart = false){ let haltIterations; // initialize secant method try { method = 0; // this is the secant method methodCode = "S"; if (!restart) { iterateBtn.hide(); resultArea.value = ""; } inputF(); // make sure f values are up to date if (!restart) { x = eval(startingXInp.value()); } maxIterations = maxIterationsInp.value(); fun = f[0]; // f(x) haltIterations = check(0); // reset tangent and vertical line strings and resultArea fInput[3].value(""); fInput[4].value(""); fInput[5].value(""); } catch (e) { alert("Required information (f(x), Starting x, 2nd starting x" + ", or Maximum iterations) missing or invalid. " + e + " (line " + e.lineNumber + ")"); } if (haltIterations) { return; } fLabel[3].html('
' + "secant "); fLabel[5].html('
' + "            "); // iterate secant method try { if (!restart) { startAt = 2; showFunction(fun); // process first x value x = eval(startingXInp.value()); fValOld = eval(fun); showIteration(methodCode, 0, x, fValOld); xOld = x fInput[4].value("L " + xOld + "; " + fValOld + "; " + xOld + "; 0; "); // process second x value let i = 1; x = eval(startingX2Inp.value()); fVal = eval(fun); absDeltaX = 1E60; // arbitrary value showIteration(methodCode, i, x, fVal); fInput[3].value("L "); } else { fVal = eval(fun); } for (let i = startAt; i < maxIterations; i++) { lastX = x; lastFVal = fVal; x = x - fVal * (lastX - xOld)/(lastFVal - fValOld); fVal = eval(fun); lastAbsDeltaX = absDeltaX; absDeltaX = abs(x - lastX); if (i == 2) { fInput[5].value(""); // not used. Erase any old values } let fSecantEnd; // end of secant depends on if lastFval and fValOld let xSecantEnd; // have oposite signs or not if (x == Infinity) { return; } if ((lastFVal * fValOld) < 0) { fSecantEnd = lastFVal; xSecantEnd = lastX; } else { fSecantEnd = 0; xSecantEnd = x; } fInput[4].value(fInput[4].value() + lastX + "; 0; " + lastX + "; " + lastFVal + "; "); fInput[3].value(fInput[3].value() + xSecantEnd + ";" + fSecantEnd + "; " + xOld + ";" + fValOld + ";"); myRedraw(); showIteration(methodCode, i, x, fVal); let fValOut; if (abs(fVal) > abs(lastFVal) && absDeltaX > abs(lastAbsDeltaX)) { resultArea.value +="\n" +"The iterations do not seem to be converging. There may" + " not be a solution that can be found with the current" + " starting point."; break; } if (isNaN(x)) { alert("Iteration terminated as x is out of range."); myRedraw(); } xOld = lastX; fValOld = lastFVal if (absDeltaX < 1.0E-10 || fVal == 0 ) { startAt = i + 1; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } return; } } } catch (e) { alert ("Iteration error, namely: " + e + "\nline " + e.lineNumber + "\n" +e.stack); } } // secantSolve function approxSolve(restart = false) { // Newton's method but approximates f'(x) let haltIterations; if (useG) { methodCode = "ga"; } else { methodCode = "a"; fInput[5].value("");// not used. Erase any old values fLabel[5].html('
' + "            "); } method = 1; // this is the approximate Newton's method iterateBtn.hide(); maxIterations = maxIterationsInp.value(); fLabel[3].html('
' + "tangent "); try { if (restart) { } else { startAt = 1; resultArea.value = ""; absDeltaX = 1E60; // arbitrary value x = eval(startingXInp.value()); fun = f[0]; // f(x) showFunction(fun); fVal = eval(fun); showIteration(methodCode, 0, x, fVal); } } catch (e) { alert ("Approximate derivative iteration error, namely: " + e); return; } // iterate approximateNewton's method try { for (let i = startAt; i < maxIterations; i++) { lastX1 = x; let delta = 1E-6; let xSave = x; x = x + delta; fPVal = (eval(fun) - fVal) / delta; // fPVal approximates derivative x = xSave; lastFVal = fVal; x = x - fVal/fPVal; // fPVal is an approximate derivative fVal = eval(fun); lastAbsDeltaX = absDeltaX; absDeltaX = abs(x - lastX1); if (i == 1) { fInput[3].value("L "); fInput[4].value("L "); if (!useG) { fInput[5].value("");// not used. Erase any old values fLabel[5].html('
' + "            "); } } fInput[4].value(fInput[4].value() + lastX1 + "; 0; " + lastX1 + "; " + lastFVal + "; "); fInput[3].value(fInput[3].value() + lastX1 + ";" + lastFVal + "; " + x + "; 0; "); fPVal = eval(funP); if (isNaN(x)) { myRedraw(); return; } showIteration(methodCode, i, x, fVal); myRedraw(); if (absDeltaX < 1.0E-10 || fVal == 0) { startAt = i + 1; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } return; } if (abs(fVal) > abs(lastFVal) && absDeltaX > abs(lastAbsDeltaX)) { resultArea.value +="\n" +"The iterations do not seem to be converging. There may" + " not be a solution that can be found with the current" + " starting point."; startAt = i + 1; iterateBtn.show(); return; } if (isNaN(x)) { alert("Iteration terminated as x is out of range."); myRedraw(); } } } catch (e) { alert ("Appximate Newton's error, namely: " + e); } } // approxSolve function newtonSolve(restart = false) { let haltIterations; methodCode = "N";; method = 1; // this is the Newton's method iterateBtn.hide(); maxIterations = maxIterationsInp.value(); fLabel[3].html('
' + "tangent "); try { if (restart) { } else { startAt = 1; resultArea.value = ""; absDeltaX = 1E60; // arbitrary value x = eval(startingXInp.value()); fun = f[0]; // f(x) funP = f[1]; // f'(x) showFunction(fun); fVal = eval(fun); showIteration(methodCode, 0, x, fVal); } } catch (e) { alert ("Newton's iteration error, namely: " + e); } // iterate Newton's method try { for (let i = startAt; i < maxIterations; i++) { let fPVal = eval(funP); lastX1 = x; lastFVal = fVal; if (isNaN(x) || isNaN(fVal)) { myRedraw(); return; } x = x - fVal/fPVal; fVal = eval(fun); lastAbsDeltaX = absDeltaX; absDeltaX = abs(x - lastX1); if (i == 1) { fInput[3].value("L "); fInput[4].value("L "); if (useG) { useG = false; methodCode = "g" } else { fInput[5].value(""); // not used. Erase any old values fLabel[5].html('
' + "            "); } } fInput[4].value(fInput[4].value() + lastX1 + "; 0; " + lastX1 + "; " + lastFVal + "; "); fInput[3].value(fInput[3].value() + lastX1 + ";" + lastFVal + "; " + x + "; 0; "); fPVal = eval(funP); showIteration(methodCode, i, x, fVal); myRedraw(); if (absDeltaX < 1.0E-10 || fVal == 0) { startAt = i + 1; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } break; } if (abs(fVal) > abs(lastFVal) && absDeltaX > abs(lastAbsDeltaX)) { resultArea.value +="\n" +"The iterations do not seem to be converging. There may" + " not be a solution that can be found with the current" + " starting point."; startAt = i + 1; iterateBtn.show(); break; } } } catch (e) { alert ("Iteration error, namely: " + e); } } // newtonSolve function modifiedNewtonSolve(restart = false) { // Solves f(x) = 0 given f' and f''. Uses Newton's method unless // it appear convergence is linear (not quadratic) indicating a multiple // root. In that case it gets the next iterate by assuming both // f and f' are zero. method = 2; // this is the modified Newton's method // initialize Newton's method maxIterations = maxIterationsInp.value(); let haltIterations; iterateBtn.hide(); let usedModified; // initialize modified Newton's method try { inputF(); // make sure f values are up to date maxIterations = maxIterationsInp.value(); fun = f[0]; funP = f[1]; // f' funPP = f[2]; // f'' haltIterations = check(2); // reset tangent and vertical line strings and resultArea if (!restart) { x = eval(startingXInp.value()); fInput[3].value(""); fInput[4].value(""); resultArea.value = ""; startAt = 1; methodCode = "N"; usedModified = false; // Modified method not used yet } } catch (e) { alert("Required information (f(x), f'(x), f''(x), Starting x" + ", or Maximum iterations) missing or invalid."); } if (haltIterations) { return; } fLabel[3].html('
' + "tangent "); // Iterate modified newton's method try { if (!restart) { showFunction(fun); fVal = eval(fun); showIteration(methodCode, 0, x, fVal); lastDeltaX = 100; // Arbitrary numbers to avoid deltaX = 80; // switching in first couple of steps fInput[3].value("L "); fInput[4].value("L "); fLabel[5].html('
' + "quad "); fInput[5].value("q"); } for (let i = startAt; i < maxIterations; i++) { // initial evaluations let fPVal = eval(funP); let fPPVal = eval(funPP); lastX = x; lastFVal = fVal; secondLastDeltaX = lastDeltaX; lastDeltaX = deltaX; deltaX = fVal/fPVal; // this deltaX is for Newton's method let absDeltaX = abs(deltaX); if ((abs(lastDeltaX/deltaX - secondLastDeltaX/lastDeltaX) > .1 || i <= 2) && methodCode == "N") { // use normal newton's method methodCode = "N"; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } x = lastX - deltaX; fVal = eval(fun); // graph iterations -= first tangent line and then vertical line fInput[4].value(fInput[4].value() + lastX + "; 0; " + lastX + "; " + lastFVal + "; "); fInput[3].value(fInput[3].value() + lastX + ";" + lastFVal + "; " + x + "; 0; "); if (isNaN(x)) { myRedraw(); return; } if (isNaN(x)) { myRedraw(); return; } showIteration(methodCode, i, x, fVal); } else { // use modified method usedModified = true; methodCode = "M"; let disc = 2 * fVal/fPPVal; if (disc == 0) { break; } // f(x) = 0 if (disc > 0) { deltaX = sqrt(disc); lastF = fVal; let x1 = x; let f1 = eval(fun); x = lastX - deltaX; let x2 = x; let f2 = eval(fun); if (abs(f1) < abs(f2)) { // pick the x that for which f(x) is smallest x = lastX + deltaX; fVal = f1; } else { x = lastX - deltaX; fVal = f2; } let a1 = -fPPVal/2; let b1 = +fPPVal*lastX; let c1 = -fPPVal*lastX*lastX/2 + lastF;; // draw iteration - first vertical line then quadratic fInput[4].value(fInput[4].value() + " " + lastX + "; 0; " + lastX + "; " + lastFVal + "; "); fInput[5].value(fInput[5].value()+ " " + a1 + "; " + b1 + "; " + c1 + "; " + lastX + "; " + x + "; "); if (isNaN(x)) { myRedraw(); return; } showIteration(methodCode, i, x, fVal); } else { resultArea.value += "\nModified method fails as square root < 0" + "\n f = " + fVal + " f'' = " + fPPVal; methodCode = "N"; // May not be a double root, return to Newton's } if (abs(fVal) > abs(lastFVal) && abs(deltaX) > abs(lastDeltaX)) { resultArea.value +="\n" +"The iterations do not seem to be converging. There may" + " not be a solution that can be found with the current" + " starting point."; startAt = i + 1; iterateBtn.show(); if (!usedModified) { fInput[5].value("");// q not used. Clear the "q" } myRedraw(); break; } } if (absDeltaX < 1.0E-10 || fVal == 0) { //////// startAt = i + 1; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } if (!usedModified) { fInput[5].value("");// q not used. Clear the "q" } myRedraw(); break; } } } catch (e) { alert ("Iteration error, namely: " + e); } } // modifiedNewtonSolve function acceleratedNewtonSolve(restart = false) { // Solves f(x) = 0 given f'. Uses Newton's method unless // it appear convergence is linear (not quadratic) indicating a multiple // root. In that case it gets the next iterate by assuming both // f and f' are zero. It accelerates by multiplying f/f' by 2 or a higher // value. method = 3; // this is the accelerated Newton's method // initialize Newton's method maxIterations = maxIterationsInp.value(); let haltIterations; iterateBtn.hide(); let accelerator; let closeEnough = 1E-20; ///// ???? try { inputF(); // make sure f values are up to date if (!restart) { x = eval(startingXInp.value()); fInput[3].value(""); fInput[4].value(""); resultArea.value = ""; startAt = 1; } fun = f[0]; funP = f[1]; // f' haltIterations = check(1); // reset tangent and vertical line strings and resultArea } catch (e) { alert("Required information (f(x), f'(x), Starting x" + ", or Maximum iterations) missing or invalid."); } if (haltIterations) { return; } fLabel[3].html('
' + "iter line "); fLabel[5].html('
' + "            "); // Iterate Newton's or accelerated Newton's method try { if (!restart) { let methodCode = "N"; // initially regular Newton's method is used showFunction(fun); fVal = eval(fun); if (isNaN(x)) { myRedraw(); return; } if (isNaN(x)) { myRedraw(); return; } showIteration(methodCode, 0, x, fVal, " "); lastDeltaX = 100; // arbitrary numbers to avoid deltaX = 80; // switching in first couple of steps accelerator = 1; // default for normal Newton's method fInput[3].value("L "); fInput[4].value("L "); fInput[5].value(""); } for (let i = startAt; i < maxIterations; i++) { // initialize line segments // initial evaluations let fPVal = eval(funP); let lastX = x; lastFVal = fVal; secondLastDeltaX = lastDeltaX; lastDeltaX = deltaX; deltaX = fVal/fPVal; // this deltaX is for Newton's method let absDeltaX = abs(deltaX); if (!restart && i == 1) { methodCode = "N"; } if ((abs(lastDeltaX/deltaX - secondLastDeltaX/lastDeltaX) > .1 || i <= 2) && methodCode == "N") { // use normal Newton's method x = lastX - deltaX; fVal = eval(fun); if (abs(fVal) > abs(lastFVal)){ if (isNaN(x)) { myRedraw(); return; } if (isNaN(x)) { myRedraw(); return; } showIteration(methodCode, i, x, fVal, " "); resultArea.value +="\n" +"The iterations do not seem to be converging. There may" + " not be a solution that can be found with the current" + " starting point."; startAt = i + 1; iterateBtn.show(); myRedraw(); return; } } else { if (methodCode == "N") { if (abs(lastDeltaX/deltaX) < 1.39) { accelerator = 4; } else if (abs(lastDeltaX/deltaX) < 1.55) { accelerator = 3; } else { accelerator = 2; } } // use accelerated method methodCode = "A"; x = lastX - accelerator * deltaX; // since convergence is slow, // accelerate the distance moved } fVal = eval(fun); // graph iterations - first the sloped line then the vertical line fInput[4].value(fInput[4].value() + lastX + "; 0; " + lastX + "; " + lastFVal + "; "); fInput[3].value(fInput[3].value() + lastX + ";" + lastFVal + "; " + x + "; 0; "); if (isNaN(x)) { myRedraw(); return; } if (accelerator > 1) { showIteration(methodCode, i, x, fVal, accelerator); } else { showIteration(methodCode, i, x, fVal, " "); } // Check for convergence failure but try regular Newton's // Perhaps the root was not a multiple root after all. if ((abs(fVal) > abs(lastFVal) || abs(deltaX) > abs(lastDeltaX)) && abs(fVal) > closeEnough) { methodCode = "N"; accelerator = 1; } myRedraw(); if (absDeltaX < 1.0E-16 || fVal == 0) { startAt = i + 1; if (abs(fVal) > 1E-30) { iterateBtn.show(); } else { iterateBtn.hide(); } break; } } } catch (e) { alert ("Iteration error, namely: " + e); } } // acceleratedNewtonSolve function useGNewton() { x = eval(startingXInp.value()); if (eval(f[0]) == 0) { // the Newton's methods can't handle this condition resultArea.value = ""; methodCode = "g"; showIteration(methodCode, 0, x, 0, " "); return; } useGFunction(false); // use regular Newton's method useG = false; } // gNewton function useGApprox() { x = eval(startingXInp.value()); if (eval(f[0]) == 0) { // the Newton's methods can't handle this condition resultArea.value = ""; methodCode = "ga"; showIteration(methodCode, 0, x, 0, " "); return; } useGFunction(true); // use approximate Newton's Method useG = false; } // aApprox function useGFunction(approx) { // called by both gNewton and gApprox to carry out iterations with // the g function try { inputF(); // make sure functions are upto date if (f[0] == "" || f[1] == "" || f[2] == "" || startingXInp.value() == "" || maxIterationsInp.value() == "") { alert("Required information (f(x), f'(x), f''(x), Starting x" + ", or Maximum iterations) missing or invalid."); return; } fLabel[3].html('
' + "tangent "); fLabel[5].html('
' + "g(x) "); fSave = f[0]; // f(x) fPSave = f[1]; // f'(x) f[0] = "(" + fSave + ")/(" + fPSave + ")"; // g(x) if (!approx) { // need g' for regular Newton's fPPSave = f[2]; // f''(x) f[1] = "1 - ((" + fSave + ") * (" + fPPSave + "))/((" + fPSave + ") * (" + fPSave + "))"; + "))"; } } catch (e) { alert("Error setting up useG method"); } fInput[5].value(f[0]); // alert(f[0] + "\n" + f[1]); useG = true; if (approx) { approxSolve(); } else { newtonSolve(); } } // useGFunction function cubicSolve() { // solves linear, quadratic, and cubics in one step let fVal; let methodCode = "C" valueChanged(); resultArea.value = ""; try { if (a == 0 && b == 0 && c == 0 && d == 0) { resultArea.value += "\nAll real numbers are solutions"; resultArea.value += "\nDid you forget to provide a, b, c, and/or" + " d values?"; return; } fun = formCubic(a, b, c, d); } catch (e) { alert("Required information is invalid (a, b, c, or d)\n" + e); return; } try { fInput[0].value(fun); fInput[1].value(formCubic(0, 3 * a, 2 * b, c)); fInput[2].value(formCubic(0, 0, 6 * a, 2 * b)); fInput[3].value(""); fInput[4].value(""); fInput[5].value(""); } catch (e) { alert("Required information is invalid (a, b, c, or d)\n" + e); return; } try { showFunction(fun); inputF(); draw(); if (a == 0 && b == 0 && c == 0) { resultArea.value += "\nThere are no solutions"; return; } fLabel[3].html('
' + "tangent"); fLabel[5].html('
' + "            "); let rt = solveCubic(a, b, c, d); if (rt.length == 0) { resultArea.value += "\nThere are no real solutions"; return; } else { if (rt.length == 1) { resultArea.value += "\nThe " + rt.length + " solution only is:"; } else { resultArea.value += "\nThe " + rt.length + " solutions are:"; } for (let j = 0; j < rt.length; j++) { x = rt[j]; let fVal = eval(fun); showIteration("C", "", x, fVal); } // end for each root } return; } catch (e) { alert("CubicSolve error: " + e); } } // cubicSolve function iterate() { // do another iteration switch (method) { case 0: secantSolve(true); break; case 1: newtonSolve(true); break; case 2: modifiedNewtonSolve(true); break; case 3: acceleratedNewtonSolve(true); break; case 4: alert("iterate: method = 4; can't iterate this method"); break; } } // iterate function zoomIn() { // zooms out with the ratio of 5. Centers graph on x. // If x is not defined, then it uses the center of the current graph for x if (typeof x == "undefined") { x =(minX + maxX)/2; y = 0; } centerGraph(x, 0, zoomRatio); } // zoomIn function zoomOut() { // zooms out with the ratio of 1/5. Centers graph on x. // If x is not defined, then it uses the center of the current graph for x if (typeof x == "undefined") { x =(minX + maxX)/2; y = 0; } centerGraph(x, 0, 1/zoomRatio); } // zoomOut function dblClick(event) { // centers the graph at the double clicked point. // the mins and maxs are offset by locOffset from the borders let xx = map(event.offsetX, locOffset, can1.width - locOffset, minX, maxX); let yy = map(event.offsetY, locOffset, can1.height - locOffset, maxY, minY); centerGraph(xx, yy, 1); } // dblClick function centerGraph(xx, yy, zoom) { // centers the graph at (xx, yy) and zooms if zoom != 1 // zoom in if zoom > 1, zoom out if 0 < zoom < 1 x = xx; let currentWidth = maxX - minX; let newWidth = currentWidth/zoom; let maxX1 = xx + newWidth/2; let minX1 = xx - newWidth/2; xMinInput.value(minX1); xMaxInput.value(maxX1); y == yy; if (yMinInput.value() != "" && yMaxInput.value() != "") { let currentHeight = maxY - minY; let newHeight = currentHeight/zoom; let maxY1 = yy + newHeight/2; let minY1 = yy - newHeight/2; yMinInput.value(minY1); yMaxInput.value(maxY1); } checkMinMax(); loop(); } // centerGraph function formCubic(a, b, c, d) { let s = ""; if (a != 0) { if (a != 1) { s += a + " * "; } s += "x**3 "; } if (b != 0 ) { if (b > 0 && a != 0) { if (b == 1) { // eval does not like +x**2 s += "+ "; } else { s += " +" + b + " * "; } } else { s += b + " * "; } s += "x**2 "; } if (c != 0) { if (c > 0) { if (b == 1) { s += "+ "; } else { s += " +" + c + " * "; } } else { s += c + " * "; } s += "x "; } if (d != 0) { if (d > 0) { s += "+ "; } s += d; } return s; } // formCubic function showIteration(mCode,i, xx, fv, accel = "") { // outputs to the result area with function value "rounded" // mCode: method code // i: iteration number // xx: x value; // f: function value // accel: accelerator (default is blank) let fValOut; if (abs(fv) < .000001 && fv != 0) { fValOut = fv.toExponential(2); } else { fValOut = fv.toPrecision(5); } resultArea.value += "\n" + mCode+accel + " " + i + "\tx = " + xx.toFixed(15) + "\tf = " +fValOut; } // showIteration function showFunction(func){ resultArea.value += "\nSolving " + fun + " = 0"; } // showFunction function check(num) { // checks to see if the necessary info has been provided. // Issues an alert for each piece of info that is missing // num: number of derivatives checked. // returns: false if all the information has been provided. // if something is missing. let haltIteration = false; if (fun == "") { alert("f(x) must be defined."); haltIteration = true; } if (num >= 1 && funP == "") { alert("f '(x) must be defined."); haltIteration = true; } if (num >= 2) { if (funPP == "") { alert("f ''(x) must be defined for the cubic method."); haltIteration = true; } } try { if (startingXError) { errorMsg = ""; startingXError = false; } let startX = eval(startingXInp.value()); // check for bad data if (startingXInp.value() == "") { alert("The starting x value must be provided. "); haltIteration = true; } if (num == 0) { let startX = eval(startingX2Inp.value()); // check for bad data if (startingX2Inp.value() == "") { alert("The 2nd starting value must be provided for the secant method."); haltIteration = true; } else { let x2 = float(startingX2Inp.value()); if (x == x2) { alert("The two starting values cannot be equal."); haltIteration = true; } } } } catch (err) { errorMsg = "A startingX value has an illegal value.\nNamely: " + err; startingXError = true; } if (maxIterations == "") { // rare as it was inistialized in setup() alert("The maximum iterations must be provided."); haltIteration = true; } return haltIteration; } // check function somethingChanged() { // loop to update graph loop(); } // somethingChanged function evalRectangular(i) { // normal function. determine x and y values try { for (let ix = 0; ix < numPts; ix++) { xVals[i][ix] = float(minX) + float((ix - numPtsOffset) * (maxX - minX) / numDiv); let yyy = func(i, xVals[i][ix]); if (yyy == ERROR) { break; } yVals[i][ix] = yyy; if (!isNaN(yyy) && yyy !== Infinity && yyy !== -Infinity) { yMaxV[i] = max(yMaxV[i], yyy); yMinV[i] = min(yMinV[i], yyy); } } xMinV[i] = minX; xMaxV[i] = xVals[i][numPts - 1]; } catch(err) { setErrorMsg("Error in function f" + i + ".\n Namely: " + err); } } // evalRectangular function gVerticalLine(i) { // Draws vertical lines (can be used in all modes) // Format: // x= xVal, yLow (optional), yHigh (optional) // Draws a vertical line at xVal from yHigh to yLow. // If yHigh and yLow are not provide the line normally will go from the // top of the graph to the bottom. // Result: // xVals[i][0] = "vLine" // xVals[i][1] = xVal // yVals[i][0] = 0, 1, 2 depending on the number of y values provided // yVals[i][1] = yLow (if provided) // this allows having the line for y>=0 // yVals[i][2] = yHigh (if provided) // yMaxV[i] if (functionError) { errorMsg = ""; functionError = false; alert("vert"); } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(2, 10000); sts = st.split(";"); let theXVal = eval(sts[0]); if (sts.length == 1 && sts[0] == "") { setErrorMsg("In f" + i + ", x= must be followed by the x value\n and" + " optionally by ; yLow; yHigh"); return; } xVals[i][0] = "vLine"; xVals[i][1] = theXVal; xMinV[i] = theXVal; xMaxV[i] = theXVal; yVals[i][0] = sts.length - 1; if (sts.length >= 2) { yVals[i][1] = eval(sts[1]); yMinV[i] = yVals[i][1]; if (sts.length >= 3) { yVals[i][2] = eval(sts[2]); yMinV[i] = min(yVals[i][2], yVals[i][1]); yMaxV[i] = max(yVals[i][2], yVals[i][1]); } } } catch (err) { setErrorMsg("Error in function " + i + " (" +f[i] + ".\nNamely: " + err); functionError = true; loop(); } } // gVerticalLine function gTheta(i) { // Draws rays at angle theta (o) // Assumes polar coordiantes but may be used in all modes // format: // o= thetaVal, rLow (optional),rHigh (optional) // Draws a ray at angle thetaVal from (rLow, theta) to (rHigh, theta) // (Assumes polar coordinates.) // If rHigh and rLow are not provided, the rLow = -10, rHigh = 10 // Result: // xVals[i][0] = "oLine" // (xVals[i][1], yVals[i][1]): one end point of ray // (xVals[i][2], yVals[i][2]): other end point of ray if (functionError) { errorMsg = ""; functionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(2, 10000); let sts = st.split(";"); if (sts.length == 1 && sts[0] == "") { setErrorMsg("o= must be followed by the theta value\n and optionally" + " by ; rLow; rHigh"); return; } xVals[i][0] = "oLine"; yVals[i][0] = 1;/// could be changed.... let thetaVal = eval(sts[0]); let rMin = -10; // these are arbitrary let rMax = 10; if (sts.length >= 2) { rMin = eval(sts[1]); // this may actually be the max if (sts.length >= 3) { rMin = min(rMin, eval(sts[2])); rMax = max(eval(sts[1]), eval(sts[2]));// rMin may have changed } } let x1 = rMin * cos(thetaVal); let y1 = rMin * sin(thetaVal); let x2 = rMax * cos(thetaVal); let y2 = rMax * sin(thetaVal); xVals[i][1] = x1; xVals[i][2] = x2; yVals[i][1] = y1; yVals[i][2] = y2; xMinV[i] = min(x1, x2); xMaxV[i] = max(x1, x2); yMinV[i] = min(y1, y2); yMaxV[i] = max(y1, y2); } catch (err) { setErrorMsg("Error in function " + i + " (" +f[i] + ".\nNamely: " + err); functionError = true; loop(); specialDefined = true; } } // gTheta function gLineSegment(i) { // Draws a straight line betweein two points. // May be used in all modes. // format: // l = x1; y1; x2; y2; ... // draws a straight line from (x1, y1) to (x2, y2). // or in polar coordinates // l = r1; theta1; r2; theta2 // Result: // xVals[i][0] = "l" // yVals[i][0] = number of line segments // xVals[i][j+1]: x value of one end point // xVals[i][j+1]: y value of one end point // xVals[i][j+2]: x value of other end point // xVals[i][j+3]: y value of other end point if (functionError) { errorMsg = ""; functionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(1, 10000); let sts = st.split(";"); // check to see if there is sufficient info noFDefined = false; if (floor(sts.length/4) == 0) { if (mode == POLAR_MODE) { setErrorMsg('In f' + i + '= "' + f[i] + '",\nL must be followed by rVal; thetaVal; rVal; thetaVal' + '\nDid you use ";"?'); } else { setErrorMsg('In f' + i + '= "' + f[i] + '",\nL must be followed by xVal; yVal; xVal; yVal' + '\nDid you use ";"?'); } return; } // process the points xVals[i][0] = "l"; yVals[i][0] = floor(sts.length/4); let xxx2, yyy2; for (let j = 0; j < floor(sts.length/4); j++) { if (mode == POLAR_MODE) { let radius = eval(sts[4 * j]); let theta = eval(sts[4 * j + 1]); let radius2 = eval(sts[4 * j + 2]); let theta2 = eval(sts[4 * j + 3]); xxx = radius * cos(theta); yyy = radius * sin(theta); xxx2 = radius2 * cos(theta2); yyy2 = radius2 * sin(theta2); } else { xxx = eval(sts[4 * j]); yyy = eval(sts[4 * j + 1]); xxx2 = eval(sts[4 * j + 2]); yyy2 = eval(sts[4 * j + 3]); } xVals[i][2 * j + 1] = xxx; yVals[i][2 * j + 1] = yyy; xVals[i][2 * j + 2] = xxx2; yVals[i][2 * j + 2] = yyy2; xMinV[i] = min(xxx, xxx2, xMinV[i]); xMaxV[i] = max(xxx, xxx2, xMaxV[i]); yMinV[i] = min(yyy, yyy2, yMinV[i]); yMaxV[i] = max(yyy, yyy2, yMaxV[i]); } } catch (err) { setErrorMsg("Error in function " + i + " (" +f[i] + ".\nNamely: " + err); functionError = true; } } // gLineSegment function gCircle(i) { // Draws circles with radius r) // May be used in all modes. // format: // r = thetaVal, center x (optional), center y (optional) // Draws a circle with center at (centerX, centerY) // If the centers are not provided, the center is the orgin, // Result: // xVals[i][0] = "r" // xVals[i][j+1]: x value of point on the curve (number of evaluation pts.) // xVals[i][j+1]: y value of point on the curve if (functionError) { errorMsg = ""; functionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(2, 10000); let sts = st.split(";"); if (sts.length == 1 && sts[0] == "") { if (mode == POLAR_MODE) { setErrorMsg("r= must be followed by the r value\n and optionally" + "by the center ; center r; center theta" ); } else { setErrorMsg("r= must be followed by the r value\n and optionally" + " by both ; center x; center theta"); } return; } let rVal = eval(sts[0]); let centerX = 0; // default center at origin let centerY = 0; if (sts.length >= 3) { // If option used, both low and high required if (mode == POLAR_MODE) { let r = eval(sts[1]); let th = eval(sts[2]); centerX = r * cos(th); centerY = r * sin(th); } else { centerX = eval(sts[1]); centerY = eval(sts[2]); } } let range = TWO_PI; xVals[i][0] = "r"; for (let j = 0; j <= numPts; j++) { let theta = j * range/numPts; xVals[i][j+1] = rVal * cos(theta) + centerX; yVals[i][j+1] = rVal * sin(theta) + centerY; } xMinV[i] = -rVal + centerX; xMaxV[i] = rVal + centerX; yMinV[i] = -rVal + centerY; yMaxV[i] = rVal + centerY; } catch (err) { setErrorMsg("Error in function " + i + " (" +f[i] + ")" + ".\nNamely: " + err); functionError = true; loop(); } } // gCircle function gPoint(i) { // graph points // Format: // p xVal, yVal, xVal, yVal, ... multiple points allowed // or in polar // p rVal, thetaVal, rVal, thetaVal ... multiple points allowed // Plots a point at each aVal, yVal pair. If yVal is missing, the // xVal is ignored. // Result: // xVals[i][0] = "p" // yVals[i][0] = number of points // xVals[i][1], yVals[i][1]: the first point // xVals[i][2], yVals[i][2]: second point, if specified // ... (likewise for any additional points) if (functionError) { errorMsg = ""; functionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(1, 10000); let sts = st.split(";"); // check to see if there is sufficient info if (floor(sts.length/2) == 0) { if (mode == POLAR_MODE) { setErrorMsg('In f' + i + '= "' + f[i] + '", p must be followed by rVal; thetaVal' + '\nDid you use ";"?'); } else { setErrorMsg('In f' + i + '= "' + f[i] + '", p must be followed by xVal; yVal' + '\nDid you use ";"?'); } loop(); return; } // process the points xVals[i][0] = "p"; yVals[i][0] = floor(sts.length/2); for (let j = 0; j < floor(sts.length/2); j++) { if (mode == POLAR_MODE) { let radius = eval(sts[2 * j]); let theta = eval(sts[2 * j + 1]); xxx = radius * cos(theta); yyy = radius * sin(theta); } else { xxx = eval(sts[2 * j]); yyy = eval(sts[2 * j + 1]); } xVals[i][j + 1] = xxx; yVals[i][j + 1] = yyy; xMinV[i] = min(xxx, xMinV[i]); xMaxV[i] = max(xxx, xMaxV[i]); yMinV[i] = min(yyy, yMinV[i]); yMaxV[i] = max(yyy, yMaxV[i]); } } catch(e) { errorMsg = "Error in point specication. Namely " + e; functionError = true; loop(); } } // gPoint function gWord(i) { // graph words (or labels) // (w for word instead of l for label as l looks too much like 1) // Format: // w xVal, yVal, word, xVal, yVal, word... multiple words allowed // or in polar // w rVal, thetaVal, word, ... multiple words allowed // Types a word or label at each aVal, yVal pair. If yVal or word is // missing, thexVal is ignored. // Result: // xVals[i][0] = "w" // yVals[i][0] = number of words // xVals[i][1], yVals[i][1]: location of the first word // yVals[i][2] the first word // xVals[i][3], yVals[i][3]: location of second word, if specified // yVals[i][4] the second word, if specified // ... (likewise for any additional words) // Notes: each word uses 2 items in the xVals and yVals arrays. // The word is centered horizontally just above the specified y value. if (functionError) { errorMsg = ""; fnctionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(1, 10000); let sts = st.split(";"); // check to make at least 1 word is declared if (floor(sts.length/3) == 0) { if (mode == POLAR_MODE) { setErrorMsg('In f' + i + '= "' + f[i] + '",\nw must be followed by rVal; thetaVal; word'); } else { setErrorMsg('In f' + i + '= "' + f[i] + '",\nw must be followed by xVal; yVal; word'); } loop(); return; } // process the words xVals[i][0] = "w"; yVals[i][0] = floor(sts.length/3); let xxx, yyy; let modeType = 1; if (mode == PARA_MODE) { modeType = 2; //This mode uses colors 0, 1, 2 } useColor[i] = colors[floor(i/modeType)]; // assume normal color for (let j = 0; j < floor(sts.length/3); j++) { if (trim(sts[3 * j]) == "color") { try { useColor[i] = colors[floor(eval(sts[3 * j + 1]))]; } catch (err) { setErrorMsg("Illegal color (" + sts[3 * j + 1] + ") for word(s) in function box " + i + ",\nnamely " + err); } return; } if (mode == POLAR_MODE) { radius = eval(sts[3 * j]); theta = eval(sts[3 * j + 1]); xxx = radius * cos(theta); yyy = radius * sin(theta); } else { xxx = eval(sts[3 * j]); yyy = eval(sts[3 * j + 1]); } xVals[i][2 * j + 1] = xxx; yVals[i][2 * j + 1] = yyy; xMinV[i] = min(xxx, xMinV[i]); xMaxV[i] = max(xxx, xMaxV[i]); yMinV[i] = min(yyy, yMinV[i]); yMaxV[i] = max(yyy, yMaxV[i]); let st = trim(sts[3 * j + 2]); for (let i = 0; i < valuesLab.length; i++) { st = myReplaceAll(st, SPECIAL_SYMBOLS[i], theValues[i]); } st = myReplaceAll(st, '^$', ';'); yVals[i][2 * j + 2] = st; } } catch (err) { errorMsg = 'Error in declaring a "word". \nNamely: ' + err; functionError = true; loop(); } } // gWord function gQuad(i) { // Draws pieces of quadrics (parabolas). // Format: // q a; b; c; x1; x2; a; b; c; x1; x2; ..... // The quadratic is ax**2 + bx +c and it is drawn between x1 and x2. // The 5 values are required but can be repeated. Unlike the // other special g options, it justs produces x and y values // that can be plotted like leany other curve. // i is the curve number if (functionError) { errorMsg = ""; functionError = false; } try { specialDefined = true; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); let st = f[i].substring(1, 10000); let sts = st.split(";"); // check to see if there is sufficient info let numQuad = floor(sts.length/5); if (numQuad == 0) { setErrorMsg('In f' + i + '= "' + f[i] + '", q must be followed by a, b, c, x1, and x2' + '\nDid you use ";"?'); } let iii = 0; // counter for points to be plotted for (let j = 0; j < numQuad; j++) { let j5 = j * 5; let aaa = eval(sts[j5]); // is float needed???? let bbb = eval(sts[j5 + 1]); let ccc = eval(sts[j5 + 2]); let xxx1 = eval(sts[j5 + 3]); let xxx2 = eval(sts[j5 + 4]); let numPts = abs(numDiv * (xxx2 - xxx1)/(maxX - minX)); let spacing = (xxx2 - xxx1)/numPts; for (let ix = 0; ix <= numPts; ix++) { let xxx = xxx1 + ix * spacing; let yyy = (aaa * xxx + bbb) * xxx + ccc; xVals[i][iii] = xxx; yVals[i][iii] = yyy; iii++; xMinV[i] = min(xMinV[i], xxx); xMaxV[i] = max(xMaxV[i], xxx); yMaxV[i] = max(yMaxV[i], yyy); yMinV[i] = min(yMinV[i], yyy); // provide separator between curves } xVals[i][iii] = Infinity; yVals[i][iii] = Infinity; iii++; } } catch (err) { errorMsg = 'Error in declaring a quadratic. \nNamely: ' + err; functionError = true; loop(); } } // gQuad // This called when any MinInput or MaxInput changes. // Both x and y axis location must be reset in case there is a blank. function checkMinMax() { // check for previous minMaxError. Clear it. It will be reset if needed if (minMaxError) { errorMsg = ""; minMaxError = false; } try { // chect t (theta) min/max if needed if (mode == POLAR_MODE || mode == PARA_MODE) { if (tMinInput.value() == "") { minT = 0; } else { minT = eval(tMinInput.value()); } if (tMaxInput.value() == "") { if (mode == POLAR_MODE) { if (modeAngle == RADIANS) { maxT = TWO_PI; } else { maxT = 360; } } else if (mode == PARAM_MODE){ maxT = 100; } } maxT = eval(tMaxInput.value()); } // check x min/max minX = xMinInput.value(); if (minX == "" && mode == RECT_MODE) { minX = -5; } else if (minX != "") { minX = eval(minX); } maxX = xMaxInput.value() ; if (maxX == "" && mode == RECT_MODE) { maxX = 5; } else if (maxX != "") { maxX = eval(maxX); } // check y min/max minY = yMinInput.value(); if (minY != "") { minY = eval(minY); } maxY = yMaxInput.value(); if (maxY != "") { maxY = eval(maxY); } } catch(err) { minMaxError = true; setErrorMsg("Invalid min or max.\nNamely: " + err); } loop(); } // checkMinMax function resetMinMax(i, aXMin, aYMin, aXMax, aYMax) { aXMin[i] = Infinity; aYMin[i] = Infinity; aXMax[i] = -Infinity; aYMax[i] = -Infinity; } // resetMinMax // locateAxes called during draw() function locateAxes() { // locate x axis if (miniY >= 0) { xAxisLoc = can1.height - locOffset; } else if (maxiY <= 0) { xAxisLoc = locOffset; } else { xAxisLoc = map(0, miniY, maxiY, can1.height - locOffset, locOffset); } if (miniX >= 0) { yAxisLoc = locOffset; } else if (maxiX <= 0) { yAxisLoc = can1.width - locOffset; } else { yAxisLoc = map(0, miniX, maxiX, locOffset, can1.width - locOffset); } } // locateAxes function inputF0() { // called when fInput[0] or fInput is[ changed. // It clears fIput[3] and fInput[4] in addition to the normal action // inputF fInput[3].value(""); fInput[4].value(""); if (method == 2 || methodCode == "g" || methodCode == "ga") { fInput[5].value(""); } inputF(); } // inputF0 function inputF() { // called by setup and everytime a function is changed noFDefined = true; for (i = 0; i < NUM_F_INPUT; i++) { f[i] = trim(fInput[i].value()); fDefined[i] = (f[i] != ''); if (fDefined[i]) { noFDefined = false; } } // recalculate checkMinMax() loop(); } // inputf // evaluate f[i] at x function func(i, x) { if (functionError) { errorMsg = ""; functionError = false; } try { let val = eval(f[i]); return val; } catch(err) { setErrorMsg("The function f" + i + " = '" + f[i] + "' is invalid:\n" + err); functionError = true; loop(); return ERROR; } } // end func // draw x hashMarks and label them function xHashMarks() { let currentWidth = maxiX - miniX; let logCW = floor(Math.log10(currentWidth)); let roundOffFactor = 100; // up to 2 decimal places let spacingMin = min(.5, .5 * 10 ** logCW); let spacing = max(round(currentWidth/10), spacingMin); if (spacing < currentWidth/10) { spacing = 2 * spacing; } let rMiniX = miniX; if (spacing >= spacingMin) { rMiniX = round(spacingMin * rMiniX)/spacingMin; } if (spacing < .01) { roundOffFactor = 10000; // 4 decimal places } else if (spacing < .1) { roundOffFactor = 1000; // 3 decimal places } let num = max(currentWidth/spacing, 5); // provide at least 5 hash marks for (let i = 0; i <= num; i++) { let xxx = float(miniX) + float(i * currentWidth / num); if (xxx != 0) { let px = map(xxx, miniX, maxiX, locOffset, can1.width - locOffset); myLine(px, xAxisLoc - 3, px, xAxisLoc + 3); myText(round(roundOffFactor * xxx)/roundOffFactor, px, xAxisLoc + 14); } } } // xHashMarks // draw y hashMarks and label them function yHashMarks() { let currentHeight = maxiY - miniY; let logCW = floor(Math.log10(currentHeight)); let roundOffFactor = 100; // up to 2 decimal places let spacingMin = min(.5, .5 * 10 ** logCW); let spacing = max(round(currentHeight/10), spacingMin); if (spacing < currentHeight/10) { spacing = 2 * spacing; } let rMiniY = miniY; if (spacing >= spacingMin) { rMiniX = round(spacingMin * rMiniY)/spacingMin; } if (spacing < .01) { roundOffFactor = 10000; } else if (spacing < .1) { roundOffFactor = 1000; } let num = max(currentHeight/spacing, 5); // provide at least 5 hash marks for (let i = 0; i <= num; i++) { let yyy = float(rMiniY) + float(i * currentHeight / num); if (yyy != 0) { let py = map(yyy, miniY, maxiY, can1.height - locOffset, locOffset); myLine(yAxisLoc - 3, py, yAxisLoc + 3, py); if (yAxisLoc <= 25) { myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc + 20, py + 5); // put y value right of axis } else { myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc - 20, py + 5); // put y value left of axis } } } } // yHashMarks function equalSpaceChanged() { // called when equalSpace is changed loop(); } // equalSpaceChanged function allowLoopChanged() { // called when allowLoop is changed loop(); } // allowLoopChanged function enlargedChanged() { enlarge = enlargeCheckBox.checked(); if (enlarge) { can2.style("display: block"); spacer.style("display: block"); } else { can2.style("display: none"); spacer.style("display: none"); } loop(); } // enlargedChanged function valueChanged() { // called when a value is changed if (valueError) { errorMsg = ""; valueError = false; } try { valInp[5].value(sValueSlider.value()); s = float(valInp[5].value()); theValues[5] = s; if (trim(valInp[4].value()) != "") { e = eval(valInp[4].value()); // eval of blank returns NaN } else { e = 0; } theValues[4] = e; if (trim(valInp[3].value()) != "") { d = eval(valInp[3].value()); // eval of blank returns NaN } else { d = 0; } theValues[3] = d; if (trim(valInp[2].value()) != "") { c = eval(valInp[2].value()); } else { c = 0; } theValues[2] = c; if (trim(valInp[1].value()) != "") { b = eval(valInp[1].value()); } else { b = 0; } theValues[1] = b; if (trim(valInp[0].value()) != "") { a = eval(valInp[0].value()); } else { a = 0; } theValues[0] = a; // The above evaluate the value e, d, ..., a in that order so the value // of e does not reflect a new value in d and so on. // So now evaluate them in the order b, c, ..., e if (trim(valInp[1].value()) != "") { b = eval(valInp[1].value()); } if (trim(valInp[2].value()) != "") { c = eval(valInp[2].value()); } if (trim(valInp[3].value()) != "") { d = eval(valInp[3].value()); } if (trim(valInp[4].value()) != "") { e = eval(valInp[4].value()); } valDisplay.html("a = " + a.toFixed(3) + ",   b = " + b.toFixed(3) + ",   c = " + c.toFixed(3) + ",   d = " + d.toFixed(3) + ",   e = " + e.toFixed(3)); } catch(err) { setErrorMsg("Invalid value.\nNamely: " + err); valueError = true; loop(); return; } checkMinMax(); loop(); } // valueChanged function numPtsChanged() { // called when the number of points is changed // check for previous numberEvalError. Clear it. It will be reset if needed if (numberEvalsError) { errorMsg = ""; numberEvalsError = false; } try { numDiv = eval(numPtsInput.value()); if (numDiv < 50 || numPtsInput.value() == "") { throw "Number of evaluation points is blank or too small"; } numPts = 1.1 * numDiv; numPtsOffset = 0.05 * numDiv; } catch(err) { setErrorMsg("Invalid number of evaluation points." + "\nNamely " + err); numberEvalsError = true; } loop(); } // numPtsChanged function functionLab(i, aMode) { // Provide an appropriate label for function boxes. // Called by setup(). There must a case for every possible curve. let u; switch (i) { case 0: u = FUNCTION_LAB0[aMode]; break; case 1: u = FUNCTION_LAB1[aMode]; break; case 2: u = FUNCTION_LAB2[aMode]; break; case 3: u = FUNCTION_LAB3[aMode]; break; case 4: u = FUNCTION_LAB4[aMode]; break; case 5: u = FUNCTION_LAB5[aMode]; break; } fLabel[i].html(u); } // functionLab function updateTMinMax(aMode) { //called by modeChanged tMinLabel.html(T_MIN_LAB[aMode]); tMinInput.value(T_MIN_VAL[aMode]); tMaxLabel.html(T_MAX_LAB[aMode]); tMaxInput.value(T_MAX_VAL[aMode]); } // updateTMinMax function sSliderChanged() { valInp[5].value(sValueSlider.value()); valueChanged(); } // sSliderChanged function sInput() { let s = this.value(); let sVal = int(s); if (sVal == s && sVal >= 0 && sVal <= 100) { sValueSlider.value(sVal); } } // sInput function setErrorMsg(msg) { // this is designed to prevent repeated alerts for the same error // expecially while looping. Repeats of the same alert msg may make // difficult to fix the error. In some cases a very similar msg may // be shown. For example if abc is an illegal variable, the alert // would repeat if the variable is changed to ab. /* if (msg != lastMsg) { lastMsg = msg; } */ errorMsg = msg; } // oneTimeAlert function displayError() { myFill("wheat"); myRect(10, 10, can1.width - 20, 90); myFill("black"); myNoStroke(); myText(errorMsg + "\n\nClick canvas to remove this message after" + " correcting the problem.", can1.width/2, 25); } function clearErrorMsg() { errorMsg = ""; loop(); } function myReplaceAll(st, from, to) { // replaces every occurance of "from" in st with "to" and // returns the result. let array = st.split(from); let s = array[0]; for (let i = 1; i < array.length; i++) { s += to + array[i]; } return s; } // myReplaceAll // save the canvas as an image file function saveImage() { mySaveCanvas("solver", "jpg"); } // an alert with data needed for an example // based on the current graph function showExampleData() { let s = " example[??] = [" for (let i = 0; i < 6; i++) { s += '"' + myReplaceAll(fInput[i].value(), ',', '^$') + '",'; } s += '\n '; s += mode + ',\n '; // add spaces to indent output s += '"' + tMinInput.value() + '", "' + tMaxInput.value() + '", '; s += '"' + xMinInput.value() + '", "' + xMaxInput.value() + '", '; s += '"' + yMinInput.value() + '", "' + yMaxInput.value() + '", '; s += equalSpace.checked() + ', '; s += allowLoop.checked() + ',\n '; for (let i = 0; i < 5; i++) { s += '"' + valInp[i].value() + '", '; } s += '"' + sValueSlider.value() + '", '; s += '"' + modeAngle + '", '; s += '"' + numPtsInput.value() + '", \n '; s += '"???? Example name ????"];'; s += '\n\n'; s += 'You can copy the above and paste it into setupExamples().\n' + 'Replace the example number [??] and example name appropriately.\n' + 'Examples are printed in the order of the example numbers [].'; var r = confirm('If you the want to setup to be sent to a file, click' + ' "OK". If you click "Cancel" the setup will be sent to a' + ' regular alert box. If your browser allows copying from the' + ' alert box, this is the easier method'); if (r) { const writer = createWriter("showSetup.txt"); writer.print(s); writer.close(); writer.clear(); alert('The setup was writen to "showSetup.txt"'); } else { alert(s); } } // showExampleData function saveExample() { // copies current data in order to make a new temporary example let newName = prompt("What do you want the setup to be called?"); if (newName == "" ||newName == null) { return; } let ex = example.length; example[ex] = []; for (let i = 0; i < NUM_F_INPUT; i++) { example[ex][i] = fInput[i].value(); } example[ex][6] = mode; let theMode = int(example[ex][6]); example[ex][7] = ""; example[ex][8] = ""; example[ex][9] = xMinInput.value(); example[ex][10] = xMaxInput.value(); example[ex][11] = yMinInput.value(); example[ex][12] = yMaxInput.value(); example[ex][13] = equalSpace.checked(); example[ex][14] = allowLoop.checked(); for (let i = 0; i < 6; i++) { // values example[ex][15+i] = trim(valInp[i].value()); // includes s } example[ex][21] = modeAngle; // mode angle not used example[ex][22] = numPtsInput.value(); example[ex][EXAMPLE_NAME] = newName; let radio = HTMLforRadioButton("examplesResult", '' + example[ex][EXAMPLE_NAME] +'', "getRadioValueExamples()", true, ex); examplesRadio[ex] = displayHTMLElements(radio, p5left, "block"); useExample(ex); } // saveExample function setupExamples() { // Examples must be numbered consecutively starting a 0 // the format of the example[i] vector: // [0] .. [5]: functions f0 .. f4 (strings) // [6]: the mode - 0: normal, 1: cubic (will automatically do cubic solve) // [7] .. [8]: min and max t (number or string) // [9] .. [10]: min and max x (number or string) // [11] .. [12]: min and max y (number or string) // [13]: equal spacing (true or false) // [14]: allow motion (true or false) // [15] .. [19]: values a.. e (number or string) // [20]: value of s (sets slider); // [21]: radians/degrees option ("radians" or "degrees") // [22]: number of evaluation points (number or string) // [23]: display title (string) ( EXAMPLE_NAME = 23 ) example[0] = ["x**3 - 2*x**2 + x", "3 * x**2 - 4*x + 1","6 * x - 4","","","", 0, "0", "1", "-1", "2.5", "-1", "3", false, false, "", "", "", "", "", "50", "radians", "200", "f(x) = x(x-1)2, solutions 0, 1, 1"]; example[1] = ["x**3 - 1","3 * x**2","6 * x","","","", 0, "0", "1", "-2", "3", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "f(x) = x3 - 1, only solution 1"]; example[2] = ["cos(x) + 1","-sin(x)","-cos(x)","","","", 0, "0", "1", "-4", "4", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "f(x) = cos(x) + 1, double solutions at ... -PI, PI, ..." + "
PI = 3.1415926535897932..."]; example[3] = ["x**4 - x**2", "4*x**3 - 2*x","12*x**2 - 2","","","", 0, "0", "1", "-2", "2", "", "12", false, false, "", "", "", "", "", "50", "radians", "200", "f(x) = x4 - x2, solutions at -1, 0, 0, 1"]; example[4] = ["", "","","","","", 1, "0", "1", "0", "4", "-10", "10", false, false, "1", "-6", "11", "-6", "", "50", "radians", "200", "Solve as a cubic. f(x) = x**3 - 6*x**2 + 11x - 6" + "
Coefficients in a, b, c, d. No functions are needed."]; example[5] = ["x**4 - 3 * x**3 + 3 * x**2 - x","4 * x**3 - 9 * x**2 + 6 * x - 1","12 * x**2 - 18 * x + 6","","","", 0, "", "", "0.5", "1.5", "-.05", ".2", false, false, "", "", "", "", "", "50", "radians", "200", "Quartic with triple root at 1, (Hint: start at 1.5)"]; example[6] = ["x**5 - 4*x**4 + 6 * x**3 - 4 * x**2 + x ","5 * x**4 - 16 * x**3 + 18 *x**2 - 8 * x + 1","20 * x**3 - 48 * x**2 + 36 * x - 8","","","", 0, "0", "1", "0.5", "1.5", "-0.05", "0.1", false, false, "", "", "", "", "", "50", "radians", "200", "Quintic with quadruple root a 1. (Hint: start at 1.5)"]; example[7] = ["sin(x)**4","4*sin(x)**3 * cos(x)", "12 * sin(x)**2 * cos(x)**2 - 4 * sin(x)**4","","","", 0, "0", "1", "2", "4", "-0.05", "0.1", false, false, "", "", "", "", "", "50", "radians", "200", "sin(x)4 has quad roots at 0, ±π... (Hint: start at 3)"]; example[8] = ["x ** 4 - 3*x**3 + 3 * x**2 - x", "4 * x ** 3 - 9 * x**2 + 6 * x - 1", "12 * x**2 - 18 * x + 6","","","", 0, "0", "1", "-0.1", "2", "-.1", ".1", false, false, "", "", "", "", "", "50", "radians", "200", "f(x) = x(x-1)3, solutions 0, 1, 1, 1, (Hint: start at 1.5)"]; } // setupExamples function displayExamples() { let heading = createElement("h3","Examples"); heading.parent(p5left); let radio; for (let i = 0; i < example.length; i++) { // s2a = createSpan("
1"); // s2a.parent(p5left); radio = HTMLforRadioButton("examplesResult", '' + example[i][EXAMPLE_NAME] +'', "getRadioValueExamples()", false, i); examplesRadio[i] = displayHTMLElements(radio, p5left, "block"); } } // displayExamples function useExample(ex) { iterateBtn.hide(); for (let i = 0; i < NUM_F_INPUT; i++) { fInput[i].value(myReplaceAll(example[ex][i], "^$", ",")); } tMinInput.value(example[ex][7]); tMaxInput.value(example[ex][8]); xMinInput.value(example[ex][9]); xMaxInput.value(example[ex][10]); yMinInput.value(example[ex][11]); yMaxInput.value(example[ex][12]); equalSpace.checked(example[ex][13]); allowLoop.checked(example[ex][14]); for (let i = 0; i < 5; i++) { valInp[i].value(example[ex][15 + i]); } sValueSlider.value(example[ex][20]); //// angleRadio.selected(example[ex][21]); // doesn't work radio problems ?? if (example[ex][21] == "radians") { radiansRadio.checked = true; } else { degreesRadio.checked = true; } getRadioValueAngle() numPtsInput.value(example[ex][22]); // the name is not displayed anywhere except in example list if (example[ex][6] == 1) { cubicSolve(); } else { inputF(); valueChanged(); } loop(); } // useExample function getRadioValueAngle() { modeAngle = getRadioValue("angles"); if ( modeAngle == "RADIANS") { angleMode(RADIANS); modeAngle = RADIANS; } else { angleMode(DEGREES); modeAngle = DEGREES; } valueChanged(); inputF(); } // getRadioValueAngle function getRadioValueExamples() { let ex = int(getRadioValue("examplesResult")); useExample(ex); } // getRadioValueExamples // the following my.... functions are intended to all working both the // normal canvas and an enlarged canvas. Simple setting functions // like myFill always set both canvases. The more complicated ones like // myText always effect the normal small canvas but only work on the enlarged // canvas if enlarge is true. function myText(msg, xLoc, yLoc) { can1.text(msg, xLoc, yLoc); if (enlarge) { can2.text(msg, xLoc * enlargeRatio, yLoc * enlargeRatio); } } // myText function myFill(color) { can1.fill(color); can2.fill(color); } // myFill function myStroke(color) { can1.stroke(color); can2.stroke(color); } // myStroke function myBackground(color) { can1.background(color); can2.background(color); } // myBackground function myNoStroke(){ can1.noStroke(); can2.noStroke(); } // noStroke function myStrokeWeight(val) { can1.strokeWeight(val); can2.strokeWeight(val); } // myStrokeWeight function myLine(x1, y1, x2, y2) { can1.line(x1, y1, x2, y2); if (enlarge) { can2.line(x1 * enlargeRatio, y1 * enlargeRatio, x2 * enlargeRatio, y2 * enlargeRatio); } } // myLine function myRect(x, y, w, h) { can1.rect(x, y, w, h); if (enlarge) { can2.rect(x * enlargeRatio, y * enlargeRatio, w * enlargeRatio, h * enlargeRatio); } } // myRect function myPoint(x, y) { can1.point(x, y); if (enlarge) { can2.point(x * enlargeRatio, y * enlargeRatio); } } // myPoint // this functions save can2 if enlarge is true, can1 otherwise function mySaveCanvas(name, tag) { if (enlarge) { can2.saveCanvas(name, tag); } else { can1.saveCanvas(name, tag); } } // mySaveCanvas /** * Changes the size of can2 in response to the enlarge slider */ function enlargeChanged() { let newSize = enlargeSlider.value(); can2.resizeCanvas(newSize, newSize); enlargeRatio = newSize/can1.width; enlargeSpan.html("Enlargement ratio (" + (round(10 * enlargeRatio)/10) + ")  "); enlargeSlider.parent(enlargeSpan); enlargeCheckBox.checked(true); inputF() enlarge = true; can2.style("display: block"); spacer.style("display: block"); inputF(); } // enlargeChanged