// James Brink, 6/15/2021 let can1; let can2; let mode = 0; // start in linear mode let s2; // span for showing mode; let f = ["", "", "", ""]; //, "", "", ""]; let fDefined = []; // the function i is definedcPnts 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 = []; // for plotted values let yVals = []; // for plotted values let xc = []; // the converted y value used for least squares let yc = []; // the converted y value used for least squares] let minYo; // minimum of yVals for original curve let maxYo; // maximum of yVals for original curve let minYc; // minimum of yc for nonlinear mo des let maxYc; // maximum of yc for nonlinear modes // The following are just to provide a default if the user clicks Other and // then cancels before having any formulas. let linearizationFormula = "Math.log(y)"; // formula to linearize data let curveFormula = "exp(a * x + b)"; // formula to change linear data to curve data let linearizationDisplay; let curveDisplay; let outputPnl; let outputUsesFunctions = []; // Used to specify which functions will be drawn outputUsesFunctions[5] = true; // always use f5 let desiredOutput; let showCalculatedBtn let askForInput = true; let testExactBtn; let testRandomBtn; let inputFile; let numOutput; // used to display number of values info let xMaxV = []; let xMinV = []; let yMaxV = []; let yMinV = []; let yMaxVals; let yMinVals; /////// resultArea; let xMinLabel, xMaxLabel, yMinLabel, yMaxLabel; let xMinInput, xMaxInput, yMinInput, yMaxInput; let tMinLabel, tMinInput, tMaxLabel, tMaxInput; let hashSpacingX, hashSpacingY; let startingXInp; let startingX2Inp; 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 linearRadio, logRadio, paramRadio; //// used for javascript buttons 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; // left panel let p5right; // right panel 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 errorMsg = ""; // error message shown in the canvas let functionError = false; let minMaxError = false; let valueError = false; let numberEvalsError = false; let startingXError = false; let dataError = false; const locOffset = 19; const ERROR = "ERROR"; const REALY_BIG = 1.2345E100; const NUM_F_INPUT = colors.length; // labels for mode // FUNCTION_LABn format = [Label for mode 0, label for mode 1, label for mode2] const FUNCTION_LAB0 = ['
x values']; const FUNCTION_LAB1 = ['
' + "y values"]; const FUNCTION_LAB2 = ['
' + "Fit to "]; const FUNCTION_LAB3 = ['
' + " f3 "]; const FUNCTION_LAB4 = ['
' + "f4 "]; const FUNCTION_LAB5 = ['
' + "f5 "]; 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", "SaddleBrown", "Indigo", "DarkGreen", "Navy"]; // for modes and examples const LINEAR_MODE = 0; const EXPONENTIAL_MODE = 1; const LOG_MODE = 2; const OTHER_MODE = 3; const DATA_MODE = 4; const MODE_LAB = ['

Mode: Linear Least Squares

', '

Mode: Exp y

', '

Mode: Log y

', '

Mode: Other

', '

Mode: Data only scatter chart

' ]; // for enlargement let enlarge = false; let enlargeRatio = 1.5; let enlargeChk; let enlargeSpan; // label for enlargeSlider let enlargeSlider; // Examples: let example = []; let examplesRadio = [] const EXAMPLE_NAME = 23; // location of the name let noDataRadio = []; function setup() { p5left = document.getElementById("p5-left"); // used with radio p5right = document.getElementById("p5-right"); can1 = createCanvasClass(430, 430); // must be square can1.parent(p5left); can1.doubleClicked(dblClick); can1.mouseClicked(clearErrorMsg); can2 = createCanvasClass(430 * enlargeRatio, 430 * enlargeRatio); big = document.getElementById("big"); can2.parent(big); can2.mouseClicked(clearErrorMsg); can2.style("display: none"); spacer = createDiv(" "); // space between canvases spacer.parent(big); spacer.style("display: none"); myTextAlign("center"); let radio; numPts = Math.floor(1.1 * numDiv); // 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); s2a = createSpan("
a: slope, b: y intercept, c: R2"); s2a.parent(p5left); // start right hand info display // first the labels and radio buttons for the mode theValues[5] = s; s2 = createSpan(MODE_LAB[mode]); s2.parent(p5right); s2a = createSpan("
"); s2a.parent(p5right); radio = HTMLforRadioButton("modeResult", '' + 'Data only scatter chart ', "getRadioValueMode()", true, DATA_MODE); dataRadio = displayHTMLElements(radio, p5right, "inline-block"); s2aa = createSpan("
"); s2aa.parent(p5right); radio = HTMLforRadioButton("modeResult", '' + 'Linear   ', "getRadioValueMode()", true, LINEAR_MODE); linearRadio = displayHTMLElements(radio, p5right, "inline-block"); radio = HTMLforRadioButton("modeResult", '' + 'exp(ax + b) ', "getRadioValueMode()", false, EXPONENTIAL_MODE); expRadio = displayHTMLElements(radio, p5right, "inline-block"); radio = HTMLforRadioButton("modeResult", '' + 'ln(ax + b) ', "getRadioValueMode()", false, LOG_MODE); logRadio = displayHTMLElements(radio, p5right, "inline-block"); s2b = createSpan("
"); s2b.parent(p5right); radio = HTMLforRadioButton("modeResult", ' ' + 'Other ', "getRadioValueMode()", false, OTHER_MODE); otherRadio = displayHTMLElements(radio, p5right, "inline-block"); s2c = createSpan("   Curve formula: "); s2c.parent(p5right); curveDisplay = createInput("", curveFormula); curveDisplay.parent(p5right); curveDisplay.mousePressed(setCurveFunction); s2d = createSpan("
       Linearization formula: "); s2d.parent(p5right); linearizationDisplay = createInput("", linearizationFormula); linearizationDisplay.parent(p5right); linearizationDisplay.mousePressed(setLinearizationFunction); // display function labels and inputs let s3 = createSpan("
To change any data, function, value, min or 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); fInput[i].changed(inputF); fInput[i].size(260); } fLabel[2].value("a * x + b"); let s3e = createSpan("
File data "); s3e.parent(p5right); inputFile = createFileInput(processFile, "abxsdafasdf"); inputFile.parent(p5right); ////// let s3f = createSpan("
"); ////// s3f.parent(p5right); ////// let chartBtn = createButton("Fit the curve"); ////// chartBtn.parent(p5right); ////// chartBtn.mousePressed(calculate); // create a help link let s4 = createSpan('

' + 'Help with the least squares app and its functions.
'); s4.parent(p5right); // Special output panel outputPnl = createDiv(); outputPnl.parent(p5right); outputPnl.style("display: none"); outputPnl.html("
Select the desired output
"); radio = HTMLforRadioButton("outputDesired", 'Original curve 
', "getRadioValueOutput()", true, 0); originalRadio = displayHTMLElements(radio, outputPnl, "inline-block"); radio = HTMLforRadioButton("outputDesired", 'Regression Line  ', "getRadioValueOutput()", false, 1); regLineRadio = displayHTMLElements(radio, outputPnl, "inline-block"); radio = HTMLforRadioButton("outputDesired", 'Both   ', "getRadioValueOutput()", false, 2); bothRadio = displayHTMLElements(radio, outputPnl, "inline-block"); s4a = createSpan("
Test with  "); s4a.parent(outputPnl); testExactBtn = createButton("Exact"); testExactBtn.parent(outputPnl); testExactBtn.mousePressed(exactTest); testRandomBtn = createButton("Random"); testRandomBtn.parent(outputPnl); testRandomBtn.mousePressed(randomTest); // create input for max and mins for x and y let instLabel = createSpan("
The minimum and maximum x and y values will" + " be
calculated automatically if you leave them blank."); instLabel.parent(p5right); xMinLabel = createSpan("
Minimum x  "); xMinLabel.parent(p5right); xMinInput = createInput("", "text"); xMinInput.parent(p5right); xMinInput.changed(checkMinMax); xMaxLabel = createSpan("
Maximum x "); xMaxLabel.parent(p5right); xMaxInput = createInput("", "text"); xMaxInput.parent(p5right); xMaxInput.changed(checkMinMax); 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 s4b = createSpan("
Doubleclick to set center of graph.
"); s4b.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, Show enlarged canvas // and Radians/degrees inputs equalSpace = createCheckbox("Equal spacing ", false); equalSpace.parent(p5right); equalSpace.changed(equalSpaceChanged); let s5 = createSpan("
"); allowLoop = createCheckbox("Allow motion", false); allowLoop.parent(p5right); allowLoop.changed(allowLoopChanged); s5.parent(p5right); enlargeChk = createCheckbox("Show enlarged canvas", false); enlargeChk.parent(p5right); enlargeChk.changed(enlargeChanged); 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"); let s5b = createSpan("
"); s5b.parent(p5right); // 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("0, 1, 2, 2.6, 3, 4, 5"); //******** fInput[1].value("10, 11.2, 11, 12.9, 14, 13.5, 15"); //******** fInput[2].value("a * x + b"); //******** // xMinInput.value(-2);//******** // xMaxInput.value(3);//******** fInput[3].value("w 2; 14.5; Linear Least Squares"); // some additional initialization inputF(); valueChanged(); checkMinMax(); frameRate(20); // finally, initialize the examples setupExamples(); displayExamples(); calculate(); } // 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 { myText("You need to define a function.", can1.width/2, can1.height/2); } noLoop(); return; } if (errorMsg != "") { displayError(); return; } desiredOutput = int(getRadioValue("outputDesired"));/////////???????? selectOutputDrawn(); // determine which functions will be drawn // *** for each function determine points to be plotted *** for (let i = 0; i < NUM_F_INPUT; i++) { //for each function except 0 & 1 // initials values, mins, and maxs if (i != 1) { // i = 1 handled by getXAndYs xVals[i] = []; yVals[i] = []; resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV); } if (i < 2) {continue;} // i = 0 - not used, i = 1 handled by getXAndYs // determine points to be ploted if (!(fDefined[i] && outputUsesFunctions[i])) {continue;} specialDefined = true; 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 and y xMaxVals = max(xMaxV); xMinVals = min(xMinV); for (i = 1; i < numF; i++) if (!outputUsesFunctions[i]) { yMinV[i] = Infinity; // curve i not drawn in this case yMaxV[i] = -Infinity; } if (mode != LINEAR_MODE && desiredOutput == 1) { yMinV[5] = Infinity; // may not label used if out of bounds yMaxV[5] = -Infinity; } 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."; 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 = 1; i < NUM_F_INPUT; i++) { //for each function // first check for vertical lines if (!(fDefined[i] && outputUsesFunctions[i])) { continue; } 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 curcular 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(6); 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++) { // 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); spacer = createDiv(" "); // space between canvases spacer.parent(big); spacer.style("display: none"); if (loopCnt > 5 && !allowLoop.checked()) { noLoop(); loopCnt = 0; } else { ++loopCnt; } } // draw function calculate() { getXAndYs(); if (mode != DATA_MODE) { calculateLeastSquares(); } else { fInput[3].value(""); fInput[4].value(""); } } // calculate function getXAndYs() { // the x and y values are converted into p style points and stored in // xVals[1] and yVals[1]. xVals[0] and yVals[0] are not used. They are also // stored in xs and ys which are used to calculate sums try { let er; // for try/catch error variable specialDefined = true; inputF(); // make sure that the x and y values are up to date xVals[1] = []; yVals[1] = []; xc = []; yc = []; resetMinMax(1, xMinV,yMinV, xMaxV, yMaxV); let stx = f[0].substring(0, 10000); let stxs = stx.split(","); let sty = f[1].substring(0, 10000); let stys = sty.split(","); // check to see if there is sufficient info let numPts = min(stxs.length,stys.length) if (numPts < 2) { setErrorMsg('Not enough x, y points have been specified.'); loop(); return; } xVals[1][0] = "p"; yVals[1][0] = numPts; let cPnts = "p "; // used for plotting converted values let ycVals = []; // used for plotting converted values for (let j = 0; j < numPts; j++) { xxx = eval(stxs[j]); y = eval(stys[j]); xVals[1][j + 1] = xxx; // for plotting original points yVals[1][j + 1] = y; xc[j] = xxx; switch (mode) { case LINEAR_MODE: yc[j] = y; // used in calculations break; case EXPONENTIAL_MODE: yc[j] = Math.log(y); // linearization formula cPnts += xxx + "; " + roundOff(yc[j], 2) + ";"; break; case LOG_MODE: yc[j] = exp(y); cPnts += xxx + "; " + roundOff(yc[j], 2) + ";"; break; case OTHER_MODE: // other mode yc[j] = eval(linearizationFormula); cPnts += xxx + "; " + roundOff(yc[j], 2) + ";"; break; case DATA_MODE: // only show data points yc[j] = y; break; } xMinV[1] = min(xxx, xMinV[1]); xMaxV[1] = max(xxx, xMaxV[1]); yMinV[1] = min(y, yMinV[1]); yMaxV[1] = max(y, yMaxV[1]); } if (mode == DATA_MODE) { fInput[3].value(""); } else if (mode != LINEAR_MODE) { fInput[3].value(cPnts); // store converted points string } } catch(er) { errorMsg = "Error in point specfication. Namely " + er; functionError = true; } } // getXAndYs function calculateLeastSquares() { let n = xc.length; //number of data points let sx = 0; // sum let sy = 0; let sxx = 0; let sxy = 0; let numData = 0; for (let i = 0; i < n; i++) { if (isFinite(xc[i] + yc[i])) { // ignore "bad" data sx += xc[i]; sy += yc[i]; sxx += xc[i] ** 2; sxy += xc[i] * yc[i]; numData++; } } a = (numData * sxy - sx * sy) / (numData * sxx - sx**2); // slope b = (sy - a * sx)/numData; // intercept // determine number display output if (numData == n) { numOutput = "Number of data points: " + n + ""; } else if (numData == 1) { numOutput = "Only 1 data point could be used"; } else if (numData == 0) { numOutput = "No data points are usable"; } else { numOutput = "Used " + numData + " of the " + n + " points"; } if (numData <= 1) { errorMsg = "Not enough data points could be used."; } else if (!isFinite(a + b)) { errorMsg = "One or more x or y value is invalid." if (d != 0 || e != 0) { errorMsg += "\nOr there may be a problem with the value of d or e."; } errorMsg += "\nPlease correct it."; dataError = true; } else { errorMsg = ""; // clear previous error, if any dataError = false; } valInp[0].value(a); valInp[1].value(b); // calculate Rsquared https://onlinestatbook.com/2/regression/accuracy.html let yHat; // estimated y value let yBar = sy/n; // average y value let syHatSq = 0; // sum(yHat - yBar)**2 let syBarSq = 0; // sum(y - yBar)**2 for (let i = 0; i < n; i++) { if (isFinite(xc[i] + yc[i])) { yHat = a * xc[i] + b; syHatSq += (yHat - yBar)**2; syBarSq += (yc[i] - yBar)**2; } } let rSquared = syHatSq / syBarSq; valInp[2].value(rSquared); // store R squared in c valueChanged(); // update variables minYo = yMinV[1]; maxYo = yMaxV[1]; if (mode == LINEAR_MODE) { fInput[5].value("w 2; 14.2; Slope: " + roundOff(a,2) + " Intercept: " + roundOff(b,2)); } else if (mode == EXPONENTIAL_MODE) { fInput[4].value("exp(a * x + b)"); // fInput[4].value(curveFormula); minYc = floor(min(xc)); maxYc = ceil(max(xc)); } else if (mode == LOG_MODE) { fInput[4].value("Math.log(a * x + b)") minYc = floor(min(xc)); maxYc = ceil(max(xc)); } else if (mode == OTHER_MODE) { fInput[4].value(curveFormula); minYc = floor(min(xc)); maxYc = ceil(max(xc)); } showOutputPnl(); loop(); } // calculateLeastSquares function showOutputPnl() { if (mode != LINEAR_MODE) { outputPnl.style("display: block"); } else { outputPnl.style("display: none"); } } // showOutputPnl function selectOutputDrawn() { // functions 0 and 1 are never drawn // as they only contain data. // function 5 is always drawn // If linear regression, the regression line is always drawn // f0: x data - never drawn // f1: data points stored in xVals and yVals // f2: linear regression line // f3: transformed (calculated) points if nonlinear regression // f4: best fit curve for original data if nonlinear regression // f5: Optional for users outputUsesFunctions[1] = desiredOutput == 0 || desiredOutput == 2; outputUsesFunctions[2] = desiredOutput == 1 || desiredOutput == 2 || mode == 0; outputUsesFunctions[3] = outputUsesFunctions[2]; outputUsesFunctions[4] = outputUsesFunctions[1] || mode == 0; calculate(); calculate(); // sometimes it takes two calculates to show new curves /////// alert(outputUsesFunctions) } // selectOutputDrawn function exactTest() { testYValues(false); } // exactTest function randomTest() { testYValues(true); } // randomTest function testYValues(randomize) { let stx = f[0].substring(0, 10000); let stxs = stx.split(","); let yString = ""; a = 2; b = 3; for (let i = 0; i < stxs.length; i++) { let x = stxs[i]; let yValue = eval(fInput[4].value()); if (randomize) { yValue = yValue * random(90, 110) / 100; } yString += yValue + ","; } yString = yString.substring(0, yString.length-1); // delete final "," fInput[1].value(yString); calculate(); } // testYValues function processFile(file) { let lines = file.data.split("\n"); let xString = ""; let yString = ""; for (let i = 0; i < lines.length - 1; i++) { let xy = lines[i].split(","); let xx = eval(xy[0]); let yy = eval(xy[1]); xString += xx + ","; yString += yy + ","; } xString = xString.substring(0, xString.length-1); yString = yString.substring(0, yString.length-1); fInput[0].value(xString); fInput[1].value(yString); } // processFile function setCurveFunction() { if (mode == OTHER_MODE) { let newFormula = prompt ("Enter the new curve formula()"); if (newFormula != null) { curveFormula = newFormula; curveDisplay.value(newFormula); } } } // setCurveFunction function setLinearizationFunction() { if (mode == OTHER_MODE) { let newFormula = prompt ("Enter the new linearization formula()"); if (newFormula != null) { linearizationFormula = newFormula; linearizationDisplay.value(newFormula); } } } // setLinearizationFunction function zoomIn() { // zooms out with the ratio of 5. CexMinV[0]nters 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 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; } } if (num >=3) { if (funPPP == "") { 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 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) { 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 // 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; } 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) // 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 between two points. // 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) { 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++) { 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] == "") { 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 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) { 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++) { 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) { 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; useColor[i] = colors[i]; // 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; } 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 { // check x min/max minX = xMinInput.value(); if (minX == ""){ minX = xMinV[1]; // calculated by getXAndYs } else if (minX != "") { minX = eval(minX); } maxX = xMaxInput.value() ; if (maxX == "") { maxX = xMaxV[1]; // calculated by getXAndYs } 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 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 valueChanged() { // called when a value is changed if (valueError) { errorMsg = ""; valueError = false; } try { valInp[5].value(sValueSlider.value()); s = 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) + "
" + numOutput); } 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 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(); } // save the canvas as an image file function saveImage() { mySaveCanvas("leastSquares", "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 += '"' + linearizationFormula + '", "",' ; // linearization formula, t not used 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 [].\n' + 'or\n' + 'Click "Read graph data" and paste the above into the dialog input\n' + 'box changing the example name as appropriate.'; 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] = linearizationFormula; 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"); displayExamples(true); useExample(ex); } // saveExample function setupExamples() { // Examples must be numbered consecutively starting a 0 // the format of the example[i] vector: // [0] .. [5]: functions f0 .. f5 (strings) // f0 x values // f1 y values // f2 a * x + b // f4 must be curve formula for modes 1-3 // [6]: the mode - 0: ax + b, 1: exp(ax + b), 2: log(ax+b) // 3: other // [7]: linearization formula // [8]: max t (number or string) -- NOT USED // [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 = 21 ) example[0] = ["1^$2^$3^$4^$5", "4.1^$3.1^$1.9^$0.8^$.1", "a * x + b", "","","", 0, "0", "1", "", "", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "Linear least squares"]; example[1] = ["0^$ 1^$ 2^$ 3^$ 4^$ 5", ".2402^$ 1.576^$ 14.12^$ 79.76^$ 587.9^$3032", "a * x + b","","exp(a * x + b)", "w 2; 3500; log y Least Squares", 1, "Math.log(y)", "", "", "", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "Fit exp(a * x + b) least squares"]; example[2] = ["1^$2^$3^$4^$5^$6", "1.648^$2.469^$ 3.081^$ 3.280^$ 3.520^$ 3.427", "a * x + b","","Math.log(a * x + b)", "w 2.5; 3.5; log y Least Squares", 2, "exp(y)", "", "", "", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "Fit log(ax + b) least squares"]; example[3] = ["0^$1^$2^$3^$4^$5", "2.144^$ 2.963^$ 3.309^$ 3.664^$ 3.879^$ 4.477", "a * x + b","","sqrt(a * x + b)", "w 4; 3; sqrt y Least Squares", 3, "y * y", "", "", "", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "Fit sqrt(x) least squares"]; example[4] = ["-1^$ 0^$ 1^$ 2^$ 5^$ 7^$ 9","1^$9^$25^$49^$169^$289^$441", "a * x + b", "", "(a * x + b) ** 2", "w 7; 40; (ax + b)**2 Least Squares", 3, "sqrt(y)", "","", "", "", "false", false, true, "", "", "", "", "", "50", "degrees", "200", "Fit (ax + b)2 Least Squares"]; example[5] = ["-4^$ -3^$ -2^$ -1.8^$ -1.2^$ -1^$ 0^$ 1^$ 2^$ 3", "-0.41^$-0.69^$-2.03^$-3.31^$3.33^$2.06^$0.64^$0.40^$0.28^$0.21", "a * x + b", " ", "1/(a * x + b)", "w 2; 5; 1/y Least Squares", 3, "1/y", "","", "", "-10", "10", false, false, "", "", "", "", "", "50", "radians", "200", "Fit 1/y Least Squares"]; example[6] = ["-40^$-10^$0^$10^$20^$47", "sin(-39)^$ sin(-12)^$ sin(3)^$ sin(12)^$ sin(17)^$ sin(45)", "a * x + b","","sin(a * x + b)", "w 4; 3; sin y (degress) Least Squares", 3, "asin(y)", "", "", "", "", "", false, false, "", "", "", "", "", "50", "degrees", "200", "Fit sin(x) least squares"]; example[7] = ["-30^$ -20^$ -10^$ 0^$ 10^$ 20^$ 30", "-1.5475^$-0.7451^$-0.2906^$0.0536^$0.42410^$0.9536^$1.8977", "a * x + b", "","tan(a * x + b)","", 3, "atan(y)", "","", "", "", "", false, false, "", "", "", "", "", "50", "degrees", "200", "Fit tan(x) least squares"]; example[8] = ["-4^$ -3^$ -2^$ -1^$ 0^$1^$2", "-2.2983^$-1.8884^$-0.9229^$0.8961^$1.8827^$2.3829^$2.6178", "a * x + b", "","Math.asinh(a * x + b)","", 3, "Math.sinh(y)", "","", "", "", "", false, false, "", "", "", "", "", "50", "radians", "200", "Fit sinh(ax + b)"]; example[9] = ["0^$0.5^$1^$1.5^$2^$2.5^$3^$3.5^$4", "1^$2.4375^$3.75^$4.9375^$6^$6.9375^$7.75^$8.4375^$9", "a * x + b", "","d * (a * x + b) ** 2 + e","", 3, "sqrt(d * (y - e))", "","", "7", "", "", false, false, "", "", "", "-1", "(s - 50)/2", "70", "radians", "200", "Fit d * (ax + b)**2 + e where one can adjust d and e (using slider)"]; example[10] = ["-40^$-10^$0^$10^$20^$47", "2*sin(-39)^$2* sin(-12)^$ 2*sin(3)^$ 2*sin(12)^$2* sin(17)^$ 2*sin(45)", "a * x +2* b", "","e * sin(a * x + b)","", 3, "asin(y/e)", "","", "", "", "", false, false, "", "", "", "", "(s - 50)/10", "70", "degrees", "200", "Fit e * sin(ax +b) where one can adjust amplitude e"]; example[11] = ["-5^$ -3^$ -1^$2^$3^$4^$5","-0.14202055453799337^$-0.30133574628648996^$1.0906458178369098^$0.13640394336057698^$0.11470744484424036^$0.09623739313787079^$0.07963449257917438","a * x + b","p -5; -7.04;-3; -3.32;-1; 0.92;2; 7.33;3; 8.72;4; 10.39;5; 12.56;","d/(a * x + b) + e","", 3, "d/(y - e)", "","", "", "-10", "10", false, false, "1.9700811210362534", "2.8148615604490996", "0.9985206807214737", "1", "(s - 50)/10", "50", "radians", "200", "Fit d/(ax + b) + e where one can adjust d and e (using slider)"]; } // setupExamples function displayExamples(onlyLast) { let start = 0; if (onlyLast) { start = example.length - 1; } else { let heading = createElement("h3","Examples"); heading.parent(p5left); } let radio; for (let i = start; i < example.length; i++) { if (!onlyLast) { radio = HTMLforRadioButton("examplesResult", '' + example[i][EXAMPLE_NAME] + ': include data', "getRadioValueExamples()", false, i); examplesRadio[i] = displayHTMLElements(radio, p5left, "block"); } a10 = createSpan("       "); a10.parent(p5left); radio = HTMLforRadioButton("examplesResult", 'Formulas only: no data', "getRadioValueNoData()", false, i); noDataRadio[i] = displayHTMLElements(radio, p5left, "inline"); } } // displayExamples function useExample(ex, start) { // start: 0: use data in fInput[0] and fInput[1] // 2: don't use data in fInput[0] and fInput[1] for (let i = start; i < NUM_F_INPUT; i++) { fInput[i].value(myReplaceAll(example[ex][i], "^$", ",")); } mode = example[ex][6]; if (mode == EXPONENTIAL_MODE) { expRadio.checked = true; updateMode(); } else if (mode == LOG_MODE) { logRadio.checked = true; updateMode(); } else if (mode == OTHER_MODE) { otherRadio.checked = true; askForInput = false; linearizationFormula = example[ex][7]; curveFormula = example[ex][4]; updateMode(); } else { linearRadio.checked = true; } // 7 & 8 are not used 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 inputF(); valueChanged() calculate(); loop(); } // useExample function getRadioValueAngle() { modeAngle = getRadioValue("angles"); if ( modeAngle == "RADIANS") { angleMode(RADIANS); modeAngle = RADIANS; } else { angleMode(DEGREES); modeAngle = DEGREES; } valueChanged(); inputF(); } // getRadioValueAngle function getRadioValueMode() { mode = int(getRadioValue("modeResult")); fInput[5].value(""); updateMode(); } // getRadioValueMode function getRadioValueExamples() { let ex = int(getRadioValue("examplesResult")); useExample(ex, 0); } // getRadioValueExamples function getRadioValueNoData() { let ex = int(getRadioValue("examplesResult")); useExample(ex, 2); } // getRadioValueNoData function getRadioValueOutput() { desiredOutput = int(getRadioValue("outputDesired")); selectOutputDrawn(); } // getRadioValueOutput function updateMode() { s2.html(MODE_LAB[mode]); if (mode == EXPONENTIAL_MODE) { linearizationFormula = "Math.log(y)"; curveFormula = "exp(a * x + b)"; } else if (mode == LOG_MODE) { lineariztionFormula = "exp(y)"; curveFormula = "Math.log(a * x + b)"; } else if (mode == OTHER_MODE) { if (askForInput) { let formula = prompt("Enter the formula for desired curve (including a and b)"); if (formula != null) { curveFormula = formula; formula = prompt("Enter the formula needed to linearize the curve " + curveFormula + " as a function of y"); if (formula != null) { linearizationFormula = formula; } } } else { askForInput = true; // askForInput should only be false if using // an example so make it true again. } } curveDisplay.value(curveFormula); linearizationDisplay.value(linearizationFormula); fInput[4].value(curveFormula); // label the function box inputs correctly /* for (let i = 0; i < NUM_F_INPUT; i++) { functionLab(i, mode); } */ // update most everything when the mode changes and do a loop valueChanged(); checkMinMax(); inputF(); loop(); } // updateMode function myReplaceAll(st, from, to) { // replaces every occurance of "from" in st with "to" andt let array = st.split(from); let s = array[0]; for (let i = 1; i < array.length; i++) { s += to + array[i]; } return s; } // myReplaceAll function roundOff(x, n) { let p = 10 ** n; return round(x * p) / p; } // roundOff // The following my.... functions are intended to work both the normal // canvas and an enlarged canvas. Simple attribute setting functions // like myFill always set both canvases. This means that they will work // even if the enlarged canvas is not currently activated. 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(); } // myNoStroke function myTextAlign(horizAlign, vertAlign) { // vertAlign is probably required can1.textAlign(horizAlign, vertAlign); can2.textAlign(horizAlign, vertAlign); } // myTextAlign 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 /* myArk is not used in diffEq2 function myArc(x,y, w, h, start, stop, mode) { // mode is probably required can1.arc(x,y, w, h, start, stop, mode); if (enlarge) { can2.arc(x * enlargeRatio, y * enlargeRatio, w * enlargeRatio, h * enlargeRatio, start, stop, mode); } } // myArc */ // 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 697 /** * Changes the size of can2 in response to the enlarge slider */ function enlargeChanged() { if (enlargeChk.checked()) { let newSize = enlargeSlider.value(); can2.resizeCanvas(newSize, newSize); enlargeRatio = newSize/can1.width; enlargeSpan.html("Enlargement ratio (" + (round(10 * enlargeRatio)/10) + ")  "); enlargeSlider.parent(enlargeSpan); enlargeChk.checked(true); inputF() enlarge = true; can2.style("display: block"); spacer.style("display: block"); inputF(); } else { enlarge = false; can2.style("display: none"); spacer.style("display:none"); } } // enlargeChanged