/** * Code taken and adapted from: * Cubes Contained Within a Cube * by Ira Greenberg. * */ import java.util.ArrayList; import peasy.*; //external large "penta-cube" //--------------------- PentaCube stage; int stageColor = 100; float stageBounds = 300; float centerX = -1, //-left +right centerY = -1, //-up +down centerZ = 0; //-into scene +outof scene PVector stageCenter = new PVector(-1,-1,0); PVector[] Corners; //nodes representing library items //--------------------------------- String[] Rows; Node[] Nodes; int[] NodeColors, Radii; float[][] Positions; ArrayList Items; ArrayList ItemsInSelectedCat; float[] Hues; float[] x, y, z; int itemsPerYear = 10, selectedCat = 10, selectedNode = 0; String[] DewCatNames = { "Computer Science", "Philosophy & Psychology", "Religion", "Social Science", "Language", "Science & Mathematics", "Technology & Applied Science", "Arts & Recreation", "Literature", "History, Geography &Biology" }; int width = 900, height = 768, windowWidth, windowHeight; PFont font; PeasyCam cam; public void setup() { size(1024, 768, P3D); windowWidth = 1024; windowHeight = 768; cam = new PeasyCam(this, 800); cam.setMinimumDistance(50); cam.setMaximumDistance(1500); //initialize array of corners //---------------------------------------- //items of similar category will be grouped in same corner //corners will be set in PentaCube constructor Corners = new PVector[10]; //set the stage //---------------------------------------- stage = new PentaCube(stageBounds, 2*stageBounds); //set Hues for the 10 Dewey categories //---------------------------------------- colorMode(HSB, 360, 100, 100); Hues = new float[10]; for(int i = 0; i < Hues.length; i++) { Hues[i] = map(i, 0, 10, 0, 360); } //load dataset //---------------------------------------- Rows = loadStrings("Top100FromEachYear-byCountDewey.csv"); Items = new ArrayList(); ItemsInSelectedCat = new ArrayList(); //Array to hold top X items from each year //---------------------------------------- //X initialized to 10 //X changed through key presses of numbers 0 - 9 initializeItemsArray(); } void initializeItemsArray() { Items.clear(); //use the following array to keep track of how many items from each //year we are inserting into our Items array.. in order to show only top X items //from each year where X is a user-specified value: int[] numItemsEachYear = new int[6]; //(6 years from 2005 to 2010) for(int i = 0; i < numItemsEachYear.length; i++) { numItemsEachYear[i] = 0; } int numFullYears = 0; //Parse items and add to Items array //--------------------------------------------------- for(int i = 0; i < Rows.length; i++) { String[] ParseString = split(Rows[i], ","); String title = ParseString[3]; String type = ParseString[5]; type = type.trim(); if(type.substring(0, 2).equals("CD")) title = title + " [CD]"; else if(type.substring(0, 3).equals("DVD")) title = title + " [DVD]"; String subj = ParseString[8]; float dew = Float.valueOf(ParseString[1]); float avgDur = Float.valueOf(ParseString[4]); int ckouts = Integer.valueOf(ParseString[2]); int year = Integer.valueOf(ParseString[0]); int num = calcCategory(dew); float hue = Hues[num]; int index = -(2005 - year); float radius = map(Integer.valueOf(ParseString[2]), 300, 14000, 5, 30); Item item = new Item(title, type, subj, dew, avgDur, ckouts, year, hue, radius); if(Items.size() == 0) { Items.add(item); numItemsEachYear[index] = numItemsEachYear[index] + 1; } else { boolean foundMatch = false; for(int j = 0; j < Items.size(); j++) { if( item.title.equals(Items.get(j).title) && item.dewey == Items.get(j).dewey && item.type.equals(Items.get(j).type)) { Items.get(j).Ckouts.add(Integer.valueOf(ParseString[2])); Items.get(j).Years.add(Integer.valueOf(ParseString[0])); Items.get(j).AvgDurs.add(Float.valueOf(ParseString[4])); } } if(!foundMatch) { if(numItemsEachYear[index] < itemsPerYear) { Items.add(item); numItemsEachYear[index] = numItemsEachYear[index] + 1; if (numItemsEachYear[index] == 10) numFullYears++; } } } if(numFullYears == 6) { break; } } //After the Items array is finished populating, we can use the //populated "Years" arrays to determine each item's position for(int j = 0; j < Items.size(); j++) { int num = calcCategory(Items.get(j).dewey); PVector corner = Corners[num]; PVector pos = Items.get(j).calcNodeCoords(corner); Items.get(j).xPos = pos.x; Items.get(j).yPos = pos.y; Items.get(j).zPos = pos.z; } } int calcCategory(float theNum) { int dewey = (int)theNum; String dewString; if(0 <= dewey && dewey < 10) { dewString = "00" + dewey; } else if(10 <= dewey && dewey < 100) { dewString = "0" + dewey; } else { dewString = "" + dewey; } int firstDigit = Integer.valueOf(dewString.substring(0, 1)); return firstDigit; } public void draw() { background(50); lights(); ambientLight(10, 10, 10); //make walls of penta-cube transparent noFill(); stroke(stageColor); //draw external penta-cube stage.create(); //highlight corner corresponding to selected category if(selectedCat < 10) { PVector pos = Corners[selectedCat]; font = createFont("Sans Serif", 20); textFont(font); textAlign(CENTER, CENTER); PVector textPos = PVector.mult(pos, (float).2); pushMatrix(); translate(pos.x+textPos.x, pos.y + textPos.y, pos.z); float[] rotations = cam.getRotations(); rotateX(rotations[0]); rotateY(rotations[1]); rotateZ(rotations[2]); fill(255); text(selectedCat + ". " + DewCatNames[selectedCat] + " [" + ItemsInSelectedCat.size() + " items]", 0, 0, 0); popMatrix(); } //draw nodes drawNodes(); } public void drawNodes() { ItemsInSelectedCat.clear(); for(int i = 0; i < Items.size(); i++) { //node's hue represents its category //---------------------------------- float hue = Items.get(i).hue; //node's size represents total # of checkouts //---------------------------------- int count = 0; for(int j = 0; j < Items.get(i).Ckouts.size(); j++) { count += Items.get(i).Ckouts.get(j); } float size = map(count, 380, 14000, 5, 30); //min, max in dataset are 380 and 14000 Items.get(i).nodeRadius = size; //node's saturation is dependent on average checkout duration //---------------------------------- float num = Items.get(i).AvgDurs.size(); float total = 0; for(int k = 0; k < Items.get(i).AvgDurs.size(); k++) { total += Items.get(i).AvgDurs.get(k); } float avg = total/num; float dur = map(avg, 10, 55, 50, 200); Items.get(i).saturation = (int)dur; //node's alpha decreases if its category is selected but brightness increases //---------------------------------- int alpha = 200; Items.get(i).brightness = 80; if(calcCategory(Items.get(i).dewey) == selectedCat) { ItemsInSelectedCat.add(Items.get(i)); alpha = 180; Items.get(i).brightness = 100; } int col = color(hue, Items.get(i).saturation, Items.get(i).brightness); noStroke(); pushMatrix(); translate(Items.get(i).xPos, Items.get(i).yPos, Items.get(i).zPos); fill(col, alpha); sphere(size); popMatrix(); } if(ItemsInSelectedCat.size() != 0) { noStroke(); pushMatrix(); //handle cycling through nodes in selected category if(selectedNode >= ItemsInSelectedCat.size()) { selectedNode = 0; } Item selNode = ItemsInSelectedCat.get(selectedNode); translate(selNode.xPos, selNode.yPos, selNode.zPos); int col = color(selNode.hue, selNode.saturation, selNode.brightness); fill(col); sphere(ItemsInSelectedCat.get(selectedNode).nodeRadius + 1); fill(255); textFont(font, 12); popMatrix(); int count = 0; for(int k = 0; k < selNode.Ckouts.size(); k++) { count += selNode.Ckouts.get(k); } float num = selNode.AvgDurs.size(); float total = 0; for(int k = 0; k < selNode.AvgDurs.size(); k++) { total += selNode.AvgDurs.get(k); } float avg = total/num; pushMatrix(); translate(selNode.xPos, selNode.yPos, selNode.zPos); float[] rotations = cam.getRotations(); rotateX(rotations[0]); rotateY(rotations[1]); rotateZ(rotations[2]); text("Title: " + selNode.title, selNode.nodeRadius + 10, selNode.nodeRadius + 10, 0); text("Dewey: " + selNode.dewey, selNode.nodeRadius + 10, selNode.nodeRadius + 10 + (textAscent() + textDescent() + 5), 0); text("Checkouts: " + count, selNode.nodeRadius + 10, selNode.nodeRadius + 10 + 2*(textAscent() + textDescent() + 5), 0); text("Subject: " + selNode.subject1, selNode.nodeRadius + 10, selNode.nodeRadius + 10 + 3*(textAscent() + textDescent() + 5), 0); text("Avg Checkout Duration: " + avg + " days", selNode.nodeRadius + 10, selNode.nodeRadius + 10 + 4*(textAscent() + textDescent() + 5), 0); text("Years in Top " + itemsPerYear + ": " + selNode.Years.toString(), selNode.nodeRadius + 10, selNode.nodeRadius + 10 + 5*(textAscent() + textDescent() + 5), 0); popMatrix(); } } public void keyPressed() { switch(key) { case'1': itemsPerYear = 10; initializeItemsArray(); break; case '2': itemsPerYear = 20; initializeItemsArray(); break; case '3': itemsPerYear = 30; initializeItemsArray(); break; case '4': itemsPerYear = 40; initializeItemsArray(); break; case '5': itemsPerYear = 50; initializeItemsArray(); break; case '6': itemsPerYear = 60; initializeItemsArray(); break; case '7': itemsPerYear = 70; initializeItemsArray(); break; case '8': itemsPerYear = 80; initializeItemsArray(); break; case '9': itemsPerYear = 90; initializeItemsArray(); break; case '0': itemsPerYear = 100; initializeItemsArray(); break; case 'a': case 'A': selectedCat--; if (selectedCat < 0) { selectedCat = 10; } selectedNode = 0; break; case 'd': case 'D': selectedCat++; if (selectedCat > 10) { selectedCat = 0; } selectedNode = 0; break; case 'w': case 'W': selectedNode++; if(selectedNode >= ItemsInSelectedCat.size()) { selectedNode = 0; } break; case 's': case 'S': selectedNode--; if(selectedNode < 0) { selectedNode = ItemsInSelectedCat.size() - 1; } break; } if(key == 'z' || key == 'Z') { stroke(255); line(-1, -1, 500, -1, -1, -500); } }