// James Brink, 6/29/2021
let can1;
let can2;
let mode = 0; // start in linear mode
let s2; // span for showing mode;
let s3h; // span for evaluate
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 n, np1, np2; // n is the degree of the regression curve
let outputPnl;
let outputUsesFunctions = []; // Used to specify which functions will be drawn
let calculateBtn
let askForInput = true;
let testExactBtn;
let testRandomBtn;
let evalBtn;
let inputFile;
let numOutput; // used to display number of values info
let xMaxV = [];
let xMinV = [];
let yMaxV = [];
let yMinV = [];
let yMaxVals;
let yMinVals;
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, cubicRadio, 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 = []; // labels for values
let valInp = []; // inputs box for values
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 QUADRATIC_MODE = 1;
const CUBIC_MODE = 2;
const QUARTIC_MODE = 3;
const DATA_MODE = 4;
const MODE_LAB = ['
Mode: Linear
',
'Mode: Quadratic
',
'Mode: Cubic
',
'Mode: Quartic
',
'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);
// s1b = createSpan("
");
// s1b.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",
''
+ 'Linear ',
"getRadioValueMode()", true, LINEAR_MODE);
linearRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'Quadratic ',
"getRadioValueMode()", false, QUADRATIC_MODE);
quadRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'Cubic ',
"getRadioValueMode()", false, CUBIC_MODE);
cubicRadio = displayHTMLElements(radio, p5right, "inline-block");
s2b = createSpan("
");
s2b.parent(p5right);
radio = HTMLforRadioButton("modeResult",
' '
+ 'Quartic ',
"getRadioValueMode()", false, QUARTIC_MODE);
quarRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
' '
+ 'Data only scatter chart',
"getRadioValueMode()", false, DATA_MODE);
otherRadio = displayHTMLElements(radio, p5right, "inline-block");
// 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);
inputFile.parent(p5right);
let s3f = createSpan("
");
s3f.parent(p5right);
let calculateBtn = createButton("Fit the curve");
calculateBtn.parent(p5right);
calculateBtn.mousePressed(calculate);
let s3g = createSpan(" ");
s3g.parent(p5right);
evalBtn = createButton("Evaluate");
evalBtn.parent(p5right);
evalBtn.mousePressed(evaluate);
s3h = createSpan("");
s3h.parent(p5right);
// create a help link
let s4 = createSpan('
'
+ 'Help with the polyRegression 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("1, 2, 3"); //********
fInput[1].value("5, 3, 1"); //********
fInput[2].value("a * x + b"); //********
// xMinInput.value(-2);//********
// xMaxInput.value(3);//********
fInput[3].value("w 2; 14.5; Linear");
// 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;
}
// *** 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
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
if ( i == 2 && mode == DATA_MODE) {
continue;
}
// 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") {
print("p i:" + i);
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() {
s3h.html("");
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";// used for plotting values
yVals[1][0] = numPts;
for (let j = 0; j < numPts; j++) {
let xxx = eval(stxs[j]);
let y = eval(stys[j]);
xVals[1][j + 1] = xxx; // for plotting original points
yVals[1][j + 1] = y;
xc[j] = xxx;
yc[j] = y;
xMinV[1] = min(xxx, xMinV[1]);
xMaxV[1] = max(xxx, xMaxV[1]);
yMinV[1] = min(y, yMinV[1]);
yMaxV[1] = max(y, yMaxV[1]);
}
} catch(er) {
errorMsg = "Error in point specfication. Namely " + er;
functionError = true;
}
} // getXAndYs
function calculateLeastSquares() {
if (mode == DATA_MODE) {return;}
n = mode + 1; // the degree of the polynomial
np1 = n + 1; // the number of rows in matrix
np2 = n + 2; // the number of columns in the extended matrix
let matrix = []; // gets the coefficients (n+1 by n+2)
let mat = []; // used to solve system, contains solution after solving
for (let i = 0; i < np1; i++) {
matrix[i] = [];
matrix[i][n+1] = 0;
mat[i] = [];
}
let xPower;
let xCurrent;
let yCurrent;
let numUsed; // count of valid data points
let numData = xc.length; // count of all data points
numUsed = 0;
for (let i = 0; i <= n; i++) { // for each row of coefficient matrix
for (let j = 0; j <= n; j++) { // for each column
if (i == 0 || j == n) { // calculate sums for the first time
matrix[i][j] = 0;
for (let k = 0; k < numData; k++) { // for each data point
xCurrent = xc[k];
yCurrent = yc[k];
if (isFinite(xCurrent + yCurrent)) { // ignore "bad" data
xPower = xCurrent ** (i + j);
matrix[i][j] += xPower;
}
if (i == 0) { // calculate right hand side using y values
matrix[j][n+1] += xPower * yCurrent;
}
if (i + j == 0) {
numUsed++;
}
}
} else { // these values have already been calculated
matrix[i][j] = matrix[i-1][j+1];
}
}
}
copyMatrix(matrix, mat, np1, 0, np2); // mat is used in calculation
solveLinearSystem(mat, np1, 1);
for (i = 0; i < np1; i++) {
theValues[i] = mat[n - i][np1];
}
a = theValues[0];
valInp[0].value(a);
b = theValues[1];
valInp[1].value(b);
if (n >= 2) {c = theValues[2]; valInp[2].value(c);}
if (n >= 3) {d = theValues[3]; valInp[3].value(d);}
if (n >= 4) {e = theValues[4]; valInp[4].value(e);}
// determine number display output in case any data points were unusable
if (numUsed == 0) {
numOutput = 'Cannot determine regression'
+ ' curve because no data points'
+ ' are usable.';
} else if (numUsed < np1) {
numOutput = 'Cannot determine regression'
+ ' curve because only ' + numUsed
+ ' data point(s) could be used. '
+ '
At least ' + np1 + ' are needed.';
} else { // have a solution
if (numUsed == numData) {
numOutput = "Number of data points: " + numUsed;
} else {
numOutput = "Used " + numUsed + " of the " + numData + " points";
}
// calculate Rsquared https://onlinestatbook.com/2/regression/accuracy.html
let yHat; // estimated, predicted or fitted y value
let yBar = matrix[0][np1]/numUsed; // average y value
let syHatSq = 0; // sum(y - yHat)**2 = sum of errors squared
let syBarSq = 0; // sum(y - yBar)**2 = total sum of squares
for (let i = 0; i < numUsed; i++) {
if (isFinite(xc[i] + yc[i])) {
x = xc[i];
y = yc[i]; // needed for eval
yHat = eval(fInput[2].value()); // estimated or fitted value
syHatSq += (y - yHat)**2;
syBarSq += (y - yBar)**2;
}
}
let rSquared = 1 - syHatSq / syBarSq;
let sigmaSquared = syHatSq/(numUsed - n - 1);
///////let adjustedRSquared = 1 - (1 - rSquared) * numUsed/(numUsed - n - 1);
let adjustedRSquared = 1 - (1- rSquared) * (numUsed - 1)/(numUsed - n - 1)
numOutput += "
R2 = " + myRoundOff(rSquared, 3)
+ " σ2 = " + myRoundOff(sigmaSquared, 3)
+ "
Adjusted R squared = " + myRoundOff(adjustedRSquared, 3);
numOutput += "
Regression curve: ";
for (i = 0; i < n - 1; i++) {
numOutput += myRoundOff(theValues[i],3) + "x" + (n - i)
+ " + ";
}
numOutput += myRoundOff(theValues[n-1], 3) + "x + "
+ myRoundOff(theValues[n], 3) + "";
valueChanged(); // update variables
// alert(numOutput);
}
// showOutputPnl();
loop();
} // calculateLeastSquares
function evaluate() {
// Uses a dialog box to display the y, calculated y and errors
// at each of the x values.
let s = "";
s += "x | y | Estimated y | Error |
";
let regCurve = fInput[2].value();
for (let i = 0; i < xc.length; i++) {
x = xc[i];
y = yc[i];
let yp = eval(regCurve); // predicted y
s += "" + myRoundOff(x, 3) + " | " + myRoundOff(y, 3)
+ " | " + myRoundOff(yp, 3)
+ " | " + myRoundOff(y - yp, 3) + " | ";
}
s += "
";
s3h.html(s);
} // evaluate
/*
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;
if (n >= 2) { c = 4; }
if (n >= 3) { d = 5; }
if (n >= 4) { e = 6; }
for (let i = 0; i < stxs.length; i++) {
let x = stxs[i];
let yValue = eval(fInput[2].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);
calculate();
} // processFile
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)
// 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 myRoundOffFactor = 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) {
myRoundOffFactor = 10000; // 4 decimal places
} else if (spacing < .1) {
myRoundOffFactor = 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(myRoundOffFactor * xxx)/myRoundOffFactor, px, xAxisLoc + 14);
}
}
} // xHashMarks
// draw y hashMarks and label them
function yHashMarks() {
let currentHeight = maxiY - miniY;
let logCW = floor(Math.log10(currentHeight));
let myRoundOffFactor = 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) {
myRoundOffFactor = 10000;
} else if (spacing < .1) {
myRoundOffFactor = 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(myRoundOffFactor * yyy)/myRoundOffFactor, yAxisLoc + 20,
py + 5); // put y value right of axis
} else {
myText(round(myRoundOffFactor * yyy)/myRoundOffFactor, 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 (n < 4) {
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value()); // eval of blank returns NaN
} else {
e = 0;
}
theValues[4] = e;
if (n < 3) {
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value()); // eval of blank returns NaN
} else {
d = 0;
}
theValues[3] = d;
if (n < 2) {
if (trim(valInp[2].value()) != "") {
c = eval(valInp[2].value());
} else {
c = 0;
}
theValues[2] = c;
}
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("polyRegression", "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 += "" + "" ; // 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] = "";
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]: min t -- NOT USED
// [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^$6", "4^$3^$2^$1^$1^$0", "a * x + b",
"","","w 2^$ 3^$Linear least squares",
0,
"0", "1", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"Fit Linear least squares"];
example[1] = ["0^$ 1^$ 2^$ 3^$ 4^$ 5",
"8^$3^$ 0^$ -1^$ 0^$ 3",
"a*x*x + b*x + c","","",
"w 2; 8; Quadratic",
1,
"", "", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"Fit Quadratic least squares"];
example[2] = ["1^$2^$3^$4^$5^$6",
"1.648^$2.469^$ 3.081^$ 3.280^$ 3.520^$ 3.427",
"a*x**3 + b*x**2 + c*x + d","","",
"w 2.5; 3.5; Cubic Least Squares",
2,
"", "", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"Fit cubic least squares"];
example[3] = ["0^$1^$2^$3^$4^$5",
"2.144^$ 2.963^$ 3.309^$ 3.664^$ 3.879^$ 4.477",
"a*x**4 + b*x**3 + c*x**2 + d*x + e","","",
"w 4; 3; Quartic least squares",
3,
"", "", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"Fit quartic least squares"];
example[4] = ["1^$1^$2^$2^$2^$2^$3^$3^$3^$3^$3^$3^$3^$3^$3^$2^$3^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$4^$5^$4^$4^$4^$5^$2^$2^$4^$3^$4^$3^$4^$4^$4^$4^$3^$3^$3^$4^$4^$3^$4^$5^$4^$5^$4^$4^$3^$5^$5^$4^$5^$3^$4^$3^$4^$6^$4^$5^$4",
"67^$62^$109^$83^$91^$88^$137^$131^$122^$122^$118^$115^$131^$143^$142^$123^$122^$138^$135^$146^$146^$145^$145^$144^$140^$150^$152^$157^$155^$153^$154^$158^$162^$161^$162^$165^$171^$171^$162^$169^$167^$188^$100^$109^$150^$140^$170^$150^$140^$140^$150^$150^$140^$150^$150^$150^$160^$140^$150^$170^$150^$150^$150^$150^$150^$150^$160^$140^$160^$130^$160^$130^$170^$170^$160^$180^$160",
"a*x**2 + b*x + c",
"w 4; 80; https://online.stat.psu.edu/stat501/lesson/9/9.8",
"w 3.5; 60; https://online.stat.psu.edu/onlinecourses/sites/stat501/files/data/bluegills.txt","w 3.5; 70; data from",
1,
"", "", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"Length dependent on age of Bluegills with Real data"];
/*
example[4] = ["1^$ 2^$ 3","5^$ 3^$ 1","a * x + b","w 2; 14.5; Linear","","",
0,
"", "", "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
"???? Example name ????"];
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]+ '',
"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 == DATA_MODE) {// probably not used
dataRadio.checked = true;
} else if (mode == QUADRATIC_MODE) {
quadRadio.checked = true;
} else if (mode == CUBIC_MODE) {
cubicRadio.checked = true;
} else if (mode == QUARTIC_MODE) {
otherRadio.checked = true;
} else {
linearRadio.checked = true;
}
updateMode();
// 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();
updateMode();
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]);
switch (mode) {
case LINEAR_MODE:
fInput[2].value("a*x + b");
break;
case QUADRATIC_MODE:
fInput[2].value("a*x**2 + b*x + c");
break;
case CUBIC_MODE:
fInput[2].value("a*x**3 + b*x**2 + c*x + d");
break;
case QUARTIC_MODE:
fInput[2].value("a*x**4 + b*x**3 + c*x**2 + d*x + e");
break;
}
calculate();
// 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 myRoundOff(x, n, spaces) {
// rounds of x to n decimal places.
// x: value to be rounded
// y: number of decimal places desired
// spaces: (optional) length of return string
// returns: if spaces is provided, string containing rounded x of length
// spaces. (unneeded decimal trailing 0's are ignored.)
// otherwise rounded x as a number.
let p = 10 ** n;
let rounded = round(x * p) / p;
if (spaces > 0) {
let s = rounded + " ";
s = s.substring(0, spaces);
return s;
}
return rounded;
} // myRoundOff
// 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