From 996337e779688e45ae26ebed397588bc4e76048f Mon Sep 17 00:00:00 2001 From: Simon B Date: Thu, 29 Jul 2021 21:43:29 +0100 Subject: [PATCH 1/3] #20 Added option to select colour of Keyword tags --- ProcessingGrapher/FileGraph.pde | 2 +- ProcessingGrapher/Filters.pde | 10 +- ProcessingGrapher/ProcessingGrapher.pde | 100 ++++++++++++++-- ProcessingGrapher/SerialMonitor.pde | 125 ++++++++++++++++---- ProcessingGrapher/data/user-preferences.xml | 8 +- 5 files changed, 202 insertions(+), 43 deletions(-) diff --git a/ProcessingGrapher/FileGraph.pde b/ProcessingGrapher/FileGraph.pde index 0c312a4..7706473 100644 --- a/ProcessingGrapher/FileGraph.pde +++ b/ProcessingGrapher/FileGraph.pde @@ -484,7 +484,7 @@ class FileGraph implements TabAPI { drawDatabox(dataColumns[i], iL, sT + (uH * tHnow), iW - (40 * uimult), iH, tH); // Remove column button - drawButton("x", c_sidebar_button, iL + iW - (20 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); + drawButton("✕", c_sidebar_button, iL + iW - (20 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); // Hide or Show data series color buttonColor = c_colorlist[i-(c_colorlist.length * floor(i / c_colorlist.length))]; diff --git a/ProcessingGrapher/Filters.pde b/ProcessingGrapher/Filters.pde index af635b8..2767805 100644 --- a/ProcessingGrapher/Filters.pde +++ b/ProcessingGrapher/Filters.pde @@ -78,9 +78,9 @@ class Filters { // 1D Total Variance denoiser case 2: { - ValidateInput userInput = new ValidateInput("Set Denoising Filter Amount", "Lambda Value:", str(0.5)); - userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe Lambda value should be between 0 - 1"); - if (userInput.checkDouble(userInput.GT, 0, userInput.LTE, 1)) { + ValidateInput userInput = new ValidateInput("Set Denoising Filter Amount", "Lambda Value:", str(1)); + userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe Lambda value should be a number greater than 0."); + if (userInput.checkDouble(userInput.GT, 0)) { double filterValue = userInput.getDouble(); DenoiseTv1D denoiseFilter = new DenoiseTv1D(); outputData = denoiseFilter.process(signalData, filterValue); @@ -91,7 +91,7 @@ class Filters { // Low pass filter case 3: { ValidateInput userInput = new ValidateInput("Set the 3dB Cutoff Frequency", "Filter Frequency (Hz):", str(100)); - userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe cutoff frequency should be a number above 0 Hz"); + userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe cutoff frequency should be a number above 0 Hz."); if (userInput.checkDouble(userInput.GT, 0)) { double filterValue = userInput.getDouble(); RcLowPass lowPassFilter = new RcLowPass(filterValue); @@ -103,7 +103,7 @@ class Filters { // High pass filter case 4: { ValidateInput userInput = new ValidateInput("Set the 3dB Cutoff Frequency", "Filter Frequency (Hz):", str(100)); - userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe cutoff frequency should be a number above 0 Hz"); + userInput.setErrorMessage("Error\nInvalid filter value entered.\nThe cutoff frequency should be a number above 0 Hz."); if (userInput.checkDouble(userInput.GT, 0)) { double filterValue = userInput.getDouble(); RcHighPass highPassFilter = new RcHighPass(filterValue); diff --git a/ProcessingGrapher/ProcessingGrapher.pde b/ProcessingGrapher/ProcessingGrapher.pde index 8f1a5d1..1bf0766 100644 --- a/ProcessingGrapher/ProcessingGrapher.pde +++ b/ProcessingGrapher/ProcessingGrapher.pde @@ -7,8 +7,8 @@ * @website https://wired.chillibasket.com/processing-grapher/ * * @copyright GNU General Public License v3 - * @date 9th May 2021 - * @version 1.2.4 + * @date 29th July 2021 + * @version 1.3.0 * * * * * * * * * * * * * * * * * * * * * * */ /* @@ -77,6 +77,7 @@ final color c_orange = color(230, 85, 37); final color c_lightgrey = color(134, 134, 138); final color c_grey = color(111, 108, 90); final color c_darkgrey = color(49, 50, 44); +final color c_black = color(0, 0, 0); // Select current colour scheme // 0 = light (Celeste) @@ -670,12 +671,83 @@ void drawLoadingScreen() { /******************************************************//** - * SIDEBAR MENU DRAWING FUNCTIONS + * @defgroup SidebarMenu + * @brief Sidebar Menu Drawing Functions * - * @info Functions used to draw the text, buttons and - * data-boxes on the sidebar menu of each tab + * Functions used to draw the text, buttons and + * data-boxes on the sidebar menu of each tab + * @{ *********************************************************/ + +void drawColorSelector(color currentColor, float lS, float tS, float iW, float iH) { + if (tS >= tabTop && tS <= height) { + rectMode(CORNER); + strokeWeight(1); + colorMode(HSB, iW, iW, iW); + int curX = -1, curY = -1; + for (int i = 0; i < iW; i++) { + for (int j = 0; j < iH; j++) { + color pointColor = color(i, j, iW); + if (currentColor == pointColor) { + curX = i; + curY = j; + } + stroke(pointColor); + point(lS + i, tS + j); + //line(lS + i, tS, lS + i, tS + iH); + } + } + colorMode(RGB, 255, 255, 255); + if (curX != -1 && curY != -1) { + stroke(c_black); + strokeWeight(uimult * 1.25); + noFill(); + ellipse(lS + curX, tS + curY, uimult * 10, uimult * 10); + } + } +} + +void drawColorBox3D(color baseColor, float lS, float tS, float iW, float iH) { + if (tS >= tabTop && tS <= height) { + rectMode(CORNER); + strokeWeight(1); + for (int i = 0; i < iW; i++) { + color horizontalColor = lerpColor(c_white, baseColor, (float) (i) / iW); + for (int j = 0; j < iH; j++) { + color verticalColor = lerpColor(horizontalColor, color(0), (float) (j) / iH); + stroke(verticalColor); + point(lS + i, tS + j); + } + } + } +} + + +void drawColorBox2D(color currentColor, color color1, color color2, float lS, float tS, float iW, float iH) { + if (tS >= tabTop && tS <= height) { + rectMode(CORNER); + strokeWeight(1); + int curPoint = -1; + for (int i = 0; i < iW; i++) { + color horizontalColor = lerpColor(color1, color2, (float) (i) / iW); + if (currentColor == horizontalColor) { + horizontalColor = c_sidebar_accent; + curPoint = i; + } + stroke(horizontalColor); + line(lS + i, tS, lS + i, tS + iH); + } + if (curPoint >= 0) { + stroke(c_sidebar_accent); + fill(c_sidebar_accent); + triangle(lS + curPoint, tS + iH, lS + curPoint - (3 * uimult), tS + iH + (6 * uimult), lS + curPoint + (3 * uimult), tS + iH + (6 * uimult)); + } + } +} + + + /** * Draw sidebar text * @@ -950,13 +1022,16 @@ void alertMessage(String message) { } } +/** @} */ /******************************************************//** - * KEYBOARD AND MOUSE INTERACTION FUNCTIONS + * @defgroup KeyboardMouse + * @brief Keyboard and Mouse interaction functions * - * @info Functions to manage user input and interaction - * using the keyboard or mouse + * Functions to manage user input and interaction + * using the keyboard or mouse + * @{ *********************************************************/ /** @@ -1180,13 +1255,16 @@ void keyReleased() { } } +/** @} */ /******************************************************//** - * SERIAL COMMUNICATION FUNCTIONS + * @defgroup SerialComms + * @brief Serial Communication Functions * - * @info Functions to manage the serial communications - * with UART devices and micro-controllers + * Functions to manage the serial communications + * with UART devices and micro-controllers + * @{ *********************************************************/ /** diff --git a/ProcessingGrapher/SerialMonitor.pde b/ProcessingGrapher/SerialMonitor.pde index 3020397..dda55ac 100644 --- a/ProcessingGrapher/SerialMonitor.pde +++ b/ProcessingGrapher/SerialMonitor.pde @@ -53,7 +53,7 @@ class SerialMonitor implements TabAPI { int recordCounter; int fileCounter; final int maxFileRows = 100000; - String[] tagColumns = {"SENT:","[Info]"}; + ArrayList serialTags = new ArrayList(); int displayRows; final int maxBuffer = 50000; @@ -64,6 +64,11 @@ class SerialMonitor implements TabAPI { int[] msgTextBounds = {0,0}; boolean autoScroll; + color previousColor = c_red; + color hueColor = c_red; + color newColor = c_red; + int colorSelector = 0; + final int[] baudRateList = {300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 250000, 500000, 1000000, 2000000}; StringList serialBuffer; @@ -104,6 +109,9 @@ class SerialMonitor implements TabAPI { menuLevel = 0; autoScroll = true; + serialTags.add(new SerialTag("SENT:", c_colorlist[0])); + serialTags.add(new SerialTag("[Info]", c_colorlist[1])); + serialBuffer = new StringList(); serialBuffer.append("--- PROCESSING SERIAL MONITOR ---"); if (showInstructions) { @@ -287,9 +295,9 @@ class SerialMonitor implements TabAPI { String textRow = serialBuffer.get(textIndex); // Figure out the text colour - for (int j = 0; j < tagColumns.length; j++) { - if (textRow.contains(tagColumns[j])) { - textColor = c_colorlist[j-(c_colorlist.length * floor(j / c_colorlist.length))]; + for (SerialTag curTag : serialTags) { + if (textRow.contains(curTag.tagText)) { + textColor = curTag.tagColor; } } @@ -568,9 +576,10 @@ class SerialMonitor implements TabAPI { String[] ports = Serial.list(); - if (menuLevel == 0) menuHeight = round((14 + tagColumns.length) * uH); + if (menuLevel == 0) menuHeight = round((14 + serialTags.size()) * uH); else if (menuLevel == 1) menuHeight = round((3 + ports.length) * uH); else if (menuLevel == 2) menuHeight = round((3 + baudRateList.length) * uH); + else if (menuLevel == 3) menuHeight = round(9 * uH + iW); // Figure out if scrolling of the menu is necessary if (menuHeight > sH) { @@ -636,16 +645,16 @@ class SerialMonitor implements TabAPI { float tHnow = 13.5; // List of Data Columns - for(int i = 0; i < tagColumns.length; i++){ + for (SerialTag curTag : serialTags) { // Column name - drawDatabox(tagColumns[i], iL, sT + (uH * tHnow), iW - (40 * uimult), iH, tH); + drawDatabox(curTag.tagText, iL, sT + (uH * tHnow), iW - (40 * uimult), iH, tH); // Remove column button - drawButton("x", c_sidebar_button, iL + iW - (20 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); + drawButton("✕", c_sidebar_button, iL + iW - (20 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); // Swap column with one being listed above button - color buttonColor = c_colorlist[i-(c_colorlist.length * floor(i / c_colorlist.length))]; - drawButton((i > 0)? "▲":"", c_sidebar, buttonColor, iL + iW - (40 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); + color buttonColor = curTag.tagColor; + drawButton("", c_sidebar, buttonColor, iL + iW - (40 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); drawRectangle(c_sidebar_divider, iL + iW - (20 * uimult), sT + (uH * tHnow) + (1 * uimult), 1 * uimult, iH - (2 * uimult)); tHnow++; @@ -679,6 +688,21 @@ class SerialMonitor implements TabAPI { } tHnow += 0.5; drawButton("Cancel", c_sidebar_accent, iL, sT + (uH * tHnow), iW, iH, tH); + + // Colour picker menu + } else if (menuLevel == 3) { + drawHeading("Select a Colour", iL, sT + (uH * 0), iW, tH); + drawColorSelector(hueColor, iL, sT + (uH * 1), iW, iW); + drawHeading("Set Brightness", iL, sT + (uH * 1.5) + iW, iW, tH); + drawColorBox2D(newColor, c_white, hueColor, iL, sT + (uH * 2.5) + iW, iW / 2, iH); + drawColorBox2D(newColor, hueColor, c_black, iL + (iW / 2), sT + (uH * 2.5) + iW, iW / 2, iH); + drawHeading("Colour Preview", iL, sT + (uH * 4) + iW, iW, tH); + drawText("New", c_idletab_text, iL, sT + (uH * 4.75) + iW, iW / 2, iH); + drawText("Old", c_idletab_text, iL + (iW / 2) + (2 * uimult), sT + (uH * 4.75) + iW, iW / 2, iH); + drawButton("", newColor, iL, sT + (uH * 5.5) + iW, (iW / 2) - (3 * uimult), iH, tH); + drawButton("", previousColor, iL + (iW / 2) + (2 * uimult), sT + (uH * 5.5) + iW, (iW / 2) - (2 * uimult), iH, tH); + drawButton("Confirm", c_sidebar_button, iL, sT + (uH * 6.5) + iW, iW, iH, tH); + drawButton("Cancel", c_sidebar_button, iL, sT + (uH * 7.5) + iW, iW, iH, tH); } // Draw bottom info bar @@ -970,7 +994,7 @@ class SerialMonitor implements TabAPI { else if (menuYclick(mouseY, sT, uH, iH, 12.5)) { final String colname = myShowInputDialog("Add a new Colour Tag","Keyword Text:",""); if (colname != null && colname.length() > 0){ - tagColumns = append(tagColumns, colname); + serialTags.add(new SerialTag(colname, c_colorlist[serialTags.size() % c_colorlist.length])); redrawUI = true; drawNewData = true; } @@ -980,33 +1004,34 @@ class SerialMonitor implements TabAPI { float tHnow = 13.5; // List of Data Columns - for(int i = 0; i < tagColumns.length; i++) { + for(int i = 0; i < serialTags.size(); i++) { if (menuYclick(mouseY, sT, uH, iH, tHnow)) { // Remove column if ((mouseX > iL + iW - (20 * uimult)) && (mouseX <= iL + iW)) { - tagColumns = remove(tagColumns, i); + serialTags.remove(i); redrawUI = true; drawNewData = true; } - // Move column up one space + // Change colour of entry else if ((mouseX >= iL + iW - (40 * uimult)) && (mouseX <= iL + iW - (20 * uimult))) { - if (i - 1 >= 0) { - String temp = tagColumns[i - 1]; - tagColumns[i - 1] = tagColumns[i]; - tagColumns[i] = temp; - } + previousColor = serialTags.get(i).tagColor; + hueColor = previousColor; + newColor = previousColor; + colorSelector = i; + + menuLevel = 3; + menuScroll = 0; redrawUI = true; - drawNewData = true; } // Change name of column else { - final String colname = myShowInputDialog("Update the Colour Tag Keyword", "Keyword Text:", tagColumns[i]); + final String colname = myShowInputDialog("Update the Colour Tag Keyword", "Keyword Text:", serialTags.get(i).tagText); if (colname != null && colname.length() > 0){ - tagColumns[i] = colname; + serialTags.get(i).tagText = colname; redrawUI = true; drawNewData = true; } @@ -1071,6 +1096,42 @@ class SerialMonitor implements TabAPI { menuScroll = 0; redrawUI = true; } + + // Select a Colour + } else if (menuLevel == 3) { + + // Colour hue selection + if ((mouseY > sT + (uH * 1)) && (mouseY < sT + (uH * 1) + iW) && (mouseX > iL) && (mouseX < iL + iW)) { + colorMode(HSB, iW, iW, iW); + hueColor = color(mouseX - iL, mouseY - (sT + uH), iW); + newColor = hueColor; + colorMode(RGB, 255, 255, 255); + redrawUI = true; + + // Colour brightness selection + } else if ((mouseY > sT + (uH * 2.5) + iW) && (mouseY < sT + (uH * 2.5) + iW + iH)) { + if (mouseX > iL && mouseX < iL + (iW / 2)) { + newColor = lerpColor(c_white, hueColor, (float) (mouseX - iL) / (iW / 2)); + redrawUI = true; + } else if (mouseX > iL + (iW / 2) && mouseX < iL + iW) { + newColor = lerpColor(hueColor, c_black, (float) (mouseX - (iL + iW / 2)) / (iW / 2)); + redrawUI = true; + } + + // Submit button + } else if ((mouseY > sT + (uH * 6.5) + iW) && (mouseY < sT + (uH * 6.5) + iW + iH)) { + serialTags.get(colorSelector).tagColor = newColor; + menuLevel = 0; + menuScroll = 0; + redrawUI = true; + redrawContent = true; + + // Cancel button + } else if ((mouseY > sT + (uH * 7.5) + iW) && (mouseY < sT + (uH * 7.5) + iW + iH)) { + menuLevel = 0; + menuScroll = 0; + redrawUI = true; + } } } @@ -1128,4 +1189,24 @@ class SerialMonitor implements TabAPI { if (recordData) stopRecording(); if (serialConnected) setupSerial(); } + + + /** + * Data structure to store info related to each colour tag + */ + class SerialTag { + public String tagText; + public color tagColor; + + /** + * Constructor + * + * @param setText The keyword text which is search for in the serial data + * @param setColor The colour which all lines containing that text will be set + */ + SerialTag(String setText, color setColor) { + tagText = setText; + tagColor = setColor; + } + } } diff --git a/ProcessingGrapher/data/user-preferences.xml b/ProcessingGrapher/data/user-preferences.xml index 26aeae5..fc71157 100644 --- a/ProcessingGrapher/data/user-preferences.xml +++ b/ProcessingGrapher/data/user-preferences.xml @@ -1,8 +1,8 @@ - - + + - - + + From 2b4a8e5b60b809e9cccf0973b0919801090dda96 Mon Sep 17 00:00:00 2001 From: Simon B Date: Sat, 31 Jul 2021 15:45:35 +0100 Subject: [PATCH 2/3] #20 Added option to change signal colours on FileGraph tab --- ProcessingGrapher/FileGraph.pde | 188 +++++++++++++----- ProcessingGrapher/Graph.pde | 77 +++++--- ProcessingGrapher/ProcessingGrapher.pde | 201 +++++++++++++++----- ProcessingGrapher/SerialMonitor.pde | 8 +- ProcessingGrapher/data/user-preferences.xml | 2 +- README.md | 3 + 6 files changed, 362 insertions(+), 117 deletions(-) diff --git a/ProcessingGrapher/FileGraph.pde b/ProcessingGrapher/FileGraph.pde index 7706473..677b300 100644 --- a/ProcessingGrapher/FileGraph.pde +++ b/ProcessingGrapher/FileGraph.pde @@ -43,8 +43,13 @@ class FileGraph implements TabAPI { String name; String outputfile; String currentfile; - String[] dataColumns = {}; Table dataTable; + ArrayList dataSignals = new ArrayList(); + + color previousColor = c_red; + color hueColor = c_red; + color newColor = c_red; + int colorSelector = 0; boolean saveFilePath = false; boolean changesMade; @@ -211,16 +216,23 @@ class FileGraph implements TabAPI { xData = -1; - // Load columns - dataColumns = new String[dataTable.getColumnCount()]; - + // Check that columns are loaded for (int i = 0; i < dataTable.getColumnCount(); i++) { - dataColumns[i] = dataTable.getColumnTitle(i); - if (dataTable.getColumnTitle(i).contains("x:")) { + + String columnTitle = dataTable.getColumnTitle(i); + if (columnTitle.contains("x:")) { xData = i; - } else if (dataTable.getColumnTitle(i).contains("l:")) { - dataColumns[i] = split(dataTable.getColumnTitle(i), ':')[1]; + } else if (columnTitle.contains("l:")) { + columnTitle = split(columnTitle, ':')[1]; } + + boolean signalCheck = false; + for (DataSignal curSig : dataSignals) { + if (curSig.signalText.equals(columnTitle)) + signalCheck = true; + } + if (!signalCheck) + dataSignals.add(new DataSignal(columnTitle, c_colorlist[i % c_colorlist.length])); } redrawUI = true; @@ -308,8 +320,8 @@ class FileGraph implements TabAPI { // Only plot it if it is within the X-axis data range if (dataX >= graph.getMinX() && dataX <= graph.getMaxX()) { if (dataTable.getColumnTitle(i).contains("l:")) { - if (dataPoint == 1) graph.plotXlabel((float) dataX, i); - } else graph.plotData((float) dataPoint, (float) dataX, i); + if (dataPoint == 1) graph.plotXlabel((float) dataX, i, dataSignals.get(i).signalColor); + } else graph.plotData((float) dataPoint, (float) dataX, i, dataSignals.get(i).signalColor); } } catch (Exception e) { println("Error trying to plot file data."); @@ -327,8 +339,8 @@ class FileGraph implements TabAPI { double dataPoint = row.getDouble(i); if (Double.isNaN(dataPoint)) dataPoint = 99999999; if (dataTable.getColumnTitle(i).contains("l:")) { - if (dataPoint == 1) graph.plotXlabel((float) currentX, i); - } else graph.plotData((float) dataPoint, (float) currentX, i); + if (dataPoint == 1) graph.plotXlabel((float) currentX, i, dataSignals.get(i).signalColor); + } else graph.plotData((float) dataPoint, (float) currentX, i, dataSignals.get(i).signalColor); } } catch (Exception e) { println("Error trying to plot file data."); @@ -392,12 +404,13 @@ class FileGraph implements TabAPI { Filters filterClass = new Filters(); if (menuLevel == 0) { - menuHeight = round((15 + dataColumns.length) * uH); + menuHeight = round((15 + dataSignals.size()) * uH); if (xData != -1) menuHeight -= uH; } else if (menuLevel == 1) { - menuHeight = round((3 + dataColumns.length) * uH); + menuHeight = round((3 + dataSignals.size()) * uH); if (xData != -1) menuHeight -= uH; } else if (menuLevel == 2) menuHeight = round((3 + filterClass.filterList.length) * uH); + else if (menuLevel == 3) menuHeight = round(9 * uH + iW); // Figure out if scrolling of the menu is necessary if (menuHeight > sH) { @@ -461,8 +474,8 @@ class FileGraph implements TabAPI { // Zoom Options if (currentfile != "" && currentfile != "No File Set") { - drawButton("Zoom", c_sidebar_button, iL, sT + (uH * 11), iW / 2, iH, tH); - drawButton("Reset", c_sidebar_button, iL + (iW / 2), sT + (uH * 11), iW / 2, iH, tH); + drawButton("Zoom", (setZoomSize >= 0)? c_sidebar_accent:c_sidebar_button, iL, sT + (uH * 11), iW / 2, iH, tH); + drawButton("Reset", (zoomActive)? c_sidebar_accent:c_sidebar_button, iL + (iW / 2), sT + (uH * 11), iW / 2, iH, tH); drawRectangle(c_sidebar_divider, iL + (iW / 2), sT + (uH * 11) + (1 * uimult), 1 * uimult, iH - (2 * uimult)); } else { drawDatabox("Zoom", c_idletab_text, iL, sT + (uH * 11), iW / 2, iH, tH); @@ -471,23 +484,23 @@ class FileGraph implements TabAPI { // Input Data Columns drawHeading("Data Format", iL, sT + (uH * 12.5), iW, tH); - if (xData != -1) drawButton("X: " + split(dataColumns[xData], ':')[1], c_sidebar_button, iL, sT + (uH * 13.5), iW, iH, tH); + if (xData != -1) drawButton("X: " + split(dataSignals.get(xData).signalText, ':')[1], c_sidebar_button, iL, sT + (uH * 13.5), iW, iH, tH); else drawDatabox("Rate: " + graph.getXrate() + "Hz", iL, sT + (uH * 13.5), iW, iH, tH); //drawButton("Add Column", c_sidebar_button, iL, sT + (uH * 12.5), iW, iH, tH); float tHnow = 14.5; // List of Data Columns - for(int i = 0; i < dataColumns.length; i++){ + for (int i = 0; i < dataSignals.size(); i++) { if (i != xData) { // Column name - drawDatabox(dataColumns[i], iL, sT + (uH * tHnow), iW - (40 * uimult), iH, tH); + drawDatabox(dataSignals.get(i).signalText, iL, sT + (uH * tHnow), iW - (40 * uimult), iH, tH); // Remove column button drawButton("✕", c_sidebar_button, iL + iW - (20 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); // Hide or Show data series - color buttonColor = c_colorlist[i-(c_colorlist.length * floor(i / c_colorlist.length))]; + color buttonColor = dataSignals.get(i).signalColor; drawButton("", buttonColor, iL + iW - (40 * uimult), sT + (uH * tHnow), 20 * uimult, iH, tH); drawRectangle(c_sidebar_divider, iL + iW - (20 * uimult), sT + (uH * tHnow) + (1 * uimult), 1 * uimult, iH - (2 * uimult)); @@ -500,13 +513,13 @@ class FileGraph implements TabAPI { drawHeading("Select a Signal", iL, sT + (uH * 0), iW, tH); float tHnow = 1; - if (dataColumns.length == 0 || (dataColumns.length == 1 && xData != -1)) { + if (dataSignals.size() == 0 || (dataSignals.size() == 1 && xData != -1)) { drawText("No signals available", c_sidebar_text, iL, sT + (uH * tHnow), iW, iH); tHnow += 1; } else { - for (int i = 0; i < dataColumns.length; i++) { + for (int i = 0; i < dataSignals.size(); i++) { if (i != xData && !dataTable.getColumnTitle(i).contains("l:")) { - drawButton(constrainString(dataColumns[i], iW - (10 * uimult)), c_sidebar_button, iL, sT + (uH * tHnow), iW, iH, tH); + drawButton(constrainString(dataSignals.get(i).signalText, iW - (10 * uimult)), c_sidebar_button, iL, sT + (uH * tHnow), iW, iH, tH); tHnow += 1; } } @@ -536,6 +549,21 @@ class FileGraph implements TabAPI { } tHnow += 0.5; drawButton("Cancel", c_sidebar_accent, iL, sT + (uH * tHnow), iW, iH, tH); + + // Colour picker menu + } else if (menuLevel == 3) { + drawHeading("Select a Colour", iL, sT + (uH * 0), iW, tH); + drawColorSelector(hueColor, iL, sT + (uH * 1), iW, iW); + drawHeading("Set Brightness", iL, sT + (uH * 1.5) + iW, iW, tH); + drawColorBox2D(newColor, c_white, hueColor, iL, sT + (uH * 2.5) + iW, iW / 2, iH); + drawColorBox2D(newColor, hueColor, c_black, iL + (iW / 2), sT + (uH * 2.5) + iW, iW / 2, iH); + drawHeading("Colour Preview", iL, sT + (uH * 4) + iW, iW, tH); + drawText("New", c_idletab_text, iL, sT + (uH * 4.75) + iW, iW / 2, iH); + drawText("Old", c_idletab_text, iL + (iW / 2) + (2 * uimult), sT + (uH * 4.75) + iW, iW / 2, iH); + drawButton("", newColor, iL, sT + (uH * 5.5) + iW, (iW / 2) - (3 * uimult), iH, tH); + drawButton("", previousColor, iL + (iW / 2) + (2 * uimult), sT + (uH * 5.5) + iW, (iW / 2) - (2 * uimult), iH, tH); + drawButton("Confirm", c_sidebar_button, iL, sT + (uH * 6.5) + iW, iW, iH, tH); + drawButton("Cancel", c_sidebar_button, iL, sT + (uH * 7.5) + iW, iW, iH, tH); } textAlign(LEFT, TOP); @@ -556,6 +584,11 @@ class FileGraph implements TabAPI { menuLevel = 0; menuScroll = 0; redrawUI = true; + } else if (setZoomSize >= 0) { + setZoomSize = -1; + cursor(ARROW); + redrawContent = true; + redrawUI = true; } } @@ -643,7 +676,7 @@ class FileGraph implements TabAPI { if (labelColumn == -1) { dataTable.addColumn("l:Labels"); labelColumn = dataTable.getColumnCount() - 1; - dataColumns = append(dataColumns, "Labels"); + dataSignals.add(new DataSignal("Labels", c_colorlist[dataSignals.size() % c_colorlist.length])); } // Draw the label and get the x-axis position @@ -707,6 +740,7 @@ class FileGraph implements TabAPI { zoomCoordOne[2] = (graph.xGraphPos(xcoord) * (graph.getMaxX() - graph.getMinX())) + graph.getMinX(); zoomCoordOne[3] = ((1 - graph.yGraphPos(ycoord)) * (graph.getMaxY() - graph.getMinY())) + graph.getMinY(); setZoomSize = -1; + zoomActive = true; if (zoomCoordOne[0] < zoomCoordOne[2]) { graph.setMinX(floorToSigFig(zoomCoordOne[0], 4)); @@ -813,10 +847,10 @@ class FileGraph implements TabAPI { // Open the filters sub-menu else if ((mouseY > sT + (uH * 5.5)) && (mouseY < sT + (uH * 5.5) + iH)){ if (currentfile != "" && currentfile != "No File Set"){ - if (xData == -1 && dataColumns.length == 1) { + if (xData == -1 && dataSignals.size() == 1) { selectedSignal = 0; menuLevel = 2; - } else if (xData != -1 && dataColumns.length == 2) { + } else if (xData != -1 && dataSignals.size() == 2) { if (xData == 0) selectedSignal = 1; else selectedSignal = 0; menuLevel = 2; @@ -906,10 +940,16 @@ class FileGraph implements TabAPI { if (currentfile != "" && currentfile != "No File Set") { // New zoom if ((mouseX > iL) && (mouseX <= iL + iW / 2)) { - zoomActive = true; - setZoomSize = 0; - cursor(CROSS); - //redrawUI = true; + if (setZoomSize >= 0) { + setZoomSize = -1; + cursor(ARROW); + redrawContent = true; + redrawUI = true; + } else { + setZoomSize = 0; + cursor(CROSS); + redrawUI = true; + } } // Reset zoom @@ -940,20 +980,20 @@ class FileGraph implements TabAPI { float tHnow = 14.5; // List of Data Columns - for(int i = 0; i < dataColumns.length; i++){ + for(int i = 0; i < dataSignals.size(); i++){ if (i != xData) { if ((mouseY > sT + (uH * tHnow)) && (mouseY < sT + (uH * tHnow) + iH)){ // Remove the signal if ((mouseX > iL + iW - (20 * uimult)) && (mouseX < iL + iW)) { - dataColumns = remove(dataColumns, i); + dataSignals.remove(i); dataTable.removeColumn(i); - if (dataColumns.length == 0 || (dataColumns.length == 1 && xData != -1)) { + if (dataSignals.size() == 0 || (dataSignals.size() == 1 && xData != -1)) { currentfile = "No File Set"; xData = -1; - dataColumns = new String[0]; + dataSignals.clear(); } changesMade = true; @@ -962,20 +1002,21 @@ class FileGraph implements TabAPI { } else if ((mouseX > iL + iW - (40 * uimult)) && (mouseX < iL + iW - (20 * uimult))) { - /* - if (i - 1 >= 0) { - String temp = dataColumns[i - 1]; - dataColumns[i - 1] = dataColumns[i]; - dataColumns[i] = temp; - } - redrawUI = true;*/ + previousColor = dataSignals.get(i).signalColor; + hueColor = previousColor; + newColor = previousColor; + colorSelector = i; + + menuLevel = 3; + menuScroll = 0; + redrawUI = true; } // Change name of column else { - final String colname = myShowInputDialog("Set the Data Signal Name", "Name:", dataColumns[i]); + final String colname = myShowInputDialog("Set the Data Signal Name", "Name:", dataSignals.get(i).signalText); if (colname != null && colname != ""){ - dataColumns[i] = colname; + dataSignals.get(i).signalText = colname; if (dataTable.getColumnTitle(i).contains("l:")) dataTable.setColumnTitle(i, "l:" + colname); else dataTable.setColumnTitle(i, colname); redrawUI = true; @@ -991,9 +1032,9 @@ class FileGraph implements TabAPI { // Signal selection sub-menu } else if (menuLevel == 1) { float tHnow = 1; - if (dataColumns.length == 0) tHnow++; + if (dataSignals.size() == 0) tHnow++; else { - for (int i = 0; i < dataColumns.length; i++) { + for (int i = 0; i < dataSignals.size(); i++) { if (i != xData && !dataTable.getColumnTitle(i).contains("l:")) { if ((mouseY > sT + (uH * tHnow)) && (mouseY < sT + (uH * tHnow) + iH)) { selectedSignal = i; @@ -1043,6 +1084,42 @@ class FileGraph implements TabAPI { menuScroll = 0; redrawUI = true; } + + // Select a Colour + } else if (menuLevel == 3) { + + // Colour hue selection + if ((mouseY > sT + (uH * 1)) && (mouseY < sT + (uH * 1) + iW) && (mouseX > iL) && (mouseX < iL + iW)) { + colorMode(HSB, iW, iW, iW); + hueColor = color(mouseX - iL, mouseY - (sT + uH), iW); + newColor = hueColor; + colorMode(RGB, 255, 255, 255); + redrawUI = true; + + // Colour brightness selection + } else if ((mouseY > sT + (uH * 2.5) + iW) && (mouseY < sT + (uH * 2.5) + iW + iH)) { + if (mouseX > iL && mouseX < iL + (iW / 2)) { + newColor = lerpColor(c_white, hueColor, (float) (mouseX - iL) / (iW / 2)); + redrawUI = true; + } else if (mouseX > iL + (iW / 2) && mouseX < iL + iW) { + newColor = lerpColor(hueColor, c_black, (float) (mouseX - (iL + iW / 2)) / (iW / 2)); + redrawUI = true; + } + + // Submit button + } else if ((mouseY > sT + (uH * 6.5) + iW) && (mouseY < sT + (uH * 6.5) + iW + iH)) { + dataSignals.get(colorSelector).signalColor = newColor; + menuLevel = 0; + menuScroll = 0; + redrawUI = true; + redrawContent = true; + + // Cancel button + } else if ((mouseY > sT + (uH * 7.5) + iW) && (mouseY < sT + (uH * 7.5) + iW + iH)) { + menuLevel = 0; + menuScroll = 0; + redrawUI = true; + } } } @@ -1105,6 +1182,7 @@ class FileGraph implements TabAPI { public void run() { // Load a file if (task == 0) { + dataSignals.clear(); dataTable = loadTable(currentfile, "csv, header"); for (int i = 0; i < dataTable.getColumnCount(); i++) { dataTable.setColumnType(i, Table.STRING); @@ -1166,4 +1244,24 @@ class FileGraph implements TabAPI { void performExit() { // Nothing to do here } + + + /** + * Data structure to store info related to each colour tag + */ + class DataSignal { + public String signalText; + public color signalColor; + + /** + * Constructor + * + * @param setText The keyword text which is search for in the serial data + * @param setColor The colour which all lines containing that text will be set + */ + DataSignal(String setText, color setColor) { + signalText = setText; + signalColor = setColor; + } + } } diff --git a/ProcessingGrapher/Graph.pde b/ProcessingGrapher/Graph.pde index e0da94c..b80cf54 100644 --- a/ProcessingGrapher/Graph.pde +++ b/ProcessingGrapher/Graph.pde @@ -329,11 +329,13 @@ class Graph { * Draw a X-axis label onto the graph * * @param dataX The x-axis position of the label + * @param type The signal type/index + * @param signalColor The colour in which the label should be drawn + * @{ */ - void plotXlabel(float dataX, int type) { + void plotXlabel(float dataX, int type, color signalColor) { if (dataX >= minX && dataX <= maxX) { - int colorIndex = type - (c_colorlist.length * floor(type / c_colorlist.length)); - graphics.stroke(c_colorlist[colorIndex]); + graphics.stroke(signalColor); graphics.strokeWeight(1 * uimult); graphics.line(map(dataX, minX, maxX, gL, gR), gT, map(dataX, minX, maxX, gL, gR), gB); @@ -341,6 +343,19 @@ class Graph { } + /** + * Draw a X-axis label onto the graph + * + * @note This is an overload function + * @see void plotXlabel(float, int, color) + */ + void plotXlabel(float dataX, int type) { + int colorIndex = type - (c_colorlist.length * floor(type / c_colorlist.length)); + plotXlabel(dataX, type, c_colorlist[colorIndex]); + } + /** @} */ + + /** * Change the graph display type * @@ -421,30 +436,16 @@ class Graph { } - /** - * Plot a new data point using default y-increment - * - * @note This is an overload function - * @see void plotData(float, float, int) - */ - void plotData(float dataY, int type) { - - // Ensure that the element actually exists in data arrays - while(lastY.length < type + 1) lastY = append(lastY, -99999999); - while(lastX.length < type + 1) lastX = append(lastX, -xStep); - - plotData(dataY, lastX[type] + xStep, type); - } - - /** * Plot a new data point on the graph * * @param dataY The Y-axis value of the data * @param dataX The X-axis value of the data * @param type The signal ID/number + * @param signalColor The colour which to draw the signal + * @{ */ - void plotData(float dataY, float dataX, int type) { + void plotData(float dataY, float dataX, int type, color signalColor) { if (validFloat(dataY) && validFloat(dataX)) { @@ -465,10 +466,9 @@ class Graph { if (dataX > maxX) dataX = maxX; if (dataX < minX) dataX = minX; - // Get relevant color from list - int colorIndex = type - (c_colorlist.length * floor(type / c_colorlist.length)); - graphics.fill(c_colorlist[colorIndex]); - graphics.stroke(c_colorlist[colorIndex]); + // Set colours + graphics.fill(signalColor); + graphics.stroke(signalColor); graphics.strokeWeight(1 * uimult); switch(plotType){ @@ -523,6 +523,35 @@ class Graph { } + /** + * Plot a new data point using default y-increment + * + * @note This is an overload function + * @see void plotData(float, float, int) + */ + void plotData(float dataY, int type) { + + // Ensure that the element actually exists in data arrays + while(lastY.length < type + 1) lastY = append(lastY, -99999999); + while(lastX.length < type + 1) lastX = append(lastX, -xStep); + + plotData(dataY, lastX[type] + xStep, type); + } + + + /** + * Plot a new data point using default y-increment + * + * @note This is an overload function + * @see void plotData(float, float, int, color) + */ + void plotData(float dataY, float dataX, int type) { + int colorIndex = type - (c_colorlist.length * floor(type / c_colorlist.length)); + plotData(dataY, dataX, type, c_colorlist[colorIndex]); + } + /** @} */ + + /** * Plot a rectangle on the graph * diff --git a/ProcessingGrapher/ProcessingGrapher.pde b/ProcessingGrapher/ProcessingGrapher.pde index 1bf0766..c21f259 100644 --- a/ProcessingGrapher/ProcessingGrapher.pde +++ b/ProcessingGrapher/ProcessingGrapher.pde @@ -7,7 +7,7 @@ * @website https://wired.chillibasket.com/processing-grapher/ * * @copyright GNU General Public License v3 - * @date 29th July 2021 + * @date 31st July 2021 * @version 1.3.0 * * * * * * * * * * * * * * * * * * * * * * */ @@ -31,7 +31,7 @@ * along with this program. If not, see . */ -String versionNumber = "1.2.4"; +final String versionNumber = "1.3.0"; // Swing for input popups import static javax.swing.JOptionPane.*; @@ -209,10 +209,12 @@ final String activeRenderer = FX2D; /******************************************************//** - * SETUP FUNCTIONS + * @defgroup SetupFunctions + * @brief Program Setup & Initialisation Functions * - * @info Methods used to initialise all parts of the - * GUI and set the values of required variables + * @details Methods used to initialise all parts of the + * GUI and set the values of required variables + * @{ *********************************************************/ /** @@ -382,16 +384,19 @@ public void exit() { exitActual(); } +/** @} End of SetupFunctions */ + /******************************************************//** - * WINDOW DRAWING FUNCTIONS - * - * @info Functions used to manage the drawing of all - * elements shown in the program window + * @defgroup WindowDrawing + * @brief Window Drawing Functions + * + * @details Functions used to manage the drawing of all + * elements shown in the program window + * @{ *********************************************************/ - /** * Processing Draw Function * @@ -668,18 +673,28 @@ void drawLoadingScreen() { text("Free Software - GNU General Public License v3", width / 2, (height / 2) + int(90 * uimult)); } +/** @} End of WindowDrawing */ + /******************************************************//** * @defgroup SidebarMenu * @brief Sidebar Menu Drawing Functions * - * Functions used to draw the text, buttons and - * data-boxes on the sidebar menu of each tab + * @details Functions used to draw the text, buttons and + * data-boxes on the sidebar menu of each tab * @{ *********************************************************/ - +/** + * Draw a colour hue selection box + * + * @param currentColor The currently selected colour + * @param lS Left X-coordinate + * @param tS Top Y-coordinate + * @param iW Width of colour selector box area + * @param tH Height of colour selector box area + */ void drawColorSelector(color currentColor, float lS, float tS, float iW, float iH) { if (tS >= tabTop && tS <= height) { rectMode(CORNER); @@ -708,22 +723,18 @@ void drawColorSelector(color currentColor, float lS, float tS, float iW, float i } } -void drawColorBox3D(color baseColor, float lS, float tS, float iW, float iH) { - if (tS >= tabTop && tS <= height) { - rectMode(CORNER); - strokeWeight(1); - for (int i = 0; i < iW; i++) { - color horizontalColor = lerpColor(c_white, baseColor, (float) (i) / iW); - for (int j = 0; j < iH; j++) { - color verticalColor = lerpColor(horizontalColor, color(0), (float) (j) / iH); - stroke(verticalColor); - point(lS + i, tS + j); - } - } - } -} - +/** + * Draw a colour gradient selection box + * + * @param currentColor The currently selected colour + * @param color1 Colour at the left of the gradient + * @param color2 Colour at the right of the gradient + * @param lS Left X-coordinate + * @param tS Top Y-coordinate + * @param iW Width of colour selector box area + * @param tH Height of colour selector box area + */ void drawColorBox2D(color currentColor, color color1, color color2, float lS, float tS, float iW, float iH) { if (tS >= tabTop && tS <= height) { rectMode(CORNER); @@ -747,7 +758,6 @@ void drawColorBox2D(color currentColor, color color1, color color2, float lS, fl } - /** * Draw sidebar text * @@ -1022,15 +1032,16 @@ void alertMessage(String message) { } } -/** @} */ +/** @} End of SidebarMenu */ + /******************************************************//** * @defgroup KeyboardMouse * @brief Keyboard and Mouse interaction functions * - * Functions to manage user input and interaction - * using the keyboard or mouse + * @details Functions to manage user input and interaction + * using the keyboard or mouse * @{ *********************************************************/ @@ -1255,15 +1266,16 @@ void keyReleased() { } } -/** @} */ +/** @} End of KeyboardMouse */ + /******************************************************//** * @defgroup SerialComms * @brief Serial Communication Functions * - * Functions to manage the serial communications - * with UART devices and micro-controllers + * @details Functions to manage the serial communications + * with UART devices and micro-controllers * @{ *********************************************************/ @@ -1340,7 +1352,6 @@ void setupSerial () { * * @param myPort The selected serial COMs port */ - void serialEvent (Serial myPort) { try { String inString; @@ -1447,16 +1458,19 @@ void checkSerialPortList() { } } +/** @} End of SerialComms */ + /******************************************************//** - * USER INPUT AND FILE SELECTION FUNCTIONS + * @defgroup UserInput + * @brief User Input and File Selection Functions * - * @info Functions to deal with the display and callback - * of user input and file selection pop-up dialogs + * @details Functions to deal with the display and callback + * of user input and file selection pop-up dialogs + * @{ *********************************************************/ - /** * Show a pop-up user input dialogue * @@ -1614,6 +1628,13 @@ void mySelectCallback(File selectedFile, String callbackMethod) { } +/** + * User input validation class + * + * The functions within this class make it easier to + * get data from a user and check that the supplied data + * is valid and within the specified bounds. + */ public class ValidateInput { private String inputString; private String errorMessage; @@ -1626,34 +1647,81 @@ public class ValidateInput { static public final int LT = 3; static public final int LTE = 4; + /** + * Constructor - Show the input dialogue box and get the user response + * + * @param heading Heading of the user input dialogue window + * @param message Message to display in the input dialogue window + * @param defaultText Default text to appear in the textbox when window is opened + */ public ValidateInput(final String heading, final String message, final String defaultText) { inputString = myShowInputDialog(heading, message, defaultText); } + /** + * Set which error message is shown if invalid user input is supplied + * @param error The error message to display + */ public void setErrorMessage(String error) { errorMessage = error; } + /** + * Check that supplied data can be parsed as a float + * @return True if data can be parsed as a float, false otherwise + */ public boolean checkFloat() { return checkDouble(NONE, 0, NONE, 0); } + /** + * Check that supplied data can be parsed as a float and is within one constraint + * @param operator1 The constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the constraint against + * @return True if data can be parsed as a float and is within the constraint + */ public boolean checkFloat(int operator1, float value1) { return checkDouble(operator1, value1, NONE, 0); } + /** + * Check that supplied data can be parsed as a float and is within two constraints + * @param operator1 The first constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the first constraint against + * @param operator2 The second constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value2 The value to check the second constraint against + * @return True if data can be parsed as a float and is within the constraints + */ public boolean checkFloat(int operator1, float value1, int operator2, float value2) { return checkDouble(operator1, value1, operator2, value2); } + /** + * Check that supplied data can be parsed as a double + * @return True if data can be parsed as a double, false otherwise + */ public boolean checkDouble() { return checkDouble(NONE, 0, NONE, 0); } + /** + * Check that supplied data can be parsed as a double and is within one constraint + * @param operator1 The constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the constraint against + * @return True if data can be parsed as a double and is within the constraint + */ public boolean checkDouble(int operator1, double value1) { return checkDouble(operator1, value1, NONE, 0); } + /** + * Check that supplied data can be parsed as a double and is within two constraints + * @param operator1 The first constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the first constraint against + * @param operator2 The second constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value2 The value to check the second constraint against + * @return True if data can be parsed as a double and is within the constraints + */ public boolean checkDouble(int operator1, double value1, int operator2, double value2) { if (inputString != null) { try { @@ -1671,14 +1739,32 @@ public class ValidateInput { return false; } + /** + * Check that supplied data can be parsed as an integer + * @return True if data can be parsed as an integer, false otherwise + */ public boolean checkInt() { return checkInt(NONE, 0, NONE, 0); } + /** + * Check that supplied data can be parsed as an integer and is within one constraint + * @param operator1 The constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the constraint against + * @return True if data can be parsed as an integer and is within the constraint + */ public boolean checkInt(int operator1, int value1) { return checkInt(operator1, value1, NONE, 0); } + /** + * Check that supplied data can be parsed as an integer and is within two constraints + * @param operator1 The first constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the first constraint against + * @param operator2 The second constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value2 The value to check the second constraint against + * @return True if data can be parsed as an integer and is within the constraints + */ public boolean checkInt(int operator1, int value1, int operator2, int value2) { if (inputString != null) { try { @@ -1697,6 +1783,12 @@ public class ValidateInput { } + /** + * Test the user supplied double value against a constraint + * @param operators The constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the constraint against + * @return True if the double is within the constraint + */ private boolean doubleConstraint(int operators, double value1) { switch (operators) { case GT: @@ -1715,6 +1807,12 @@ public class ValidateInput { return true; } + /** + * Test the user supplied integer value against a constraint + * @param operators The constraint type: GT (>), GTE (>=), LT (<), LTE (<=) + * @param value1 The value to check the constraint against + * @return True if the integer is within the constraint + */ private boolean intConstraint(int operators, int value1) { switch (operators) { case GT: @@ -1733,25 +1831,39 @@ public class ValidateInput { return true; } + /** + * @return The parsed double + */ public double getDouble() { return doubleValue; } + /** + * @return The parsed float + */ public float getFloat() { return (float) doubleValue; } + /** + * @return The parsed integer + */ public int getInt() { return intValue; } } +/** @} End of UserInput */ + + /******************************************************//** - * UTILITY AND DATA OPERATION FUNCTIONS + * @defgroup UtilityFunctions + * @brief Utility and Data Operation Functions * - * @info Functions used to perform common data - * manipulation operations + * @details Functions used to perform common data + * manipulation operations + * @{ *********************************************************/ /** @@ -1913,6 +2025,9 @@ String constrainString(String inputText, float maxWidth) { return inputText; } +/** @} End of UtilityFunctions */ + + /**************************************************************************************//** * Abstracted TAB API Interface diff --git a/ProcessingGrapher/SerialMonitor.pde b/ProcessingGrapher/SerialMonitor.pde index dda55ac..a69c89a 100644 --- a/ProcessingGrapher/SerialMonitor.pde +++ b/ProcessingGrapher/SerialMonitor.pde @@ -891,10 +891,10 @@ class SerialMonitor implements TabAPI { break; default: - print("Unknown character: "); - print(keyChar); - print(" "); - println(keyCodeInt); + //print("Unknown character: "); + //print(keyChar); + //print(" "); + //println(keyCodeInt); break; } } diff --git a/ProcessingGrapher/data/user-preferences.xml b/ProcessingGrapher/data/user-preferences.xml index fc71157..3f32cd7 100644 --- a/ProcessingGrapher/data/user-preferences.xml +++ b/ProcessingGrapher/data/user-preferences.xml @@ -3,6 +3,6 @@ - + diff --git a/README.md b/README.md index 4218829..b27a572 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,9 @@ A full set of instructions and documentation can be found on my website at: [htt
## Changelog +1. (31st July 2021) Version 1.3.0 [Release] + 1. ([#20](https://github.com/chillibasket/processing-grapher/issues/20)): Added option to change the colour of keyword tags on the "Serial" tab and signals on the "File Graph" tab. Simply click on the colour on the sidebar menu and select a new one using the colour picker. + 2. Minor UI improvements on "File Graph" tab to highlight zoom options. The escape key can now be used to cancel an active zoom operation. 1. (9th May 2021) Version 1.2.4 1. ([#18](https://github.com/chillibasket/processing-grapher/issues/18)): Added button to pause/resume the "Live Graph" to make it easier to analyse real-time data. Thanks to [aka-Ani](https://github.com/aka-Ani) for suggesting this feature. 1. Other minor UI improvements and bug fixes. From 011bb00b71295f53f5c9d79566eaeb3bf34527ee Mon Sep 17 00:00:00 2001 From: Simon B Date: Sat, 31 Jul 2021 16:22:12 +0100 Subject: [PATCH 3/3] #21 Added a scroll to bottom button on the serial monitor --- ProcessingGrapher/SerialMonitor.pde | 21 ++++++++++++++++++++- ProcessingGrapher/data/user-preferences.xml | 4 ++-- README.md | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ProcessingGrapher/SerialMonitor.pde b/ProcessingGrapher/SerialMonitor.pde index a69c89a..d8851c1 100644 --- a/ProcessingGrapher/SerialMonitor.pde +++ b/ProcessingGrapher/SerialMonitor.pde @@ -315,6 +315,16 @@ class SerialMonitor implements TabAPI { totalHeight -= yTextHeight; } + + // If scrolled up, draw a return to bottom button + if (scrollUp > 0) { + fill(c_sidebar); + rect(cR - (40 * uimult), cB - (40 * uimult), (30 * uimult), (30 * uimult)); + fill(c_background); + strokeWeight(1 * uimult); + stroke(c_terminal_text); + triangle(cR - (30*uimult), cB - (29*uimult), cR - (20*uimult), cB - (29*uimult), cR - (25*uimult), cB - (20*uimult)); + } } else { if (recordData) { fill(c_terminal_text); @@ -908,7 +918,16 @@ class SerialMonitor implements TabAPI { * @param ycoord Y-coordinate of the mouse click */ void contentClick (int xcoord, int ycoord) { - // Nothing here yet + + // Scroll down to bottom of serial message button + if (scrollUp > 0) { + if ((xcoord > cR - (40*uimult)) && (ycoord > cB - (40*uimult)) && (xcoord < cR - (10*uimult)) && (ycoord < cB - (10*uimult))) { + scrollUp = 0; + autoScroll = true; + redrawUI = true; + redrawContent = true; + } + } } diff --git a/ProcessingGrapher/data/user-preferences.xml b/ProcessingGrapher/data/user-preferences.xml index 3f32cd7..6e251c0 100644 --- a/ProcessingGrapher/data/user-preferences.xml +++ b/ProcessingGrapher/data/user-preferences.xml @@ -1,8 +1,8 @@ - + - + diff --git a/README.md b/README.md index b27a572..c44160e 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ A full set of instructions and documentation can be found on my website at: [htt 1. (31st July 2021) Version 1.3.0 [Release] 1. ([#20](https://github.com/chillibasket/processing-grapher/issues/20)): Added option to change the colour of keyword tags on the "Serial" tab and signals on the "File Graph" tab. Simply click on the colour on the sidebar menu and select a new one using the colour picker. 2. Minor UI improvements on "File Graph" tab to highlight zoom options. The escape key can now be used to cancel an active zoom operation. + 3. ([#21](https://github.com/chillibasket/processing-grapher/issues/21)) Added pop-up button which allows you to scroll back down to the newest entry in the "Serial" tab after scrolling up. 1. (9th May 2021) Version 1.2.4 1. ([#18](https://github.com/chillibasket/processing-grapher/issues/18)): Added button to pause/resume the "Live Graph" to make it easier to analyse real-time data. Thanks to [aka-Ani](https://github.com/aka-Ani) for suggesting this feature. 1. Other minor UI improvements and bug fixes.