/* 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();}