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