Datasurfer Surface Plot Tool

Corner View of surface plot

Datsurfer

Datasurfer is a four-dimensional surface plot developed in Processing, for Introduction to Computational Media.  Datasurfer takes a two-dimensional spreadsheet (CSV) and translates values into a 3D surface, which can be manipulated by moving the viewpoint, or by shrinking or growing a two-dimensional window (in the case of the NYISO Grid Data, hours of the day/days of the year) of which blocks are plotted.  Moving the viewpoint changes the apparent representation of the data, allowing for a wider set of conclusions to be made–in this case, it is easy to see that there is substantial variation in the load both over the course of the year, and over the course of the day.

Components

The processing sketch is based on a two-dimensional parsing algorithm from Jonathan Cousins and Nick Sears, and adapted with help from Zannah Marsh.  The interface buttons and sliders come from the spectacular plugin set ControlP5, and the 3-D manipulation is provided by PeasyCam (much easier than writing my own manipulation with the Processing Camera commands).  Of course, neither ControlP5 nor PeasyCam are perfect, but they do work quite well.  A special thanks is due to Andreas Schlegel, author of ControlP5, for modifying the library to allow for changing the slider edge width – right when I needed it at the Ebay Design Expo.

At the ITP show, this is being displayed on a giant touchscreen, which is really a lot of fun, but unfortunately is a little hard to manipulate due to being only single-touch.  Multitouch (which everyone loves on tablets, phones, and now laptops) is going to really extend the computing experience, much more than it has already.

Expansion

As a programmable interface, there is nearly infinite ability for customization of the tool to different datasets–half the work of information visualization is setting up the tool in such a way as to yield valid conclusions and minimize artifacts.  One of the goals of this project is to build a real-time interface for data from a feed, such as a Tweet-A-Watt.

Special Thanks

Thanks to Jonathan Cousins and Nick Sears for a great fall introduction to information visualization and the seed of this project (and the start of the code), Zannah Marsh for the opportunity (need) to develop this for ICM, the Processing community for developing great plugins (ControlP5, PeasyCam), and of course ITP (specifically George Agudow and Red Burns).

Advertisements

Arduino Flowmeter

Thanks to Eric Rosenthal, Teague Labs, and Koolance, I have a handy fluid flow meter, that is cheap, flexible, and hopefully reliable.

Starting with Teague’s DIY Arduino Watermeter, I’ve made some modifications to the code to compensate for the fact that I’m not using a Yellowjacket Arduino with WiFi, and the need to preserve precious digital I/O lines for actual I/O, and not use them as ground lines.

And more importantly, I have found and solved a significant problem in their implementation: if the wheel in the Koolance meter comes to rest with the magnet close to the Hall Effect sensor, the sensor will be stuck ‘on’—and read as though it is continuously spinning.  This requires a double-latching arrangement, to detect the spinning wheel by picking up first a HIGH signal (magnet) then a LOW signal (no magnet).

Latching Code (Arduino):

//latching arrangement for pin A
//must receive signal then no signal to indicate spin
int signalA = digitalRead(FLOW_PIN_A);
//detects tachometer signal, flow pin goes high every time the magnet spins past the sensor NOTE: must be integer
if(signalA==1){
latchA1=true;
//set first latch
}
if(signalA==0){
latchA2=true;
//set second latch
}
if(latchA1==true and latchA2==true){  //if both latches are enabled, increment counters
countA++;
totalCount++;
//reset latches
latchA1 = false;
latchA2=false;
}

Full Code (Arduino):

This is set up for two meters, and can be expanded to more, or easily adapted to only read one flowmeter.

inline void digitalInputWithPullup(byte pin, boolean b) {  //enables pullups
  pinMode(pin, INPUT);
  digitalWrite(pin, b?HIGH:LOW);
}

#define FLOW_PIN_A 2
#define FLOW_PIN_B 3
//#define FLOW_PIN_X_GND Y //if you want to use a digital IO pin as a ground, enable this line, and modify X and Y

unsigned long totalCount = 0;
unsigned long previousCount = 0; //used for interlocking the counter 

unsigned long windowCountA=0;

//storage variable for the timer
unsigned long previousMillis=0;
int interval=1000; //in milliseconds

//counters for each flowmeter
unsigned long countA = 0;
//latches so the counter doesn't get stuck counting if the magnet stays next to the sensor
boolean latchA1 = false;
boolean latchA2 = false;

unsigned long countB = 0;
boolean latchB1 = false;
boolean latchB2 = false;

void setup()
{
  Serial.begin(19200);
  digitalInputWithPullup(FLOW_PIN_A, true);
  digitalInputWithPullup(FLOW_PIN_B, true);
//  pinMode(FLOW_PIN_X_GND, OUTPUT);  //for using digital I/O pins as ground lines
//  digitalWrite(FLOW_PIN_X_GND, LOW); 
}

void loop(){

//latching arrangement pin A: must receive signal then no signal to indicate spin
  int signalA = digitalRead(FLOW_PIN_A); //detects tachometer signal, flow pin goes high every time the magnet spins past the sensor. NOTE: must be int.
  if(signalA==1){
    latchA1=true;
  }
  if(signalA==0){
    latchA2=true;
  }
  if(latchA1==true and latchA2==true){
    windowCountA++; //increment window counter (counts/unit time)
    countA++; //cumulative count for flowmeter A
    totalCount++; //total count on all meters
    //reset latches
    latchA1=false;
    latchA2=false;
  }

//latching arrangement pin B
  int signalB = digitalRead(FLOW_PIN_B);
  if(signalB==1){
    latchB1=true;
  }
  if(signalB==0){
    latchB2=true;
  }
  if(latchB1==true and latchB2==true){
    countB++;
    totalCount++;
    latchB1=false;
    latchB2=false;
  }

/*
//debugging code to see if signals are present (will display a 1 if there is signal)
Serial.print(signalA);
Serial.print("|");
Serial.println(signalB);
*/

//cumulative totals output
  if (totalCount > previousCount){
    Serial.print(countA);
    Serial.print(" | ");
    Serial.print(countB);
    Serial.print(" || ");
    Serial.println(totalCount);
    previousCount = totalCount; //stores L<-R, updating previousCount so it recycles
  }

/*  
//window counters
  unsigned long currentMillis = millis(); //set storage variable currentMillis to current time
  if (currentMillis - previousMillis > interval) { //if within the rolling window [interval]
    previousMillis = currentMillis;     //save last time the clock updated in previousMillis
    Serial.print(currentMillis); //timestamp
    Serial.print(" | ");
    Serial.println(windowCountA); //count of 'clicks' within the window
    windowCountA=0; //reset window counter, cumulative counters unaffected.
  }
*/

}//end loop