// 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