// grapher
// James Brink, 4/26/2021
let can1;
let can2;
let spacer;
let mode = 0; // start in rectangular modemotion
let s2; // span for showing mode
let f = ["", "", "", "", "", ""];
let fDefined = []; // the function i is defined
let noFDefined;
let specialDefined = false; // true if a special like x=, o=, L, ... defined
let fLabel = [];
let fInput = [];
let x, y, t;
let minX, maxX, minY, maxY, minT, maxT; // used before checking equalSpace
let miniX, maxiX, miniY, maxiY; // in case equalSpace true
let xAxisLoc, yAxisLoc;
let xVals = [];
let yVals = [];
let 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 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 = 2; // multiplier for zoom
let rectRadio, polarRadio, 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;
let p5right;
let big; // enlarged canvas
let valuesLab = ["a", "b", "c", "d", "e", "s"];
let a = 0, b = 0, c = 0, d = 0, e = 0, s;
let theValues = [0, 0, 0, 0, 0, 0];
let valDisplay;
let colors = ["red", "blue", "green", "maroon", "magenta", "navy"];
// Number of colors determines NUM_F_INPUT
let numF = colors.length; // changed to 4??? for parametric ????
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;
const LOC_OFFSET = 19;
const ERROR = "ERROR";
const RECT_MODE = 0;
const POLAR_MODE = 1;
const PARA_MODE = 2;
const REALY_BIG = 1.2345E100;
const NUM_F_INPUT = colors.length;
// labels for mode
const MODE_LAB = ['
Mode: Rectangular coordinates
'
+ 'Use x as independent variable.',
'Mode: Polar coordinates
'
+ 'Use o, t, or x for the independent variable θ.',
'Mode: Parametric quations
'
+ 'Use t for the independent variable.']
// FUNCTION_LABn format = [Label for mode 0, label for mode 1, label for mode2]
const FUNCTION_LAB0 = ['
f0(x) = ',
'
f0(θ) = ',
'
x = f0(t) '];
const FUNCTION_LAB1 = ['
f1(x) = ',
'
f1(θ) = ',
'
y = g0(t)'];
const FUNCTION_LAB2 = ['
f2(x) = ',
'
f2(θ) = ',
'
x = f1(t) '];
const FUNCTION_LAB3 = ['
f3(x) = ',
'
f3(θ) = ',
'
y = g1(t)'];
const FUNCTION_LAB4 = ['
f4(x) = ',
'
f4(θ) = ',
'
x = f2(t) '];
const FUNCTION_LAB5 = ['
f5(x) = ',
'
f5(θ) = ',
'
y = g2(t)'];
const FUNCTION_LAB = [FUNCTION_LAB0, FUNCTION_LAB1, FUNCTION_LAB2,
FUNCTION_LAB3, FUNCTION_LAB4, FUNCTION_LAB5];
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"]; // for examples
// for enlargement
let enlarge = false;
let enlargeRatio = 1.5;
let enlargeCheckBox;
let enlargeSpan; // label for enlargeSlider
let enlargeSlider;
// Examples:
let example = [];
let examplesRadio = []
const EXAMPLE_NAME = 23; // location of the name
function setup() {
p5left = document.getElementById("p5-left"); // used with radio
p5right = document.getElementById("p5-right");
can1 = createCanvasClass(430, 430); // must be square
can1.doubleClicked(dblClick);
can1.parent(p5left);
can1.textAlign("center");
can1.mouseClicked(clearErrorMsg);
can2 = createCanvasClass(430 * enlargeRatio, 430 * enlargeRatio);
big = document.getElementById("big");
can2.parent(big);
can2.textAlign("center");
can2.mouseClicked(clearErrorMsg);
can2.style("display: none");
spacer = createDiv(" ");
spacer.parent(big);
spacer.style("display: none");
numPts = Math.floor(1.1 * numDiv); // moved here
let radio;
// start left hand info display below the graph
let s0 = createSpan("
Values");
s0.parent(p5left);
for (i = 0; i < valuesLab.length; i++) {
if (i == 3 ) { // start a new line
let s1 = createSpan("
"
+ " ");
s1.parent(p5left);
}
valLab[i] = createSpan(" " + valuesLab[i] + " ")
valLab[i].parent(p5left);
valInp[i] = createInput("", "text");
valInp[i].parent(p5left);
valInp[i].size(109);
valInp[i].changed(valueChanged);
}
// Handle s input in a special way
valInp[5].input(sInput);
// create slider for s
let sleft = createSpan("
"
+ " "
+ " Adjust value of s (0 - 100) ");
sleft.parent(p5left);
sValueSlider = createSlider(0, 100, 50);
sValueSlider.parent(p5left);
sValueSlider.changed(sSliderChanged);
// create display for values for a ... e
s1a = createSpan("
");
s1a.parent(p5left);
valDisplay = createSpan("");
valDisplay.parent(p5left);
// start right hand info display
// 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",
''
+ 'y = f(x) ',
"getRadioValueMode()", true, 0);
rectRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'y = f(θ) ',
"getRadioValueMode()", false, 1);
polarRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'x = f(t), y = g(t)',
"getRadioValueMode()", false, 2);
paramRadio = displayHTMLElements(radio, p5right, "inline-block");
// display function labels and inputs
let s3 = createSpan("
To change a function, value, mins and max, type"
+ '
the desired value and then press "Enter".');
s3.parent(p5right);
for (i = 0; i < NUM_F_INPUT; i++) {
myFill(colors[i]);
fLabel[i] = createSpan(""); // create label
functionLab(i, mode); // provide an initial label
fLabel[i].parent(p5right);
fInput[i] = createInput(f[i]);
fInput[i].parent(p5right);
fInput[i].changed(inputF);
fInput[i].size(260);
}
// create a help link
let s4 = createSpan('
'
+ 'Help with grapher app and its functions.
');
s4.parent(p5right);
// The polar division contains inputs for max and mins of both theta and t
// It is displayed only in the polar and parametric modes
polarDiv = createDiv();
polarDiv.parent(p5right);
polarDiv.style("display: none");
tMinLabel = createSpan(T_MIN_LAB[mode]);
tMinLabel.parent(polarDiv);
tMinInput = createInput(T_MIN_VAL[mode], "text");
tMinInput.parent(polarDiv);
tMinInput.changed(checkMinMax);
tMaxLabel = createSpan(T_MAX_LAB[mode])
tMaxLabel.parent(polarDiv);
tMaxInput = createInput(T_MAX_VAL[mode], "text");
tMaxInput.parent(polarDiv);
tMaxInput.changed(checkMinMax);
// create input for max and mins for x and y
xMinLabel = createSpan("
Minimum x ");
xMinLabel.parent(p5right);
xMinInput = createInput("-5", "text");
xMinInput.parent(p5right);
xMinInput.changed(checkMinMax);
xMaxLabel = createSpan("
Maximum x ");
xMaxLabel.parent(p5right);
xMaxInput = createInput("5", "text");
xMaxInput.parent(p5right);
xMaxInput.changed(checkMinMax);
instLabel = createSpan("
The minimum and maximum y values will"
+ " be
calculated automatically if you leave them blank.");
instLabel.parent(p5right);
yMinLabel = createSpan("
Minimum y ");
yMinLabel.parent(p5right);
yMinInput = createInput("", "text");
yMinInput.parent(p5right);
yMinInput.changed(checkMinMax);
yMaxLabel = createSpan("
Maximum y ");
yMaxLabel.parent(p5right);
yMaxInput = createInput("", "text");
yMaxInput.parent(p5right);
yMaxInput.changed(checkMinMax);
// zoom buttons
let s4a = createSpan("
Doubleclick to set center of graph.
");
s4a.parent(p5right);
zoomInBtn = createButton("Zoom in");
zoomInBtn.parent(p5right);
zoomInBtn.mousePressed(zoomIn);
zoomOutBtn = createButton("Zoom out");
zoomOutBtn.parent(p5right);
zoomOutBtn.mousePressed(zoomOut);
// create Equal spacing, Allow motion, Show enlarged canvas
// and Radians/degrees inputs
let s5 = createSpan("
");
s5.parent(p5right);
equalSpace = createCheckbox("Equal spacing ", false);
equalSpace.parent(p5right);
equalSpace.changed(equalSpaceChanged);
allowLoop = createCheckbox("Allow motion", false);
allowLoop.parent(p5right);
allowLoop.changed(allowLoopChanged);
enlargeCheckBox = createCheckbox("Show enlarged canvas", false);
enlargeCheckBox.parent(p5right);
enlargeCheckBox.changed(enlargedChanged);
enlargeSpan = createSpan("Enlargement ratio ("
+ (round(10 * enlargeRatio)/10) + ") ");
enlargeSpan.parent(p5right);
enlargeSlider = createSlider(430, 1000, 645);
enlargeSlider.parent(enlargeSpan);
enlargeSlider.changed(enlargeChanged);
let s5a = createSpan("
");
s5a.parent(p5right);
radio = HTMLforRadioButton("angles", "RADIANS", "getRadioValueAngle()",
true);
radiansRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("angles", "DEGREES", "getRadioValueAngle()");
degreesRadio = displayHTMLElements(radio, p5right, "inline-block");
// create number of evaluations inputs
numPtsLabel = createSpan("
Number of evaluation points ");
numPtsLabel.parent(p5right);
numPtsInput = createInput(200, "text");
numPtsInput.parent(p5right);
numPtsInput.size(87);
numPtsInput.changed(numPtsChanged);
// create buttons for displaying and reading examples
let s6 = createSpan("
");
s6.parent(p5right);
showExampleDataBtn = createButton("Show graph info");
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);
// some additional initialization
inputF();
valueChanged();
checkMinMax();
frameRate(20);
// initialize arrays xVals and yVals which hold points to be plotted
for (let i = 0; i < NUM_F_INPUT; i++) {
xVals[i] = [];
yVals[i] = [];
}
// finally, initialize the examples
setupExamples();
displayExamples();
} // setup
function draw() {
// set background color for plot area
myBackground("lightblue");
// warning if no function defined and hence no graph can be drawn
if (noFDefined) {
// Has there been an error?
if (errorMsg != "") {
displayError();
} else {
myText("You need to define a function.", can1.width/2, can1.height/2);
}
noLoop();
return;
}
// update value of s, all values, and the mins and maxs
valueChanged();
checkMinMax();
if (errorMsg != "") {
displayError();
return;
}
// *** for each function determine points to be plotted ***
for (let i = 0; i < NUM_F_INPUT; i++) { //for each function
// initials values, mins, and maxs
xVals[i] = [];
yVals[i] = [];
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
// determine points to be ploted
if (!fDefined[i]) {continue;}
specialDefined = false;
if (f[i] != "") {
let firstChar = f[i].charAt(0).toLowerCase();
let first2Char = f[i].substring(0, 2).toLowerCase();
if (first2Char == "x=") { // vLine
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 if (mode == RECT_MODE) {
evalRectangular(i);
} else if (mode == POLAR_MODE) {
evalPolar(i);
} else {// mode == PARA_MODE
if (i == 0 || i == 2 || i == 4) { // parametric functions are in pairs
if (!fDefined[i+1]) {continue;}
evalParam(i);
}
}
}
} // end of evaluation of points loop
// start determining the min and max of x
xMaxVals = max(xMaxV);
xMinVals = min(xMinV);
yMaxVals = max(yMaxV);
yMinVals = min(yMinV);
if (abs(xMaxVals) == Infinity || abs(xMinVals) == Infinity
|| abs(yMaxVals) == Infinity || abs(yMinVals) == Infinity)
{
if (!specialDefined) {
// If special defined, an attempt at a special such x=
// but no the max and mins were not set so no graph can be drawn.
// If there was an error, it should already registered.
// The following should only happen if no values are valid points
// or no values calculated because the only function is illegal
let s = "The values cannot be calculated. Possible division by 0"
+ " or invalid \nsquare root."
+ " Or only function is illegal or incomplete.";
if (mode == 2) {
s += "Or both f(t) and g(t) must be specified.";
}
setErrorMsg(s);
}
} else { // process anything that has been defined
// check to see if max and min are too close or the same)
if (abs(xMaxVals - xMinVals) < .01 * xMaxVals) {
if (xMaxVals > 0) {
xMaxVals = 1.1 * xMaxVals;
xMinVals = 0;
} else if (xMaxVals == 0) {
xMaxVals = 1;
xMinVals = -1;
} else {
xMaxVals = 0;
xMinVals = 1.1 * xMinVals;
}
}
if (yMaxVals == yMinVals) {
if (yMaxVals > 0) {
yMaxVals = 1.1 * yMaxVals;
yMinVals = 0;
} else if (yMaxVals == 0) {
yMaxVals = 1;
yMinVals = -1;
} else {
yMaxVals = 0;
yMinVals = 1.1 * yMinVals;
}
}
// determine maxX, minX, maxY and minY
if (maxX == "") {
maxX = ceil(xMaxVals);
}
if (minX == "") {
minX = floor(xMinVals);
}
if (maxY == "") {
maxY = ceil(yMaxVals);
}
if (minY == "") {
minY = floor(yMinVals);
}
// check for equal spacing setting maxiX, miniX, maxiY, miniY
maxiX = maxX;
miniX = minX;
maxiY = maxY;
miniY = minY;
if (equalSpace.checked()) {
if (maxX - minX > maxY - minY) {
let dif = maxX - minX - maxY + minY ;
maxiY += dif/2;
miniY -= dif/2;
} else {
let dif = maxY - minY - maxX + minX;
maxiX += dif/2;
miniX -= dif/2;
}
}
// locate and draw axis and hashmarks
locateAxes();
myStroke("black");
myLine(0, xAxisLoc, can1.width, xAxisLoc);
myLine(yAxisLoc, 0, yAxisLoc, can1.height);
myFill("blue");
xHashMarks();
yHashMarks();
myNoStroke();
// lastPx and lastPy are used to detect vertical asymptotes
let lastPx, lastPy;
let px, py;
// *** start drawing for each function ***
for (i = 0; i < NUM_F_INPUT; i++) { //for each function
// first check for vertical lines
if (!fDefined[i]) { continue; }
if (mode == PARA_MODE) {
myStroke(colors[floor(i/2)]);
} else {
myStroke(colors[i]);
}
lastPy = NaN;
if (xVals[i][0] == "vLine") { // check for line
let xx = xVals[i][1];
let px = map(xx, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
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 - LOC_OFFSET,
LOC_OFFSET);
let py2 = map(yHigh, miniY, maxiY, can1.height - LOC_OFFSET,
LOC_OFFSET);
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, LOC_OFFSET, can1.width - LOC_OFFSET);
let py1 = map(yVals[i][j + 1], miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
let px2 = map(xVals[i][j + 2], miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
let py2 = map(yVals[i][j + 2], miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
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, LOC_OFFSET, can1.width - LOC_OFFSET);
let py = map(yy, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
if (j > 1) {
myLine(px, py, lastPx, lastPy);
}
lastPx = px;
lastPy = py;
}
} 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, LOC_OFFSET, can1.width - LOC_OFFSET);
let py = map(yy, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
myStrokeWeight(5);
myPoint(px, py);
myStrokeWeight(1);
}
// check for words
} else if (xVals[i][0]== "w") {
for (let j = 0; j < yVals[i][0]; j++) {
let xx = xVals[i][2*j+1];
let yy = yVals[i][2*j+1];
let px = map(xx, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
let py = map(yy, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
myNoStroke();
myFill(useColor[i]);
myText(yVals[i][2*j + 2], px, py);
}
// having taken care of special cases, plot normal curves
} else { // normal curves
for (let ix = 0; ix < yVals[i].length; ix++) {
if (mode == PARA_MODE && (i == 1 || i == 3 || i == 5)) {continue}
// the first time in this loop is just to initialize lastPx and lastPy
if (!isNaN(yVals[i][ix])) {
let xx = xVals[i][ix];
let yy = yVals[i][ix];
if (isNaN(xx) || isNaN(yy)
|| abs(xx) == Infinity || abs(yy) == Infinity) {
lastPy = NaN;
} else {
let px = map(xx, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
let py = map(yy, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
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
can1.text(loopCnt, 30, 15); // only show on original canvas
if (loopCnt > 5 && !allowLoop.checked()) {
noLoop();
loopCnt = 0;
} else {
++loopCnt;
}
} // draw
function zoomIn() {
// zooms out with the ratio of 2. Centers graph on x.
// If x is not defined, then it uses the center of the current graph for x
if (typeof x == "undefined") {
x =(minX + maxX)/2;
y = 0;
}
centerGraph(x, 0, zoomRatio);
} // zoomIn
function zoomOut() {
// zooms out with the ratio of 1/2. 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 LOC_OFFSET from the borders
// (LOC_OFFSET is the marging beyond the x and y's mins and maxs
let xx = map(event.offsetX, LOC_OFFSET, can1.width - LOC_OFFSET, minX, maxX);
let yy = map(event.offsetY, LOC_OFFSET, can1.height - LOC_OFFSET, 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 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 evalPolar(i) {
// evaluate x and y values given polar coordinates
try {
// tt is angle theta
for (let it = 0; it < numPts; it++) { //for each theta (t)value
let tt = float(minT) + float((it) * (maxT - minT) / numPts);
let r = func(i, tt);
if (r == ERROR) {
break;
}
if (!isNaN(r) && r !== Infinity) {
let xxx = r * cos(tt)
xVals[i][it] = xxx;
let yyy = r * sin(tt);
yVals[i][it] = yyy;
if (!isNaN(t) && t !== Infinity) {
xMaxV[i] = max(xMaxV[i], xxx);
xMinV[i] = min(xMinV[i], xxx);
yMaxV[i] = max(yMaxV[i], yyy);
yMinV[i] = min(yMinV[i], yyy);
}
}
}
} catch(err) {
setErrorMsg("Error in function f" + i + ". \nNamely: " + err);
}
} // evalPolar
function evalParam(i) {
// evalute x and y for parametization
try {
for (let it = 0; it < numPts; it++) { //for each t value
let tt = float(minT) + float((it) * (maxT - minT) / numPts);
let xxx = func(i, tt);
let yyy = func(i + 1, tt);
xVals[i][it] = xxx;
yVals[i][it] = yyy;
if (!isNaN(xxx) && xxx !== Infinity && !isNaN(yyy) && yyy !== Infinity) {
xMaxV[i] = max(xMaxV[i], xxx);
xMinV[i] = min(xMinV[i], xxx);
yMaxV[i] = max(yMaxV[i], yyy);
yMinV[i] = min(yMinV[i], yyy);
}
}
} catch(err) {
setErrorMsg("Error in function f" + i + ". \nNamely: " + err);
}
} // evalParam
function gVerticalLine(i) {
// Draws vertical lines (can be used in all modes)
// Format:
// x= xVal, yLow (optional), yHigh (optional)
// Draws a vertical line at xVal from yHigh to yLow.
// If yHigh and yLow are not provide the line normally will go from the
// top of the graph to the bottom.
// Result:
// xVals[i][0] = "vLine"
// xVals[i][1] = xVal
// yVals[i][0] = 0, 1, 2 depending on the number of y values provided
// yVals[i][1] = yLow (if provided) // this allows having the line for y>=0
// yVals[i][2] = yHigh (if provided)
// yMaxV[i]
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(2, 10000);
sts = st.split(";");
let theXVal = eval(sts[0]);
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("In f" + i + ", x= must be followed by the x value\n and"
+ " optionally by ; yLow; yHigh");
return;
}
xVals[i][0] = "vLine";
xVals[i][1] = theXVal;
xMinV[i] = theXVal;
xMaxV[i] = theXVal;
yVals[i][0] = sts.length - 1;
if (sts.length >= 2) {
yVals[i][1] = eval(sts[1]);
yMinV[i] = yVals[i][1];
if (sts.length >= 3) {
yVals[i][2] = eval(sts[2]);
yMinV[i] = min(yVals[i][2], yVals[i][1]);
yMaxV[i] = max(yVals[i][2], yVals[i][1]);
}
}
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
loop();
}
} // gVerticalLine
function gTheta(i) {
// Draws rays at angle theta (o)
// Assumes polar coordiantes but may be used in all modes
// format:
// o= thetaVal, rLow (optional),rHigh (optional)
// Draws a ray at angle thetaVal from (rLow, theta) to (rHigh, theta)
// (Assumes polar coordinates.)
// If rHigh and rLow are not provided, the rLow = -10, rHigh = 10
// Result:
// xVals[i][0] = "oLine"
// (xVals[i][1], yVals[i][1]): one end point of ray
// (xVals[i][2], yVals[i][2]): other end point of ray
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(2, 10000);
let sts = st.split(";");
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("o= must be followed by the theta value\n and optionally"
+ " by ; rLow; rHigh");
return;
}
xVals[i][0] = "oLine";
yVals[i][0] = 1;/// could be changed....
let thetaVal = eval(sts[0]);
let rMin = -10; // these are arbitrary
let rMax = 10;
if (sts.length >= 2) {
rMin = eval(sts[1]); // this may actually be the max
if (sts.length >= 3) {
rMin = min(rMin, eval(sts[2]));
rMax = max(eval(sts[1]), eval(sts[2]));// rMin may have changed
}
}
let x1 = rMin * cos(thetaVal);
let y1 = rMin * sin(thetaVal);
let x2 = rMax * cos(thetaVal);
let y2 = rMax * sin(thetaVal);
xVals[i][1] = x1;
xVals[i][2] = x2;
yVals[i][1] = y1;
yVals[i][2] = y2;
xMinV[i] = min(x1, x2);
xMaxV[i] = max(x1, x2);
yMinV[i] = min(y1, y2);
yMaxV[i] = max(y1, y2);
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
loop();
}
} // gTheta
function gLineSegment(i) {
// Draws a straight line betweein two points.
// May be used in all modes.
// format:
// l = x1; y1; x2; y2; ...
// draws a straight line from (x1, y1) to (x2, y2).
// or in polar coordinates
// l = r1; theta1; r2; theta2
// Result:
// xVals[i][0] = "l"
// yVals[i][0] = number of line segments
// xVals[i][j+1]: x value of one end point
// xVals[i][j+1]: y value of one end point
// xVals[i][j+2]: x value of other end point
// xVals[i][j+3]: y value of other end point
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(1, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
noFDefined = false;
if (floor(sts.length/4) == 0) {
if (mode == POLAR_MODE) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\nL must be followed by rVal; thetaVal; rVal; thetaVal'
+ '\nDid you use ";"?');
} else {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\nL must be followed by xVal; yVal; xVal; yVal'
+ '\nDid you use ";"?');
}
return;
}
// process the points
xVals[i][0] = "l";
yVals[i][0] = floor(sts.length/4);
let xxx2, yyy2;
for (let j = 0; j < floor(sts.length/4); j++) {
if (mode == POLAR_MODE) {
let radius = eval(sts[4 * j]);
let theta = eval(sts[4 * j + 1]);
let radius2 = eval(sts[4 * j + 2]);
let theta2 = eval(sts[4 * j + 3]);
xxx = radius * cos(theta);
yyy = radius * sin(theta);
xxx2 = radius2 * cos(theta2);
yyy2 = radius2 * sin(theta2);
} else {
xxx = eval(sts[4 * j]);
yyy = eval(sts[4 * j + 1]);
xxx2 = eval(sts[4 * j + 2]);
yyy2 = eval(sts[4 * j + 3]);
}
xVals[i][2 * j + 1] = xxx;
yVals[i][2 * j + 1] = yyy;
xVals[i][2 * j + 2] = xxx2;
yVals[i][2 * j + 2] = yyy2;
xMinV[i] = min(xxx, xxx2, xMinV[i]);
xMaxV[i] = max(xxx, xxx2, xMaxV[i]);
yMinV[i] = min(yyy, yyy2, yMinV[i]);
yMaxV[i] = max(yyy, yyy2, yMaxV[i]);
}
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
}
} // gLineSegment
function gCircle(i) {
// Draws circles with radius r)
// May be used in all modes.
// format:
// r = thetaVal, center x (optional), center y (optional)
// Draws a circle with center at (centerX, centerY)
// If the centers are not provided, the center is the orgin,
// Result:
// xVals[i][0] = "r"
// xVals[i][j+1]: x value of point on the curve (number of evaluation pts.)
// xVals[i][j+1]: y value of point on the curve
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(2, 10000);
let sts = st.split(";");
if (sts.length == 1 && sts[0] == "") {
if (mode == POLAR_MODE) {
setErrorMsg("r= must be followed by the r value\n and optionally"
+ "by the center ; center r; center theta" );
} else {
setErrorMsg("r= must be followed by the r value\n and optionally"
+ " by both ; center x; center theta");
}
return;
}
let rVal = eval(sts[0]);
let centerX = 0; // default center at origin
let centerY = 0;
if (sts.length >= 3) { // If option used, both low and high required
if (mode == POLAR_MODE) {
let r = eval(sts[1]);
let th = eval(sts[2]);
centerX = r * cos(th);
centerY = r * sin(th);
} else {
centerX = eval(sts[1]);
centerY = eval(sts[2]);
}
}
let range = TWO_PI;
xVals[i][0] = "r";
for (let j = 0; j <= numPts; j++) {
let theta = j * range/numPts;
xVals[i][j+1] = rVal * cos(theta) + centerX;
yVals[i][j+1] = rVal * sin(theta) + centerY;
}
xMinV[i] = -rVal + centerX;
xMaxV[i] = rVal + centerX;
yMinV[i] = -rVal + centerY;
yMaxV[i] = rVal + centerY;
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i] + ")"
+ ".\nNamely: " + err);
functionError = true;
loop();
}
} // gCircle
function gPoint(i) {
// graph points
// Format:
// p xVal, yVal, xVal, yVal, ... multiple points allowed
// or in polar
// p rVal, thetaVal, rVal, thetaVal ... multiple points allowed
// Plots a point at each aVal, yVal pair. If yVal is missing, the
// xVal is ignored.
// Result:
// xVals[i][0] = "p"
// yVals[i][0] = number of points
// xVals[i][1], yVals[i][1]: the first point
// xVals[i][2], yVals[i][2]: second point, if specified
// ... (likewise for any additional points)
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(1, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
if (floor(sts.length/2) == 0) {
if (mode == POLAR_MODE) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '", p must be followed by rVal; thetaVal'
+ '\nDid you use ";"?');
} else {
setErrorMsg('In f' + i + '= "' + f[i]
+ '", p must be followed by xVal; yVal'
+ '\nDid you use ";"?');
}
loop();
return;
}
// process the points
xVals[i][0] = "p";
yVals[i][0] = floor(sts.length/2);
for (let j = 0; j < floor(sts.length/2); j++) {
if (mode == POLAR_MODE) {
let radius = eval(sts[2 * j]);
let theta = eval(sts[2 * j + 1]);
xxx = radius * cos(theta);
yyy = radius * sin(theta);
} else {
xxx = eval(sts[2 * j]);
yyy = eval(sts[2 * j + 1]);
}
xVals[i][j + 1] = xxx;
yVals[i][j + 1] = yyy;
xMinV[i] = min(xxx, xMinV[i]);
xMaxV[i] = max(xxx, xMaxV[i]);
yMinV[i] = min(yyy, yMinV[i]);
yMaxV[i] = max(yyy, yMaxV[i]);
}
} catch(e) {
errorMsg = "Error in point specication. Namely " + e;
functionError = true;
loop();
}
} // gPoint
function gWord(i) {
// graph words (or labels)
// (w for word instead of l for label as l looks too much like 1)
// Format:
// w xVal, yVal, word, xVal, yVal, word... multiple words allowed
// or in polar
// w rVal, thetaVal, word, ... multiple words allowed
// Types a word or label at each aVal, yVal pair. If yVal or word is
// missing, thexVal is ignored.
// Result:
// xVals[i][0] = "w"
// yVals[i][0] = number of words
// xVals[i][1], yVals[i][1]: location of the first word
// yVals[i][2] the first word
// xVals[i][3], yVals[i][3]: location of second word, if specified
// yVals[i][4] the second word, if specified
// ... (likewise for any additional words)
// Notes: each word uses 2 items in the xVals and yVals arrays.
// The word is centered horizontally just above the specified y value.
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(1, 10000);
let sts = st.split(";");
// check to make at least 1 word is declared
if (floor(sts.length/3) == 0) {
if (mode == POLAR_MODE) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\nw must be followed by rVal; thetaVal; word');
} else {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\nw must be followed by xVal; yVal; word');
}
loop();
return;
}
// process the words
xVals[i][0] = "w";
yVals[i][0] = floor(sts.length/3);
let xxx, yyy;
let modeType = 1;
if (mode == PARA_MODE) {
modeType = 2; //This mode uses colors 0, 1, 2
}
useColor[i] = colors[floor(i/modeType)]; // assume normal color
for (let j = 0; j < floor(sts.length/3); j++) {
if (trim(sts[3 * j]) == "color") {
try {
useColor[i] = colors[floor(eval(sts[3 * j + 1]))];
} catch (err) {
setErrorMsg("Illegal color (" + sts[3 * j + 1]
+ ") for word(s) in function box " + i + ",\nnamely " + err);
}
return;
}
if (mode == POLAR_MODE) {
radius = eval(sts[3 * j]);
theta = eval(sts[3 * j + 1]);
xxx = radius * cos(theta);
yyy = radius * sin(theta);
} else {
xxx = eval(sts[3 * j]);
yyy = eval(sts[3 * j + 1]);
}
xVals[i][2 * j + 1] = xxx;
yVals[i][2 * j + 1] = yyy;
xMinV[i] = min(xxx, xMinV[i]);
xMaxV[i] = max(xxx, xMaxV[i]);
yMinV[i] = min(yyy, yMinV[i]);
yMaxV[i] = max(yyy, yMaxV[i]);
let st = trim(sts[3 * j + 2]);
for (let i = 0; i < valuesLab.length; i++) {
st = myReplaceAll(st, SPECIAL_SYMBOLS[i], theValues[i]);
}
st = myReplaceAll(st, '^$', ';');
yVals[i][2 * j + 2] = st;
}
} catch (err) {
errorMsg = 'Error in declaring a "word". \nNamely: ' + err;
functionError = true;
loop();
}
} // gWord
function gQuad(i) {
// Draws pieces of quadrics (parabolas).
// Format:
// q a; b; c; x1; x2; a; b; c; x1; x2; .....
// The quadratic is ax**2 + bx +c and it is drawn between x1 and x2.
// The 5 values are required but can be repeated. Unlike the
// other special g options, it justs produces x and y values
// that can be plotted like leany other curve.
// i is the curve number
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(1, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
let numQuad = floor(sts.length/5);
if (numQuad == 0) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '", q must be followed by a, b, c, x1, and x2'
+ '\nDid you use ";"?');
}
let iii = 0; // counter for points to be plotted
for (let j = 0; j < numQuad; j++) {
let j5 = j * 5;
let aaa = eval(sts[j5]);
let bbb = eval(sts[j5 + 1]);
let ccc = eval(sts[j5 + 2]);
let xxx1 = eval(sts[j5 + 3]);
let xxx2 = eval(sts[j5 + 4]);
let numPts = abs(numDiv * (xxx2 - xxx1)/(maxX - minX));
let spacing = (xxx2 - xxx1)/numPts;
for (let ix = 0; ix <= numPts; ix++) {
let xxx = xxx1 + ix * spacing;
let yyy = (aaa * xxx + bbb) * xxx + ccc;
xVals[i][iii] = xxx;
yVals[i][iii] = yyy;
iii++;
xMinV[i] = min(xMinV[i], xxx);
xMaxV[i] = max(xMaxV[i], xxx);
yMaxV[i] = max(yMaxV[i], yyy);
yMinV[i] = min(yMinV[i], yyy);
// provide separator between curves
}
xVals[i][iii] = Infinity;
yVals[i][iii] = Infinity;
iii++;
}
} catch (err) {
errorMsg = 'Error in declaring a quadratic. \nNamely: ' + err;
functionError = true;
loop();
}
} // gQuad
// This called when any MinInput or MaxInput changes.
// Both x and y axis location must be reset in case there is a blank.
function checkMinMax() {
// check for previous minMaxError. Clear it. It will be reset if needed
if (minMaxError) {
errorMsg = "";
minMaxError = false;
}
try {
// chect t (theta) min/max if needed
if (mode == POLAR_MODE || mode == PARA_MODE) {
if (tMinInput.value() == "") {
minT = 0;
} else {
minT = eval(tMinInput.value());
}
if (tMaxInput.value() == "") {
if (mode == POLAR_MODE) {
if (modeAngle == RADIANS) {
maxT = TWO_PI;
} else {
maxT = 360;
}
} else if (mode == PARAM_MODE){
maxT = 100;
}
}
maxT = eval(tMaxInput.value());
}
// check x min/max
minX = xMinInput.value();
if (minX == "" && mode == RECT_MODE) {
minX = -5;
} else if (minX != "") {
minX = eval(minX);
}
maxX = xMaxInput.value() ;
if (maxX == "" && mode == RECT_MODE) {
maxX = 5;
} else if (maxX != "") {
maxX = eval(maxX);
}
// check y min/max
minY = yMinInput.value();
if (minY != "") {
minY = eval(minY);
}
maxY = yMaxInput.value();
if (maxY != "") {
maxY = eval(maxY);
}
} catch(err) {
minMaxError = true;
setErrorMsg("Invalid min or max.\nNamely: " + err);
}
loop();
} // checkMinMax
function resetMinMax(i, aXMin, aYMin, aXMax, aYMax) {
aXMin[i] = Infinity;
aYMin[i] = Infinity;
aXMax[i] = -Infinity;
aYMax[i] = -Infinity;
} // resetMinMax
// locateAxes called during draw()
function locateAxes() {
// locate x axis
if (miniY >= 0) {
xAxisLoc = can1.height - LOC_OFFSET;
} else if (maxiY <= 0) {
xAxisLoc = LOC_OFFSET;
} else {
xAxisLoc = map(0, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
}
if (miniX >= 0) {
yAxisLoc = LOC_OFFSET;
} else if (maxiX <= 0) {
yAxisLoc = can1.width - LOC_OFFSET;
} else {
yAxisLoc = map(0, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
}
} // locateAxes
function inputF() {
let i;
// check for previous functionError. Clear it. It will be reset if needed
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
// 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;
}
}
checkMinMax();
loop();
} catch(e) {
setErrorMsg("There was an error in function " + i
+ ". Namely " + e);
functionError = true;
}
} // inputF
// evaluate f[i] at x
function func(i, x) {
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
t = x;
let o = x;
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, LOC_OFFSET, can1.width - LOC_OFFSET);
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 - LOC_OFFSET, LOC_OFFSET);
myLine(yAxisLoc - 3, py, yAxisLoc + 3, py);
if (yAxisLoc <= 25) {
myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc + 20,
py + 5); // put y value right of axis
} else {
myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc - 20,
py + 5); // put y value left of axis
}
}
}
} // yHashMarks
function equalSpaceChanged() {
// called when equalSpace is changed
loop();
} // equalSpaceChanged
function allowLoopChanged() {
// called when allowLoop is changed
loop();
} // allowLoopChanged
function enlargedChanged() {
enlarge = enlargeCheckBox.checked();
if (enlarge) {
can2.style("display: block");
spacer.style("display: block");
} else {
can2.style("display: none");
spacer.style("display: none");
}
loop();
} // enlargedChanged
function valueChanged() {
// called when a value is changed
if (valueError) {
errorMsg = "";
valueError = false;
}
try {
valInp[5].value(sValueSlider.value());
s = valInp[5].value();
theValues[5] = s;
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value()); // eval of blank returns NaN
} else {
e = 0;
}
theValues[4] = e;
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value()); // eval of blank returns NaN
} else {
d = 0;
}
theValues[3] = d;
if (trim(valInp[2].value()) != "") {
c = eval(valInp[2].value());
} else {
c = 0;
}
theValues[2] = c;
if (trim(valInp[1].value()) != "") {
b = eval(valInp[1].value());
} else {
b = 0;
}
theValues[1] = b;
if (trim(valInp[0].value()) != "") {
a = eval(valInp[0].value());
} else {
a = 0;
}
theValues[0] = a;
// The above evaluate the value e, d, ..., a in that order so the value
// of e does not reflect a new value in d and so on.
// So now evaluate them in the order b, c, ..., e
if (trim(valInp[1].value()) != "") {
b = eval(valInp[1].value());
}
if (trim(valInp[2].value()) != "") {
c = eval(valInp[2].value());
}
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value());
}
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value());
}
valDisplay.html("a = " + a.toFixed(3)
+ ", b = " + b.toFixed(3)
+ ", c = " + c.toFixed(3)
+ ", d = " + d.toFixed(3)
+ ", e = " + e.toFixed(3));
} catch(err) {
setErrorMsg("Invalid value.\nNamely: " + err);
valueError = true;
loop();
return;
}
checkMinMax();
loop();
} // valueChanged
function numPtsChanged() {
// called when the number of points is changed
// check for previous numberEvalError. Clear it. It will be reset if needed
if (numberEvalsError) {
errorMsg = "";
numberEvalsError = false;
}
try {
numDiv = eval(numPtsInput.value());
if (numDiv < 50 || numPtsInput.value() == "") {
throw "Number of evaluation points is blank or too small";
}
numPts = 1.1 * numDiv;
numPtsOffset = 0.05 * numDiv;
} catch(err) {
setErrorMsg("Invalid number of evaluation points."
+ "\nNamely " + err);
numberEvalsError = true;
}
loop();
} // numPtsChanged
// this method is not being called when using javascript radio buttons
function modeChanged() {
// called when the mode is changed
getRadioValueMode();
if (mode == 0) {
// mode = 0;
numF = 6;
polarDiv.style("display: none"); // hide max and min for t and theta
} else if (mode == 1) {
// mode = 1;
polarDiv.style("display: block"); // show for theta
numF = 6;
} else {
mode = 2;
polarDiv.style("display: block"); // show for when using parameters
numF = 6;
}
s2.html(MODE_LAB[mode]);
// 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
updateTMinMax(mode);
valueChanged();
checkMinMax();
inputF();
loop();
} // modeChanged
function functionLab(i, aMode) {
// Provide an appropriate label for function boxes.
// Called by setup() and modeChanged()
let u;
switch (i) {
case 0: u = FUNCTION_LAB0[aMode];
break;
case 1: u = FUNCTION_LAB1[aMode];
break;
case 2: u = FUNCTION_LAB2[aMode];
break;
case 3: u = FUNCTION_LAB3[aMode];
break;
case 4: u = FUNCTION_LAB4[aMode];
break;
case 5: u = FUNCTION_LAB5[aMode];
break;
}
fLabel[i].html(u);
} // functionLab
function updateTMinMax(aMode) {
//called by modeChanged
tMinLabel.html(T_MIN_LAB[aMode]);
tMinInput.value(T_MIN_VAL[aMode]);
tMaxLabel.html(T_MAX_LAB[aMode]);
tMaxInput.value(T_MAX_VAL[aMode]);
} // updateTMinMax
function sSliderChanged() {
valInp[5].value(sValueSlider.value());
valueChanged();
} // sSliderChanged
function sInput() {
let s = this.value();
let sVal = int(s);
if (sVal == s && sVal >= 0 && sVal <= 100) {
sValueSlider.value(sVal);
}
} // sInput
function setErrorMsg(msg) {
// this is designed to prevent repeated alerts for the same error
// expecially while looping. Repeats of the same alert msg may make
// difficult to fix the error. In some cases a very similar msg may
// be shown. For example if abc is an illegal variable, the alert
// would repeat if the variable is changed to ab.
errorMsg = msg;
} // oneTimeAlert
function displayError() {
myFill("wheat");
myRect(10, 10, can1.width - 20, 90);
myFill("black");
myNoStroke();
myText(errorMsg + "\n\nPlease correct the problem and press enter",
can1.width/2, 25);
}
function clearErrorMsg() {
errorMsg = "";
loop();
}
function myReplaceAll(st, from, to) {
// replaces every occurance of "from" in st with "to" and
// returns the result.
let array = st.split(from);
let s = array[0];
for (let i = 1; i < array.length; i++) {
s += to + array[i];
}
return s;
} // myReplaceAll
// save the canvas as an image file
function saveImage() {
mySaveCanvas("grapher", "jpg");
}
// an alert with data needed for an example
// based on the current graph
function showExampleData() {
let s = " example[??] = ["
for (let i = 0; i < 6; i++) {
s += '"' + myReplaceAll(fInput[i].value(), ',', '^$') + '",';
}
s += '\n ';
s += mode + ',\n '; // add spaces to indent output
s += '"' + tMinInput.value() + '", "' + tMaxInput.value() + '", ';
s += '"' + xMinInput.value() + '", "' + xMaxInput.value() + '", ';
s += '"' + yMinInput.value() + '", "' + yMaxInput.value() + '", ';
s += equalSpace.checked() + ', ';
s += allowLoop.checked() + ',\n ';
for (let i = 0; i < 5; i++) {
s += '"' + valInp[i].value() + '", ';
}
s += '"' + sValueSlider.value() + '", ';
s += '"' + modeAngle + '", ';
s += '"' + numPtsInput.value() + '", \n ';
s += '"???? Example name ????"];';
s += '\n\n';
s += 'You can copy the above and paste it into setupExamples().\n'
+ 'Replace the example number [??] and example name appropriately.\n'
+ 'Examples are printed in the order of the example numbers [].\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 new example 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] = tMinInput.value();
example[ex][8] = tMaxInput.value();
example[ex][9] = xMinInput.value();
example[ex][10] = xMaxInput.value();
example[ex][11] = yMinInput.value();
example[ex][12] = yMaxInput.value();
example[ex][13] = equalSpace.checked();
example[ex][14] = allowLoop.checked();
for (let i = 0; i < 6; i++) { // values
example[ex][15+i] = trim(valInp[i].value()); // includes s
}
example[ex][21] = modeAngle; // mode angle not used
example[ex][22] = numPtsInput.value();
example[ex][EXAMPLE_NAME] = newName;
let radio = HTMLforRadioButton("examplesResult",
''
+ example[ex][EXAMPLE_NAME] +'',
"getRadioValueExamples()", true, ex);
examplesRadio[ex] = displayHTMLElements(radio, p5left, "block");
useExample(ex);
} // saveExample
function setupExamples() {
// the format of the example[i] vector:
// [0] .. [5]: functions f0 .. f4 (strings)
// [6]: the mode - 0: rectangular, 1: polar, 2: parametric
// [7] .. [8]: min and max t (number or string)
// [9] .. [10]: min and max x (number or string)
// [11] .. [12]: min and max y (number or string)
// [13]: equal spacing (true or false)
// [14]: allow motion (true or false)
// [15] .. [19]: values a.. e (number or string)
// [20]: value of s (sets slider);
// [21]: radians/degrees option ("radians" or "degrees")
// [22]: number of evaluation points (number or string)
// [23]: display title (string) ( EXAMPLE_NAME = 21 )
example[0] = ["x * x /3", "5 * cos(x)", "5 * sin(x)", "c * sqrt(x)", "0.4/x",
"",
0,
"0", "1", "-5", "5", "", "", false, false,
"", "", "6", "", "", "50", "radians", "200",
"5 function rectangular coordiante example"];
example[1] = ["exp((-sq(x - a)/b)/2)/(b*sqrt(TWO_PI))",
"x=a+1; 0;exp((-sq(1)/b)/2)/(b*sqrt(TWO_PI))",
"x=a-1; 0;exp((-sq(-1)/b)/2)/(b*sqrt(TWO_PI))",
"w a+1;.05;+1 st. dev.", "w a-1;.05;-1 st. dev.",
"w a; d; normal curve with mean ^a, st.dev. ^b ",
0,
"0", "1", "-4", "4", "", "", false, false,
"(s-50)/20", "1", "", "1.2*exp((-sq(0)/b)/2)/(b*sqrt(TWO_PI))", "",
"50", "radians", "200",
"Normal distribution, a = mean, b = st. dev. Use slider to adjust"
+ " the mean."];
//d is 1.2 times the normal curve value at center
example[2] = ["sin(x)", "b * (x - d) + a", "p d; sin(d)", "x=d; 0; sin(d)",
"", "",
0,
"0", "1", "-5", "5", "-2", "2", false, true,
"sin(d)", "cos(d)", "", "minX + (maxX - minX) * s / 100", "", "50",
"radians", "200",
"Tangent to sin x at the x set with the slider"];
example[3] = ["sqrt(x*x-a*a) // top half hyperbola",
"-sqrt(x*x -a*a) // bottom half of hyperbola", "x", "-x", "", "",
0,
"0", "1", "-5", "5", "", "", false, false,
"2", "", "", "", "", "50", "radians", 200,
"Hyperbola with asymptotes"];
example[4] = ["a * sin(o) * sq(cos(o))", "a * cos(o) * sq(sin(o))",
"", "", "", "",
1,
0, "TWO_PI", "", "", "", "", true, false,
2, "", "", "", "", "50", "radians", 200,
"Bifolium (2 of them), size a"];
example[5] = ["a * sin(b*o)", "a * cos(b*o)", "", "", "", "",
1,
0, "TWO_PI", "", "", "", "", true, false,
2, 3, "", "", "", "50", "radians", 200,
"Roses (2 of them), (size a, b controls number of petals)"];
example[6] = ["a + b * cos(o)", "a + a * cos(o)", "a + c * cos(o)",
"w a+b-.3; PI/10; ^a + ^b * cos(o); color;0;",
"w 2*a; PI/15; ^a + ^a * cos(o); color;1;",
"w a+c -.3; PI/30; ^a + ^c * cos(o); color;2;",
1,
"0", "TWO_PI", "", "", "", "", true, false,
"3", "5", "2", "", "", "50", "radians", "200",
"Limacon of Pascal (3 of them), a < b, a = a, a > c"];
example[7] = ["a * o", "", "", "", "", "",
1,
0, "2 * TWO_PI", "", "", "", "", true, false,
2, 0, 0, 0, 0, "50", "radians", 200,
"Spiral of Archimedes (try making the minimum t negative)"];
example[8] = [" exp(a*o)", "", "", "", "", "",
1,
"-2 * TWO_PI", "2 * TWO_PI", "", "", "", "", true, false,
.1, "", "", "", "", "50", "radians", 200,
"Logarithmic spiral (best if |a| < 1)"];
example[9] = ["a/sin(o)", "a/cos(o)", "a/(cos(o + QUARTER_PI))",
"o=PI/3; 0", "", "",
1,
"-PI/2", "PI/3", -2, 3, -2, 3, true, false,
2, "", "", "", "", "50", "radians", 200,
"Straight lines with a ray from the origin"];
example[10] = ["a * t - b * sin(t)","a - b * cos(t)",
"p a*c; a; a*c + b*cos(c + PI/2); -b* sin(c +PI/ 2) + a",
"l a*c; a; a*c + b*cos(c + PI/2); -b* sin(c +PI/ 2) + a",
"a *c + a* cos(t +PI/2)","a + a* sin(t + PI/2)",
2,
"-20", "20", "-100", "100", "", "", true, true,
"5", "s/10", "(frameCount/10)%(12.5*PI) - 37*PI/6", "", "",
"50", "radians", "200",
"Cycloid with rolling circle when b = a. Prolate cycloid"
+ " when b > a. Curtate cycloid when 0 < b < a. A straight line"
+ " when b = 0. Use the slider to change the value of b."];
example[11] = ["r= a",
"r= b; e*cos(c); e*sin(c); e*cos(c) - d*cos",
"e*cos(t) - d*cos(e/b*t)",
"e*sin(t) - d*sin(e/b*t)",
"p e*cos(c); e*sin(c); e*cos(c) - d*cos(e/b*c);"
+ "e*sin(c) - d*sin(e/b*c)",
"l e*cos(c); e*sin(c); e*cos(c) - d*cos(e/b*c);"
+ "e*sin(c) - d*sin(e/b*c)",
2,
"0", "2*PI", "-5", "5", "", "", true, true,
"3", "1", "(frameCount/20)%(2*PI)", "s/50", "a+b", "60", "radians", "200",
"Epicycloid when 0 < b < a. a: radius large circle, b: radius small
"
+ "circle, c: helps locate rotating circle, d: length of arm,"
+ " (Use slider to adjust d.) e = a + b; If b < 0 we have a "
+ "hypercycloid."];
example[12] = ["a * (cos(t) - cos(3 * t))/2", "a * (sin(t) - sin(3 * t))/2",
"", "", "", "",
2,
"-PI", "PI", "-3", "3", "-3", "3", true, false,
2, "", "", "", "", "50", "radians", 200,
'Nephroid, size = a'];
} // setupExamples
function displayExamples() {
let heading = createElement("h3","Examples");
heading.parent(p5left);
let info = createSpan("The example's color shows the mode:"
+ '
"'
+ 'y = f(x)"'
+ ', "r = f(θ)"'
+ ', or "x = f(t), y = g(t)"');
info.parent(p5left);
/* =================
exampleRadio = createRadio();
exampleRadio.parent(p5left);
exampleRadio.style("width: 430px");
exampleRadio.changed(useExample);
=============== */
let radio;
for (let i = 0; i < example.length; i++) {
/* =================
exampleRadio.option(i, ''
+ example[i][EXAMPLE_NAME] +'' + "
");
=============== */
// s2a = createSpan("
1");
// s2a.parent(p5left);
radio = HTMLforRadioButton("examplesResult",
''
+ example[i][EXAMPLE_NAME] +'',
"getRadioValueExamples()", false, i);
examplesRadio[i] = displayHTMLElements(radio, p5left, "block");
}
} // displayExamples
function useExample(ex) {
for (let i = 0; i < NUM_F_INPUT; i++) {
fInput[i].value(myReplaceAll(example[ex][i], "^$", ","));
}
if (example[ex][6] == 0) {
rectRadio.checked = true;
} else if (example[ex][6] == 1) {
polarRadio.checked = true;
} else {
paramRadio.checked = true;
}
modeChanged(); // must be done before setting values
tMinInput.value(example[ex][7]);
tMaxInput.value(example[ex][8]);
xMinInput.value(example[ex][9]);
xMaxInput.value(example[ex][10]);
yMinInput.value(example[ex][11]);
yMaxInput.value(example[ex][12]);
equalSpace.checked(example[ex][13]);
allowLoop.checked(example[ex][14]);
for (let i = 0; i < 5; i++) {
valInp[i].value(example[ex][15 + i]);
}
sValueSlider.value(example[ex][20]);
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();
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"));
updateMode();
} // getRadioValueMode
function getRadioValueExamples() {
let ex = int(getRadioValue("examplesResult"));
useExample(ex);
}
function updateMode() {
// called when the mode is changed
if (mode == 0) {
numF = 6;
polarDiv.style("display: none"); // hide max and min for t and theta
} else if (mode == 1) {
polarDiv.style("display: block"); // show for theta
numF = 6;
} else {
polarDiv.style("display: block"); // show for when using parameters
numF = 6;
}
s2.html(MODE_LAB[mode]);
// 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
updateTMinMax(mode);
valueChanged();
checkMinMax();
inputF();
loop();
} // updateMode
// the following my.... functions are intended to all working both the
// normal canvas and an enlarged canvas. Simple setting functions
// like myFill always set both canvases. The more complicated ones like
// myText always effect the normal small canvas but only work on the enlarged
// canvas if enlarge is true.
function myText(msg, xLoc, yLoc) {
can1.text(msg, xLoc, yLoc);
if (enlarge) {
can2.text(msg, xLoc * enlargeRatio, yLoc * enlargeRatio);
}
} // myText
function myFill(color) {
can1.fill(color);
can2.fill(color);
} // myFill
function myStroke(color) {
can1.stroke(color);
can2.stroke(color);
} // myStroke
function myBackground(color) {
can1.background(color);
can2.background(color);
} // myBackground
function myNoStroke(){
can1.noStroke();
can2.noStroke();
} // noStroke
function myStrokeWeight(val) {
can1.strokeWeight(val);
can2.strokeWeight(val);
} // myStrokeWeight
function myLine(x1, y1, x2, y2) {
can1.line(x1, y1, x2, y2);
if (enlarge) {
can2.line(x1 * enlargeRatio, y1 * enlargeRatio,
x2 * enlargeRatio, y2 * enlargeRatio);
}
} // myLine
function myRect(x, y, w, h) {
can1.rect(x, y, w, h);
if (enlarge) {
can2.rect(x * enlargeRatio, y * enlargeRatio,
w * enlargeRatio, h * enlargeRatio);
}
} // myRect
function myPoint(x, y) {
can1.point(x, y);
if (enlarge) {
can2.point(x * enlargeRatio, y * enlargeRatio);
}
} // myPoint
// this functions save can2 if enlarge is true, can1 otherwise
function mySaveCanvas(name, tag) {
if (enlarge) {
can2.saveCanvas(name, tag);
} else {
can1.saveCanvas(name, tag);
}
} // mySaveCanvas
/**
* Changes the size of can2 in response to the enlarge slider
*/
function enlargeChanged() {
let newSize = enlargeSlider.value();
can2.resizeCanvas(newSize, newSize);
enlargeRatio = newSize/can1.width;
enlargeSpan.html("Enlargement ratio ("
+ (round(10 * enlargeRatio)/10) + ") ");
enlargeSlider.parent(enlargeSpan);
enlargeCheckBox.checked(true);
inputF()
enlarge = true;
can2.style("display: block");
spacer.style("display: block");
inputF();
} // enlargeChanged