static int DAYS = 31; static float SCALE = 10.0; int LEVEL1_AMT = 10; int LEVEL2_AMT = 5; int WIDTH = 700; int HEIGHT = 520; int FRAME_HEIGHT = 600; final int VERT = -1; final int HORZ = 1; ArrayList rects = new ArrayList(); ArrayList transactions = new ArrayList(); ArrayList majorCategories = new ArrayList(); ArrayList minorCategories = new ArrayList(); Map categoryMap; ArrayList rectanglesMap = new ArrayList(); PImage horizontal, vertical; String categoryToDraw = ""; int currentDay = 2; int displayDay = 2; boolean animate = true; boolean showGlow = true; void setup() { size(WIDTH, FRAME_HEIGHT); //textFont(loadFont("ArialNarrow-BoldItalic-48.vlw"), 48); textFont(loadFont("GillSans-24.vlw"), 24); text("Loading...", 30, 30); loadTransactions("splxmldata.txt"); loadCategoryMap("dewey.xml"); ArrayList level1 = new ArrayList(); ArrayList level2 = new ArrayList(); // It gets angry without the 0th element. rects.add(new ArrayList()); majorCategories.add(new ArrayList()); minorCategories.add(new ArrayList()); level1.add(new ArrayList()); level2.add(new ArrayList()); rectanglesMap.add(new HashMap()); for(int i = 1; i < DAYS; i++) { majorCategories.add(i, TransactionUtils.countMajorCategories((ArrayList)transactions.get(i))); minorCategories.add(i, TransactionUtils.countMinorCategories((ArrayList)transactions.get(i))); level1.add(i, makeFirstDeweyRects((ArrayList)majorCategories.get(i))); level2.add(i, new ArrayList()); rectanglesMap.add(i, new HashMap()); //sortRectsByCount((ArrayList)level1.get(i), -1); int level1Count = getTotalCount((ArrayList)level1.get(i)); int orientation = VERT; if (WIDTH < HEIGHT) { orientation = HORZ; } squarify((ArrayList)level1.get(i), orientation, Float.POSITIVE_INFINITY, 0f, 0f, (float)WIDTH, (float)HEIGHT, (float)WIDTH, (float)HEIGHT, (WIDTH * HEIGHT) / level1Count, 0); for (int j = 0; j < ((ArrayList)level1.get(i)).size(); j++) { Rect r = (Rect)((ArrayList)level1.get(i)).get(j); ArrayList nrs = makeSecondDeweyRects((Set)((ArrayList)minorCategories.get(i)).get(j), (ArrayList)level1.get(i)); sortRectsByCount(nrs, -1); int level2Count = getTotalCount(nrs); int orientation2 = VERT; if (r.w <= r.h) { orientation2 = HORZ; } squarify(nrs, orientation2, Float.POSITIVE_INFINITY, r.x, r.y, r.w, r.h, r.w, r.h, (r.w * r.h) / level2Count, 0); ((ArrayList)level2.get(i)).addAll(nrs); } rects.add(i, level1.get(i)); ((ArrayList)rects.get(i)).addAll((ArrayList)level2.get(i)); addRectanglesMap((HashMap)rectanglesMap.get(i), (ArrayList)rects.get(i)); //println(rectanglesMap.get(i)); } horizontal = loadImage("test2.tga"); vertical = loadImage("test.tga"); } public void addRectanglesMap(HashMap rM, ArrayList r) { for(int i = 0; i < r.size(); i++) { rM.put(((Rect)r.get(i)).deweyClass, r.get(i)); } } public int getTotalCount(ArrayList rs) { int totalCount = 0; for (int i = 0; i < rs.size(); i++) { Rect rect = (Rect) rs.get(i); totalCount += rect.count; } return totalCount; } public color getColor(int i, int alpha) { switch(i) { case 0: return color(187, 187, 187, alpha); case 1: return color(115, 60, 84, alpha); case 2: return color(255, 134, 33, alpha); case 3: return color(104, 13, 134, alpha); case 4: return color(122, 117, 88, alpha); case 5: return color(19, 76, 146, alpha); case 6: return color(22, 125, 56, alpha); case 7: return color(145, 0, 4, alpha); case 8: return color(74, 107, 130, alpha); case 9: return color(118, 31, 0, alpha); default: return color(255, alpha); } } public ArrayList makeFirstDeweyRects(ArrayList categories) { ArrayList rs = new ArrayList(); for(int i = 0; i < categories.size(); i++) { Integer value = ((Integer)categories.get(i) != 0) ? (Integer)categories.get(i) : 1; color strokeColor = color(255); int alpha = 50; rs.add(i, new Rect(1, i, (Integer.toString(i) + "00s"), value, getColor(i, alpha), strokeColor)); } return rs; } public ArrayList makeSecondDeweyRects(Set categories, ArrayList previousRectangles) { ArrayList rs = new ArrayList(); Iterator it = categories.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); color strokeColor = color(255); color oldColor = ((Rect)previousRectangles.get( Integer.parseInt(((String)entry.getKey()).substring(0, 1)) )).col; float newAlpha = alpha(oldColor) + Integer.parseInt(((String)entry.getKey()).substring(1)) * 2; color newColor = color(red(oldColor), green(oldColor), blue(oldColor), newAlpha); rs.add(new Rect(2, Integer.parseInt((String)entry.getKey()), (String)entry.getKey() + " - " + (String)categoryMap.get((String)entry.getKey()) + " (" + Integer.toString((Integer)entry.getValue()) + ")", (Integer)entry.getValue(), newColor, strokeColor)); } return rs; } public void loadTransactions(String fileName) { for(int i = 0; i < 31; i++) { transactions.add(new ArrayList()); } println("Loading file \"" + fileName + "\""); String[] lines = loadStrings(fileName); for (int i = 0; i < lines.length; i++) { Transaction t = parseTransaction(lines[i]); if(Integer.parseInt(t.ckodate.substring(6, 7)) == 9) // If it was checked out in September... { ((ArrayList)transactions.get(Integer.parseInt(t.ckodate.substring(8)))).add(t); } } } public Transaction parseTransaction(String line) { String sections[] = split(line, ","); Transaction t = new Transaction(); //store all fields t.itemNumber = sections[0]; t.ckodate = sections[1]; t.ckidate = sections[2]; t.itemtype = sections[3]; t.title = sections[4]; t.deweyClass = sections[5]; int numSubjects = sections.length - 6; t.subjects = new String[numSubjects]; arraycopy(sections, 6, t.subjects, 0, numSubjects); return t; } public void loadCategoryMap(String fileName) { categoryMap = new HashMap(); String[] sections; println("Loading category map..."); String[] lines = loadStrings(fileName); for(int i = 0; i < lines.length; i++) { sections = split(lines[i], ","); categoryMap.put(sections[0], sections[1]); } } void squarify(ArrayList rs, int orientation, float currentAspectRatio, float x, float y, float w, float h, float fullw, float fullh, float scalar, int currentRectIdx) { //println("in squarify : orientation = " + orientation + ", x/y/w/h = " + x + "/" + y + "/" + w + "/" + h + " fullw/fullh = " + fullw + "/" + fullh); int currentCount = 0; int startIdx = currentRectIdx; int endIdx = currentRectIdx; float uw = 0f; float uh = 0f; int tempCount = 0; if (orientation == VERT) { //println("\t\tin VERT..."); while (endIdx < rs.size() ) { Rect nextRect = (Rect) rs.get(currentRectIdx); tempCount += nextRect.count; //println("\t\there... currentRectIdx = " + currentRectIdx); float tw = (tempCount * scalar) / h; float th = h; if (max(tw/th, th/tw) <= currentAspectRatio || endIdx == rs.size() - 1) { endIdx++; currentCount = tempCount; currentRectIdx++; uw = tw; uh = th; currentAspectRatio = max(tw/th, th/tw); } else { break; } } float sy = y; for (int i = startIdx; i < endIdx; i++) { if (i >= rs.size()) { break; } Rect cr = (Rect) rs.get(i); cr.x = x; cr.y = sy; cr.w = uw; cr.h = uh * ((float)cr.count / (float)currentCount); cr.orientation = VERT; sy += cr.h; } if (endIdx < rs.size()) { squarify(rs, HORZ, Float.POSITIVE_INFINITY, x + uw, y, fullw - uw, h, fullw - uw, fullh, scalar, endIdx); } return; } else //(orientation == HORZ) { //println("\t\tin HORZ..."); while (endIdx < rs.size()) { Rect nextRect = (Rect) rs.get(currentRectIdx); tempCount += nextRect.count; //println("\t\there... currentRectIdx = " + currentRectIdx); float th = (tempCount * scalar) / w; float tw = w; if (max(tw/th, th/tw) < currentAspectRatio || endIdx == rs.size() - 1) { endIdx++; currentCount = tempCount; currentRectIdx++; uw = tw; uh = th; currentAspectRatio = max(tw/th, th/tw); } else { break; } } float sx = x; for (int i = startIdx; i < endIdx; i++) { if (i >= rs.size()) { break; } Rect cr = (Rect) rs.get(i); float winc = uw * ((float)cr.count / (float)currentCount); cr.x = sx; cr.y = y; cr.h = uh; cr.w = winc; cr.orientation = HORZ; sx += winc; } if (endIdx < rs.size()) { if (endIdx == rs.size() - 1) { return; } else { squarify(rs, VERT, Float.POSITIVE_INFINITY, x, y + uh, w, fullh - uh, fullw, (fullh - uh), scalar, endIdx); } } return; } } void draw() { background(0); Rect r1 = new Rect(), r2 = new Rect(); pushMatrix(); translate(0, (FRAME_HEIGHT - HEIGHT) / 2 - 3); if(currentDay != displayDay) { for (int i = 10; i < ((ArrayList)rects.get(currentDay)).size(); i++) { r1 = (Rect)((ArrayList)rects.get(currentDay)).get(i); r2 = (Rect)((HashMap)rectanglesMap.get(currentDay + 1)).get(r1.deweyClass); if(r2 == null) { r2 = new Rect(); r2.w = 0; r2.h = 0; r2.x = r1.x; r2.y = r1.y; } r1.w += (r2.w - r1.w) / SCALE; r1.h += (r2.h - r1.h) / SCALE; r1.x += (r2.x - r1.x) / SCALE; r1.y += (r2.y - r1.y) / SCALE; r1.draw(); } for (int i = 0; i < 10; i++) { r1 = (Rect) ((ArrayList)rects.get(currentDay)).get(i); r2 = (Rect)((ArrayList)rects.get(currentDay + 1)).get(i); r1.w += (r2.w - r1.w) / SCALE; r1.h += (r2.h - r1.h) / SCALE; r1.x += (r2.x - r1.x) / SCALE; r1.y += (r2.y - r1.y) / SCALE; r1.draw(); } if(abs(r1.w - r2.w) < .1 && abs(r1.h - r2.h) < .1) { currentDay = displayDay; } } else { for (int i = 10; i < ((ArrayList)rects.get(currentDay)).size(); i++) { r1 = (Rect)((ArrayList)rects.get(currentDay)).get(i); r1.draw(); } for (int i = 0; i < 10; i++) { r1 = (Rect) ((ArrayList)rects.get(currentDay)).get(i); r1.draw(); } if(animate && millis() % 250 == 0) { if(displayDay == 30) { displayDay = 1; } else { displayDay++; } } } popMatrix(); fill(255); textAlign(CENTER); text(categoryToDraw, WIDTH / 2, FRAME_HEIGHT - 12); textAlign(CENTER); fill(255); text(displayDay + TransactionUtils.getOrdinal(displayDay) + " of September, 2008", WIDTH / 2, 27); } void keyPressed() { if(key == CODED) { if(keyCode == RIGHT) { if(displayDay == 30) { displayDay = 1; } else { displayDay++; } } else if(keyCode == LEFT) { if(displayDay == 1) { displayDay = 30; } else { displayDay--; } } } if(key == 'a') { animate = !animate; } if(key == 'g') { showGlow = !showGlow; } } //sort a list of Map.Entrys by their value. This assumes that the value is an Integer! //order = 1 for Ascending, order = -1 for descending. void sortRectsByCount(ArrayList entryList, final int order) { Collections.sort(entryList, new Comparator() { public int compare(Object o1, Object o2) { Rect r1 = (Rect) o1; Rect r2 = (Rect) o2; return (r1.count - r2.count) * order; } } ); }