// James Brink, 8/2/2021
let can2;
let spacer;
let mode = 0; // start in line mode
let s2; // span for showing the mode choices
let f = ["", "", "", "", "", ""];
let fDefined = []; // the function i is defined
let noFDefined;
let specialDefined = true; // all graph items are special in charter
let fLabel = [];
let fInput = [];
let x, y, t; // t is not used in charter
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 equalSpaceChk; // check box for Equal Spacing
let allowLoopChk; // check box for Allow motion
let zoomInBtn; // enlarge picture around last iteration
let zoomOutBtn; // zoom out
let zoomRatio = 2; // multiplier for zoom
let lineRadio, columnRadio, pieRadio, scatterRadio;
// used for javascript buttons. Is apt is based on grapher. When
// grapher was being written, p5js radio buttons did not work properly
// so javascript radio buttons were used instead.
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"]; // Values in value boxes
let a = 0, b = 0, c = 0, d = 0, e = 0, s;
let the = [0, 0, 0, 0, 0, 0]; // the values of the variables,
// they are changed when a value is changed
let valDisplay;
let colors = ["red", "blue", "green", "maroon", "magenta", "navy", "black"];
// Number of colors determines NUM_F_INPUT
let numF = colors.length;
let useColor = []; // used to allow specifying color with words
let valLab = [];
let valInp = [];
let numDiv = 200;
let numPts;
let numPtsOffset = .05 * numDiv; // 2 * numPtsOffset + numDiv = numPts
let errorMsg = ""; // error message shown in the canvas
let functionError = false; // Possible types of errors. These errors
let minMaxError = false; // normally auto correct when corrected
let valueError = false;
let numberEvalsError = false;
let chartError = false
const LOC_OFFSET = 19; // offset for axis
const ERROR = "ERROR";
const REALY_BIG = 1.2345E100; // used in grapher to help take care of vertical
// asymptotes
const NUM_F_INPUT = colors.length;
const NUM_DATA = NUM_F_INPUT - 1; // excludes Labels input box
// labels for mode
const MODE_LAB = ['
Chart Mode: Line
',
'Chart Mode: Column
',
'Chart Mode: Pie
',
'Chart Mode: XY Scatter
']
// FUNCTION_LABn format = [Label for mode 0, label for mode 1, label for mode2]
const FUNCTION_LAB0 = ['
Data 0 '];
const FUNCTION_LAB1 = ['
Data 1 '];
const FUNCTION_LAB2 = ['
Data 2 '];
const FUNCTION_LAB3 = ['
Data 3 '];
const FUNCTION_LAB4 = ['
Data 4 '];
const FUNCTION_LAB5 = ['
Data 5 '];
const FUNCTION_LABEL = ['
Labels'];
const FUNCTION_LAB = [FUNCTION_LAB0, FUNCTION_LAB1, FUNCTION_LAB2,
FUNCTION_LAB3, FUNCTION_LAB4, FUNCTION_LAB5, FUNCTION_LABEL];
const T_MIN_LAB = ['', '
Minimum θ ',
'
Maximum t ']; // primarily for grapher
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'];
// used in "w" words to allow values in words
const MODE_COLORS = ["Crimson", "SaddleBrown", "Indigo", "Green"];
// for chart type
// 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 = 28; // location of the name in examples
// charter variables
let dataValues = [];
let scatterValues = [];
let legend = []; // array of legends for data arrays
let showLegend = false;
let legendRect;
let legendX, legendY; // upper left corner of legend (absolute)
let numDataRows; // number of rows of data used in chart
let numColumns; // number off columns in column chart
let spacing = 0;
let labels = [];
let yAxisOffset;
let usedDataRows = 0; // for column and pie charts
let connectChk, valueChk, percentChk, labelChk;
//let draggingMotion = true;
let bVals = []; // bottom values vertical bars
const LINE_MODE = 0;
const COLUMN_MODE = 1;
const PIE_MODE = 2;
const SCATTER_MODE = 3;
const PIE_COLORS = ["#f00", "#66f", "#0f0", "#ff0", "#f0f", "#0ff",
"#fc0", "#f0c", "#0fc", "#cf0", "#c0f", "#0cf",
"#c00", "#0c0", "#00c", "#cc0", "#c0c", "#0cc",
"#8f0", "#80f", "#08f", "#f80", "#f08", "#0f8",
"#800", "#080", "#008", "#8c0", "#80c", "#08c",
"#c80", "#c08", "#0c8", "#880", "#808", "#088"
]; // for PIE charts
const LEGEND_IGNORE = "`";
function setup() {
p5left = document.getElementById("p5-left");
p5right = document.getElementById("p5-right");
can1 = createCanvasClass(430, 430); // must be square
can1.doubleClicked(dblClick);
can1.parent(p5left);
can1.mouseClicked(clearErrorMsg);
can2 = createCanvasClass(430 * enlargeRatio, 430 * enlargeRatio);
big = document.getElementById("big");
can2.parent(big);
can2.mouseClicked(clearErrorMsg);
can2.style("display: none");
myTextAlign(CENTER, BASELINE);
spacer = createDiv(" ");
spacer.parent(big);
spacer.style("display: none");
numPts = Math.floor(1.1 * numDiv);
let radio;
// start left hand info display below the graph
let s0 = createSpan('Click "Draw the chart" after changing values.'
+ '
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);
slider = createSlider(0, 100, 50);
slider.parent(p5left);
slider.changed(sSliderChanged);
// create display for values 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
s2 = createSpan(MODE_LAB[mode]);
s2.parent(p5right);
s2a = createSpan("
");
s2a.parent(p5right);
radio = HTMLforRadioButton("modeResult", // mode is the graph type
''
+ 'Line ',
"getRadioValueMode()", true, 0);
lineRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'Column ',
"getRadioValueMode()", false, 1);
columnRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'Pie ',
"getRadioValueMode()", false, 2);
pieRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'XY Scatter ',
"getRadioValueMode()", false, 3);
scatterRadio = displayHTMLElements(radio, p5right, "inline-block");
// display Data (function) labels and inputs
let s3 = createSpan("
To change data, 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);
}
let s3a = createSpan("
");
s3a.parent(p5right);
connectChk = createCheckbox("Connect", false);
connectChk.parent(p5right);
connectChk.style("display: inline-block");
labelChk = createCheckbox("Label", false);
labelChk.parent(p5right);
labelChk.style("display: inline-block");
valueChk = createCheckbox("Value", false);
valueChk.parent(p5right);
valueChk.style("display: inline-block");
percentChk = createCheckbox("Percent", false);
percentChk.parent(p5right);
percentChk.style("display: inline-block");
setOptions();
let s3e = createSpan("
");
s3e.parent(p5right);
let chartBtn = createButton("Draw the chart");
chartBtn.parent(p5right);
chartBtn.mousePressed(drawChart);
// create a help link
let s4 = createSpan('
'
+ 'Help for the Charter app.
');
s4.parent(p5right);
// create input for max and mins for x and y
instLabel = createSpan("
The minimum and maximum x and y values will"
+ "
be calculated automatically if you leave them"
+ '
blank. Click "Draw the chart" after changes.');
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 s4a = createSpan("
Double click 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);
equalSpaceChk = createCheckbox("Equal spacing ", false);
equalSpaceChk.parent(p5right);
equalSpaceChk.changed(equalSpaceChanged);
allowLoopChk = createCheckbox("Allow motion", false);
allowLoopChk.parent(p5right);
allowLoopChk.changed(allowLoopChanged);
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");
// 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 saving 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(true); // also checkes mins and maxs
frameRate(20);
// initialize arrays xVals and yVals which hold points to be plotted
for (let i = 0; i < NUM_DATA; i++) {
xVals[i] = [];
yVals[i] = [];
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
}
// initialize legend
legendRect = new Rectangle(4, 100, 70, 50, "ivory", "black", can1);
// finally, initialize the examples
setupExamples();
displayExamples();
} // setup
function draw() {
// set background color for plot area
myBackground("lightblue");
// warning if no data defined and hence no graph can be drawn
if (noFDefined) {
// Has there been an error?
if (errorMsg != "") {
displayError();
} else {
myText("You need to provide some data points.", can1.width/2,
can1.height/2);
}
noLoop();
return;
}
// update value of a .. e, s and the mins and maxs
valueChanged(false); // also checks min and max
if (errorMsg != "") {
displayError();
return;
}
// *** for each data set determine points to be plotted ***
for (let i = 0; i < NUM_DATA; i++) { //for each function check
// initialize, mins, and maxs
if (fDefined[i] && fInput[i].value().substring(0,1) == "w") {
xVals[i] = [];
yVals[i] = [];
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
}
// check for special effects before any drawing anything
if (f[i] != "") {
let firstChar = f[i].charAt(0).toLowerCase();
let first2Char = f[i].substring(0, 2).toLowerCase();
if (first2Char == "r=") { // circle
gCircle(i);
} else if (firstChar == "l") { // line segment
gLineSegment(i);
} else if (firstChar == "p") { // point
gPoint(i);
} else if (firstChar == "w") { //word (text, label)
gWord(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 (xMinVals > 0 && xMaxVals < 1.4 * (xMaxVals - xMinVals)) {
xMinVals = 0;
}
// *** start checking max and mins ***
// 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 (equalSpaceChk.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 ***
yAxisOffset = 0;
if (mode == COLUMN_MODE) {
columnAdjustAxis();
yAxisOffset = 40;
} else if (mode == LINE_MODE) {
yAxisOffset = 20;
}
locateAxes();
if (mode != PIE_MODE) {
myStroke("black");
// x axis
if (mode == SCATTER_MODE) {
myLine(0, xAxisLoc, can1.width, xAxisLoc); // xAxis
} else {
myLine(yAxisLoc - yAxisOffset, xAxisLoc, can1.width, xAxisLoc); // xAxis
}
// y axis
if (mode == SCATTER_MODE || miniY < 0) {
myLine(yAxisLoc - yAxisOffset, 0, yAxisLoc - yAxisOffset,
can1.height); // y axis or negative y values
} else {
let py = map(0, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
myLine(yAxisLoc - yAxisOffset, 0, yAxisLoc - yAxisOffset, py);
}
myFill("blue");
xHashMarks();
yHashMarks();
myNoStroke();
}
let px, py;
usedDataRows = 0; // used in verticalBar
// *** start drawing for each function ***
for (i = 0; i < NUM_DATA; i++) { //for each data row
myStroke(colors[i]);
if (xVals[i][0] == "l") { // draw lines
drawLines(i);
} else if (xVals[i][0] == "r") { // check for circular curves
drawCircles(i); // for r= data
} else if (xVals[i][0] == "pi") { // for pie charts
drawPie(i, can1.width/2, can1.height/2);
} else if (xVals[i][0] == "p") { // for points
drawPoint(i);
} else if (xVals[i][0] == "vb") { // vertical bars (columns)
drawVerticalBar(i);
} else if (xVals[i][0]== "w") { // words
drawWord(i);
}
}
if (showLegend) {
legendRect.addInfo(legend, colors, numDataRows);
legendRect.show(can1.mouseX, can1.mouseY);
}
// Has there been an error?
if (errorMsg != "") {
displayError();
}
// check to see if we are looping
myText(loopCnt, 30, 15);
if (loopCnt > 3 && !allowLoopChk.checked()) {
noLoop();
loopCnt = 0;
} else {
++loopCnt;
}
} // draw
function drawChart() {
inputF(); //makes sure things are up to date
getPoints();
redraw();
loop();
} // drawChart
function getPoints() {
try{
if (chartError) {
errorMsg = "";
chartError = false;
}
inputF(); // In case Enter not pressed after supplying data
numDataRows = 0;
numColumns = 0; // for column charts
showLegend = false;
let barChartLine;
let stacked; // used for stacked data sets
// initialize legend, values will be overwritten if they are provided
for (let i = 0; i < NUM_DATA; i++) {
legend[i] = "Data " + i;
}
let increment; // normally 1 but 2 for scatter charts
if (mode == SCATTER_MODE) {
increment = 2;
} else {
increment = 1;
}
// reset min and maxes
for (let i = 0; i < NUM_DATA; i++) { // ALL min/max must be reset
resetMinMax(i, xMinV, yMinV, xMaxV, yMaxV);
}
// process data row by row
for (let i = 0; i < NUM_DATA; i += increment) {
if (!fDefined[i]) {
// ignore legend for blank rows
legend[i] = LEGEND_IGNORE;
if (mode == SCATTER_MODE) {
legend[i+1] = LEGEND_IGNORE;
}
xVals[i] = [];
yVals[i] = []; ////////////////////////////
} else { // the data is not blank
barChartLine = false; // assume this is not a line in bar chart
stacked = false;
// first check for special words, lines, points, or circles
let firstChar = fInput[i].value().substring(0,1).toLowerCase();
if (firstChar == "w" || firstChar == "l" // For special functions
|| firstChar == "p" || firstChar == "r") {
continue; // exit loop, the g function will LEGEND_IGNORE them
};
// likewise, ignore y data line legend in scatter charts
if (increment == 2) { // ignore odd data for scatter mode
legend[i+1] = LEGEND_IGNORE;
}
// process the data
xVals[i] = [];
yVals[i] = [];
bVals[i] = [];
numDataRows++; // In SCATTER_MODE a row of x and a row of y
// values count as one
let tempX = split(fInput[i].value(), ",");
let tempY;
if (tempX[0].charAt(0) == "@") {
legend[i] = trim(tempX[0]).substring(1);
tempX.shift(); // shift left to remove legend element
showLegend = true;
}
// allow for lines in combination charts
if (tempX[0].substring(0, 1).charAt(0) == "_") {
barChartLine = true;
tempX.shift();
}
// allow for lines in stacked charts
if (tempX[0].substring(0,2).toLowerCase() == "st") {
stacked = true;
tempX.shift();
if (i == 0) {
setErrorMsg("The Data 0 can not be stacked");
chartError = true;
loop();
return;
} else if (mode == PIE_MODE || mode == SCATTER_MODE) {
setErrorMsg("Data in pie and stacked charts can not be stacked");
chartError = true;
loop();
return;
}
numColumns = numColumns - 1;
}
bVals[i][0] = stacked; // tell drawVerticalBars if data is stacked
if (mode == SCATTER_MODE) {
tempY = split(fInput[i+1].value(), ",");
xVals[i+1] = []; // otherwise draw() would expect some values
} else {
tempY = tempX;
}
// x data - by default except for scatter charts
for (let j = 0; j < tempX.length; j++) {
let item = trim(tempX[j])
if (mode == SCATTER_MODE) {
xVals[i][j+1] = myEval(tempX[j]);
} else {
xVals[i][j+1] = j;
}
xMinV[i] = min(xVals[i][j+1], xMinV[i]);
xMaxV[i] = max(xVals[i][j+1], xMaxV[i]);
}
// y data
for (let j = 0; j < tempY.length; j++) {
if (stacked) {
yVals[i][j+1] = myEval(tempY[j]) + yVals[i-1][j+1];
bVals[i][j+1] = yVals[i-1][j+1];
} else {
yVals[i][j+1] = myEval(tempY[j]);
bVals[i][j+1] = 0; // only useful for column charts
}
yMinV[i] = min(yVals[i][j+1], yMinV[i]);
yMaxV[i] = max(yVals[i][j+1], yMaxV[i]);
}
// warn if scatter chart data not same length
if (mode == SCATTER_MODE && xVals[i].length != yVals[i].length) {
setErrorMsg("The number of values in Data"
+ i + " and Data" + (i+1) + " must be equal.");
chartError = true;
loop();
return;
// set special effect for column and bar charts
} else if (mode == COLUMN_MODE) {
if (barChartLine) {
xVals[i][0] = "p";
connectChk.checked(true);
} else {
numColumns ++;
xVals[i][0] = "vb"; // The first xVals for column charts is "vb"
}
// for Vertical column (bar) chart
} else if (mode == PIE_MODE) {
xVals[i][0] = "pi";
if (yMinV[i] < 0) {
setErrorMsg("Pie charts can not have negative data values.");
chartError = true;
}
xMinV[i] = 0; // reset limits for pie charts
xMaxV[i] = 10;
yMinV[i] = 0;
yMaxV[i] = 10;
} else { // line mode or scatter mode
xVals[i][0] = "p"; // The first xVals is "p" line and scatter plots
}
yVals[i][0] = xVals[i].length - 1; // The first yVals is # of points
}
// adjust left margin for line and colum charts
if (mode == LINE_MODE || mode == COLUMN_MODE) {
xMinV[i] = -.3;
}
}
} catch (e) {
setErrorMsg("Error in chart data. Namely " + e + "\nstack:"
+ e.stack + "\nline: " + e.line);
chartError = true;
loop();
}
} // getPoints
function setOptions() {
// set options for charts
switch (mode) {
case LINE_MODE:
connectChk.checked(true);
labelChk.checked(true);
valueChk.checked(false);
percentChk.checked(false)
break;
case PIE_MODE:
connectChk.checked(false);
labelChk.checked(true);
valueChk.checked(true);
percentChk.checked(false);
break;
case COLUMN_MODE:
case SCATTER_MODE:
connectChk.checked(false);
labelChk.checked(true);
valueChk.checked(false);
percentChk.checked(false);
break;
}
} // setOptions
function mousePressed() {
// for dragging legend rectangles - must allow motion
allowLoopChk.checked(true);
redraw(); // required because legend rectangle only drawn in draw()
legendRect.pressed(can1.mouseX, can1.mouseY); // determines if really a drag
} // mousePressed
function mouseReleased() {
/// terminate drag
allowLoopChk.checked(false);
legendRect.notPressed();
} // mouseReleased
function drawLines(i) {
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);
}
} // drawLines
function drawCircles(i) {
let lastPx = xVals[i][1];
let lastPy = yVals[i][1];
myStroke(colors[i]);
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;
}
} // drawCircles
function drawPie(i, x, y) {
// This function is called by draw()
let d = 500;
let r = d/2; // this full size i = 0
r = r * (10 - i)/10;
let numVals = xVals[i].length;
let start = [];
// determine the pie shapes
let sum = 0;
for (let j = 1; j < numVals; j++) { // yVals[i][0] is number values
sum += yVals[i][j];
}
// start determines the beginning side of the pie
start[1] = -PI; // first pie shape above left of center
for (let j = 1; j < numVals; j++) {
start[j+1] = start[j] + TWO_PI * yVals[i][j]/sum;
}
// draw the pie shapes
for (let j = 1; j < numVals; j++) {
myStroke("black");
myFill(PIE_COLORS[(j-1)%PIE_COLORS.length]);
myArc(x, y, r, r, start[j], start[j+1], PIE);
let ave = (start[j] + start[j+1])/2;
let c = r * cos(ave)/2;
let s = r * sin(ave)/2;
let x1 = c + x;
let y1 = s + y;
let x2 = 1.3 * c + x;
let y2 = 1.3 * s + y;
let x3 = 1.5 * c + x;
let y3 = 1.5 * s + y;
let x4 = .83 * c + x;
let y4 = .83 * s + y;
myFill("black");
if (i == 0) {
if (labelChk.checked()) {
myTextAlign(CENTER, CENTER);
myLine(x1, y1, x2, y2);
if (j-1 < labels.length && labels.length > 0 ) {
myText(labels[j-1], x3, y3);
} else {
myText(j, x3, y3);
}
}
}
if (valueChk.checked()) {
if (percentChk.checked()) {
myText(round(1000*yVals[i][j]/sum)/10 + "%", x4, y4); // nearst tenth
} else {
myText(yVals[i][j], x4, y4);
}
}
}
myTextAlign(CENTER, BOTTOM)
} // drawPie
/**
* This fuction is called in draw in order to plot the points specfied by a
* "p" data line. Also used on line and scatter charts.
*/
function drawPoint(i) {
let pxSave, pySave;
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);
if (connectChk.checked() && j > 0) {
myLine(pxSave, pySave, px, py);
}
myStrokeWeight(7);
myPoint(px, py);
myStrokeWeight(1);
pxSave = px;
pySave = py;
if (valueChk.checked()) {
myText(yVals[i][j+1], px, py - 10);
}
};
} // drawPoint
function drawVerticalBar(i) {
let spacing = map(1, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET)
- map(0, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
// is at LOC_SET so this the distance between 1 and 0
let columnWidth = spacing/(2 * numColumns);
//// let pZero = map(0, miniY, maxiY, can1.height - LOC_OFFSET, LOC_OFFSET);
// The location of the bottom of the bar
for (let j = 0; j < yVals[i][0]; j++) {
myFill(colors[i]); // color for bar
let xx = xVals[i][j+1]; // top left corner of bar
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);
let bMin = max(bVals[i][j+1], minY); // In case minY is bigger
let pb = map(bMin, miniY, maxiY, can1.height - LOC_OFFSET,
LOC_OFFSET);
let offset = (-numColumns + 2 * usedDataRows) * columnWidth/2
if (bVals[i][0]) {
offset = offset - columnWidth;
}
myRect(px + offset, py, columnWidth, pb - py);
if (valueChk.checked()) {
myText(yVals[i][j+1], px + offset + columnWidth/2, py - 10);
}
}
// increment usedDataRows unless data is stacked
if (!bVals[i][0]) {
usedDataRows++;
}
} // drawVerticalBar
function drawWord(i) {
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);
}
} // drawWord
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
// yVals[i][j+1]: y value of one end point
// xVals[i][j+2]: x value of other end point
// yVals[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]);
}
legend[i] = LEGEND_IGNORE;
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
}
} // gLineSegment
function gCircle(i) {
// Draws circles with radius r)
// May be used in all modes.
// format:
// r = thetaVal, center x (optional), center y (optional)
// Draws a circle with center at (centerX, centerY)
// If the centers are not provided, the center is the orgin,
// Result:
// xVals[i][0] = "r"
// xVals[i][j+1]: x value of point on the curve (number of evaluation pts.)
// xVals[i][j+1]: y value of point on the curve
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
resetMinMax(i, xMinV,yMinV, xMaxV, yMaxV);
let st = f[i].substring(2, 10000);
let sts = st.split(",");
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("r= must be followed by the r value\n and optionally"
+ " by both ; center x; center theta");
functionError = true;
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;
legend[i] = LEGEND_IGNORE;
} 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 {
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');
functionError = true;
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]);
}
legend[i] = LEGEND_IGNORE;
} 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) {
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;
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;
}
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], the[i]);
}
st = myReplaceAll(st, '^$', ';');
yVals[i][2 * j + 2] = st;
}
legend[i] = LEGEND_IGNORE;
} catch (err) {
errorMsg = 'Error in declaring a "word". \nNamely: ' + err;
functionError = true;
loop();
}
} // gWord
function zoomIn() {
// zooms out with the zoomRatio 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(true);
loop();
} // centerGraph
// 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(allowLoop) {
// 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 = eval(minX);
}
maxX = xMaxInput.value() ;
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);
}
if (allowLoop) {
loop();
}
} // checkMinMax
function resetMinMax(i, aXMin, aYMin, aXMax, aYMax) {
// Normally, these are set to +/- infinity but for charter they are set to 0
// to make 0 a minimum or maximum value.
aXMin[i] = 0;
aYMin[i] = 0;
aXMax[i] = -0;
aYMax[i] = -0;
} // resetMinMax
function inputF() {
let i;
// check for previous functionError. Clear it. It will be reset if needed
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
// called by many other functions
noFDefined = true;
for (i = 0; i < NUM_DATA; i++) {
f[i] = trim(fInput[i].value());
fDefined[i] = (f[i] != '');
if (fDefined[i]) {
noFDefined = false;
}
}
if (fInput[6].value() != "") {
labels = split(fInput[6].value(), ",");
for (let i = 0; i < labels.length; i++) {
labels[i] = trim(labels[i]);
}
} else {
labels = [];
}
loop();
} catch(e) {
setErrorMsg("There was an error in function inputF when i = " + i
+ ". Namely " + e);
functionError = true;
}
} // inputf
// Adjust x axis to allow for bars
function columnAdjustAxis() {
// Note: miniX == 0 to begin
let num = maxX + 1; // The largest number of columns
let columnWidth = maxiX/num; // Appoximate - will be smaller
if (miniX == 0) {
miniX = -columnWidth/2;
}
maxiX += columnWidth/2;
} // columnAdjustAxis
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);
}
// locate y axis
// adjust y axis for Column and line plot
let yLoc;
if (mode == LINE_MODE || mode == COLUMN_MODE) {
if (maxiX > 0) {
miniX = map(-40, 0, width, 0, max(maxiX)); // allow space left of y axis
yLoc = 0;
} else { // in case there is only 1 point
miniX = -1;
maxiX = 2;
}
}
if (miniX >= 0) {
yAxisLoc = LOC_OFFSET;
} else if (maxiX <= 0) {
yAxisLoc = can1.width - LOC_OFFSET;
} else {
yAxisLoc = map(yLoc, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
}
} // locateAxes
// draw x hashMarks and label them
function xHashMarks() {
let num;
let currentWidth = maxiX - miniX;
let logCW = floor(Math.log10(currentWidth));
let roundOffFactor = 100; // up to 2 decimal places
let spacingMin = min(1, .5 * 10 ** logCW);
let spacing = max(round(currentWidth/10), spacingMin);
if (spacing < 1) {
spacing = 1;
}
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
}
if (mode != SCATTER_MODE) {
num = maxX;
} else {
num = maxX + 1;
}
let startLabels = miniX;
if (mode != SCATTER_MODE) {
startLabels = 0;
}
for (let i = 0; i <= num; i++) {
let xxx = float(startLabels) + float(i); // draw hashmark
if (labelChk.checked() || mode == SCATTER_MODE) {
if (xxx != 0 || mode != SCATTER_MODE) {
let px = map(xxx, miniX, maxiX, LOC_OFFSET, can1.width - LOC_OFFSET);
myLine(px, xAxisLoc - 3, px, xAxisLoc + 3);
if (i < labels.length && mode != SCATTER_MODE) {
myText(labels[i], px, xAxisLoc + 14);
} else {
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 - yAxisOffset, py, yAxisLoc + 3 - yAxisOffset, py);
if (yAxisLoc <= 25) {
myText(round(roundOffFactor * yyy)/roundOffFactor,
yAxisLoc + 20 - yAxisOffset, py + 5); // put y value right of axis
} else {
myText(round(roundOffFactor * yyy)/roundOffFactor,
yAxisLoc - 20 - yAxisOffset, 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(allowLoop) {
// called when a value is changed
// also calls checkMinMax
if (valueError) {
errorMsg = "";
valueError = false;
}
try {
valInp[5].value(slider.value());
s = valInp[5].value();
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value()); // eval of blank returns NaN
} else {
e = 0;
}
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value()); // eval of blank returns NaN
} else {
d = 0;
}
if (trim(valInp[2].value()) != "") {
c = eval(valInp[2].value());
} else {
c = 0;
}
if (trim(valInp[1].value()) != "") {
b = eval(valInp[1].value());
} else {
b = 0;
}
if (trim(valInp[0].value()) != "") {
a = eval(valInp[0].value());
} else {
a = 0;
}
// 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());
}
the[5] = s; // used for replacement values in gWord
the[4] = e;
the[3] = d;
the[2] = c;
the[1] = b;
the[0] = a;
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(allowLoop); // checkMinMax causes a 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() and modeChanged()
let u;
aMode = 0; // Temp fix as only mode = 0 defined
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;
case 6: u = FUNCTION_LABEL[aMode];
break;
}
fLabel[i].html(u);
} // functionLab
function sSliderChanged() {
valInp[5].value(slider.value());
valueChanged(true);
} // sSliderChanged
function sInput() {
let s = this.value();
let sVal = int(s);
if (sVal == s && sVal >= 0 && sVal <= 100) {
slider.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("charter", "jpg");
}
// an alert with data needed for an example
// based on the current graph
function showExampleData() {
let s = " example[??] = ["
for (let i = 0; i < NUM_F_INPUT; i++) {
s += '"' + myReplaceAll(fInput[i].value(), ',', '^$') + '",';
}
s += '\n ';
s += mode + ',\n '; // add spaces to indent output
if (showLegend) {
let legendLoc = legendRect.getLocation();
s += legendLoc[0] + ',' + legendLoc[1] + ', ';
} else {
s += 0 + ',' + 0 + ', ';
}
s += '"' + xMinInput.value() + '", "' + xMaxInput.value() + '", ';
s += '"' + yMinInput.value() + '", "' + yMaxInput.value() + '", ';
s += equalSpaceChk.checked() + ', ';
s += allowLoopChk.checked() + ',\n ';
for (let i = 0; i < 5; i++) {
s += '"' + valInp[i].value() + '", ';
}
s += '"' + slider.value() + '", ';
s += '"' + modeAngle + '", ';
s += '"' + numPtsInput.value() + '", \n ';
s += connectChk.checked() + ',';
s += labelChk.checked() + ',';
s += valueChk.checked() + ',';
s += percentChk.checked() + ', \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 [].';
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][7] = mode;
let theMode = int(example[ex][6]);
if (showLegend) {
let legendLoc = legendRect.getLocation();
example[ex][8] = legendLoc[0];
example[ex][9] = legendLoc[1];
} else {
example[ex][8] = 0;
example[ex][9] = 0;
}
example[ex][10] = xMinInput.value();
example[ex][11] = xMaxInput.value();
example[ex][12] = yMinInput.value();
example[ex][13] = yMaxInput.value();
example[ex][15] = equalSpaceChk.checked();
example[ex][16] = allowLoopChk.checked();
for (let i = 0; i < 6; i++) { //
example[ex][16+i] = trim(valInp[i].value()); // includes s
}
example[ex][22] = modeAngle; // mode angle not used
example[ex][23] = numPtsInput.value();
example[ex][24] = connectChk.checked();
example[ex][25] = labelChk.checked();
example[ex][26] = valueChk.checked();
example[ex][27] = percentChk.checked();
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] .. [6]: functions f0 .. f5, & f6 labels(strings)
// [7]: the mode - 0: Line, 1: Column, 2: Pie,, 3: Scatter
// [8] .. [9]: legend location (x, y) (absolute location)
// [10] .. [11]: min and max x (number or string)
// [12] .. [13]: min and max y (number or string)
// [14]: equal spacing (true or false)
// [15]: allow motion (true or false)
// [16] .. [20]: a .. e (number or string)
// [21]: value of s (sets slider);
// [22]: radians/degrees option ("radians" or "degrees")
// [23]: number of evaluation points (number or string)
// [24] .. [27]: connect, label, value, percent (true or false)
// [28]: display title (string) ( EXAMPLE_NAME = 24 )
//
// Note: "^$" have replaced ",", useExample() automatically replaces them
example[0] = ["6^$5^$3^$5","4^$3^$4^$4","1^$2^$1^$3^$4",
"3^$1^$2","w 1^$6.3^$LINE CHART","","a^$b^$c^$d",
0,
0, 1, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
true, true, false, false,
"Simple Line Chart"];
example[1] = ["3^$4^$1^$5","4^$2^$6^$4","2^$3^$7^$5","3^$1^$2",
"","w .8^$6.5, COLUMN CHART","a^$b^$c^$d",
1,
0, 1, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false, true, false, false,
"Simple Column chart"];
example[2] = ["3^$4^$1^$5","","","","w 5^$9.4^$PIE CHART","",
"red^$blue^$green^$yellow ",
2,
0, 1, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false, true, true, false,
"Simple Pie chart"];
example[3] = ["1^$3^$5^$6^$7","2^$3^$5^$1^$2","2^$3^$4^$7^$9",
"3^$5^$1^$6^$4","w 5^$6.7^$SCATTER CHART", "", "",
3,
0, 0, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false, true, false, false,
"Simple Scatter Chart"];
example[4] = ["1^$3^$5^$6^$7","2^$3^$5^$1^$2","2^$3^$4^$7^$9",
"3^$5^$1^$6^$4","w 5^$6.7^$CONNECTED SCATTER CHART", "", "",
3,
0, 0, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
true, true, false, false,
"Simple Connected Scatter Chart"];
example[5] = ["@2020,400^$300^$450^$100^$500",
"@2021,300^$200^$250^$400^$300",
"w 2^$15^$Fruit Production", "", "", "",
"apples^$ oranges^$ cherries^$ peaches^$ pears",
0,
4, 100, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
true,true,false,false,
"Fruit Production - line chart"];
example[6] = ["@2020,400^$300^$450^$100^$500",
"@2021,300^$200^$250^$400^$300",
"w 1^$500^$ Fruit Production","","","",
"apples^$ oranges^$ cherries^$ peaches^$ pears",
1,
346, 343, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false,true,false,false,
"Fruit Production - column chart"];
example[7] = ["@2020 label 1^$400^$300^$450^$100^$500",
"@2021 label 1^$st^$300^$200^$250^$400^$300",
"@2020 label 2^$300^$200^$470^$200^$300",
"@2021 label 2^$st^$350^$240^$350^$150^$250",
"w 2^$ 860^$ label 1: Best Fruits; label 2: Fine Foods",
"w 2^$900^$ Fruit Production",
"apples^$ oranges^$ cherries^$ peaches^$ pears",
1,
346, 303, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false,true,false,false,
"Fruit Production - Stacked column chart"];
example[8] = ["@Sales^$ 351000^$ 213000^$ 341000^$ 400000",
"@Profit^$_^$132000^$ 75000^$ 142000^$ 192000",
"","","","","2017^$ 2018^$ 2019^$ 2020",
1,
330, 350, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
true,true,false,false,
"Sales and Profit - combination column and line chart"];
example[9] = ["@Females^$0^$1^$2^$3^$4^$6^$8^$10^$12^$14^$16^$18",
"19.5^$29^$33.75^$37.25^$39.75^$45.5^$50.5^$54.5^$59.5^$63.25^$64"
+ "^$64.25",
"@Males^$0^$1^$2^$3^$4^$6^$8^$10^$12^$14^$16^$18",
"20^$30^$34.5^$38^$40.25^$45.5^$50.5^$54.75^$58.75^$64.25^$68^$69.5"
,"w 17^$ 3^$ Age in Years","w 5^$ 67^$ Average Height in Inches","",
3,
242, 238, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
true,false,false,false,
"Average Height for Kids - scatter chart"];
example[10] = ["821^$734^$730^$ 699^$385","","","","",
"w 5^$9.4^$Enrolment by Class Year^$color^$6^$",
"First year^$ Sophomore^$Junior^$ Senior^$ Graduate",
2,
0, 0, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false,true,true,false,
"Enrollment by Class Year - pie chart"];
example[11] = ["@Planned^$ a^$ b^$ c^$ 4000^$ e","w 5^$9.3^$Total Planned Budget = $^d","","@Current^$ 4300^$ 3500^$ 1500^$ 2500^$ 1000","L 8.35^$0.6^$ 6.9^$2.4^$ 8.35^$0.1^$ 5.4^$2.75","r= .5^$ 4.2^$ 3.5","Housing^$ Food^$ Clothing^$ Transportation^$ Donations",
2,
348,376, "", "", "", "", false, false,
"4800", "4200", "1800", "a + b + c + 4000 + e", "20 * s", "50", "radians", "200",
false,true,true,false,
"Budget - Enhanced pie chart"];
example[12] = ["@Laptops^$3^$5^$1^$2^$2^$5",
"@Desktops^$2^$4^$2^$6^$4^$5",
"@Phones^$1^$3^$6^$3^$2^$1",
"p 1.95^$ 6.1^$ 2.15^$6.25^$ 2.35^$ 6.1",
"L 1^$6.4^$1.8^$5.9",
"w 1^$ 6.5^$ Record one day phone sales",
" Mon^$ Tues^$ Wed^$ Thu^$ Fri^$ Sat",
1,
4, 100, "", "", "", "", false, false,
"", "", "", "", "", "50", "radians", "200",
false,true,false,false,
"Weekly Sales - Enhanced column chart"];
} // setupExamples
function displayExamples() {
let heading = createElement("h3","Examples");
heading.parent(p5left);
let radio;
for (let i = 0; i < example.length; i++) {
radio = HTMLforRadioButton("examplesResult",
''
+ myReplaceAll(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++) { /////maybe this should be changed to NUM_F_INPUT and items renumbered
fInput[i].value(myReplaceAll(example[ex][i], "^$", ","));
}
mode = example[ex][7];
///// modeRadio.value(example[ex][6]); // doesn't work radio problems ??
if (example[ex][7] == 0) {
lineRadio.checked = true;
} else if (example[ex][7] == 1) {
columnRadio.checked = true;
} else if (example[ex][7] == 2) {
pieRadio.checked = true;
} else if (example[ex][7] == 3) {
scatterRadio.checked = true;
}
// 8 & 9 taken care of later
xMinInput.value(example[ex][10]);
xMaxInput.value(example[ex][11]);
yMinInput.value(example[ex][12]);
yMaxInput.value(example[ex][13]);
equalSpaceChk.checked(example[ex][14]);
allowLoopChk.checked(example[ex][15]);
for (let i = 0; i < 5; i++) {
valInp[i].value(example[ex][16 + i]);
}
slider.value(example[ex][21]);
getRadioValueAngle()
numPtsInput.value(example[ex][23]);
updateMode(); // must be done before setting connect, label, value, percent
connectChk.checked(example[ex][24]);
labelChk.checked(example[ex][25]);
valueChk.checked(example[ex][26]);
percentChk.checked(example[ex][27]);
// the name is not displayed anywhere except in example list
valueChanged(true); // also check min and max
drawChart();
if (example[ex][8] > 0) {
showLegend = true;
legendRect.setLocation(example[ex][8], example[ex][9]);
legendRect.addInfo(legend, colors, legend.length);
} else {
showLegend = false;
}
redraw();
} // useExample
function getRadioValueAngle() {
modeAngle = getRadioValue("angles");
if ( modeAngle == "RADIANS") {
angleMode(RADIANS);
modeAngle = RADIANS;
} else {
angleMode(DEGREES);
modeAngle = DEGREES;
}
valueChanged(true);
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
s2.html(MODE_LAB[mode]);
// label the function box inputs correctly
/*
for (let i = 0; i < NUM_F_INPUT; i++) {
functionLab(i, mode);
}
*/
valueChanged(true); // also checks MinMax
inputF();
setOptions()
loop();
} // updateMode
// 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
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
// myEval is special in that returns 0 if the string is empty,
// Causes an alert if the string is invalid but then returns 0
function myEval(string) {
let val;
try {
if (trim(string) == "") {
val = 0;
} else {
val = eval(string);
}
} catch (er) {
alert('********* Invalid data namely "' + string + '"\n' + er);
val = 0;
}
return val; // myEval
} // myEval
/**
* 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