import java.awt.Color; import java.awt.Point; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.TreeMap; import processing.core.PApplet; import processing.core.PFont; /* * Pehr Hovey * MAT259 Project 1 Winter 2009 * * MAT259_Project_1.java * Graph Checkout duration, collection code and time in 2D * * Load any .txt files in the /data/ folder * * Developed for JDK1.4 compliance and not 5.0 to try to keep it compatible with processing * */ ArrayList transactions = null; // global List of the data, used for sorting // to make new views ArrayList transactions_plot = null; // copy of the main list, used for // plotting final int NUM_MILLIS_PER_DAY = 1000 * 60 * 60 * 24; static boolean debug = true; // Print debug messages? boolean do_draw_delay = true; // delay a bit between each point to see the // progression? int draw_delay = 100; // millisecond delay between each point drawn int delay_stride = 100; long initTimeStamp; int threshold_percentage = 2; // what min % of the dataset does a catagory // need to occupy to have it included? // use this to discard low-participation catagories to clean up the // visualization // Dictionaries to map a code to printable name HashMap sections; HashMap collections; HashMap itemtypes; // Calculate checkout duration extremes for scaling purposes long maxDuration; long minDuration; // anchor points for axes, parameterize everything to make it flexible Point origin_anchor; Point top_left_anchor; Point bottom_right_anchor; PFont gotham24; PFont gotham16; final int WIDTH = 1024; final int HEIGHT = 800; // final int PADDING = 128; //A major parameter that controls how many // things are layed out final int PADDING = 140;//HEIGHT / 8; // A major parameter that controls how many // things are layed out // the rightmost drawing coord varies depending on whether buttons are shown // or not final int RIGHT_X_WITH_BUTTONS = (int) (WIDTH - 1.5 * PADDING); final int RIGHT_X_NO_BUTTONS = WIDTH - PADDING; final int AXES_COLOR = Color.gray.getRGB(); final int AXES_LINE_WIDTH = 4; final int YLABEL_COLOR = Color.gray.darker().getRGB();// color(100,100,255); // //light blue final int XLABEL_COLOR = YLABEL_COLOR; final int DATA_COLOR = Color.RED.getRGB(); final int TEXT_COLOR = Color.gray.darker().getRGB(); final int DURATION_COLOR = Color.GREEN.getRGB(); final int BACKGROUND_COLOR = Color.BLACK.getRGB(); final int DATA_ALPHA = 75; // How translucent the vertical dataslice is final int DATA_POINT_WIDTH = 1; final int DATA_POINT_HEIGHT = 4; final int NUM_X_LABELS = 11; final float X_LABEL_ROTATION = 0.24f; final float XLABEL_Y = HEIGHT - PADDING + 20; final float DURATION_LEGEND_X = 100; final float DURATION_LEGEND_Y = 100; final float DURATION_LEGEND_WIDTH = 64; final float DURATION_LEGEND_HEIGHT = 64; final int TEXT_SIZE = 24; String filename = "june_04_hr13.txt"; // for display TreeMap yAxisLabels; // map the label to y-value relative to origin // Visualization-specific quantities private ArrayList itemTypeCounts; // These are ArrayLists of CountEntry // objects private ArrayList collectionCounts; private ArrayList sectionCounts; float max_duration_height = 128; // modified by the initYLabels functions // so it fits between y-quantization // levels final int COUNTS_X = (WIDTH / 2); // where to start printing out the section // counts final int COUNTS_Y = (int) Math.floor(0.75 * PADDING);//HEIGHT - (int) Math.floor(0.25 * PADDING); final int COUNTS_SIZE = 16; final int COUNTS_COLOR = TEXT_COLOR;// color(128,128,128); final int TITLE_X = WIDTH / 2; // assumes textAlign is CENTER final int TITLE_Y = (int) Math.floor(0.5 * PADDING); final int CONTROL_X = RIGHT_X_WITH_BUTTONS;//WIDTH - 2 * PADDING; final int CONTROL_Y = PADDING; final int CONTROL_SIZE = 24; final int CONTROL_DIV_WIDTH = 4; final int CONTROL_PADDING = PADDING / 6; final int CONTROL_Y_PADDING = PADDING / 3; // help space out the buttons // vertically final int CONTROL_BUTTON_WIDTH = 150; final int CONTROL_BUTTON_HEIGHT = 28; final int CONTROL_BUTTON_COLOR = color(128); final int CONTROL_BUTTON_H_COLOR = color(200); // button outline final int CONTROL_BUTTON_A_COLOR = color(236, 255, 129); // when it has // been clicked // last final int CONTROL_BUTTON_TEXT_SIZE = 16;//(int) (0.75 * CONTROL_SIZE); boolean show_buttons = false; boolean distribute_x_equally = false; //implement support for alternative means of distributing x coords // GUI buttons to let user change the plot during runtime Button btn_do_section, btn_do_collection, btn_do_itemtype; Button btn_sort_time, btn_sort_duration, btn_sort_title; Button btn_then_sort_by; // Button btn_height_scale; Button btn_x_distribution; final int SORT_LABEL_COLOR = TEXT_COLOR;// color(128,128,128); final int SORT_LABEL_X = PADDING; final int SORT_LABEL_Y = HEIGHT - (int) Math.floor(0.3 * PADDING); final int SORT_LABEL_SIZE = 16; // text size // The vars below will drastically change how viz looks. // They are fields so they can be modified at runtime before redrawing // Use this to tell later parts of the program what we are using for the // Y-axis String what_to_do = "section"; // section, collection, itemtype // Use this to tell the draw method how to sort the data String how_to_sort = "duration"; // time, title, duration, y-axis boolean then_sort_by_y = false; // do a second sort by the y-axis bins to // make it easier to interpret boolean log_scale = false; // how to calculate the data height boolean drawing = false; String counts_str = ""; // Arraylists to cache the sort results to speed up further views of them // This uses the Singleton pattern to ensure uniqueness // Large sets may cause memory issues // naming convention: t_%what_to_do%_%how_to_sort% and maybe _%2% for 2nd // sort ArrayList t_section_duration; ArrayList t_collection_duration; ArrayList t_itemtype_duration; ArrayList t_section_duration_2; ArrayList t_collection_duration_2; ArrayList t_itemtype_duration_2; ArrayList t_section_time; ArrayList t_collection_time; ArrayList t_itemtype_time; ArrayList t_section_time_2; ArrayList t_collection_time_2; ArrayList t_itemtype_time_2; ArrayList t_section_title; ArrayList t_collection_title; ArrayList t_itemtype_title; ArrayList t_section_title_2; ArrayList t_collection_title_2; ArrayList t_itemtype_title_2; long start = 0, end = 0; // helps with timing stuff public void setup() { initTimeStamp = System.currentTimeMillis(); // use in naming screenshots size(WIDTH, HEIGHT); noLoop(); // only re-draw screen if we know something has changed fill(BACKGROUND_COLOR); rect(0, 0, WIDTH, HEIGHT); // clear the screen // The whole graph part is relative to these points // Changing them allows the whole graph to move or scale origin_anchor = new Point(PADDING, HEIGHT - PADDING); top_left_anchor = new Point(origin_anchor.x, PADDING); if (show_buttons) bottom_right_anchor = new Point(RIGHT_X_WITH_BUTTONS, HEIGHT - PADDING); else bottom_right_anchor = new Point(RIGHT_X_NO_BUTTONS, HEIGHT - PADDING); background(Color.BLACK.getRGB()); gotham24 = loadFont("GothamBold-24.vlw"); gotham16 = loadFont("GothamBold-16.vlw"); textFont(gotham24, 24); initControlButtons(); minDuration = Integer.MAX_VALUE; // init to worst-case so we can // iteratively calculate the right // vals maxDuration = Integer.MIN_VALUE; // Init dictionaries sections = TransactionUtils.getSectionsMap(); collections = TransactionUtils.getCollectionsMap(); itemtypes = TransactionUtils.getItemTypesMap(); long before = millis(); this.transactions = loadTransactions(); println("loaded " + this.transactions.size() + " transactions in " + (millis() - before) + " miiliseconds."); println("check-out duration varies from " + minDuration + " to " + maxDuration + " in milliseconds"); println("check-out duration varies from " + minDuration / NUM_MILLIS_PER_DAY + " to " + maxDuration / NUM_MILLIS_PER_DAY + " in days"); // TransactionUtils.filterOutNonAlphabeticalTitles(transactions); itemTypeCounts = TransactionUtils.calculateItemTypeCounts(transactions); System.out.println("itemTypeCounts:\n" + itemTypeCounts); TransactionUtils.sortCountsByCount(itemTypeCounts); collectionCounts = TransactionUtils .calculateCollectionCounts(transactions); TransactionUtils.sortCountsByCount(collectionCounts); System.out.println("collectionCodeCounts:\n" + collectionCounts); sectionCounts = TransactionUtils.calculateSectionCounts(transactions); System.out.println("sectionCodeCounts:\n" + sectionCounts); TransactionUtils.sortCountsByCount(sectionCounts); pruneTransactions(transactions); // discard unpopular catagories. // must be called after __counts are initalized and before making any // copies of the transactions array println("After pruning we have " + transactions.size() + " to plot"); yAxisLabels = new TreeMap(); // printTransactions(500); // testDictionaries(); //try using the dictionaries to decode itemtype, // etc this.transactions_plot = TransactionUtils .copyTransactions(this.transactions); update_transactions(); // initialize the drawing params stored in each // transaction System.out.println("Done with setup()!"); // System.exit(0); } // Overload processing's redraw method to fit our purposes public void redraw() { start = System.currentTimeMillis(); // println("redraw"); update_transactions(); super.redraw(); } private void initControlButtons() { String l1 = "Section"; String l2 = "Collection"; String l3 = "Item Type"; String l4 = "Duration"; String l5 = "Checkin Time"; String l6 = "Title"; String l7 = "Then Sort By..."; //String l8 = "Log Scale"; String l8 = "Dist. X Equally"; btn_do_section = new Button(CONTROL_X + CONTROL_PADDING, CONTROL_Y + CONTROL_Y_PADDING, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l1); btn_do_collection = new Button(CONTROL_X + CONTROL_PADDING, CONTROL_Y + 2 * CONTROL_Y_PADDING, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l2); btn_do_itemtype = new Button(CONTROL_X + CONTROL_PADDING, CONTROL_Y + 3 * CONTROL_Y_PADDING, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l3); btn_sort_duration = new Button(CONTROL_X + CONTROL_PADDING, (int) (CONTROL_Y + 4.75 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l4); btn_sort_time = new Button(CONTROL_X + CONTROL_PADDING, (int) (CONTROL_Y + 5.75 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l5); btn_sort_title = new Button(CONTROL_X + CONTROL_PADDING, (int) (CONTROL_Y + 6.75 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l6); btn_then_sort_by = new Button(CONTROL_X + CONTROL_PADDING, (int) (CONTROL_Y + 8.25 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l7); // btn_height_scale = new Button(CONTROL_X + CONTROL_PADDING,(int)( CONTROL_Y // + 9.25 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, // CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, // CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l8); btn_x_distribution = new Button(CONTROL_X + CONTROL_PADDING, (int) (CONTROL_Y + 9.25 * CONTROL_Y_PADDING), CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, CONTROL_BUTTON_COLOR, CONTROL_BUTTON_H_COLOR, CONTROL_BUTTON_A_COLOR, l8); // } // Given an ArrayList of CountEntry objects, make a composite string to // display later private String getCounts(ArrayList counts, String what_to_do) { String result = ""; String label = ""; int count = 0; CountEntry entry; double max_length = (WIDTH - COUNTS_X) / 12.0; for (int l = 0; l < counts.size(); l++) { entry = (CountEntry) counts.get(l); label = entry.label; count = entry.count; if (what_to_do == "section") label = getSectionName(label); else if (what_to_do == "collection") label = getCollectionName(label); else if (what_to_do == "itemtype") label = getItemTypeName(label); result += (label + ": " + count); if (result.length() > max_length) { // keep the string on screen, // truncate what is left result += "... "; break; } else { result += " "; } } result += " Total: " + transactions_plot.size(); return result; } private void drawCounts() { fill(COUNTS_COLOR); textAlign(CENTER); textFont(gotham16, COUNTS_SIZE); text(counts_str, COUNTS_X, COUNTS_Y); } void drawSortLabels() { fill(SORT_LABEL_COLOR); textFont(gotham16, SORT_LABEL_SIZE); textAlign(LEFT); // Sort the data and put a label on the screen if (how_to_sort == "time") { text("Sorted by Check-in Time --->", SORT_LABEL_X, SORT_LABEL_Y); } else if (how_to_sort == "duration") { text("Sorted by Check-out Duration (days)--->", SORT_LABEL_X, SORT_LABEL_Y); } else if (how_to_sort == "title") { text("Sorted by Title --->", SORT_LABEL_X, SORT_LABEL_Y); } else if (how_to_sort == "y-axis") { if (what_to_do == "section") { text("Sorted by Section --->", SORT_LABEL_X, SORT_LABEL_Y); } else if (what_to_do == "collection") { text("Sorted by Collection --->", SORT_LABEL_X, SORT_LABEL_Y); } else if (what_to_do == "itemtype") { text("Sorted by Item Type --->", SORT_LABEL_X, SORT_LABEL_Y); } } // Do the 'then by' label below the main label if (then_sort_by_y) { if (what_to_do == "section") { text(" then by Section --->", SORT_LABEL_X, SORT_LABEL_Y + SORT_LABEL_SIZE); } else if (what_to_do == "collection") { text(" then by Collection --->", SORT_LABEL_X, SORT_LABEL_Y + SORT_LABEL_SIZE); } else if (what_to_do == "itemtype") { text(" then by Item Type --->", SORT_LABEL_X, SORT_LABEL_Y + SORT_LABEL_SIZE); } } } /** ******************** */ String getSectionName(String section_code) { String name = (String) sections.get(section_code); if (name == null) name = section_code; return name; } String getCollectionName(String collection_code) { String name = (String) collections.get(collection_code); if (name == null) name = collection_code; return name; } String getItemTypeName(String item_type) { String name = (String) itemtypes.get(item_type); if (name == null) name = item_type; return name; } // Get an array of x-coords to plot some labels //Equally distributed between 0 and the length float[] getXLabelCoords(int num) { float[] coords = new float[num]; float x_axis_length = bottom_right_anchor.x - origin_anchor.x; for (int i = 0; i < num; i++) { coords[i] = PADDING + x_axis_length * (i / (float) (num - 1)); } return coords; } // do the heavy lifting here before calling redraw() // update params stored in each transaction // puts the results in 'transactions' and returns it so it can be cached private void update_transactions() { if (what_to_do == "section") { initYLabels(sectionCounts, sections); counts_str = getCounts(sectionCounts, "section"); // println(sectionCounts); } else if (what_to_do == "itemtype") { initYLabels(itemTypeCounts, itemtypes); counts_str = getCounts(itemTypeCounts, "itemtype"); // println(itemTypeCounts); } else if (what_to_do == "collection") { initYLabels(collectionCounts, collections); counts_str = getCounts(collectionCounts, "collection"); // println(collectionCounts); } // see if we have a cached copy of the transactions ArrayList, otherwise // draw it ArrayList cached_trans = getCached(); if (cached_trans != null) { transactions_plot = cached_trans; // this is ready to draw since // the drawing method does not // mutate it // println("we have "+transactions_plot.get(123)); } else { // Manipulate the base ArrayList and save a deep copy it for // future re-use // Sort the data and put a label on the screen if (how_to_sort == "time") { TransactionUtils.sortTransactionsByTimeStamp(transactions); } else if (how_to_sort == "duration") { TransactionUtils.sortTransactionsByDuration(transactions); } else if (how_to_sort == "title") { TransactionUtils.sortTransactionsByTitle(transactions); } else if (how_to_sort == "y-axis") { if (what_to_do == "section") { TransactionUtils.sortTransactionsBySection(transactions); } else if (what_to_do == "collection") { TransactionUtils.sortTransactionsByCollection(transactions); } else if (what_to_do == "itemtype") { TransactionUtils.sortTransactionsByItemType(transactions); } } // Do a second sort if desired if (then_sort_by_y) { if (what_to_do == "section") { TransactionUtils.sortTransactionsBySection(transactions); } else if (what_to_do == "collection") { TransactionUtils.sortTransactionsByCollection(transactions); } else if (what_to_do == "itemtype") { TransactionUtils.sortTransactionsByItemType(transactions); } } // Calculate the position and characteristics of each transaction float y = 0; float[] xx, xx2; Transaction trans; String key = ""; float h; float last_y = 0; for (int t = 0; t < transactions.size(); t++) { trans = (Transaction) transactions.get(t); if (what_to_do == "section") { key = getSectionName(TransactionUtils .getSection(trans.collcode)); } else if (what_to_do == "collection") { key = getCollectionName(TransactionUtils .getCollection(trans.collcode)); } else if (what_to_do == "itemtype") { key = getItemTypeName(TransactionUtils .getItemType(trans.itemtype)); } Object val = yAxisLabels.get(key); if (val == null) { System.err.println("bad key for " + what_to_do + ": " + key + " Transaction:" + trans); } else { // Calc and store coords for each transaction y = ((Float) val).floatValue(); //xx = getXforDataPoint(t); // this gets x's for buttons or // no buttons shown xx2 = getXforDataPoint_equal(trans.ckoduration); //the equal distribution method -- looks like linear slope xx = getXforDataPoint(t); //distribute based on # of transactions, looks like exponential curve trans.duration_height_log = getLogHeightForDuration(trans.ckoduration); trans.duration_height_lin = getLinHeightForDuration(trans.ckoduration); trans.x = xx[0]; trans.x_b = xx[1]; trans.x_equal = xx2[0]; trans.x_b_equal = xx2[1]; trans.y = y; trans.label = getXLabel(trans, how_to_sort); // cache the // label for // this // datapoint if (then_sort_by_y && (y != last_y)) { // draw a label and // line at sort // boundary trans.on_boundary = true; } else { trans.on_boundary = false; } last_y = y; // save it // System.out.println("Plotting "+trans); } } // println("we have2 "+transactions.get(123)); putCached(transactions); transactions_plot = TransactionUtils.copyTransactions(transactions);// make // deep // copy // to // plot } } // return the proper arraylist if we have one already else return null so we // know to make a new one // shallow copy OK since we should not be mutating it after we cache it private ArrayList getCached() { ArrayList cached = null; // Sort by duration if (what_to_do == "section" && how_to_sort == "duration" && !then_sort_by_y) { if ((t_section_duration != null)) { cached = new ArrayList(t_section_duration); println("Using cached t_section_duration"); } else { println("making cached t_section_duration"); } } else if (what_to_do == "section" && how_to_sort == "duration" && then_sort_by_y) { if (t_section_duration_2 != null) { cached = new ArrayList(t_section_duration_2); println("Using cached t_section_duration_2"); } else { println("making cached t_section_duration_2"); } } else if (what_to_do == "collection" && how_to_sort == "duration" && !then_sort_by_y) { if (t_collection_duration != null) { cached = new ArrayList(t_collection_duration); println("Using cached t_collection_duration"); } else { println("making cached t_collection_duration"); } } else if (what_to_do == "collection" && how_to_sort == "duration" && then_sort_by_y) { if (t_collection_duration_2 != null) { cached = new ArrayList(t_collection_duration_2); println("Using cached t_collection_duration_2"); } else { println("making cached t_collection_duration_2"); } } else if (what_to_do == "itemtype" && how_to_sort == "duration" && !then_sort_by_y) { if (t_itemtype_duration != null) { cached = new ArrayList(t_itemtype_duration); println("Using cached t_itemtype_duration"); } else { println("making cached t_itemtype_duration"); } } else if (what_to_do == "itemtype" && how_to_sort == "duration" && then_sort_by_y) { if (t_itemtype_duration_2 != null) { cached = new ArrayList(t_itemtype_duration_2); println("Using cached t_itemtype_duration_2"); } else { println("making cached t_itemtype_duration_2"); } // Sort by // time---------------------------------------------------------------- } else if (what_to_do == "section" && how_to_sort == "time" && !then_sort_by_y) { if ((t_section_time != null)) { cached = new ArrayList(t_section_time); println("Using cached t_section_time"); } else { println("making cached t_section_time"); } } else if (what_to_do == "section" && how_to_sort == "time" && then_sort_by_y) { if (t_section_time_2 != null) { cached = new ArrayList(t_section_time_2); println("Using cached t_section_time_2"); } else { println("making cached t_section_time_2"); } } else if (what_to_do == "collection" && how_to_sort == "time" && !then_sort_by_y) { if (t_collection_time != null) { cached = new ArrayList(t_collection_time); println("Using cached t_collection_time"); } else { println("making cached t_collection_time"); } } else if (what_to_do == "collection" && how_to_sort == "time" && then_sort_by_y) { if (t_collection_time_2 != null) { cached = new ArrayList(t_collection_time_2); println("Using cached t_collection_time_2"); } else { println("making cached t_collection_time_2"); } } else if (what_to_do == "itemtype" && how_to_sort == "time" && !then_sort_by_y) { if (t_itemtype_time != null) { cached = new ArrayList(t_itemtype_time); println("Using cached t_itemtype_time"); } else { println("making cached t_itemtype_time"); } } else if (what_to_do == "itemtype" && how_to_sort == "time" && then_sort_by_y) { if (t_itemtype_time_2 != null) { cached = new ArrayList(t_itemtype_time_2); println("Using cached t_itemtype_time_2"); } else { println("making cached t_itemtype_time_2"); } // Sort by // title--------------------------------------------------------------- } else if (what_to_do == "section" && how_to_sort == "title" && !then_sort_by_y) { if ((t_section_title != null)) { cached = new ArrayList(t_section_title); println("Using cached t_section_title"); } else { println("making cached t_section_title"); } } else if (what_to_do == "section" && how_to_sort == "title" && then_sort_by_y) { if (t_section_title_2 != null) { cached = new ArrayList(t_section_title_2); println("Using cached t_section_title_2"); } else { println("making cached t_section_title_2"); } } else if (what_to_do == "collection" && how_to_sort == "title" && !then_sort_by_y) { if (t_collection_title != null) { cached = new ArrayList(t_collection_title); println("Using cached t_collection_title"); } else { println("making cached t_collection_title"); } } else if (what_to_do == "collection" && how_to_sort == "title" && then_sort_by_y) { if (t_collection_title_2 != null) { cached = new ArrayList(t_collection_title_2); println("Using cached t_collection_title_2"); } else { println("making cached t_collection_title_2"); } } else if (what_to_do == "itemtype" && how_to_sort == "title" && !then_sort_by_y) { if (t_itemtype_title != null) { cached = new ArrayList(t_itemtype_title); println("Using cached t_itemtype_title"); } else { println("making cached t_itemtype_title"); } } else if (what_to_do == "itemtype" && how_to_sort == "title" && then_sort_by_y) { if (t_itemtype_title_2 != null) { cached = new ArrayList(t_itemtype_title_2); println("Using cached t_itemtype_title_2"); } else { println("making cached t_itemtype_title_2"); } } // if(cached != null){ // println("returning "+cached.get(123)); // }else{ // println("returning NULL"); // } return cached; // this will be null if we didnt find a cached opy } // Need to make deep copies into the cache private void putCached(ArrayList trans) { // sort by duration if (what_to_do == "section" && how_to_sort == "duration" && !then_sort_by_y) { t_section_duration = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "section" && how_to_sort == "duration" && then_sort_by_y) { t_section_duration_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "duration" && !then_sort_by_y) { t_collection_duration = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "duration" && then_sort_by_y) { t_collection_duration_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "duration" && !then_sort_by_y) { t_itemtype_duration = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "duration" && then_sort_by_y) { t_itemtype_duration_2 = TransactionUtils.copyTransactions(trans); // sort by time // ----------------------------------------------------------------------- } else if (what_to_do == "section" && how_to_sort == "time" && !then_sort_by_y) { t_section_time = TransactionUtils.copyTransactions(trans); // } else if (what_to_do == "section" && how_to_sort == "time" && then_sort_by_y) { t_section_time_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "time" && !then_sort_by_y) { t_collection_time = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "time" && then_sort_by_y) { t_collection_time_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "time" && !then_sort_by_y) { t_itemtype_time = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "time" && then_sort_by_y) { t_itemtype_time_2 = TransactionUtils.copyTransactions(trans); // sort by title // ----------------------------------------------------- } else if (what_to_do == "section" && how_to_sort == "title" && !then_sort_by_y) { t_section_title = TransactionUtils.copyTransactions(trans); // } else if (what_to_do == "section" && how_to_sort == "title" && then_sort_by_y) { t_section_title_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "title" && !then_sort_by_y) { t_collection_title = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "collection" && how_to_sort == "title" && then_sort_by_y) { t_collection_title_2 = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "title" && !then_sort_by_y) { t_itemtype_title = TransactionUtils.copyTransactions(trans); } else if (what_to_do == "itemtype" && how_to_sort == "title" && then_sort_by_y) { t_itemtype_title_2 = TransactionUtils.copyTransactions(trans); } } // Main assignment visualization // Changing it around to do only drawing here, no calculating private void draw_cko_duration_plot(ArrayList transactions) { // Plot checkout duration on x-axis, sections on y-axis with duration as // a rectangle above the pt fill(0); noStroke(); rect(0, 0, WIDTH, HEIGHT); // clear the screen // print("Drawing based on "+what_to_do +", sorting by "+how_to_sort); // if(then_sort_by_y) // println(" then by "+what_to_do); // else // println(); drawCounts(); drawYLabels(); drawSortLabels(); if (!then_sort_by_y) // the 2nd sort makes labels confusing so omit // them drawXLabels(NUM_X_LABELS, how_to_sort); // ********* stuff above must be run first ***********/ float y = 0; float x = 0; float h; Transaction trans; for (int t = 0; t < transactions_plot.size(); t++) { trans = (Transaction) transactions_plot.get(t); y = trans.y; // x depends on whether buttons are being shown or not if (!show_buttons) { if (distribute_x_equally) x = trans.x_equal; else x = trans.x; } else { if (distribute_x_equally) x = trans.x_b_equal; else x = trans.x_b; } if (log_scale) h = trans.duration_height_log; else h = trans.duration_height_lin; fill(DATA_COLOR, DATA_ALPHA); rect(x, top_left_anchor.y + y, DATA_POINT_WIDTH, DATA_POINT_HEIGHT); // the // data // point fill(DURATION_COLOR, DATA_ALPHA); rect(x, top_left_anchor.y + y - h, DATA_POINT_WIDTH, h); } } private void initYLabels(ArrayList counts, HashMap names) { // populate yAxisLabels from given information // Calculate y-coords for the labels and data, relative to y-axis yAxisLabels.clear(); // start over String label = ""; String printable; float y; float y_axis_length = Math.abs(top_left_anchor.y - origin_anchor.y); for (int l = 0; l < counts.size(); l++) { y = (float) (y_axis_length * ((counts.size() - l) / (float) counts.size()) - (1.5 * AXES_LINE_WIDTH)); label = ((CountEntry) counts.get(l)).label; printable = (String) names.get(label); // translate it to printable // form if (printable != null) label = printable; yAxisLabels.put(label, new Float(y)); } // Update max duration_height so the duration bars fit max_duration_height = y_axis_length / counts.size(); // System.out.println("Y-Labels and relative coords: \n"+yAxisLabels+" // Duration_height = "+max_duration_height); } public void draw() { // drawing = true; // Plot section, itemtype or collection on y-axis along with duration. X // depends on how trans are sorted draw_cko_duration_plot(transactions); drawAxes(); if (show_buttons) drawControl(); drawMisc(); // drawing = false; // println("drawing="+drawing); if (start != 0) { end = System.currentTimeMillis(); println("redraw took " + (end - start) + " ms (" + ((end - start) / 1000) + " s)"); } } void drawMisc() { fill(TEXT_COLOR); textAlign(CENTER); textFont(gotham24, TEXT_SIZE); // text("Pehr Hovey MAT259 Winter 2009 Item Checkout Duration // "+filename, TITLE_X, TITLE_Y); String what = what_to_do + "s"; String w = what_to_do.substring(0, 1).toUpperCase(); what = w + what.substring(1, what.length()); text("Item Checkout Duration Across " + what, TITLE_X, TITLE_Y); } // Draw buttons and such for graph control void drawControl() { fill(TEXT_COLOR); textAlign(CENTER); textFont(gotham24, 24); text("Graph Control", CONTROL_X + CONTROL_PADDING + CONTROL_BUTTON_WIDTH / 2, CONTROL_Y); // strokeWeight(CONTROL_DIV_WIDTH); fill(COUNTS_COLOR); textFont(gotham16, 16); text("What To Display", CONTROL_X + CONTROL_PADDING + CONTROL_BUTTON_WIDTH / 2, CONTROL_Y + (int) (0.7 * CONTROL_Y_PADDING)); text("How To Sort", CONTROL_X + CONTROL_PADDING + CONTROL_BUTTON_WIDTH / 2, (int) (CONTROL_Y + 4.5 * CONTROL_Y_PADDING)); updateButtons(); // this figures out what color they should be and // displays them } void drawAxes() { stroke(AXES_COLOR); strokeWeight(AXES_LINE_WIDTH); line(top_left_anchor.x, top_left_anchor.y, origin_anchor.x, origin_anchor.y); line(bottom_right_anchor.x, bottom_right_anchor.y, origin_anchor.x, origin_anchor.y); line(bottom_right_anchor.x, top_left_anchor.y, bottom_right_anchor.x, HEIGHT - PADDING); } // give an index in the transactions list, what pixel to plot at? // returns [x.x_b] two x's depending on wh //This method of allocating x coords puts the trans on the x-axis proportional to the //total number of samples so discontinuities in the duration curve hints at //durations that are not present float[] getXforDataPoint(int num) { float[] x = new float[2]; float left_offset = origin_anchor.x; float x_axis_length = RIGHT_X_WITH_BUTTONS - origin_anchor.x; float x_axis_length_no_buttons = RIGHT_X_NO_BUTTONS - origin_anchor.x; x[0] = x_axis_length_no_buttons * (num / (float) transactions.size()) + left_offset; x[1] = x_axis_length * (num / (float) transactions.size()) + left_offset; return x; } //This method of allocating x coords puts the transaction on the axis //equally distributed so 1/2 the max duration is at the midpoint //nonpresent durations are obvious as the float[] getXforDataPoint_equal(long duration) { float[] x = new float[2]; float left_offset = origin_anchor.x; float x_axis_length = RIGHT_X_WITH_BUTTONS - origin_anchor.x; float x_axis_length_no_buttons = RIGHT_X_NO_BUTTONS - origin_anchor.x; float incr = x_axis_length / (maxDuration - minDuration); float incr_ = x_axis_length_no_buttons / (maxDuration - minDuration); x[0] = incr_ * duration + left_offset; x[1] = incr * duration + left_offset; return x; } float getLinHeightForDuration(long duration) { float h; h = ((((float) duration) / (maxDuration - minDuration)) * max_duration_height); // System.out.println(duration+" -> "+h); return h; } float getLogHeightForDuration(long duration) { float h; h = (float) (log(duration) / (log(maxDuration - minDuration)) * max_duration_height); // System.out.println(duration+" -> "+h); return h; } void drawYLabels() { fill(YLABEL_COLOR); textAlign(RIGHT, BOTTOM); textFont(gotham16, 16); String label = ""; Set key_set = yAxisLabels.keySet(); Object[] labels = key_set.toArray(); float y; for (int l = 0; l < labels.length; l++) { label = (String) labels[l]; y = ((Float) yAxisLabels.get(label)).floatValue(); text(label, origin_anchor.x - 16, top_left_anchor.y + y); } } // Tell it which transaction data-field to sample and how many // equally-spaced samples to take void drawXLabels(int num, String what_field) { fill(XLABEL_COLOR); textFont(gotham16, 16); float[] coords = getXLabelCoords(num); // equally spaced x-coords to // put labels at // Get the datapoints Transaction trans; int index; double frac = transactions_plot.size() / (double) num; float y = XLABEL_Y; String field = ""; for (int f = 0; f < num; f++) { index = (int) Math.floor(f * frac); trans = (Transaction) transactions_plot.get(index); if (what_field == "time") { field = trans.ckidate + " " + trans.ckitime; } else if (what_field == "duration") { field = "" + (trans.ckoduration / NUM_MILLIS_PER_DAY); } else if (what_field == "title") { field = trans.title; if (field.length() > 17) field = field.substring(0, 16) + "..."; // truncate to save space } pushMatrix(); translate(coords[f], y); rotate(X_LABEL_ROTATION); text(field, 0, 0); popMatrix(); // text(field, coords[f], y); // System.out.println("num: "+f+" index: "+index+" field: "+field); } } // Just return the field label, do not draw it private String getXLabel(Transaction trans, String what_field) { String field = ""; if (!then_sort_by_y) { // no labels if 2nd sorting if (what_field == "time") { field = trans.ckidate + " " + trans.ckitime; } else if (what_field == "duration") { field = "" + (trans.ckoduration / NUM_MILLIS_PER_DAY); // duration // in // days } else if (what_field == "title") { field = trans.title; if (field.length() > 15) field = field.substring(0, 14) + "..."; // truncate to save // space } } return field; } public ArrayList loadTransactions() { ArrayList transactions = new ArrayList(); println("loading file... \"" + filename + "\""); String[] lines = loadStrings(filename); Transaction trans; for (int i = 0; i < lines.length; i++) { trans = TransactionUtils.parseTransaction(lines[i]); // some have checkout dates at the epoch which is an error so // discard them if (trans.ckodate.substring(0, 4).equals("1970")) continue; // discard very large durations to keep plot readable if (trans.ckoduration > (long) 10000000000l) { // added an 'l' // at the end to // specify its a // long not an // int continue; } if (trans.ckoduration < minDuration) minDuration = trans.ckoduration; else if (trans.ckoduration > maxDuration) maxDuration = trans.ckoduration; transactions.add(trans); } if (minDuration < 0) minDuration = 0; return transactions; } // Go through transactions and remove ones whose catagories do not have // enough items void pruneTransactions(ArrayList trans) { if (itemTypeCounts == null || sectionCounts == null || collectionCounts == null) { System.err .println("Error: cannot prune transactions if counts are null!"); return; } else { int start_size = trans.size(); int threshold = (int) Math.floor((threshold_percentage / 100.0) * start_size); println("Total: " + start_size + " pct: " + threshold_percentage + "% so threshold = " + threshold); // Figure out which catagories to discard: ArrayList bad_itemtypes = new ArrayList(); ArrayList bad_sections = new ArrayList(); ArrayList bad_collections = new ArrayList(); CountEntry ce; Iterator itr = itemTypeCounts.iterator(); while (itr.hasNext()) { ce = (CountEntry) itr.next(); if (ce.count < threshold) { bad_itemtypes.add(ce.label); // remove this catagory from the counts itr.remove(); // println("Will discard itemtype = "+ce.label+" bc count // "+ce.count+" < "+threshold ); } } itr = sectionCounts.iterator(); // use an iterator to ensure we hit // every element even when we remove // some while (itr.hasNext()) { ce = (CountEntry) itr.next(); if (ce.count < threshold) { bad_sections.add(ce.label); // remove this catagory from the counts itr.remove(); println("Will discard section = " + ce.label + " bc count " + ce.count + " < " + threshold); } } itr = collectionCounts.iterator(); while (itr.hasNext()) { ce = (CountEntry) itr.next(); if (ce.count < threshold) { bad_collections.add(ce.label); // remove this catagory from the counts itr.remove(); // println("Will discard collection = "+ce.label+" bc count // "+ce.count+" < "+threshold ); } } // iterate thru transactions and remove the ones with 'bad' // catagories Transaction t; String section = ""; String itemtype = ""; String collection = ""; itr = trans.iterator(); while (itr.hasNext()) { t = (Transaction) itr.next(); section = TransactionUtils.getSection(t.collcode); collection = TransactionUtils.getCollection(t.collcode); itemtype = TransactionUtils.getItemType(t.itemtype); if (bad_sections.contains(section)) { // println("Found bad section "+section+" in trans "+t); itr.remove(); // println(trans.size()); continue; // no need to check other catagories } if (bad_itemtypes.contains(itemtype)) { // println("Found bad itemtype "+itemtype+" in trans "+t); itr.remove(); // println(trans.size()); continue; // no need to check other catagories } else if (bad_collections.contains(collection)) { // println("Found bad collection "+collection+" in trans // "+t); itr.remove(); // println(trans.size()); continue; // no need to check other catagories } } println("Final discarded itemtypes = " + bad_itemtypes); println("Final discarded sections = " + bad_sections); println("Final discarded collections = " + bad_collections); println("Started with " + start_size + " now have " + trans.size()); // Have to re-calculate counts since they may have changed due to // pruning. // Though the remaining counts are for catagories that are highly // populated // Something with a 'good' section could have been deleted because // its itemtype was 'bad' itemTypeCounts = TransactionUtils .calculateItemTypeCounts(transactions); TransactionUtils.sortCountsByCount(itemTypeCounts); collectionCounts = TransactionUtils .calculateCollectionCounts(transactions); TransactionUtils.sortCountsByCount(collectionCounts); sectionCounts = TransactionUtils .calculateSectionCounts(transactions); TransactionUtils.sortCountsByCount(sectionCounts); } } // This function returns all the files in a directory as an array of File // objects // This is useful if you want more info about the file public File[] listFiles(String dir) { File file = new File(dir); if (file.isDirectory()) { File[] files = file.listFiles(); return files; } else { // If it's not a directory return null; } } // print every 'stride'th transaction void printTransactions(int stride) { for (int t = 0; t < transactions.size(); t += stride) { System.out.println(t + ": " + transactions.get(t)); } } void testDictionaries() { System.out.println("test collection-code and item-type decoding"); Transaction trans; for (int i = 0; i < transactions.size(); i++) { trans = (Transaction) transactions.get(i); System.out.print(trans.collcode + " --> " + sections.get(TransactionUtils.getSection(trans.collcode)) + " " + collections.get(TransactionUtils .getCollection(trans.collcode))); System.out.println(" | " + trans.itemtype + " --> " + itemtypes.get(TransactionUtils .getItemType(trans.itemtype))); } } // Old starter visualization private void draw_cko_duration_bargraph() { // Plot checkout duration on x-axis, sections on y-axis fill(BACKGROUND_COLOR); rect(0, 0, WIDTH, HEIGHT); // clear the screen initYLabels(sectionCounts, sections); // populate y-axis labels for // this visualization drawYLabels(); noStroke(); fill(DATA_COLOR, 25); // semi-transparent datapoints float y = 0; float x = 0; Transaction trans; String key = ""; float x_axis_length = bottom_right_anchor.x - origin_anchor.x; // how // many // pixels // we // have // to // draw // data // on for (int t = 0; t < transactions.size(); t++) { trans = (Transaction) transactions.get(t); key = getSectionName(TransactionUtils.getSection(trans.collcode)); Object val = yAxisLabels.get(key); if (val == null) { System.err.println("bad key " + key + " " + trans); } else { y = ((Float) val).floatValue(); x = trans.ckoduration / ((float) maxDuration - minDuration) * (x_axis_length) + PADDING + 16; ellipse(x, top_left_anchor.y + y, 16, 16); // System.out.println("Plotting "+trans); } } } // does what is needed to show or hide buttons but does NOT redraw void toggleShowButtons() { show_buttons = !show_buttons; if (show_buttons) bottom_right_anchor.x = RIGHT_X_WITH_BUTTONS; else bottom_right_anchor.x = RIGHT_X_NO_BUTTONS; } // *********** User Input Stuff *********************/ // Keyboard listener to change animation parameters public void keyPressed() { // System.out.println("Processing KeyStroke: "+key); char key_processed = ("" + key).toLowerCase().charAt(0); //convert key to lowercase switch (key_processed) { case 'q': case 'Q': // take a single screenshot // Each file from same program run will have same prefix, followed // by frame number String filename = "screenshots/" + initTimeStamp + "-" + frameCount + ".png"; saveFrame(filename); System.out.println("Saved Screenshot to " + filename); break; case 'e': what_to_do = "section"; println("what_to_do = " + what_to_do); redraw(); break; case 'r': what_to_do = "collection"; println("what_to_do = " + what_to_do); redraw(); break; case 't': what_to_do = "itemtype"; println("what_to_do = " + what_to_do); redraw(); break; case 'd': how_to_sort = "duration"; println("how_to_sort = " + how_to_sort); redraw(); break; case 'f': how_to_sort = "time"; println("how_to_sort = " + how_to_sort); redraw(); break; case 'g': how_to_sort = "title"; println("how_to_sort = " + how_to_sort); redraw(); break; case 'c': then_sort_by_y = !then_sort_by_y; println("then_sort_by_y = " + then_sort_by_y); redraw(); break; case 'v': log_scale = !log_scale; println("log_scale = " + log_scale); redraw(); break; case 'b': distribute_x_equally = !distribute_x_equally; println("distribute_x_equally = " + distribute_x_equally); redraw(); break; case ' ': toggleShowButtons(); println("show_buttons = " + show_buttons); redraw(); break; case 'p': do_draw_delay = !do_draw_delay; println("do_draw_delay = " + do_draw_delay); redraw(); break; } } public void mousePressed() { // System.out.println("Mouse clicked at "+mouseX+" "+mouseY); check_buttons(mouseX, mouseY); } // This method processes button clicks void check_buttons(int x, int y) { if (mousePressed && !drawing) { if (btn_do_section.pressed(x, y)) { what_to_do = "section"; redraw(); // force refresh } else if (btn_do_collection.pressed(x, y)) { what_to_do = "collection"; redraw(); // force refresh } else if (btn_do_itemtype.pressed(x, y)) { what_to_do = "itemtype"; redraw(); // force refresh } else if (btn_sort_title.pressed(x, y)) { how_to_sort = "title"; redraw(); // force refresh } else if (btn_sort_duration.pressed(x, y)) { how_to_sort = "duration"; redraw(); // force refresh } else if (btn_sort_time.pressed(x, y)) { how_to_sort = "time"; redraw(); // force refresh } else if (btn_then_sort_by.pressed(x, y)) { then_sort_by_y = !then_sort_by_y;// toggle redraw(); // force refresh // } else if (btn_height_scale.pressed(x, y)) { // log_scale = !log_scale;// toggle // // redraw(); // force refresh // } } else if (btn_x_distribution.pressed(x, y)) { distribute_x_equally = !distribute_x_equally;// toggle redraw(); // force refresh } // / println("Now drawing based on "+what_to_do+" and sorting based // on "+how_to_sort); } } void updateButtons() { if (what_to_do == "section") { btn_do_section.activate(); } else { btn_do_section.deactivate(); } if (what_to_do == "collection") { btn_do_collection.activate(); } else { btn_do_collection.deactivate(); } if (what_to_do == "itemtype") { btn_do_itemtype.activate(); } else { btn_do_itemtype.deactivate(); } if (how_to_sort == "title") { btn_sort_title.activate(); } else { btn_sort_title.deactivate(); } if (how_to_sort == "duration") { btn_sort_duration.activate(); } else { btn_sort_duration.deactivate(); } if (how_to_sort == "time") { btn_sort_time.activate(); } else { btn_sort_time.deactivate(); } if (then_sort_by_y) { btn_then_sort_by.activate(); } else { btn_then_sort_by.deactivate(); } // if (log_scale) { // btn_height_scale.activate(); // } else { // btn_height_scale.deactivate(); // } if (distribute_x_equally) { btn_x_distribution.activate(); } else { btn_x_distribution.deactivate(); } } // Simple button class for basic GUI interactivity // Based on examples from Processing.org boolean locked = false; class Button { int x, y; int basecolor, highlightcolor, currentcolor, activecolor; boolean over = false; boolean pressed = false; int w, h; // width and height String label = ""; Button(int ix, int iy, int iwidth, int iheight, int icolor, int ihighlight, int iactivecolor, String ilabel) { x = ix; y = iy; w = iwidth; h = iheight; basecolor = icolor; highlightcolor = ihighlight; activecolor = iactivecolor; currentcolor = basecolor; label = ilabel; } boolean over(int mx, int my) { if (overRect(x, y, w, h, mx, my)) { over = true; return true; } else { over = false; return false; } } void activate() { currentcolor = activecolor; display(); } void deactivate() { currentcolor = basecolor; display(); } void display() { stroke(255); fill(currentcolor); rect(x, y, w, h); fill(highlightcolor); textAlign(CENTER, TOP); textFont(gotham16, CONTROL_BUTTON_TEXT_SIZE); text(label, x + (w / 2), y + 10); noStroke(); // the absence of this was causing problems } boolean pressed(int x, int y) { if (over(x, y)) { // activate(); return true; } else { // deactivate(); return false; } } boolean over() { return true; } // given the button params and mousex, mouse y, see if we are in the // bounds boolean overRect(int x, int y, int w, int h, int mx, int my) { // System.out.println("over? "+x+" "+y+" "+mx+" "+my); if (mx >= x && mx <= x + w && my >= y && my <= y + h) { // println("yes"); return true; } else { // println("no"); return false; } } boolean overCircle(int x, int y, int diameter) { float disX = x - mouseX; float disY = y - mouseY; if (sqrt(sq(disX) + sq(disY)) < diameter / 2) { return true; } else { return false; } } }