> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.9SlI4JxGt8A/rev.3879
 * 
 * authors: 
 *   
 *   charles.perin
 *   
 *   frederic.vernier
 *   JDF
 *   
 *   
 *   dragice

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/* @pjs preload="/static/uploaded_resources/p.2418/logoUPSUD.png"; */
/* @pjs preload="/static/uploaded_resources/p.2418/tstreams4.csv"; */
// data from http://geography.uoregon.edu/GeogR/data/csv/tstreams4.csv
static class DView {
  int          colx = 0, coly = 0, cols = 0;
  static float data[][] = null;
  
  public DView (int colx, int coly, int cols){
    this.colx=colx;  this.coly=coly; this.cols=cols;
  }// Constructor()
  
  static void drawScatterPlot(PImage bg, int dimx, int dimy, color col){
    for(int j=0; j<bg.height; j++) {
      for (int i=0; i<bg.width; i++) {
        bg.pixels[j*bg.width+i]= 0xffffffff;
      }
    }
   
    for (int j=0; j<DView.data.length-2; j++) { 
      int x =int((bg.width-1) *norm(DView.data[j][dimx], DView.data[DView.data.length-2][dimx], DView.data[DView.data.length-1][dimx]));
      int y =int((bg.height-1)*norm(DView.data[j][dimy], DView.data[DView.data.length-1][dimy], DView.data[DView.data.length-2][dimy]));
      bg.pixels[y*bg.width+x] = col; 
    }
  }
  
  // compute the min/max on each axis
  static void initData(){
    String[] list = splitTokens(lines[1], ",\"");
    data = new float[lines.length+1][list.length]; // keep two lines to store min/max
    
    for (int i=0; i<list.length; i++) {            // init min and max
      data[lines.length-1][i] = float(list[i]); 
      data[lines.length]  [i] = float(list[i]);
    }
    for (int j=1; j<lines.length; j++) {
      list = splitTokens(lines[j],  ",\"");
      for (int i=0; i<list.length; i++) {
        data[j-1][i] = float(list[i]);               // retrieve data + update min and max
        data[lines.length-1][i] = min(data[lines.length-1][i], data[j-1][i]); 
        data[lines.length]  [i] = max(data[lines.length]  [i], data[j-1][i]);
      }
    }
  }// initData()
}


//////////////////////////////////////////////////////////////////////////////////////////////
class TextBox {
  int x,y, width, height, size, marginx, marginy, angle=0; 
  String txt;
  PImage bg;
  color coltxt = 0;
  color colbg0 = 0;
  color colbg1 = 0xffffff;
  color colbrd = 0;
    
  TextBox(int x, int y, String txt, int size, int marginx, int marginy) {
    this.x       = x;         this.y = y;
    this.txt     = txt;
    this.size    = size;
    this.coltxt  = color(64);
    this.colbg0  = color(96);
    this.colbg1  = color(192);
    this.colbrd  = color(0);
    this.marginx = marginx;   this.marginy = marginy;
    update();
  }// Constructor with default colors
  
  TextBox(int x, int y, String txt, int size, int marginx, int marginy, color colxt, color colbg0, color colbg1, color colbrd) {
    this.x       = x;         this.y = y;
    this.txt     = txt;
    this.size    = size;
    this.coltxt  = coltxt;
    this.colbg0  = colbg0;
    this.colbg1  = colbg1;
    this.colbrd  = colbrd;
    this.marginx = marginx;   this.marginy = marginy;
    update();
  }// Constructor with given colors
  
  void update () {
    String[] lines = split(txt,  '\n');  
    if (width!=textWidth(txt)+4+2*marginx || height!=size*lines.length+2+2*marginy) {
      width    = int(textWidth(txt)+4+2*marginx);
      height   = size*lines.length+2+2*marginy;
      float dr = red(colbg1)-red(colbg0);
      float dg = green(colbg1)-green(colbg0);
      float db = blue(colbg1)-blue(colbg0);
      bg = createImage(width, height, RGB);
      for(int j=0; j<height; j++) {
        color c = color(red(colbg0)+dr*j*2/height, green(colbg0)+dg*j*2/height, blue(colbg0)+db*j*2/height);  
        if (j>height/2)  
          c = color(red(colbg1)-(j-height/2)*dr/height, green(colbg1)-(j-height/2)*dg/height, blue(colbg1)-(j-height/2)*db/height);
        for (int i=0; i<width; i++) {
          bg.pixels[j*width+i]= c;
        }
      }
    }
  }// update()
  
  boolean isIn(int x0, int y0){
    int x1 = x0-x;
    int y1 = y0-y;
    float ar = angle*PI/180;
    float x2 = x1*cos(ar)+y1*sin(ar);
    float y2 = -x1*sin(ar)+y1*cos(ar);
    return (x2>=0 && x2<=width && y2>=0 && y2<=height);
  }
  
  void draw(){
    update();
    String[] lines = split(txt, '\n');  
    textSize(size);
    noFill(); stroke(colbrd);
    translate(x, y);
    rotate(angle*PI/180);
    if(bg!=null) image(bg, 0, 0);
    else {fill(colbg0);}
    rect(0, 0, width, height);
    textFont(font); 
    for (int i=0; i<lines.length; i++) {
      fill(0);
      text(lines[i], 3+marginx, 1+(i+1)*size+marginy); 
      fill(coltxt);
      text(lines[i], 2+marginx, (i+1)*size+marginy);
    }
    rotate(-angle*PI/180);
    translate(-x, -y);
  }// draw()
}
//////////////////////////////////////////////////////////////////////////////////////////////
 
// global variables
int     MAXX, MAXY, MINX, MINY, MINSIZE=2, MAXSIZE=16;
int     NX  = 96, NY = 128;
DView   cView, nView;
int     percent = 0;
TextBox tbx, tby, tbs, tbi;
PFont   font;
PImage  img;
PImage  scmatrix[][];
TextBox tbcol[];
int indexspector  =  -1;
static String  lines[];
 
// setup the processing canvas
void setup() {
  size(1040,480);
  MAXX = width-4;
  MAXY = height-20;
  MINX = 400;
  MINY = 4;
  // loading resources
  font = loadFont("SansSerif-12.vlw");
  if (font==null) font = loadFont("FFScala-32.vlw"); 
  if (font==null) font = loadFont("ArialNarrow-12.vlw");   
  img   = loadImage("/static/uploaded_resources/p.2418/logoUPSUD.png"); 
  if (img==null) img   = loadImage("logoUPSUD.png"); 
  lines = loadStrings("/static/uploaded_resources/p.2418/tstreams4.csv");
  if (lines==null) lines = loadStrings("tstreams4.csv");
  
  // re-load the last saved state
  String l2[] = loadStrings("toto.txt"); 
  if (l2==null) {l2=new String[3];l2[0]="2"; l2[1]="3"; l2[2]="4";}   
  int colx = int(l2[0]);
  int coly = int(l2[1]);
  int cols = int(l2[2]);
 
  // create a view. It also compute min/max for each axis
  DView.initData();
  
  cView = new DView(colx, coly, cols);
  scmatrix = new PImage[DView.data[0].length-2][DView.data[0].length-2];
  int simg = int(MINX/(DView.data[0].length-2))-1;
  for (int j=2; j<DView.data[0].length; j++) {
    for (int i=2; i<DView.data[0].length; i++) {
      scmatrix[j-2][i-2] = createImage(simg, simg, RGB);
      DView.drawScatterPlot(scmatrix[j-2][i-2], i, j, color(0, 0, 0, 255));
    }
  }    
      
  //  create labels for each axis
  String[] list = splitTokens(lines[0],  ",\"");  
  tbx = new TextBox(width-80, height-25, cView.colx+": "+list[cView.colx], 12, 2,  2);
  tby = new TextBox(10,      2,          cView.coly+": "+list[cView.coly], 12, 2,  2);
  tby.angle = -90;
  tbs = new TextBox(width/2, 2,          cView.cols+": "+list[cView.cols], 12, 12, 2);
  tbi = new TextBox(0,       0,          "", 12, 12, 2, color(64), color(255), color(192), color(0));
 
  tbcol = new TextBox[list.length-2];
  for (int i=2; i<list.length; i++) {
    tbcol[i-2] = new TextBox((simg+1)*(i-2)-10,       (height-MINX)/2-10,          list[i], 12, 12, 2);
    tbcol[i-2].angle = -29;
  }
 
  noLoop();
}
 
 
void finishAnim() {
  if (nView!=null) cView = nView;
  nView = null; percent = 0; 
  String[] list = splitTokens(lines[0],  ",\"");    
  tbx.txt = cView.colx+": "+list[cView.colx]; tbx.update();
  tby.txt = cView.coly+": "+list[cView.coly]; tby.update();
  tbs.txt = cView.cols+": "+list[cView.cols]; tbs.update();
  noLoop();
  // save the state for the next launch of the view
  String words = ""+cView.colx+" "+cView.coly+" "+cView.cols+" ";
  String[] list2 = split(words, ' ');
  saveStrings("toto.txt", list2);
  redraw();
}//finishAnim()
 
// repaint the canvas
void draw() {
  // handle the animation (evolve or finish it)
  if (nView!=null) percent++;
  if (nView!=null && percent>100) finishAnim();  
  
  // blue-grey background 
  background(210, 210, 235);
  int simg= MINX/(DView.data[0].length-2);
  for (int i=0; i<tbcol.length; i++)
    tbcol[i].draw();
  for (int j=2; j<DView.data[0].length; j++) {
    for (int i=2; i<DView.data[0].length; i++) {
      image (scmatrix[j-2][i-2], (i-2)*(simg), (j-2)*(simg)+(height-MINX)/2);
    }
  }
  
  stroke (255, 0, 0);
  noFill();
  rect((cView.colx-2)*(simg)-1, (cView.coly-2)*(simg)+(height-MINX)/2-1, simg, simg);
  scale(1, -1);translate(0, -height);
  
  // use 2 loops to display ticks along the axis
  stroke(0, 0, 0, 255); strokeWeight(1); noSmooth();
  line(MINX-2, MINY, MAXX+2, MINY); line(MINX, MINY-2, MINX, MAXY+2); 
  for (int i=0; 2+i*height/NX<height-20; i++)
    line(MINX, MINY+i*height/NX, MINX+2+(i%10==0?3:0), MINY+i*height/NX);
  for (int i=0; 2+i*width/NY<width;      i++)
    line(MINX+i*width/NY, MINY,  MINX+i*width/NY,    MINY+2+(i%10==0?3:0));
  smooth();
 
  int nbdata = DView.data.length-2; fill(255);
  // display the data (potentially interpolated between 2 views)
  for (int j=0; j<DView.data.length-2; j++) { 
    int x = MINX+int((MAXX-MINX)  *norm(DView.data[j][cView.colx], DView.data[nbdata][cView.colx], DView.data[nbdata+1][cView.colx]));
    int y = MINY+int((MAXY-MINY)*norm(DView.data[j][cView.coly], DView.data[nbdata][cView.coly], DView.data[nbdata+1][cView.coly])); 
    int s = MINSIZE+int((MAXSIZE-MINSIZE)  *norm(DView.data[j][cView.cols], DView.data[nbdata][cView.cols], DView.data[nbdata+1][cView.cols]));
 
    if (nView!=null) {
      int x2 = MINX +int((MAXX-MINX)  *norm(DView.data[j][nView.colx], DView.data[nbdata][nView.colx], DView.data[nbdata+1][nView.colx]));
      int y2 = MINY+int((MAXY-MINY)*norm(DView.data[j][nView.coly], DView.data[nbdata][nView.coly], DView.data[nbdata+1][nView.coly]));
      int s2 = MINSIZE+int((MAXSIZE-MINSIZE)  *norm(DView.data[j][nView.cols], DView.data[nbdata][nView.cols], DView.data[nbdata+1][nView.cols]));
      stroke(192);
      line (x,y, x2, y2);
      stroke(0);
      x = (x*(100-percent) + x2*percent)/100;
      y = (y*(100-percent) + y2*percent)/100;
      s = (s*(100-percent) + s2*percent)/100;
    }
    ellipse(x, y, s, s);
  }
  
  translate(0, height);scale(1, -1);
  // display the inspector
  if (indexspector>=0) {
    String[] list = splitTokens(lines[indexspector],  ",\"");
    String[] titles = splitTokens(lines[0],  ",\"");
    tbi.x = MINX+int((MAXX-MINX)  *norm(DView.data[indexspector][cView.colx], DView.data[nbdata][cView.colx], DView.data[nbdata+1][cView.colx]));
    tbi.y = MINY+height-int((MAXY-MINY)*norm(DView.data[indexspector][cView.coly], DView.data[nbdata][cView.coly], DView.data[nbdata+1][cView.coly])); 
    tbi.x = min(tbi.x, width-tbi.width);
    tbi.y = min(tbi.y, height-tbi.height);
    tbi.txt = "elem #"+indexspector+"\n";
    for (int i=0; i<list.length; i++)
      tbi.txt += titles[i]+":  "+list[i]+"\n";
    tbi.draw();
  }
  
  // display the logo
  if (img!=null) image(img, width-img.width/8, 0, img.width/8, img.height/8);
  // draw texts in boxes
  tbx.x = MAXX-tbx.width-2;
  tby.y = tby.width+2;
  tby.x = MINX+2;
  tbs.x = (MINX+MAXX)/2;
  tbx.draw(); tby.draw();tbs.draw();
  // draw 2 circles to surround the label for sizes
  fill(255);
  ellipse(tbs.x+5, tbs.y+tbs.height/2, 2, 2);
  ellipse(tbs.x+tbs.width-7, tbs.y+tbs.height/2, 12, 12);
}// draw()
 
// callback when the user click on the mouse button
void mouseClicked() {
  String[] list = splitTokens(lines[0],  ",\"");
  int simg= MINX/(DView.data[0].length-2);
  int mx = int(mouseX/simg);
  int my = int((mouseY-(height-MINX)/2)/simg);
  
  for (int i=0; i<tbcol.length; i++)
    if (tbcol[i].isIn(mouseX, mouseY)) {
      nView = new DView(cView.colx, cView.coly, i+2);
      tbs.txt = list[cView.cols]+"->"+list[nView.cols];
      percent = 1; frameRate(100); loop();
    }
  
  if (mouseY>(height-MINX)/2 && mouseY<height-(height-MINX)/2 &&mx>=0 && mx<=10 && my>=0 && my<=10){
    if (nView!=null && nView.colx==mx+2 && nView.coly==my+2)
      finishAnim();
    else {
      nView = new DView(mx+2, my+2, cView.cols);
      tbx.txt = list[cView.colx]+"->"+list[nView.colx];
      percent = 1; frameRate(25); loop();
    }
  }
}
// callback when the user press a mouse button
void mousePressed() {
  String[] list = splitTokens(lines[0],  ",\"");
  if      (tbx.isIn(mouseX, mouseY) && percent>0) finishAnim();
  else if (tby.isIn(mouseX, mouseY) && percent>0) finishAnim(); 
  else if (tbs.isIn(mouseX, mouseY) && percent>0) finishAnim();   
  else if (tbx.isIn(mouseX, mouseY)){
    int nv = (cView.colx+(mouseButton == LEFT?-1:1));
    if (nv>=DView.data[0].length) nv = 2;
    if (nv<2) nv = DView.data[0].length-1;
    nView = new DView(nv, cView.coly, cView.cols);
    tbx.txt = list[cView.colx]+"->"+list[nView.colx];
    percent = 1; frameRate(25); loop();
  } else if (tby.isIn(mouseX, mouseY)) {
    int nv = (cView.coly+(mouseButton == LEFT?-1:1));
    if (nv>=DView.data[0].length) nv = 2;
    if (nv<2) nv = DView.data[0].length-1;
    nView = new DView(cView.colx, nv, cView.cols);
    tby.txt = list[cView.coly]+"->"+list[nView.coly];
    tby.y = tby.width+2;
    percent = 1; frameRate(25); loop();
  } else if (tbs.isIn(mouseX, mouseY) && percent==0) {
    int nv = (cView.cols+(mouseButton == LEFT?-1:1));
    if (nv>=DView.data[0].length) nv = 2;
    if (nv<2) nv = DView.data[0].length-1;
    nView = new DView(cView.colx, cView.coly, nv);
    tbs.txt = list[cView.cols]+"->"+list[nView.cols];
    percent = 1; frameRate(100); loop();
  }
}
 
void mouseMoved() {
  indexspector = -1;
  int nbdata = DView.data.length-2;
  
  for (int i=0; i<tbcol.length; i++)
    if (tbcol[i].isIn(mouseX, mouseY)) tbcol[i].coltxt = color(128); else tbcol[i].coltxt = color(0); 
  if (tbx.isIn(mouseX, mouseY)) tbx.coltxt = color(128); else tbx.coltxt = color(0); 
  if (tby.isIn(mouseX, mouseY)) tby.coltxt = color(128); else tby.coltxt = color(0); 
  if (tbs.isIn(mouseX, mouseY)) tbs.coltxt = color(128); else tbs.coltxt = color(0); 
  
  for (int j=0; j<nbdata; j++) {
    int x = MINX+int((MAXX-MINX)  *norm(DView.data[j][cView.colx], DView.data[nbdata][cView.colx], DView.data[nbdata+1][cView.colx]));
    int y = MINY+int((MAXY-MINY)*norm(DView.data[j][cView.coly], DView.data[nbdata][cView.coly], DView.data[nbdata+1][cView.coly])); 
    int s = MINSIZE+int((MAXSIZE-MINSIZE)  *norm(DView.data[j][cView.cols], DView.data[nbdata][cView.cols], DView.data[nbdata+1][cView.cols]));
    float d = dist(x, y, mouseX, height-mouseY);
    if (percent==0 && d<=s)
      indexspector = j;
  }
  redraw();
}
void mouseReleased() {redraw();}