Translate my 2D visualization of awake times to 3D space by incorporating another layer of data. I selected the Dewey classes and itemtypes for this third dimension, none of which I had used for previous visualizations. This required me to run queries on the SPL2 database on a subset marked by individual dewey classes and itemtypes, and subsequently defining the “awake time” in each of these classes. As in my previous work, the awake time is defined as the difference (in minutes) between the first check out of a day and a last checkout of a day.
Make an interactive visualization that lets the user adjust the position and perspective of the visualization using ControlP5 modules, but also has an option to highlight specific layers/dimensions of the data. For the former, I used standard ControlP5 slider objects (upper left corner of the screen) as used widely in class demos. For the selection/highlighting of specific data dimensions, I used a ControlP5 listbox item (upper right corner of the screen). I extended the ControlP5 listbox item class so it can show the currently selected item in a different color than the rest of the list (“active color”).
- panning can be done via three sliders in the x, y and z directions (lower left corner of the screen). This greatly simplifies panning through the three dimensional data matrix in interactive mode, especially when looking at the matrix from wide range of angles or zoom levels covering coarse and more granular scales in one and the same application.
- perspective and distances between layers can also be altered in all three spacial dimensions (lower right corner of the screen). This feature was implemented by modifying the p parameter in p*(a,b,c)*d, where a, b and c are the three dimensional lengths of the individual building blocks and d is the number of the layer.
Other applications of this code include multi-year data sets and the animated visualization of data collected at a high temporal sampling rate.
Code: Select all
// importing peasycam into processing, peasycam is used for manipualting the camera positions
import peasy.test.*;
import peasy.org.apache.commons.math.*;
import peasy.*;
import peasy.org.apache.commons.math.geometry.*;
// setting up PeasyCam
PeasyCam cam;
// including  ControlP5
import controlP5.*;
ControlP5 controlP5;
ControlP5 controlP5List;
ListBox l;
PMatrix3D currCameraMatrix;
PGraphics3D g3; 
// setting up fonts for text
PFont  myHelvetica10 = createFont("Helvetica",10, true);
PFont  myHelvetica12 = createFont("Helvetica",12, true);
PFont  myHelvetica16 = createFont("Helvetica",16, true);
PFont  myHelvetica20 = createFont("Helvetica",20, true);
PFont  myHelvetica24 = createFont("Helvetica",24, true);
color colForeground = 0xffaa0000;
color colBackground = 0xff660000;
color colActive = 0xffff0000;
//to store the data table
float [][][] dataMatrix = null;
int numWeekdays = 7;
int numWeeks = 52;
int numDewey = 10;
String[] weekdayNames = {
  "Sunday", 
  "Monday", 
  "Tuesday", 
  "Wednesday", 
  "Thursday", 
  "Friday", 
  "Saturday"
};
String[] deweyNames = {
  "1-100", 
  "100-200", 
  "200-300", 
  "300-400", 
  "400-500", 
  "500-600",
  "600-700", 
  "700-800", 
  "800-900", 
  "900-1000"   
};
float factDepth;
float morningShift;
int [][][] dataAwake = null;
int [][][] dataEarliestCout = null;
int [][][] dataLatestCout = null;
int maxAwake; //store max value
int numRowsGlobal = 3300;
int numRows;
int numCols = 8; //# of rows and columns of original data file
PFont myFont1 = createFont( "Helvetica", 24, true);
PFont myFont2 = createFont( "Helvetica", 16, true);
//  text formatting variables 
float constantMarginOnRows = 160;
float constantMarginOnColumns = 100;
Table myTable; 
float blockWidth = 40;
float blockHeight = 60;   // initial 80
float blockDepth = 35;
int p5Margin = 30;
int p5Distance = 22;
int buttonWidth = 150;
int buttonHeight = 20;
boolean textFlag= false;
boolean numberFlag = false;
boolean lineFlag = false; 
//int minimumNumber = 1;
// init values for interactivity PARAMS:
int transparency = 64;
int alpha;
float pHeight = 0;
float pWidth = 0;
float pDepth = 1;
float xPos = -5;
float yPos = 10;
float zPos = -36;
int deweySelect = 1; 
void drawGUI() 
      {
        currCameraMatrix = new PMatrix3D(g3.camera);
        camera();
        controlP5.draw();
        g3.camera = currCameraMatrix;
      }
void setupGUI()
      {        
              controlP5= new ControlP5(this);
              
                controlP5.setColorForeground(colForeground);
                controlP5.setColorBackground(colBackground);
                controlP5.setColorLabel(0xffdddddd);
                controlP5.setColorValue(color(220));//(0xffff88ff);
                 controlP5.setColorActive(colActive);
              controlP5.setFont(myHelvetica12);
              controlP5.setColorLabel(color(255,128));
              //controlP5.setBackground(color(40));
              //controlP5.setColorBackground(s2DbuttonBackCol);
              //controlP5.setColorForeground(s2DbuttonForeCol);
              controlP5.setColorCaptionLabel(color(220));
             Tab theParent;
              Toggle t1 = controlP5.addToggle("Labels",false,  p5Margin, p5Margin + 2 * p5Distance , buttonWidth , buttonHeight) ;  
              t1.setLabel("Labels");
        controlP5.Label lt1 = t1.captionLabel();
lt1.style().marginTop = -23; //move upwards (relative to button size)
lt1.style().marginLeft = 2; //move to the right
              //controlP5.addToggle("Text Flag",  p5Margin, p5Margin + 2 * p5Distance , buttonWidth , buttonHeight) ;             
             // controlP5.addToggle("Number Flag",p5Margin, p5Margin + 3 * p5Distance , buttonWidth , buttonHeight) ;           
              Toggle t = controlP5.addToggle("Awake Times",false, p5Margin, p5Margin + 3 * p5Distance , buttonWidth , buttonHeight) ;
                   t.setLabel("Awake Times");
        controlP5.Label lt = t.captionLabel();
lt.style().marginTop = -23; //move upwards (relative to button size)
lt.style().marginLeft = 2; //move to the right           
              //controlP5.addToggle("Line Flag",p5Margin, p5Margin + 4 * p5Distance , buttonWidth , buttonHeight) ;                                    
              Slider sAlpha = controlP5.addSlider("alpha", (int)255*0.2  ,(int)255*0.8 ,transparency ,p5Margin, p5Margin + 4 * p5Distance , buttonWidth , buttonHeight) ;
              
              controlP5.Label sAlphaLabel = sAlpha.captionLabel();
              sAlphaLabel.style().marginLeft = -45; //move horizontally 
              
              //block distance and layer perspective sliders
               // add blank button for heading
                controlP5.addButton("Layer distance & perspective")
                .setPosition(p5Margin+1200-30, p5Margin + 680)
                .setSize(0 , buttonHeight)
                .setColorBackground(color(255))
                .setColorForeground(color(255))
                .setColorCaptionLabel(color(1));
              
              Slider sPerspX = controlP5.addSlider("pHeight" ,-25 ,25,p5Margin+1200, p5Margin + 680+ p5Distance , buttonWidth , buttonHeight) ;
              Slider sPerspY = controlP5.addSlider("pWidth" ,-25 ,25 ,p5Margin+1200, p5Margin + 680+ 2*p5Distance , buttonWidth , buttonHeight) ;
              Slider sPerspZ = controlP5.addSlider("pDepth" ,1 ,25 ,p5Margin+1200, p5Margin + 680+ 3*p5Distance , buttonWidth , buttonHeight) ;
              
              controlP5.Label sPerspLabelX = sPerspX.captionLabel();
              controlP5.Label sPerspLabelY = sPerspY.captionLabel();
              controlP5.Label sPerspLabelZ = sPerspZ.captionLabel();
              sPerspLabelX.style().marginLeft = -61; //move horizontally 
              sPerspLabelY.style().marginLeft = -61; //move horizontally 
              sPerspLabelZ.style().marginLeft = -61; //move horizontally                
              
              //three dimensional panning sliders
               // add blank button for heading
                controlP5.addButton("3D Panning")
                .setPosition(p5Margin+30, p5Margin + 680)
                .setSize(0 , buttonHeight)
                .setColorBackground(color(255))
                .setColorForeground(color(255))
                .setColorCaptionLabel(color(1));
              Slider sPanning = controlP5.addSlider("xPos" ,-100 ,100,p5Margin, p5Margin + 680+ p5Distance , buttonWidth , buttonHeight) ;
              //sPanning.setLabel("xPos");
        controlP5.Label sPanningLabel = sPanning.captionLabel();
//sPanningLabel.style().marginTop = 1; //move upwards (relative to button size)
sPanningLabel.style().marginLeft = -42; //move horizontally   
              Slider sPanningY = controlP5.addSlider("yPos" ,-50 ,50 ,p5Margin, p5Margin + 680+ 2*p5Distance , buttonWidth , buttonHeight) ;
              Slider sPanningZ = controlP5.addSlider("zPos" ,-150 ,50 ,p5Margin, p5Margin + 680+ 3*p5Distance , buttonWidth , buttonHeight) ;
              //controlP5.addButton("Number Flag",1.0 , p5Margin, p5Margin + 3 * p5Distance , buttonWidth , buttonHeight) ; 
              
              controlP5.Label sPanningLabelY = sPanningY.captionLabel();
              controlP5.Label sPanningLabelZ = sPanningZ.captionLabel();
              sPanningLabelY.style().marginLeft = -42; //move horizontally 
              sPanningLabelZ.style().marginLeft = -42; //move horizontally   
              
              //controlP5List.setColorLabel(color(1,128));
              //controlP5List.setColorCaptionLabel(color(1));
              //ListBox l;
              l = controlP5.addListBox("myList")
                 .setPosition(p5Margin+1200, p5Margin + 3 * p5Distance)
                 .setSize(buttonWidth,buttonWidth)
                 .setItemHeight(14)
                 .setBarHeight(buttonHeight)
                 .setColorActive(colActive)
                 .setColorForeground(colForeground)
                 .setColorBackground(colBackground)
                 ;
                  l.captionLabel().toUpperCase(true);
                  l.captionLabel().set("Select Dewey Class");
                  //l.captionLabel().setColor(0xffff0000);
                  l.captionLabel().style().marginTop = 3;
                  l.valueLabel().style().marginTop = 3;
  
                  for (int i=0;i<numDewey;i++) {
                    ListBoxItem lbi = l.addItem(deweyNames[i], i);
                    //lbi.setColorBackground(0xffff0000);
                    //l.setValue(i);
                    if (i==deweySelect){lbi.setColorBackground(colActive);}
                  }
              
              
              controlP5.setAutoDraw(false);
        }  
void controlEvent(ControlEvent theEvent) 
                {
  
                 
                if(theEvent.isController()) 
                  {
              
                  if(theEvent.controller().name()=="Labels") {
                                                                          if(theEvent.controller().value()==1)
                                                                             {
                                                                               textFlag = true;
                                                                             }
                                                                           else
                                                                             {
                                                                               textFlag = false;                                                                       
                                                                             }                                                                               
                                                                       
                                                                    }
                                                                   
                                                                          
                   if(theEvent.controller().name()=="Awake Times") {
                                                                           if(theEvent.controller().value()==1)
                                                                             {
                                                                               numberFlag = true;
                                                                             }
                                                                           else
                                                                             {
                                                                               numberFlag = false;                                                                       
                                                                             }                                                                             
                                                                          }     
                  
      
                    
                   if(theEvent.controller().name()=="alpha") {
                                                                    transparency = int ( theEvent.controller().value() ) ;
                                                                    }     
               
                   if(theEvent.controller().name()=="pHeight") {
                                                                    pHeight = int ( -theEvent.controller().value() ) ;
                                                                    }     
               
                    if(theEvent.controller().name()=="pWidth") {
                                                                    pWidth = int ( theEvent.controller().value() ) ;
                                                                    }     
                    
                    if(theEvent.controller().name()=="pDepth") {
                                                                    pDepth = int ( theEvent.controller().value() ) ;
                                                                    }  
                    if(theEvent.controller().name()=="xPos") {
                                                                    xPos = int ( theEvent.controller().value() ) ;
                                                                    }     
               
                    if(theEvent.controller().name()=="yPos") {
                                                                    yPos = int ( theEvent.controller().value() ) ;
                                                                    }     
                    
                    if(theEvent.controller().name()=="zPos") {
                                                                    zPos = int ( theEvent.controller().value() ) ;
                                                                    }  
                                                                 
                   //if(theEvent.controller().name()=="myList") {
                   //                                                 deweySelect = int ( theEvent.controller().value() ) ;
                   //                                                 }   
    /*                                          
                    if(theEvent.controller().name()=="View 1") {
                                                                 
                                                                 cam.lookAt(0,0,0,2800,3000);
                                                                  }        
                    */
                                                                                
                  
                  print("control event from : "+theEvent.controller().name());
                  println(", value : "+theEvent.controller().value());
                  
                    }
    
    if (theEvent.isGroup()) {
    // an event from a group e.g. scrollList
    println(theEvent.group().value()+" from "+theEvent.group());
  }
  
  //   if(theEvent.isGroup() && theEvent.name().equals("myList")){
//    deweySelect = int ( theEvent.value() );
//    println("deweyClass: "+deweySelect);
//  } 
   
   //code block to highlight selected item in dewey scroll list                                       
   if(theEvent.name().equals("myList")){
    int currentIndex = (int)theEvent.group().value();
    println("oldDewey select:  "+deweySelect);
    if(deweySelect >= 0){//if something was previously selected
      l.getItem(deweySelect).setColorBackground(colBackground);
      //ListBoxItem previousItem = l.getItem(deweySelect);//get the item
      //println(previousItem.getColorBackground());
      //previousItem.setColorBackground(colBackground);//and restore the original bg colours
    }
    deweySelect = currentIndex;//update the selected index
    println("new Dewey select:  "+deweySelect);
    l.getItem(deweySelect).setColorBackground(colActive);//and set the bg colour to be the active/'selected one'...until a new selection is made and resets this, like above
   }               
             
}
void draw()
{
  // refresh the canvas everyframe
  background(255);
  //modifying the cell size according to window size
  translate( -width/2, -height/2);   // Peasycam intially sets lookAt() at (0,0,0) hence we need to transalte the axis
  if (controlP5.window(this).isMouseOver()) 
  {
    cam.setActive(false);
  } else 
  {
    cam.setActive(true);
  }
  // create base lines  
  //fill(255, 192);
  // creating grid lines in 3D
  //  if ( lineFlag)
  //  {
  //    stroke(64, 128);
  //    for ( int i =0; i <= blockWidth * (numWeekdays); i+= blockWidth)
  //    {
  //      pushMatrix();                                                  //setting up a matrix to keep the current graphi coordinates
  //      translate(constantMarginOnRows, constantMarginOnColumns);      //translate to the correct position
  //      line( i, 0, 0, i, blockHeight * (numWeeks-1), 0);              //creating a line (x,y,z)
  //      popMatrix();                                                   // going back to previos projection matrix
  //    }           
  //    for ( int i = 0; i < blockHeight* numWeeks; i+= blockHeight)
  //    {
  //      pushMatrix();
  //      translate(constantMarginOnRows, constantMarginOnColumns); 
  //      line ( 0, i, 0, blockWidth* numWeekdays, i, 0);
  //      popMatrix();
  //    }
  //  }
  //
  //displaying the cells                        
  //for ( int d = numDewey-1; d>=0; d--) {
  for ( int d = 0; d<numDewey; d++) {
    if (d == deweySelect) {//case layer is selected
      alpha = 255;
    } else { //standard setting common for all other layers
      alpha = transparency;
    }
    for ( int  i = 0; i<  numWeeks; i++) //numWeeks
    {
      for ( int  j = 0; j < numWeekdays; j++)           // -1 as the last column is empty, our dataMatrix is smaller than the table by one column
      {
        if ( dataAwake[i][j][1]!=0)
        {
          noStroke();
          colorMode(HSB);
          //draw inner box  
          pushMatrix();                                                          // doing the same for all the boxes, sadly that is the only way to do this
          fill(1, 192, 224* dataAwake[i][j][d]/maxAwake, alpha);//, 224* dataAwake[i][j]/maxAwake );
          translate(constantMarginOnRows, constantMarginOnColumns);              // translating coordiantes
            factDepth = (float)(dataLatestCout[i][j][d]-dataEarliestCout[i][j][d])/24;//hours dependent scaling factor for depth
          morningShift = (float)(((dataLatestCout[i][j][d]+dataEarliestCout[i][j][d])/2)-12)/24 * blockHeight;
          //int zTranslation = (dataAwake[1][1]*255/ maxAwake);//(dataAwake[i][j]*255/ maxAwake)* 3;
          //translate( blockWidth/2  , blockHeight/2   , i*numWeekdays*blockHeight + j*blockHeight + morningShift);
          translate( i* blockWidth + blockWidth/2 - xPos*blockWidth  + d*pWidth*blockWidth, j* blockHeight + blockHeight/2 + morningShift + yPos* blockHeight + d*pHeight*blockHeight, 1 + zPos*blockDepth - d*pDepth*blockDepth);     // translating coordiantes to box position     
          //data cube only:
          //translate( i* blockWidth + blockWidth/2 - xPos*blockWidth , j* blockHeight + blockHeight/2 + morningShift + yPos* blockHeight, 1 + zPos*blockDepth - d*blockDepth);     // translating coordiantes to box position     
          // note the movement in x position, it is half of the box's x length, this is because the box is drawn at the center
          //box( blockWidth, blockHeight, (dataMatrix[i][j])* 3  );                                    // creating a cube of appropriate size 
          box( blockWidth, blockHeight*factDepth, blockDepth);   
          // d*pDepth*blockDepth
          // ==> d*pDepth*blockDepth
            // floating boxes instad of bar graphs
          // drawing text over the boxes
          if ( numberFlag)
          {
            // box values
            translate( 0, 0, blockDepth/2 + 2 );     // translating coordinates to box position                      
            textFont(myHelvetica16);
            textAlign(CENTER, CENTER);
            fill(0);       
            text( dataAwake[i][j][d], 0, 0  );
          }
          popMatrix();
          //draw outer glass box  
          pushMatrix(); 
          stroke(120);
          strokeWeight(1);
          //colorMode(RGB); 
          fill(1, 1, 224, 0);//, 224* dataAwake[i][j]/maxAwake );
          translate(constantMarginOnRows, constantMarginOnColumns);
          translate( i* blockWidth + blockWidth/2 - xPos*blockWidth  + d*pWidth*blockWidth, j* blockHeight + blockHeight/2 + yPos* blockHeight + d*pHeight*blockHeight, 1 + zPos*blockDepth - d*pDepth*blockDepth);     // translating coordiantes to box position     
          //translate( i* blockWidth + blockWidth/2 - xPos*blockWidth  , j* blockHeight + blockHeight/2 + yPos* blockHeight, 1 + zPos*blockDepth - d*blockDepth);     // translating coordiantes to box position     
          box( blockWidth, blockHeight, blockDepth);   
          if (textFlag) {
            if (d == 0 || d == numDewey-1) { //label only first and last dimension...
              if (i==0) {//case first week of the year ==> write labels on left side of matrix
                translate( -1*blockWidth, 0, blockDepth/2 + 2 );     // translating coordinates to box position                      
                textFont(myHelvetica20);
                textAlign(RIGHT, CENTER);
                fill(66); 
                text(weekdayNames[j], 0, 0 );
                if (j == numWeekdays-1) { //case January
                  translate( blockWidth, blockHeight, blockDepth/2 + 2 );     // translating coordinates to box position                         
                  textFont(myHelvetica20);
                  textAlign(CENTER, CENTER);
                  fill(66); 
                  text("January", 0, 0 );
                }
              } else if (i == 22 & j == 0 & d!=0) { // case title
                translate(0, -2*blockHeight, blockDepth/2 + 2 );
                textFont(myHelvetica24);
                textAlign(CENTER, CENTER);
                fill(66);
                text( "Awake: Empirical hours of a library - across multiple, adjustable dimensions (darker colors refer to longer awake times)", constantMarginOnRows, constantMarginOnColumns/2 );
              } else if ((i ==numWeeks-1) & (j == numWeekdays-1)) { // case December
                translate( 0, blockHeight, blockDepth/2 + 2 );     // translating coordinates to box position                      
                textFont(myHelvetica20);
                textAlign(CENTER, CENTER);
                fill(66); 
                text("December", 0, 0 );
              }
            }
          }
          popMatrix();
        }
      }
    }
  }
  drawGUI();
}      // end of draw
void setup()
{
  
  size(1440,900,OPENGL);                      // setting up the size of window
  
  if (frame != null) {
    frame.setResizable(true);          // resizable window
  }
  
  //setting up the camera
  cam= new PeasyCam( this,0,0,0,1000);       
  cam.setMinimumDistance(-5000);        // how near the camera can get 0,0,0
  cam.setMaximumDistance(8000);       // how far the camera can travel
  //control P5 stuff      
  controlP5 = new ControlP5(this); 
  g3 = (PGraphics3D)g;     
  setupGUI();        
        
  background(240);                     //set background to white
  //noLoop();                            //to run draw() only once
  // initialize the 3D array with number of rows and columns
  dataMatrix = new float[numRowsGlobal][numCols][numDewey];
  
for ( int d = 0; d<numDewey; d++){
  myTable = new Table();                 //allocating memory to new table 
  //load the awake data set
  String filename =  "awake_dewey_" + deweyNames[d] + ".csv";
  myTable = loadTable(filename, "header");
  
  //header for col names  as follows:
  //barcode  deweyClass  weekday_num  week_num  awake  earliest_cout_hour  latest_cout_hour
  // assign these variables to contain the number of rows and columns from myTable
  numRows = myTable.getRowCount();
  numCols = myTable.getColumnCount();
  // copy everything from table into a 2D array
  maxAwake = 0; //find max awake value [minutes] for scaling of colorbar
  for ( int i = 0; i< numRows; i++)          // a for loop where i is set to 0  and increments all the way to numRows by 1
  {
    println(filename);
    for ( int j = 0; j< numCols; j++)// a for loop where j is set to 0  and increments all the way to numColumns by 1
    {
      dataMatrix[i][j][d] = myTable.getInt(i, j);      // copying the table integer value at mytable (i,j) position into the dataMatrix
      //print( dataMatrix[i][j][d] + " ");            // print out the value of dataMatrix
    }                  
    if ( dataMatrix[i][4][d] > maxAwake)      // this is an if statement which checks for the condition in brackets                                                         // if true, it executes the statements in brackets 
    {
      maxAwake = (int)dataMatrix[i][4][d];
    }
    //println();      // switch to next line in the prompt, improves legibility
  }
      
   println ( "maximum value is:"+ maxAwake);
   println ( "rowCount: "+ numRows);
   println ( "columnCount: "+ numCols);
}
  //get sorted week - weekday matrix from getWeekdayWeek function
  println("getting sorted week - weekday - dewey matrix...");
  dataAwake = getWeekWeekday(dataMatrix, 4); // 4 is colIndex for awake
  println("interpolating missing data...");
  dataAwake = interpMissing(dataAwake);
  dataEarliestCout = getWeekWeekday(dataMatrix, 5); // 4 is colIndex for earliest cout hour
  dataLatestCout = getWeekWeekday(dataMatrix, 6); // 4 is colIndex for earliest cout hour
}
     
   // Mapping function to convert SQL output to week-weekday matrix that is easier to plot
// This look up table could be generated much more efficiently, but given the size of the dataset it works OK.
int[][][] getWeekWeekday(float[][][]data, int colIndex) {
  int[][][] dataWeekWeekday = new int[numWeeks][numWeekdays][numDewey];
  for (int d=1; d<=numDewey; d++) {
  for (int w=1; w<=numWeeks; w++) {
    for (int wd=1; wd<=numWeekdays; wd++) {   
      dataWeekWeekday[w-1][wd-1][d-1] = 0;//zero initializer
      for (int i=0; i<data.length; i++) {//go through values in data file and sort into week weekday matrix
        if (((int)data[i][3][d-1] == w) && ((int)data[i][2][d-1] == wd)) {
          if ((int)data[i][colIndex][d-1] > dataWeekWeekday[w-1][wd-1][d-1]) {
            dataWeekWeekday[w-1][wd-1][d-1] = (int)data[i][colIndex][d-1];
          }
        }
      }
      print( dataWeekWeekday[w-1][wd-1][d-1] + " ");
    }
    println();      // switch to next line in the prompt, improves legibility
  }
  }
  return dataWeekWeekday;
}
// Interpolation function for missing values in data matrix
int[][][] interpMissing(int[][][]data) {
    for (int d=1; d<=numDewey; d++) {
  for (int w=1; w<=numWeeks; w++) {
    for (int wd=1; wd<=numWeekdays; wd++) {   
      if (data[w-1][wd-1][d-1] == 0) {
        println("zero value detected, interpolate with data for the same day from rest of the year");
        int wdaySum = 0;
        for (int w1=1; w1<=numWeeks; w1++) {
          wdaySum += data[w1-1][wd-1][d-1];
        }
        data[w-1][wd-1][d-1] = wdaySum/numWeeks;
      }
      print( data[w-1][wd-1][d-1] + " ");
    }
    println();      // switch to next line in the prompt, improves legibility
  }
    }
  return data;
}