A More Complex (Yet Still Easy To Use) Serial Grapher

edited January 2015 in Share Your Work

I've been working on a graphing sketch to visualise data coming over the serial port, especially with the Arduino in mind. It is a step up from the basic one you find on the Arduino website (on which this is very loosely based) as it constantly scrolls across in real time, automatically zooms to the current range of values of the screen and saves the data as a .csv file so you can open it in Excel and graph it. I'd just like to say thanks to the great Processing developers and the Processing community which was really helpful in taking the quality of this up a notch (or two :P ). I know at least if no one else uses it, it's really helpful to me!

Here it is:

/* Dynamic Grapher
 Graphs data coming from the serial port.
 In slowGraph mode, the range is automatically adjusted so it zooms to the highest
 and lowest values, but can't sample as fast so you have to leave about a 40ms gap between
 successive values over the Serial port, or the program will flush out extra values
 it can't keep up with to keep the graph in real time (it will print out "Too fast!"
 in the console when this happens. It's probably best that you set it fast and let it 
 eliminate the extra values so switching to fastGraph is easier, and the speed in slowGraph
 is optimised (or at least I think that's how it would work). You can press the '+' button
 on your keyboard to zoom in and centre on on the current values coming through, and zoom
 back out by pressing '-'. The number at the top left is the highest value being displayed
 on the screen, the bottom left is the lowest, and the middle number is what's currently
 coming through. Pressing 's' switches between slow and fastGraph
 In fast graph, there will be no zoom available but it will be able to sample information
 much faster. The points will simply be displayed from rangeMin to rangeMax. The number
 in the bottom left is the most recent/current value coming through.
 The window is resizable so there's no need to worry about setting width and height,
 unless you want to change the size that the sketch starts as. If you want full screen,
 set fullScreen to true and it will set the sketch to full width and height and 
 use present mode.
 All the points received will be recorded (raw) along with their time since the end of 
 setup() so the data can be transferred to Excel or other applications. It is saved as
 "data.csv" in the sketch's data folder. The sketch can also open this file for you when 
 it closes using the default application set to open .csv files. You can turn the automatic 
 opening off by setting autoOpen to false. In Excel, click on one of the points in a cell
 and select all (on Mac: command a, on Windows: ctrl a, maybe) or highlight them in your
 preferred way and go to charts -> smooth lined scatter (or any scatter you want).
 Pressing 'p' will pause/play the graph, but you can still zoom in and change the size of the
 sketch, and it will autozoom, acting as normal.

 Feel free to ask me anything relevant and worth asking over the Arduino or 
 Processing forums, which is where I presume you got this from.

 This has only been tested on Mac, but should work without problems on any other OS
 that Processing supports.

 MAKE SURE YOU  LOOK AT AND READ THE VARIABLES BELOW TO SUIT YOUR COMPUTER!
*/

import processing.serial.*;


//-------------------------------------- CHANGE THESE ----------------------------------------//
boolean autoOpen = true; // whether or not to auto open data file (data.csv) at end of sketch

// If false, can graph faster but no zoom. If true, Arduino must send info with at least about a 40ms gap
//Can change by pressing 's' while running
boolean slowGraph = true; 

int myWidth = 1280, myHeight = 720; // Starting screen size, just here for convenience

//What number your desired port is in the serialList()[] of serial ports which you can see in
byte serialListNum = 0; // the console after starting. Once again here for convenience
int baudRate = 9600; //Match up with your device. Once more, for convenience

//Location of the processing folder. The data file is saved inside the data folder of this sketch
String p5Location ="/Users/JoeBloggs/Documents/Processing"; 

//Expected minimum and maximum values (range) to come over the serial port
//set to a larger, more extreme range if unsure and the auto zoom will adjust
float rangeMin = 0, rangeMax = 1023;

boolean fullScreen = false; //set to true to run in full screen / present mode

color backgroundColor = color(0);

color pointColor = color(255); //colour of the line

float thickness = 2; //how many pixels thick the line should be (using strokeWeight())

//--------------------------------------- LEAVE THESE ----------------------------------------//

Serial myPort;        // The serial port
PrintWriter dataFile;
int initTime;

boolean play = true; //pause/play when you press p
long lastClick; //last time mouse was clicked

float inByte; //inString converted to a float
String inString;//the most recent value over the serial port as a string
float[] points;//where all the values are stored
int arrayPos; //Where we are in the array cycle
float currentVal; //shortcut for graphing

float lowerBound; //used for auto zoom
float higherBound;
float zoom = 1; //for manual zoom when pressing '-' and '=' (plus)keys
float zoomCount; //how many times zoomed in



//------------------------------------------------- SETUP ------------------------------------------------//
void setup() { 
  if (fullScreen) size(displayWidth, displayHeight);
  else size(myWidth, myHeight);
  println(Serial.list());
  myPort = new Serial(this, Serial.list()[serialListNum], baudRate); //************************ Serial stuff *****************//
  // don't generate a serialEvent() unless you get a newline character:
  myPort.bufferUntil('\n');
  // set inital background: 
  if (frame != null) {
    frame.setResizable(true);
  }
  points = new float[displayWidth];

  dataFile = createWriter("data/data.csv"); //for excel output
  dataFile.println("Time,Reading"); //titles for the columns in excel


  prepareExitHandler();
  // List all the available serial ports

  //colour the points
  stroke(pointColor);
  strokeWeight(thickness);

  delay(1500);//stop spike in beginning of serial

  initTime = millis();
}



//------------------------------------------------ DRAW ------------------------------------------------//
void draw () {
  if (play) {
    if (myPort.available() > 0) {
      if (myPort.available() > 10) {
        println("Too fast!");
        myPort.clear();
      }
      // get the ASCII string:
      inString = myPort.readStringUntil('\n');

      if (inString != null) {
        // trim off any whitespace:
        inString = trim(inString);
        // convert to an int and map to the screen height:

        //println(points[arrayPos]);

        inByte = float(inString);
        dataFile.println(millis() - initTime  + "," + inByte);
        dataFile.flush();

        if (slowGraph) {
          points[arrayPos] = inByte;
          slowGraph();
        } else {

          points[arrayPos] = map(inByte, rangeMin, rangeMax, 1, height);
          fastGraph();
        }
        arrayPos++; //increment the array position
        if (arrayPos >= points.length) arrayPos = 0;//reset the array
      }
    }
  } else slowGraph(); //allow zooming/adjusting while paused
}


//------------------------------------------------ FUNCTIONS ------------------------------------//
void slowGraph(){  
  lowerBound = rangeMax; //set to opposite ends of range so it works
  higherBound = rangeMin;
  //graph the points
  beginShape(LINES);
  for (int i = 0; i < width; i++) {
    if (arrayPos + 1 + i < width) {   
      //determine highest + lowest boundaries of points on screen for autozoom
      if (points[arrayPos + 1 + i] < lowerBound) lowerBound = points[arrayPos + 1 + i];
      else if (points[arrayPos + 1 + i] > higherBound) higherBound = points[arrayPos + 1 + i];
    } else {//continue around cycle
      if (points[arrayPos + 1 + i - width] < lowerBound) lowerBound = points[arrayPos + 1 + i - width];
      else if (points[arrayPos + 1 + i - width] > higherBound) higherBound = points[arrayPos + 1 + i - width];
    }
  }
  background(backgroundColor);
  text(inByte, 5, height / 2); //display the incoming value
  text(lowerBound, 5, height - 4); 
  text(higherBound, 5, 9);
  for (int i = 0; i < width; i++) {
    if (arrayPos + 1 + i < width) currentVal = points[arrayPos + 1 + i];
    else  currentVal = points[arrayPos + 1 + i - width];
    if (zoomCount == 0)
      vertex(i, height - map(currentVal, lowerBound, higherBound, 1, height)); // 1 so 0 and 1023 are visible
    else
      vertex(i, height - map(currentVal, points[arrayPos] - (points[arrayPos] / (zoomCount * zoomCount)), points[arrayPos] + (points[arrayPos] / (zoomCount * zoomCount)), 1, height));
  }
  endShape();
  //println(zoom);
}

void fastGraph() {
  background(backgroundColor);
  //println(frameRate);
  text(inByte, 5, height - 4); //display the incoming value

  //graph the points
  beginShape(LINES);
  for (int i = 0; i < width; i++) {
    if (arrayPos + 1 + i < width) {   
      vertex(i, height - points[arrayPos + 1 + i] - 1); // -1 so 0 and 1023 are visible
    } else //continue around cycle
    vertex(i, height - points[arrayPos + 1 + i - width]);
  }
  endShape();
}

void keyPressed() {
  if (key == 's') {
    slowGraph = !slowGraph;
    if(!slowGraph) {
      for(int i = 0; i < points.length; i++)
        map(points[i], rangeMin, rangeMax, 1, height); //convert all values to fastGraph() range
    } else {
      for(int i = 0; i < points.length; i++)
        map(points[i], 1, height, rangeMin, rangeMax); //convert all values back to slowGraph() range
    }
  }
  if (key == '=') zoomCount++;
  if (key == '-' && (zoomCount != 0)) zoomCount--; //stop it inverting
  if (key == 'p') play = !play;
}


// When exiting sketch, run what's in run() here:
private void prepareExitHandler () {

  Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

    public void run () { //run this when stopping

      println("Shutdown Procedure");
      // application exit code here
      dataFile.close();
      //println("dataFile closed");
      //Open the data file in its location, trim() just in case
      if (autoOpen) open(trim(p5Location) + "/Dynamic_Grapher/data/data.csv"); 
    }
  }
  ));
}

boolean sketchFullScreen() {
  return fullScreen;
}
Tagged:
Sign In or Register to comment.