/* * Author : Bita Mazloom * Course : MAT259 Winter 2011 * Title : Dewey TreeMap * Reference : Ben Fry's Visualizing Data */ import java.io.*; import java.util.regex.*; import java.lang.Integer; import de.bezier.data.sql.*; import processing.core.PApplet; import processing.core.PFont; import processing.core.PImage; public class DeweyTreeMap extends PApplet { private static final long serialVersionUID = 1L; // SBK:warning fix in final boolean DEBUG_MODE = false; final int DEBUG_ZOOM_MULTIPLE = 1; final boolean CONNECT_TO_MYSQL = false; final boolean ENABLE_BARCODE2TITLE_SEARCH = false; final int MAX_NUM_DEWEY_CLASSES = 10; final int DEWEY_CLASS_SIZE = 100; final int DEWEY_SUBCLASS1_SIZE = 10; final int DEWEY_SUBCLASS2_SIZE = 1; final int MARGIN_SIZE = 15; final int VERTICAL_LEGEND_WIDTH = 240; final int HORIZONTAL_LEGEND_HEIGHT = 0; final int BACKGROUND_COLOR = 175; final int SLIDER_COLOR = 225; final int SLIDER_SHADOW_COLOR = 100; final int ZOOM_DEPTH = 4; final int MAX_NUM_SUBCLASS3_DEWEYS = 10; final int MAX_NUM_SUBCLASS3_TITLES = 3; final int ZOOM_PLUS = ZOOM_DEPTH; final int ZOOM_MINUS = ZOOM_DEPTH+1; final int MAX_INVALID_ZOOMIN_MESSAGE_TIME = 1; final int IMAGE_BOOK_WIDTH = 80; final int IMAGE_BOOK_HEIGHT = 97; final int TITLE_TEXT_COLOR = 255; final int TITLE_HEIGHT = 30; final int TITLE_VERTICAL_SPACE = 0; final int COUNT_LEGEND_HEIGHT = 80; final int LEGEND_TEXT_COLOR = 255; final String REFERENCE_LABEL = "Visualizing Data\nBita Mazloom"; final String REFERENCE_LINK = "http://benfry.com"; final String barcodeFileName = "barcodes.tsv"; final String barcode2TitleFileName = "barCodeToTitleNoIll_short.tsv"; Coordinates reference_label_coordinates; int width = 1024; int height = 700; int treemap_X0, treemap_Y0; int treemap_width, treemap_height; DeweyClass rootItem; SubClass rolloverItem; DeweyClass taggedItem; BoundsIntegrator zoomBounds; DeweyClass zoomItem; DeweyClass highlightedItem; Coordinates[] zoomBarPositions = new Coordinates[ZOOM_DEPTH+2]; // + 2 for plus and minus signs String[] zoomBarLabels = new String[ZOOM_DEPTH]; double[] zoomBarCounts = new double[ZOOM_DEPTH]; int zoom_width = 8; int zoom_height = 40; int currentZoomDepth = 0; int mouseX_prev_class, mouseY_prev_class; int invalidZoomInMessageStartTime = 0; boolean showZoomInMessage = false; RankedLongArray numTransactions = new RankedLongArray(); PFont font; PFont title_font; PFont reference_font; PFont invalid_action_font; static File C_FILE; // Used for getting file separator in platform independent manner DeweyClass[] temp_deweyClasses = new DeweyClass[MAX_NUM_DEWEY_CLASSES]; String server = "vislab2.mat.ucsb.edu"; String user = "mat259"; String pass = "V1sual1zat1on"; String database = "spl"; String table = "items"; String theQuery = ""; MySQL msql; public void setup() { // NOTE : numeric values required for Eclipse Processing exporter size(1024, 700); size(width, height); treemap_X0 = MARGIN_SIZE; treemap_Y0 = MARGIN_SIZE; treemap_width = width - (MARGIN_SIZE*2+VERTICAL_LEGEND_WIDTH); treemap_height = height - MARGIN_SIZE*2; zoomBounds = new BoundsIntegrator(treemap_X0, treemap_Y0, treemap_width, treemap_height); cursor(CROSS); rectMode(CORNERS); smooth(); noStroke(); font = createFont("SansSerif", 13); title_font = createFont("SansSerif", 16, true); reference_font = createFont("SansSerif", 9, true); invalid_action_font = createFont("SansSerif", 12, true); for(int i=0; i < ZOOM_DEPTH; i++) { zoomBarLabels[i] = ""; zoomBarCounts[i] = 0; } if(!DEBUG_MODE) { msql = new MySQL( this, server, database, user, pass ); print("Connecting to mysql...\n\n"); if ( CONNECT_TO_MYSQL && msql.connect() ) System.out.println("yay! connected to database :-)"); else msql = null; } // NOTE : below query takes ~6 hours for 500K queries if(msql != null && !(new File(".." + File.separator + "data" + File.separator + barcode2TitleFileName).exists())) { System.out.println("DeweyTreeMap : setup() : file barcode_title.tsv found."); queryBarcodeTitles(); } DeweyClass root_dewey = buildDeweyRanges(); setRoot(root_dewey); } public void queryBarcodeTitles() { String query; String barcode, title; String[] lines; String dataDir = ".." + File.separator + "data" + File.separator; String filename = dataDir + barcode2TitleFileName; PrintWriter outfile = createWriter(filename); outfile.println("barcode\ttitle"); lines = loadStrings(dataDir + barcodeFileName); for (int i = 1; i < lines.length; i++) { barcode = lines[i].replaceAll("\\n", ""); query = "SELECT title FROM items WHERE barcode = \'" + barcode + "\'"; msql.query(query); if(msql.next()) { title = msql.getString("title"); System.out.println(i + "\t" + barcode + "\t" + title); outfile.println(barcode + "\t" + title); } } outfile.close(); } public String getTitleOfBarcode(String barcode) { String title = ""; String line; String[] tokens; String dataDir = ".." + File.separator + "data" + File.separator; FileInputStream fstream; DataInputStream in; BufferedReader br; try { fstream = new FileInputStream(dataDir + barcode2TitleFileName); in = new DataInputStream(fstream); br = new BufferedReader(new InputStreamReader(in)); while((line = br.readLine()) != null) { //System.out.println("line = " + line); tokens = line.split("\t"); if(tokens.length < 2) continue; if(tokens[0].compareTo(barcode) == 0) { title = tokens[1]; break; } } } catch(IOException e) { e.printStackTrace(); } return title; } DeweyClass buildDeweyRanges() { int i = 0, low, high; int subclass_1, subclass_2; String label = ""; String dataDir = ".." + File.separator + "data" + File.separator; String filename; String lines[ ]; Pattern pattern_deweyClass = Pattern.compile("^class\t*"); Pattern pattern_subclass_1 = Pattern.compile("^subclass_1\t*"); Pattern pattern_subclass_2 = Pattern.compile("^\tsubclass_2\t*"); Pattern pattern_unassigned1 = Pattern.compile("Unassigned"); Pattern pattern_unassigned2 = Pattern.compile("Not assigned"); Pattern pattern; Matcher matcher; DeweyClass root_dewey; Range dewey_range; DeweyClass[] temp_classes = new DeweyClass[MAX_NUM_DEWEY_CLASSES]; ReadDeweyProperties tmp_read_dewey; ReadDeweyProperties[] tmp_read_deweys = new ReadDeweyProperties[MAX_NUM_DEWEY_CLASSES]; filename = dataDir + "dewey_class_hierarchy.txt"; lines = loadStrings(filename); for (i = 0; i < MAX_NUM_DEWEY_CLASSES; i++) { matcher = pattern_deweyClass.matcher(lines[i]); if(matcher.find()) { low = Integer.parseInt(lines[i].substring(6, 9)); high = low + DEWEY_CLASS_SIZE-1; label = lines[i].substring(9); dewey_range = new Range(low, high, label, msql); tmp_read_dewey = new ReadDeweyProperties(dewey_range, 0); tmp_read_deweys[low/DEWEY_CLASS_SIZE] = tmp_read_dewey; } } for (; i < lines.length; i++) { matcher = pattern_unassigned1.matcher(lines[i]); if (matcher.find()) continue; matcher = pattern_unassigned2.matcher(lines[i]); if (matcher.find()) continue; matcher = pattern_subclass_1.matcher(lines[i]); if (matcher.find()) { low = Integer.parseInt(lines[i].substring(11, 14)); high = low + DEWEY_SUBCLASS1_SIZE-1; label = lines[i].substring(15); dewey_range = new Range(low, high, label, msql); tmp_read_dewey = new ReadDeweyProperties(dewey_range, 0); tmp_read_deweys[low/DEWEY_CLASS_SIZE].addChild(tmp_read_dewey); subclass_1 = (low%DEWEY_CLASS_SIZE)/DEWEY_SUBCLASS1_SIZE; for(subclass_2 = low; subclass_2 < (low+DEWEY_SUBCLASS1_SIZE); subclass_2++) { dewey_range = new Range(subclass_2, subclass_2+DEWEY_SUBCLASS2_SIZE-1, "[UNASSIGNED]", msql); tmp_read_dewey = new ReadDeweyProperties(dewey_range, 0); tmp_read_deweys[low/DEWEY_CLASS_SIZE].getChildAt(subclass_1).addChild(tmp_read_dewey); } continue; } matcher = pattern_subclass_2.matcher(lines[i]); if (matcher.find()) { low = Integer.parseInt(lines[i].substring(12, 15)); subclass_1 = (low%DEWEY_CLASS_SIZE)/DEWEY_SUBCLASS1_SIZE; subclass_2 = (low%10); //high = low + DEWEY_SUBCLASS2_SIZE-1; label = lines[i].substring(16); tmp_read_deweys[low/DEWEY_CLASS_SIZE].getChildAt((low%DEWEY_CLASS_SIZE)/DEWEY_SUBCLASS1_SIZE).setChildsLabel(subclass_2, label); continue; } } // FOR-LOOP : LINE IN FILE //System.out.println("dewey hierarchy = " ); //for(i = 0; i < tmp_read_deweys.length; i++) // System.out.println(tmp_read_deweys[i]); filename = dataDir + "dewey_transactions.tsv"; lines = loadStrings(filename); int dewey, count; String subclass_3 = ""; String title; String[] barcodes; pattern = Pattern.compile("^([A-Za-z]*+)([0-9]++)(.[0-9]*+)?\t([0-9]*+)\t([0-9,]++)"); boolean matchFound; //int test_num = 0; for (i = 0; !DEBUG_MODE && i < lines.length; i++) { matcher = pattern.matcher(lines[i]); matchFound = matcher.find(); if (matchFound) { dewey = Integer.parseInt(matcher.group(2)); if(dewey < 0 || dewey > 999) continue; count = Integer.parseInt(matcher.group(4)); subclass_1 = (dewey%DEWEY_CLASS_SIZE)/DEWEY_SUBCLASS1_SIZE; subclass_2 = (dewey%10); if(matcher.group(3) != null) subclass_3 = (matcher.group(3)).substring(1); else subclass_3 = ""; barcodes = (matcher.group(5)).split(","); for(int j = 0; j max_label_width) { labelY1 = zoomBarPositions[i].getY1(); drawMultiLineText(zoomBarLabels[i], font, labelX0, labelY1, max_label_width); } else { fill(SLIDER_SHADOW_COLOR); text(zoomBarLabels[i], labelX0+1, zoomBarPositions[i].getY1()+1); fill(LEGEND_TEXT_COLOR); text(zoomBarLabels[i], labelX0, zoomBarPositions[i].getY1()); } } } public void drawZoomSlider() { PImage b; //int arc_height = 22; int plus_length = 8; int plus_thickness = 2; int image_book_bundle_width = 80; int image_book_bundle_height = 95; // 80x95 int zoomX0 = treemap_width + (VERTICAL_LEGEND_WIDTH - IMAGE_BOOK_WIDTH/2) + MARGIN_SIZE; int zoomY0 = MARGIN_SIZE + zoom_height + IMAGE_BOOK_HEIGHT + TITLE_HEIGHT; int zoomY1; int label_width; int label_height; pushMatrix(); translate(zoomX0, zoomY0); noStroke(); // --------------------------- // shadow // --------------------------- fill(SLIDER_SHADOW_COLOR); // bottom control zoomY1 = (ZOOM_DEPTH+1) * zoom_height; // bar rectMode(CORNERS); rect(0+2, 0, zoom_width+2, (ZOOM_DEPTH+1)*zoom_height); // --------------------------- // face // --------------------------- // top control fill(SLIDER_COLOR); // image imageMode(CENTER); b = loadImage("../images/book_bundle.png"); b.resize(IMAGE_BOOK_WIDTH, image_book_bundle_height); image(b, 0+zoom_width/2, -plus_length*3-image_book_bundle_height/2); // minus sign rectMode(CENTER); fill(SLIDER_SHADOW_COLOR); rect(0+zoom_width/2+1, -(plus_length*3)/2+1, plus_length*3, plus_length*3); fill(SLIDER_COLOR); rect(0+zoom_width/2, -(plus_length*3)/2, plus_length*3, plus_length*3); noTint(); fill(SLIDER_SHADOW_COLOR); rect(0+zoom_width/2+1, -(plus_length*3)/2+1, plus_length+4, plus_thickness+4); fill(SLIDER_COLOR); rect(0+zoom_width/2, -(plus_length*3)/2, plus_length, plus_thickness); zoomBarPositions[ZOOM_MINUS] = new Coordinates(zoomX0+zoom_width/2-(plus_length*3)/2, zoomY0-(plus_length*3), zoomX0+zoom_width+(plus_length*3)/2, zoomY0+(plus_length*3)/2); // bottom control fill(SLIDER_COLOR); zoomY1 = (ZOOM_DEPTH+1) * zoom_height + 1; // book bundle b = loadImage("../images/book_bundle_open.png"); b.resize(IMAGE_BOOK_WIDTH, IMAGE_BOOK_HEIGHT); image(b, 0+zoom_width/2, zoomY1+IMAGE_BOOK_HEIGHT/2+plus_length*3); // reference label textFont(reference_font); label_height = (int)(textAscent() - textDescent()); label_width = (int)textWidth(REFERENCE_LABEL); reference_label_coordinates = new Coordinates(zoomX0+zoom_width/2-label_width/2, zoomY0+zoomY1+IMAGE_BOOK_HEIGHT+plus_length*3+label_height, zoomX0+zoom_width/2+label_width/2, zoomY0+zoomY1+IMAGE_BOOK_HEIGHT+plus_length*3+label_height*3); // plus sign rectMode(CENTER); fill(SLIDER_SHADOW_COLOR); rect(0+zoom_width/2+1, zoomY1+(plus_length*3)/2+1, plus_length*3, plus_length*3); fill(SLIDER_COLOR); rect(0+zoom_width/2, zoomY1+(plus_length*3)/2, plus_length*3, plus_length*3); noTint(); fill(SLIDER_SHADOW_COLOR); rect(0+zoom_width/2+1, zoomY1+(plus_length*3)/2+1, plus_length+4, plus_thickness+4); rect(0+zoom_width/2+1, zoomY1+(plus_length*3)/2+1, plus_thickness+4, plus_length+4); fill(SLIDER_COLOR); rect(0+zoom_width/2, zoomY1+(plus_length*3)/2, plus_length, plus_thickness); rect(0+zoom_width/2, zoomY1+(plus_length*3)/2, plus_thickness, plus_length); zoomBarPositions[ZOOM_PLUS] = new Coordinates(zoomX0+zoom_width/2-(plus_length*3)/2, zoomY0+zoomY1, zoomX0+zoom_width+(plus_length*3)/2, zoomY0+zoomY1+plus_length*3); // bar fill(SLIDER_COLOR); rectMode(CORNERS); rect(0, 0, zoom_width, (ZOOM_DEPTH+1)*zoom_height+1); zoomY1 = 0; stroke(SLIDER_SHADOW_COLOR); line(0, zoomY1, zoom_width, zoomY1); for(int i = 0; i < ZOOM_DEPTH; i++) { zoomY1 += zoom_height; if(i != ZOOM_DEPTH) line(2, zoomY1, zoom_width-2, zoomY1); zoomBarPositions[i] = new Coordinates(zoomX0-zoom_width/2, zoomY0+zoomY1-zoom_height*2, zoomX0+zoom_width, zoomY0+zoomY1); } zoomY1 += zoom_height; line(0, zoomY1, zoom_width, zoomY1); popMatrix(); noFill(); stroke(BACKGROUND_COLOR); } public void drawZoomPosition() { int arc_width = 16; int draw_X0 = (zoomBarPositions[currentZoomDepth].getX0() + zoomBarPositions[currentZoomDepth].getX1())/2; int zoom_divider_height = 4; rectMode(CENTER); stroke(SLIDER_SHADOW_COLOR); fill(SLIDER_COLOR); // NOTE: TRY USING ROUNDED RECTANGLE HERE INSTEAD... ellipseMode(RADIUS); ellipse(draw_X0, zoomBarPositions[currentZoomDepth].getY1(), arc_width, zoom_divider_height); line(draw_X0+2, zoomBarPositions[currentZoomDepth].getY1(), draw_X0-2, zoomBarPositions[currentZoomDepth].getY1()); } public void drawFrameBorder() { fill(BACKGROUND_COLOR); rect(0, 0, width, treemap_Y0); rect(width, 0, treemap_width, height); rect(0, treemap_Y0+treemap_height, width, height); rect(0, 0, MARGIN_SIZE, height); } public void drawInvalidZoomInMessage() { int message_width; int message_height; String message; if(second() > invalidZoomInMessageStartTime+MAX_INVALID_ZOOMIN_MESSAGE_TIME) showZoomInMessage = false; if(showZoomInMessage) { textAlign(CENTER); textFont(invalid_action_font); message_height = (int)(textAscent() - textDescent()) + 3; message = "Choose a Dewey class "; message_width = (int)textWidth(message); fill(255, 0, 0); text(message, mouseX-message_width/2, mouseY); fill(SLIDER_SHADOW_COLOR); text(message, mouseX-message_width/2+1, mouseY+1); message = "to zoom into. "; fill(255, 0, 0); text(message, mouseX-message_width/2, mouseY+message_height); fill(SLIDER_SHADOW_COLOR); text(message, mouseX-message_width/2+1, mouseY+message_height+1); } } public void mousePressed() { // -1 nothing selected // 0 is no change (effectively similar to -1) // 1 zoom in // 2 zoom out int change_depth = selectZoomDepth(); if(change_depth > 0) { //System.out.println("\nmousePressed() : zoom depth action = " + change_depth + " current_zoom_depth = " + currentZoomDepth + " mouseX = " + this.mouseX + " mouseY = " + this.mouseY); drawZoomPosition(); if(change_depth == 1) { if(highlightedItem == null) { invalidZoomInMessageStartTime = second(); showZoomInMessage = true; return; } zoomItem = highlightedItem; if(change_depth == 1) zoomItem.increaseZoomDepth(); highlightedItem = null; } else if(change_depth == 2) zoomItem.decreaseZoomDepth(); } else if (zoomItem != null) { zoomItem.mousePressed(); } if(overReferenceLabel(mouseX, mouseY)) { link(REFERENCE_LINK, "_new"); } } public int selectZoomDepth() { if(this.mouseY > zoomBarPositions[ZOOM_PLUS].getY1() || this.mouseY < zoomBarPositions[ZOOM_MINUS].getY0()) return -1; if ((this.mouseY > zoomBarPositions[ZOOM_PLUS].getY0()) && (this.mouseY < zoomBarPositions[ZOOM_PLUS].getY1()) && (this.mouseX > zoomBarPositions[ZOOM_PLUS].getX0()) && (this.mouseX < zoomBarPositions[ZOOM_PLUS].getX1()) ) { if(currentZoomDepth < ZOOM_DEPTH-1) { if (highlightedItem != null) currentZoomDepth++; } else return -1; return 1; } if ( (this.mouseY > zoomBarPositions[ZOOM_MINUS].getY0()) && (this.mouseY < zoomBarPositions[ZOOM_MINUS].getY1()) && (this.mouseX > zoomBarPositions[ZOOM_MINUS].getX0()) && (this.mouseX < zoomBarPositions[ZOOM_MINUS].getX1()) ) { System.out.println(" here minus"); if(currentZoomDepth > 0) currentZoomDepth--; else return -1; return 2; } for(int i = 0; i < ZOOM_DEPTH; i++) { if ( (this.mouseY > zoomBarPositions[i].getY0()-4) && (this.mouseY < zoomBarPositions[i].getY1()+4) && (this.mouseX > zoomBarPositions[i].getX0()) && (this.mouseX < zoomBarPositions[i].getX1()) ) { System.out.println("here 2 i = " + i); if(i == currentZoomDepth) return 0; if (highlightedItem != null && i > currentZoomDepth) { currentZoomDepth = i; return 1; } else if (i < currentZoomDepth) { currentZoomDepth = i; return 2; } } } return -1; } boolean overReferenceLabel(int x, int y) { int x0, y0, w, h; x0 = reference_label_coordinates.getX0(); y0 = reference_label_coordinates.getY0(); w = reference_label_coordinates.getX1() - x0; h = reference_label_coordinates.getY1() - y0; if(x >= x0 && x <= x0+w && y >= y0 && y <= y0+h) return true; return false; } static double truncate(double x) { if ( x > 0 ) return Math.floor(x * 100)/100; else return Math.ceil(x * 100)/100; } int getMinClassSize() { return min_class_size; } int getMaxClassSize() { return max_class_size; } private int min_class_size = Integer.MAX_VALUE; int max_class_size = 0; }