Monday, July 1, 2019

3D Scatter Plot

So here's where things get a bit more complicated. I spent the last couple of days learning Python as well as learning the methods of drawing 3D scatter plots. I'm not really sure how I did it. Even though 99% of the code is original, I sort of just... did it without really putting too much thinking aside from looking up some specific details. Essentially, the professor provided us with a sketch that had a list of NBA player details, and I basically ignored his whole sketch except for the actual data, because ain't nobody got time to retype all that. But I used his array to build an ArrayList of cubes to 3D plot it. This code easily took me 12-15 hours to make, but it was worth the effort because I learned a lot by trying to do this. The Processing sketch has 5 tabs. Copy and paste these code blocks into 5 different tabs and execute it in order to play around with it.

Video will be embedded first because there's a lot of code.



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
ArrayList<Cube> cubes; // Calls the array of cubes.
float wheelCount = 0; // Counts the number of scrolls in.
float event = 0; // Detects a scroll in and stores it.
float camXPos = 150.0; // Camera X Position, with initial view.
float camYPos = -100.0; // Camera Y Position, with initial view.
float camZPos = 100.0; // Camera Z Position, with initial view.

void setup() {
  size(1280, 720, P3D); //Sets render mode to 3D OpenGL render mode.
  background(0); 
  cubes = new ArrayList<Cube>(); //Defines cube array instance.
  println("Navigate the viewport with WASD and QE, and use the scroll wheel to zoom.");
  println("Press R to reset the view back to the default.");
}

void draw() {
  background(0); //Redraws background in order to prevent trails.

  //This section was an attempt to get the pivot point at the center of mass
  //of the cubes. It didn't work but it does something, so I'm keeping it.

  float xSum = 0;
  float ySum = 0;
  float zSum = 0;
  for (int i = 1; i <= 12; i++) {

    xSum = xSum + float(nbaData[i][16]);
    ySum = ySum +float(nbaData[i][17]);
    zSum = zSum +float(nbaData[i][18]);
  }
  //End of semi-useless section.

  //This section zooms in and out towards the origin.
  if (wheelCount > 0) {
    event = event + wheelCount*3; // Change the coefficient to change speed.
    camera(event+600, 0.0, 0.0, xSum, ySum, zSum, 0.0, 1.0, 0.0);
    //Previous line positions camera. Here's where the useless section
    //was supposed to do something.
    wheelCount = 0;
  }
  if (wheelCount <= 0) {
    event = event + wheelCount*3; // Change it here too to the same value.
    camera(event+600, 0, 0, -xSum, -ySum, -zSum, 0.0, 1.0, 0.0);
    //Previous line positions camera. Here's where the useless section
    //was supposed to do something.
    wheelCount = 0;
  }
  // End of zoom section.  

  // This section defines keyboard navigation. The WASD and QE keys
  // navigate the viewport, albeit poorly. It does the job, though.

  if (keyPressed) {
    if (key == 'w' || key == 'W') {
      camXPos = camXPos + 0.01;
    }
    if (key == 's' || key == 'S') {
      camXPos = camXPos - 0.01;
    }
    if (key == 'a' || key == 'A') {
      camYPos = camYPos + 0.01;
    }
    if (key == 'd' || key == 'D') {
      camYPos = camYPos - 0.01;
    }
    if (key == 'q' || key == 'Q') {
      camZPos = camZPos + 0.01;
    }
    if (key == 'e' || key == 'E') {
      camZPos = camZPos - 0.01;
    }
    if (key == 'r' || key == 'R') { //Resets camera view to default.
      camXPos = 150.0;
      camYPos = -100.0;
      camZPos = 100.0;
      event = 0;
    }
  }

  rotateX(camXPos); //These are what actually rotate the view.
  rotateY(camYPos);
  rotateZ(camZPos);

  // End of keyboard nav section.

  colorMode(HSB, 100); //Sets color mode to HSB from RGB in order to allow easier
                       // color cycling of the cubes.

  // The meat of the plotter. This is the most important part of the scatter plot.

  for (int i = 1; i <= 12; i++) {
    float xPos = float(nbaData[i][16]);
    float yPos = float(nbaData[i][17]);
    float zPos = float(nbaData[i][18]);    
    float newXPos = map(xPos, 0, 1, 0, 300);
    float newYPos = map(yPos, 0, 1, 0, 300);
    float newZPos = map(zPos, 0, 1, 0, 300);
    float cubeHue = float(nbaData[i][3]);
    cubeHue = map(cubeHue, 21, 38, 1, 90);
    
    String fullName = nbaData[i][1]+ " " + nbaData[i][2];
    String playerData = "Age: " + int(nbaData[i][3]) + " FG%: " + nbaData[i][16] + " 3P%: " + nbaData[i][17] + " FT%: " + nbaData[i][18];

    cubes.add(new Cube(playerData, fullName, cubeHue, newXPos, newYPos, newZPos));
  }
  // End of main plotting definer.

  // Calls the grid functions to draw them. First the plane grids, then the axes
  // information on top of them.
  drawXZGrid();
  drawXYGrid();
  drawYZGrid();
  axes();
  // The XY, XZ, YZ labeling may be incorrect to the corresponding grid, but
  // the code works and that's what's important.

  // This section actually draws the cubes that were previously plotted, as well
  // as their labelings.
  for (int i=0; i <=11; i++) {
    Cube c = cubes.get(i);


    c.makeCube();
    c.makeText();
  }
}





 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// This array was copied from the provided sketch because there's no point in 
// retyping the entire dataset, since that's not the point of this project.


String [][] nbaData = { // Columns go across, rows go down.  Each is numbered starting from 0
    //          0      1             2              3     4    5       6          7         8       9     10    11     12    13       14   15      16     17     18    19    20   
/* 0 */  { "Rank", "PlayerFname", "PlayerLname", "Age", "Tm", "First", "PtsWon", "PtsMax", "Share", "G", "MP", "PTS", "TRB", "AST", "STL", "BLK", "FG%", "3P%", "FT%", "WS", "WS/48" }, 
/* 1 */  { "1", "Stephen", "Curry", "26.0", "GSW", "100.0", "1198.0", "1300", "0.922", "80", "32.7", "23.8", "4.3", "7.7", "2.0", "0.2", ".487", ".443", ".914", "15.7", ".288" }, 
/* 2 */  { "2", "James", "Harden", "25.0", "HOU", "25.0", "936.0", "1300", "0.720", "81", "36.8", "27.4", "5.7", "7.0", "1.9", "0.7", ".440", ".375", ".868", "16.4", ".265" }, 
/* 3 */  { "3", "LeBron", "James", "30.0", "CLE", "5.0", "552.0", "1300", "0.425", "69", "36.1", "25.3", "6.0", "7.4", "1.6", "0.7", ".488", ".354", ".710", "10.4", ".199" }, 
/* 4 */  { "4", "Russell", "Westbrook", "26.0", "OKC", "0.0", "352.0", "1300", "0.271", "67", "34.4", "28.1", "7.3", "8.6", "2.1", "0.2", ".426", ".299", ".835", "10.6", ".222" }, 
/* 5 */  { "5", "Anthony", "Davis", "21.0", "NOP", "0.0", "203.0", "1300", "0.156", "68", "36.1", "24.4", "10.2", "2.2", "1.5", "2.9", ".535", ".083", ".805", "14.0", ".274" }, 
/* 6 */  { "6", "Chris", "Paul", "29.0", "LAC", "0.0", "124.0", "1300", "0.095", "82", "34.8", "19.1", "4.6", "10.2", "1.9", "0.2", ".485", ".398", ".900", "16.1", ".270" }, 
/* 7 */  { "7", "LaMarcus", "Aldridge", "29.0", "POR", "0.0", "6.0", "1300", "0.005", "71", "35.4", "23.4", "10.2", "1.7", "0.7", "1.0", ".466", ".352", ".845", "8.6", ".165" }, 
/* 8 */  { "9T", "Marc", "Gasol", "30.0", "MEM", "0.0", "3.0", "1300", "0.002", "81", "33.2", "17.4", "7.8", "3.8", "0.9", "1.6", ".494", ".176", ".795", "10.2", ".182" }, 
/* 9 */  { "9T", "Blake", "Griffin", "25.0", "LAC", "0.0", "3.0", "1300", "0.002", "67", "35.2", "21.9", "7.6", "5.3", "0.9", "0.5", ".502", ".400", ".728", "9.0", ".183" }, 
/* 10 */  { "12T", "Tim", "Duncan", "38.0", "SAS", "0.0", "1.0", "1300", "0.001", "77", "28.9", "13.9", "9.1", "3.0", "0.8", "2.0", ".512", ".286", ".740", "9.6", ".207" }, 
/* 11 */  { "12T", "Kawhi", "Leonard", "23.0", "SAS", "0.0", "1.0", "1300", "0.001", "64", "31.8", "16.5", "7.2", "2.5", "2.3", "0.8", ".479", ".349", ".802", "8.6", ".204" }, 
/* 12 */  { "12T", "Klay", "Thompson", "24.0", "GSW", "0.0", "1.0", "1300", "0.001", "77", "31.9", "21.7", "3.2", "2.9", "1.1", "0.8", ".463", ".439", ".879", "8.8", ".172" }
} ;





  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
// This tab defines the bounding box in order to draw the various axes and labels.

//This section draws the axes' labels.
void axes() {  
  stroke(100, 100, 100);
  fill(100, 0, 100);
  textMode(MODEL); // This allows us to manipulate the text as a 3D object.
  textSize(20);
  
  textAlign(CENTER, CENTER); //Aligns in exact center of text for better
                             //aligning with axes.
  
  //Draws one set of axes labels                           
  text("20%", 60, -10, 0);
  text("40%", 120, -10, 0);
  text("60%", 180, -10, 0);
  text("80%", 240, -10, 0);
  text("100%", 300, -10, 0);
  
  //Draws another set of axes labels
  text("20%", -10, 60, 0);
  text("40%", -10, 120, 0);
  text("60%", -10, 180, 0);
  text("80%", -10, 240, 0);
  text("100%", -10, 300, 0);
  
  //Draws final set of axes labels
  text("20%", -10, 0, 60);
  text("40%", -10, 0, 120);
  text("60%", -10, 0, 180);
  text("80%", -10, 0, 240);
  text("100%", -10, 0, 300);

  // This section defines axes names.

  stroke(100, 100, 100); //Defines label color and size.
  strokeWeight(2);
  fill(16, 70, 100);
  textSize(20);
  
  //FT% axis label.
  pushMatrix();
  rotateX(PI/2);
  rotateY(PI/2);
  rotateZ(-PI/2);
  translate(-150, -140, -147);
  text("Free Throw Percentage", 0, 150, 150);
  popMatrix();
  
  //TP% axis label.
  pushMatrix();
  rotateX(PI/2);
  rotateY(PI/2);
  rotateZ(0);
  translate(150, -140, -147);
  text("Three Point Percentage", 0, 150, 150);
  popMatrix();
  
  //FG% axis label.
  pushMatrix();
  rotateX(0);
  rotateY(0);
  rotateZ(0);
  translate(150, -140, -147);
  text("Field Goal Percentage", 0, 150, 150);
  popMatrix();
}

//This section defines the grids. The code is a modified version of my
// "aesthetic" program grid code which is why the variables are named as such.

void drawXZGrid() {
  stroke(0, 100, 100); //Red Grid
  int xgridr = 0; 
  int ygridr = 0; 
  while (ygridr <= 300) {
    while (xgridr <= 300) {
      line(xgridr, 300, 0, xgridr, 0, 0); //Draws one set of lines
      xgridr = xgridr + 30;
    }
    line(0, ygridr, 0, 300, ygridr, 0); //Draws perpendicular set of lines
    ygridr = ygridr +30;
  }
}

void drawXYGrid() {
  stroke(80, 100, 100); //Blue grid
  int xgridr = 0; 
  int ygridr = 0; 
  while (ygridr <= 300) {
    while (xgridr <= 300) {
      line(0, xgridr, 0, 0, xgridr, 300); //Draws one set of lines.
      xgridr = xgridr + 30;
    }
    line(0, 300, ygridr, 0, 0, ygridr); //Draws perpendicular set of lines.
    ygridr = ygridr +30;
  }
}

void drawYZGrid() {
  stroke(30, 100, 100); //Green grid
  int xgridr = 0; 
  int ygridr = 0; 
  while (ygridr <= 300) {
    while (xgridr <= 300) {
      line(0, 0, xgridr, 300, 0, xgridr); //Draws one set of lines.
      xgridr = xgridr + 30;
    }
    line(ygridr, 0, 0, ygridr, 0, 300); //Draws perpendicular set of lines.
    ygridr = ygridr +30;
  }
}





 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// This section defines the cube object, with parameters from the plotter.

class Cube {

  float cubeColor; //These are the defined variables used in the cube object.
  float cubeXPos;
  float cubeYPos;
  float cubeZPos;
  String playerData;
  String playerName;

  Cube(String tempData, String tempName, float tempCubeColor, float tempCubeXPos, float tempCubeYPos, float tempCubeZPos) {
    playerData = tempData;     // This takes the input information and passes
    playerName = tempName;     // it into the cube object.
    cubeColor = tempCubeColor;
    cubeXPos = tempCubeXPos;
    cubeYPos = tempCubeYPos;
    cubeZPos = tempCubeZPos;
  }

  void makeCube() { // The part that actually defines an individual cube.

    pushMatrix();
    translate(cubeXPos, cubeYPos, cubeZPos); //Translates cube to defined position.

    rotateY(frameCount/100.0); // This is just for aesthetic flair to make
    rotateX(frameCount/100.0); // the chart look more dynamic. It rotates
    rotateZ(frameCount/100.0); // each individual cube upon its local axis
                               // because it's within a push/pop matrix.

    stroke(0); // Sets the stroke to black for contrast
    strokeWeight(2);
    colorMode(HSB);
    fill(cubeColor, 100, 100); // Cube color is the 4th dimension. 
                               // Red end of spectrum is younger, purple is older.
    box(5); // Actually draws the box. Change this value to change box size.
    popMatrix();
  }
  
  void makeText() { // This section draws the info text since Processing lacks a 
                    // 3D cursor preventing adding mouseOver/mouseClick events.
    pushMatrix();
    textMode(MODEL); // Locally defines text mode to a 3D model.
    noStroke();
    textSize(10);
    textAlign(RIGHT); // Right aligns for better positioning.
    text(playerData, cubeXPos-10, cubeYPos+3, cubeZPos);
    textAlign(LEFT);  // Left aligns for better positioning.
    text(playerName, cubeXPos+10, cubeYPos+3, cubeZPos);
    popMatrix();
  }
}





1
2
3
4
5
6
7
// This small section could've been appended to the main program but for 
// organization of other functions, I kept it separate. It just checks for
// mouse scrolls in order to be used in the main program.

void mouseWheel(MouseEvent event) { 
  wheelCount = event.getCount();
}

No comments:

Post a Comment