import java.awt.Color; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import processing.core.PApplet; import processing.core.PConstants; import processing.core.PFont; /* * Pehr Hovey * pehr.hovey@gmail.com * MAT259 Data Visualization * Winter 2009 * * MAT259_Project2.java * * Compare two quantities using the metaphor of a seesaw / balance beam */ String checkins_filename = "data_in.txt"; //a full day String checkouts_filename = "data_out.txt"; boolean randomPalette = false; //whether to load a random color palette on startup boolean load_over_time = true; // animate the loading over time boolean loaded = false; // done animating the load? double loading_percent = 0; boolean draw_histograms = true; double load_incr = .01;// what % to load each frame double default_pct = 0.5; // if not loading over time, default to this // percentage (0.5 = 50%) boolean whiteout = false; // set bg to white for easy screenshot printing int palette = 0; //which color palette is being used //[palette][left,right] //stores color schemes for the accent colors (left and right) int[][] colors; int WIDTH = 900; // may be changed by the fullscreen code... int HEIGHT = 600; long initTimeStamp; // used as a prefix for screenshots PFont gotham24, gotham16; int PADDING = WIDTH / 6; // Initial locations of the balance objects (center of object) int CENTER_X = WIDTH / 2; int CENTER_Y = HEIGHT / 3; int LEFT_X = PADDING; // start out level int LEFT_Y = CENTER_Y; int RIGHT_Y = CENTER_Y; int RIGHT_X = WIDTH - PADDING; int TITLE_Y = PADDING / 3; int NUM_TICKS = 10; int TICK_WIDTH = 2; ArrayList ticks; //x-coords of tick marks int TIME_LEFT_X = PADDING / 2; int TIME_RIGHT_X = WIDTH - (TIME_LEFT_X); int TIME_Y = (int) (5 * HEIGHT / 6); int TIME_HEIGHT = (int)(HEIGHT/12.0); int CEILING = 2 * PADDING; // how high the object can go int FLOOR = (int) (2 * HEIGHT / 3);// (HEIGHT - 1* PADDING); // how low the // object can go int PIVOT_WIDTH = 25; // initial width of pivot int PIVOT_Y = FLOOR; int PIVOT_LEFT_X = CENTER_X - (PIVOT_WIDTH / 2); int PIVOT_RIGHT_X = CENTER_X + (PIVOT_WIDTH / 2); int MAX_PIVOT_WIDTH = WIDTH / 3;// 2*PADDING; int PIVOT_WIDTH_UNIT = PADDING / 4; // used to divide a big number into a // smaller number for width int MIN_PIVOT_WIDTH = PADDING / 12; //Histograms int HIST_PAD = 20; int HIST_WIDTH = 32; float MIN_COUNT = 1; // used to gen some random data float MAX_COUNT = 5000; float MIN_SIZE = WIDTH/14; // what size the ellipses for the BalanceObjects float MAX_SIZE = (int)(WIDTH/5.5); // Try counting the freq of the y for each object to plot graphically the // trend int[] left_hist; int[] right_hist; // Physics-y stuff double LEVER_LENGTH = (RIGHT_X - LEFT_X) / 2; // dist between pivot point // and center of mass object float MIN_NET_TORQUE = (float) (-MAX_COUNT * LEVER_LENGTH); // Right Object // is really big float MAX_NET_TORQUE = (float) (MAX_COUNT * LEVER_LENGTH); // Left object // is really big double MAX_ANGLE = 35; // max angle of rotation.. increase this for smaller data sets float INIT_COUNT = MIN_COUNT; // default initial count before we give it // data int bgColor = color(255, 255); // opaque, white int textColor = color(0, 255); // black int shadow_fill_color = color(220, 220, 220); int timeline_color = shadow_fill_color;// Color.LIGHT_GRAY.getRGB(); int timeline_highlight_color = color(90, 90, 90);// Color.DARK_GRAY.getRGB(); int timeline_tick_color = Color.WHITE.getRGB(); int timeline_label_color = Color.BLACK.getRGB(); int object_fill_color_l; int object_fill_color_r; int object_stroke_color = bgColor; int object_text_color = color(0, 0, 0); int pivot_stroke_color = color(51, 214, 165, 255); int pivot_fill_color = timeline_highlight_color;// color(0,255);//color( // 143,119,106);//color(21,16,107); // //From Colour lovers: // Grammar, dark blue // beam fill not currently applicable since it is just a line int beam_fill_color = shadow_fill_color;// color(199,237,232);//color(51,214,165,255); // //From Colour lovers: cherry bomb int beam_stroke_color = timeline_color; // shadow_fill_color;//color(51,214,165,255); BalanceObject left; BalanceObject right; BalanceBeam beam; BalancePivot pivot; Timeline timeline; // Point integrators for left, right objects Integrator x1_integrator; Integrator x2_integrator; Integrator y1_integrator; Integrator y2_integrator; Integrator left_radius_integrator; // These control the size of each circle, // left and right Integrator right_radius_integrator; Integrator pivot_width_integrator; float pos_integrator_attraction = 0.2f; // larger == faster initial movement // from rest (more spastic) float pos_integrator_damping = 0.81f; // smaller == more friction/damping // less movement float pivot_width_integrator_damping = 0.4f; // smaller == more // friction/damping less // movement float radius_integrator_attraction = 0.4f; float radius_integrator_damping = 0.5f; // Data variables final int NUM_MILLIS_PER_HOUR = 1000 * 60 * 60; final int NUM_MILLIS_PER_DAY = NUM_MILLIS_PER_HOUR * 24; final int NUM_MILLIS_PER_MONTH = NUM_MILLIS_PER_DAY * 30; // assume 30 day // month ArrayList transactions_in;// Checkin data ArrayList transactions_out;// Checkout data ArrayList transactions_all; // in + out, should be sorted by time so we can // assemble a timeline ArrayList midpoints; //this stores doubles that are the percentage representing balance between cki and cko // calculated statistics from the data long minDuration, maxDuration; long minCheckinTimestamp, maxCheckinTimestamp; // calculated based on // checkin data only! long minCheckoutTimestamp, maxCheckoutTimestamp; float checkin_ms, checkin_hours, checkin_days, checkin_months; // The current numbers to display. Will be changed as we mouseOver, etc int num_checkins; int num_checkouts; boolean clear_screen = true; // clear screen each frame? boolean fade_screen = true; // if not clearing, should we fade the old stuff // out? String[] fieldNames = new String[] // all possible column names { "itemNumber", "bibNumber", "ckodate", "ckotime", "ckidate", "ckitime", "collcode", "itemtype", "barcode", "title", "callNumber", "deweyClass", "subject" }; String[] checkin_fields; // this will store the name of each field in the // order it appears in the file String[] checkout_fields; // this will store the name of each field in the // order it appears in the file String[] monthName = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; String[] monthNameAbbrev = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" }; String current_title = ""; String current_totals = ""; String current_timeframe = ""; String current_subtitle = ""; String current_time = ""; void initColorPalettes(){ int num_palettes = 7; colors = new int[num_palettes][2]; //Red colors[0][0] = color(243,102,102); colors[0][1] = color(243,37,37); //Orange colors[1][0] = color(243,172,102); colors[1][1] = color(243,139,37); //yellow colors[2][0] = color(243,235,102); colors[2][1] = color(255,204,0); //green colors[3][0] = color(154,243,102); colors[3][1] = color(88,220,0); //Blue colors[4][0] = color(102,174,243); colors[4][1] = color(37,142,243); //Violet colors[5][0] = color(144,131,214); colors[5][1] = color(101,83,198); //Brown colors[6][0] = color(208,163,123); colors[6][1] = color(197,130,72); } public void colorize(int which){ int l,r; if(which == 9){//random from palette l = colors[(int)Math.floor(random(0,colors.length))][0]; r = colors[(int)Math.floor(random(0,colors.length))][1]; }else{ which%=colors.length; l = colors[which][0]; r = colors[which][1]; } object_fill_color_l = l; object_fill_color_r = r; left.obj_fill = l; right.obj_fill = r; redraw(); } public void setup() { size(WIDTH, HEIGHT, JAVA2D); // ***************************************************/ int numBins = HEIGHT; left_hist = new int[numBins]; right_hist = new int[numBins]; initTimeStamp = System.currentTimeMillis(); smooth(); frameRate(30); background(255);// White gotham24 = loadFont("GothamBold-24.vlw"); gotham16 = loadFont("GothamBold-16.vlw"); textFont(gotham24, 24); x1_integrator = new Integrator(LEFT_X); x2_integrator = new Integrator(RIGHT_X); y1_integrator = new Integrator(LEFT_Y); y2_integrator = new Integrator(RIGHT_Y); left_radius_integrator = new Integrator(INIT_COUNT); right_radius_integrator = new Integrator(INIT_COUNT); pivot_width_integrator = new Integrator(PIVOT_WIDTH); pivot_width_integrator.damping = pivot_width_integrator_damping; x1_integrator.attraction = pos_integrator_attraction; x2_integrator.attraction = pos_integrator_attraction; y1_integrator.attraction = pos_integrator_attraction; y2_integrator.attraction = pos_integrator_attraction; left_radius_integrator.attraction = radius_integrator_attraction; right_radius_integrator.attraction = radius_integrator_attraction; x1_integrator.damping = 0; // 'infinite' damping so beam doesnt appear // to be stretchy x2_integrator.damping = 0; // x1_integrator.damping = (float) 0.5; //'infinite' damping so beam // doesnt appear to be stretchy // x2_integrator.damping = (float) 0.5; y1_integrator.damping = pos_integrator_damping; y2_integrator.damping = pos_integrator_damping; left_radius_integrator.damping = radius_integrator_damping; right_radius_integrator.damping = radius_integrator_damping; initData(); // init data before anything else so the objects can use it initObjects(); // If not animating loading, set it at a default state if (!load_over_time) { showSnapshotAt(0.5); loaded = true; } else { timeline.tick_snapping = false; // turn off snapping while we // animate the loading } current_title = "Number of Checkins vs Checkouts"; current_subtitle = getPrintableDay(((Transaction) transactions_all .get(0)).getNaturalTimestamp()); int total = transactions_in.size() + transactions_out.size(); current_totals = total + " Transaction"; if (total > 1) current_totals += "s"; // make it plural current_timeframe = getPrintableDate(minCheckinTimestamp) + " - " + getPrintableDate(maxCheckinTimestamp); initColorPalettes(); //set initial color palette if(randomPalette){ int index = (int)Math.floor((random(0,colors.length))); colorize(index); println("picking palette "+index); }else colorize(4); } void initObjects() { left = new BalanceObject(LEFT_X, LEFT_Y, object_fill_color_l); right = new BalanceObject( RIGHT_X, RIGHT_Y,object_fill_color_r); beam = new BalanceBeam( LEFT_X, LEFT_Y, RIGHT_X, RIGHT_Y, CENTER_X,CENTER_Y); pivot = new BalancePivot(PIVOT_LEFT_X, PIVOT_Y, CENTER_X, CENTER_Y, PIVOT_RIGHT_X, PIVOT_Y); int timelinewidth = TIME_RIGHT_X- TIME_LEFT_X; ticks = new ArrayList(); //calculate tick x coords and store for(int i=0; i <= NUM_TICKS; i++){ Float tx= new Float(TIME_LEFT_X+(((float)i) / NUM_TICKS)*timelinewidth); ticks.add(tx); //println(tx); } //convert midpoint data from parent to easy to plot x's float[] midpoints_y = new float[(int) timelinewidth]; float highlight_vpad = 5; float pct, mpy; int index; for(int xx=0; xx"+mpy); midpoints_y[xx]=mpy; } String[] labels = new String[NUM_TICKS + 1]; for (int n = 0; n <= NUM_TICKS; n++) { pct = (float) (((double) n) / NUM_TICKS); index = (int) Math.floor(pct * (transactions_all.size() - 1)); Transaction current = (Transaction) transactions_all.get(index); labels[n] = current.getNaturalTimeString(); //System.out.println(current.itemNumber+" --- "+labels[n]); } // Transaction f = (Transaction) transactions_all.get(0); // Transaction l = (Transaction) transactions_all.get(transactions_all // .size() - 1); // // System.out.println("first:"+f.title+"--"+f.itemNumber+" last:"+l.itemNumber); timeline = new Timeline( TIME_LEFT_X, TIME_Y,timelinewidth , TIME_HEIGHT,labels,midpoints_y, ticks); assignPivotWidth(transactions_in.size(), transactions_out.size()); } void renderTimeline(){ //draw timeline and highlight rect representing the current position in the dataset timeline.highlight_integrator.update(); timeline.current_x = timeline.highlight_integrator.value(); fill(timeline_color); rect(timeline.x,timeline.y,timeline.width,timeline.height); //Draw highlight if(timeline.draw_midpoints){ //visually show the balance of the objects over time float mpx,mpy; for(int xx=(int)timeline.x; xx < timeline.current_x; xx++){ mpy = timeline.midpoints_y[(xx-(int)timeline.x)]; //xx is a plotting pixel, starting at leftmost side of timeline strokeWeight(1); stroke(object_fill_color_l); line(xx, timeline.y+timeline.highlight_vpad, xx, mpy); // rect(xx,y,1,mpy-y); // System.out.println(xx+" "+midpoints_y[(int) (xx-x)]+ " "+(mpy-y)); stroke(object_fill_color_r); line(xx, mpy, xx, timeline.y+timeline.height-timeline.highlight_vpad); } }else{ //just draw a simple rectangle fill(timeline_highlight_color); noStroke(); if(timeline.current_x > (timeline.x)) timeline.current_x = timeline.x+width; if(timeline.current_x>timeline.x) rect(timeline.x,timeline.y+timeline.highlight_vpad,(timeline.current_x-timeline.x),height-2*timeline.highlight_vpad); } //Draw tick marks and labels strokeWeight(TICK_WIDTH); float tx; //for(int i=0; i <= NUM_TICKS; i++){ Iterator itr = ticks.iterator(); int i=0; while(itr.hasNext()){ tx = ((Float) itr.next()).floatValue(); stroke(timeline_tick_color); line(tx, timeline.y, tx, timeline.y+height); // System.out.println("tick at "+tx); fill(timeline_label_color); textAlign(PConstants.CENTER,PApplet.TOP); text(timeline.labels[i],tx,timeline.y+timeline.height+timeline.highlight_vpad); i++; } //Draw current time at cursor, above timeline fill(timeline_label_color); textAlign(PApplet.CENTER, PApplet.BOTTOM); text(current_time,timeline.current_x,timeline.y-timeline.highlight_vpad); } float beam_stroke_weight = 15f; public void renderBalanceBeam(){ fill(beam_fill_color); //parent.noStroke(); strokeWeight(beam_stroke_weight); stroke(beam_stroke_color); line(beam.x1,beam.y1,beam.x2,beam.y2); } int l_h_min = 0, l_h_max = 20; void doneLoading() { int max = -1; int min = Integer.MAX_VALUE; for (int i = 0; i < left_hist.length; i++) { if (left_hist[i] > max) max = left_hist[i]; else if (left_hist[i] < min) min = left_hist[i]; } l_h_min = min; l_h_max = max; } // Re-run the loading animation void reload() { load_over_time = true; loaded = false; loading_percent = 0; left_hist = new int[HEIGHT]; right_hist = new int[HEIGHT]; } public void draw() { if (load_over_time && !loaded) { // animate the loading timeline.tick_snapping = false;//disable snapping for smoother effect int l_y = (int) Math.floor(left.y); int r_y = (int) Math.floor(right.y); if(l_y < 0) l_y = 0; if(r_y < 0) r_y = 0; left_hist[l_y]++; right_hist[r_y]++; showSnapshotAt(loading_percent); loading_percent += load_incr; if (loading_percent > 1) { showSnapshotAt(1); // make sure we get the last data loaded = true; timeline.tick_snapping = true; // turn snapping back on // doneLoading(); } } // *********** Update Integrators ************** x1_integrator.update(); // get new coords x2_integrator.update(); y1_integrator.update(); y2_integrator.update(); // println(""+y1_integrator.moving()+y1_integrator.velocty()+" "+y1_integrator.acceleration()+" "); left_radius_integrator.update(); right_radius_integrator.update(); left.x = x1_integrator.value(); right.x = x2_integrator.value(); left.y = y1_integrator.value(); right.y = y2_integrator.value(); left.updateSize(left_radius_integrator.value()); right.updateSize(right_radius_integrator.value()); pivot_width_integrator.update(); pivot.setWidth(pivot_width_integrator.value()); //pivot.setMidPercent(map((float)normalized_net,-1f,1f,0f,1.0f)); pivot.setMidPercent((double)left.getCount()/(left.getCount()+right.getCount())); // println(left.getCount()/(left.getCount()+right.getCount())); // *********** Drawing ********************* if (clear_screen) { clearScreen(); } else if (fade_screen) { fill(255, 45); noStroke(); rect(0, 0, WIDTH, HEIGHT); } drawTitle(current_title, current_subtitle); drawTotals(current_totals); // Draw shadows under the objects fill(shadow_fill_color); ellipse(left.x, pivot.y1, 0.75f * left.size, 0.09f * left.size); ellipse(right.x, pivot.y1, 0.85f * right.size, 0.09f * right.size); beam.update(left.x, left.y, right.x, right.y); renderBalanceBeam(); // Draw objs after beam so that they cover the beam //Draw needles pointing to the histogram stroke(object_fill_color_l); strokeWeight(5f); line(2*HIST_WIDTH, left.y, left.x, left.y); renderObject(left,true); stroke(object_fill_color_r); strokeWeight(5f); line(WIDTH-2*HIST_WIDTH, right.y, right.x, right.y); renderObject(right,false); renderBalancePivot(); // Pivot on top // draw some histogram action if (draw_histograms) drawHistograms(); renderTimeline(); // Run until the animation has settled, then pause to save CPU cycles if (!integrators_moving()) { noLoop(); // println("Stop"); } } public void renderObject(BalanceObject object, boolean left) { strokeWeight(5f); stroke(object_stroke_color); // parent.noStroke(); if(left) fill(object_fill_color_l); else fill(object_fill_color_r); ellipseMode(CENTER); ellipse(object.x, object.y, object.size, object.size); fill(color(0)); textAlign(CENTER, CENTER); textFont(gotham24); text("" + (int) Math.rint(object.getCount()), object.x, object.y); } // The actual value for size will change over time as it animates. //The value for count, mass should not change after it is set into motion. //Get the final size so we can integrate it... float getTargetObjectSize(BalanceObject obj){ // use some sort of scaling algorithm here if needed return map(obj.getCount(), MIN_COUNT,MAX_COUNT,MIN_SIZE,MAX_SIZE); } public void renderBalancePivot(){ noStroke(); //Draw two trianges, left and right fill(object_fill_color_l); triangle(pivot.x1,pivot.y1,pivot.apex_x,pivot.apex_y,pivot.midx+3,pivot.y2); //left make it overlap the right one a bit so there arent aliased boundaries fill(object_fill_color_r); triangle(pivot.midx,pivot.midy,pivot.apex_x,pivot.apex_y,pivot.x2,pivot.y2); //right fill(textColor); textAlign(PApplet.RIGHT); //Label percentages textFont(gotham16); text(""+(int)(100*pivot.mid_pct)+"%",pivot.x1-5,pivot.y1); textAlign(PApplet.LEFT); text(""+(int)Math.ceil((100*(1-pivot.mid_pct)))+"%",pivot.x2+5,pivot.y2); //Ceil it to round up so l and r add up to 100 //circle at apex stroke(bgColor); fill(pivot_fill_color); strokeWeight(5); ellipse(pivot.apex_x,pivot.apex_y,pivot.pivot_point_size,pivot.pivot_point_size); noStroke(); } void drawHistograms() { float param; int raw; int h_max = transactions_all.size(); // println(l_h_min+" "+l_h_max); strokeWeight(4); for (int i = TITLE_Y; i < pivot.y1; i++) { //dont need to look in entire histogram array... /************* Left Side **********/ raw = left_hist[i]; if (raw > 0) param = map(2*left_hist[i], l_h_min, l_h_max, 0, 255); else param = 0; // println(left_hist[i]+"--> "+param); stroke(object_fill_color_r, param); line(HIST_PAD, i, HIST_WIDTH + HIST_PAD, i); /************* Right Side **********/ raw = right_hist[i]; if (raw > 0) param = map(2*right_hist[i], l_h_min, l_h_max, 0, 255); else param = 0; // println(left_hist[i]+"--> "+param); stroke(object_fill_color_r, param); line(WIDTH - HIST_PAD, i, WIDTH - HIST_PAD - HIST_WIDTH, i); } } boolean integrators_moving() { return (y1_integrator.moving() || left_radius_integrator.moving() || right_radius_integrator.moving() || pivot_width_integrator .moving()); } void drawTitle(String title, String subtitle) { textFont(gotham24, 24); fill(textColor); textAlign(CENTER); text(title, WIDTH / 2, TITLE_Y); textFont(gotham16, 16); text(subtitle, WIDTH / 2, TITLE_Y + 20); } void drawTotals(String tot) { textFont(gotham16, 16); fill(textColor); textAlign(CENTER); text(tot, pivot.apex_x, pivot.y1 + PADDING / 8); // put it centered // under the pivot } void drawTimeframe(String time) { textFont(gotham16, 16); fill(textColor); textAlign(CENTER); text(time, pivot.apex_x, pivot.y1 + 2 * PADDING / 8); // put it centered // under the // pivot } void clearScreen() { if (whiteout) fill(255); else fill(bgColor); noStroke(); rect(0, 0, WIDTH, HEIGHT); } // Test the integrators void randomCounts() { float l = random(MIN_COUNT, MAX_COUNT); float r = random(MIN_COUNT, MAX_COUNT); assignCounts(l, r); } void assignCounts(float l, float r) { left.updateCount(l); // Store new count immediately to calculate final // resting place, etc right.updateCount(r); left_radius_integrator.target(getTargetObjectSize(left)); // integrate the // size to // animate the // change right_radius_integrator.target(getTargetObjectSize(right)); assignPivotWidth(l, r);// pass it on to update pivot updateObjects(); // this updates the x/y integrators to make it see saw } void assignPivotWidth(float checkins, float checkouts) { float pct = (float) (checkins + checkouts) / (transactions_all.size()); float width = pct * MAX_PIVOT_WIDTH; if (width > MAX_PIVOT_WIDTH) width = MAX_PIVOT_WIDTH; if (width < MIN_PIVOT_WIDTH) width = MIN_PIVOT_WIDTH; pivot_width_integrator.target(width); } void randomPivotWidth() { float w = random(100, 10000); assignPivotWidth(w, w); } double normalized_net; // Calculate final resting place of the current configuration and seed // integrator... public void updateObjects() { // torque is force*distance // distance is length dist between object and center point // force is mass * gravity // gravity acts equally on both masses so it cancels out // If lever length is the same, this reduces to just the mass since // gravity is balanced double lever_l = left.extension_pct * LEVER_LENGTH; double lever_r = right.extension_pct * LEVER_LENGTH; // System.out.println(""+lever_l+" "+lever_r); double torque1 = left.getCount() * lever_l; double torque2 = right.getCount() * lever_r; double net_torque = torque1 - torque2; // if this is positive, Left // side is really big normalized_net = (double) map((float) net_torque, MIN_NET_TORQUE, MAX_NET_TORQUE, -1f, 1f); // maybe do some log // magic here double ang_deg = normalized_net * MAX_ANGLE; double x1 = Math.cos(Math.toRadians(-ang_deg));// unit offsets double y1 = Math.sin(Math.toRadians(-ang_deg)); x1_integrator.target((float) (CENTER_X - lever_l * x1)); y1_integrator.target((float) (CENTER_Y - lever_l * y1)); x2_integrator.target((float) (CENTER_X + lever_r * x1)); y2_integrator.target((float) (CENTER_Y + lever_r * y1)); /* * println("m1 " + (int) left.mass + " m2 " + (int) right.mass + " L" + * LEVER_LENGTH + " x1 " + " t1 " + (int) torque1 + " t2 " + (int) * torque2 + " net_trq " + (float) net_torque + " normalized net " + * normalized_net + " ang_deg " + (int) ang_deg + " x1 " + (int) x1 + * " y1 " + (int) y1 + " x2 " + (int) x2 + " y2 " + (int) y2); */ loop(); // restart drawing loop } void initData() { minDuration = Integer.MAX_VALUE; // init to worst-case so we can maxDuration = Integer.MIN_VALUE; minCheckinTimestamp = Long.MAX_VALUE; maxCheckinTimestamp = Long.MIN_VALUE; minCheckoutTimestamp = Long.MAX_VALUE; maxCheckoutTimestamp = Long.MIN_VALUE; long before = millis(); this.transactions_in = loadTransactions(checkins_filename, true); //set is_checkin flag this.transactions_out = loadTransactions(checkouts_filename, false); num_checkins = transactions_in.size(); num_checkouts = transactions_out.size(); MAX_COUNT = Math.max(num_checkins, num_checkouts); println("loaded " + (this.transactions_in.size() + this.transactions_out.size()) + " transactions in " + (millis() - before) + " miiliseconds."); println("check-out timestamp varies from " + new Date(minCheckoutTimestamp) + " to " + new Date(maxCheckoutTimestamp)); Date start = new Date(minCheckinTimestamp); Date stop = new Date(maxCheckinTimestamp); float diff = maxCheckinTimestamp - minCheckinTimestamp; checkin_ms = diff; println("check-in timestamp varies from " + start + " to " + stop); // Combine: transactions_all = new ArrayList(transactions_in); transactions_all.addAll(transactions_out); midpoints = new ArrayList(); // Sort: TransactionUtils.sortTransactionsByNaturalTimeStamp(transactions_all); // Calculate cumulative sums: TransactionUtils.calculateCumulativeValuesAndMidPts(transactions_all, midpoints); //println("we have "+midpoints.size()+" midpoints ... samples:"+midpoints.get(5)+" "+midpoints.get(25)+" "+midpoints.get(43)); } public ArrayList loadTransactions(String filename, boolean is_checkin) { ArrayList transactions = new ArrayList(); println("loading file... \"" + filename + "\""); String[] lines = loadStrings(filename); String[] fields = { "" }; Transaction trans; for (int i = 0; i < lines.length; i++) { if (i == 0) { // read first line which tells us what fields we have fields = PApplet.split(lines[i], ","); println("This file has these fields: " + Arrays.toString(fields)); } else { // println(i); trans = TransactionUtils.parseTransaction(fields, lines[i]); trans.is_checkin = is_checkin; trans.calculateDuration(); trans.calculateTimeStamps(); // 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(is_checkin){ // if (trans.ckoduration < minDuration) // minDuration = trans.ckoduration; // else if (trans.ckoduration > maxDuration) // maxDuration = trans.ckoduration; // // if(trans.ckitimestamp < minCheckinTimestamp) // minCheckinTimestamp = trans.ckitimestamp; // else if(trans.ckitimestamp > maxCheckinTimestamp) // maxCheckinTimestamp = trans.ckitimestamp; // // if(trans.ckotimestamp < minCheckoutTimestamp) // minCheckoutTimestamp = trans.ckotimestamp; // else if(trans.ckotimestamp > maxCheckoutTimestamp) // maxCheckoutTimestamp = trans.ckotimestamp; // } // System.err.println("DEBUG Discarding some transactions in loadTrans..."); // if(is_checkin || (i%10 == 0)) transactions.add(trans); } } if (minDuration < 0) minDuration = 0; return transactions; } // September 23, 2008 String getPrintableDay(long timestamp) { Date d = new Date(timestamp); java.util.GregorianCalendar gc = new java.util.GregorianCalendar(); gc.setTimeInMillis(timestamp); String result = ""; result += monthName[gc.get(Calendar.MONTH)] + " " + gc.get(Calendar.DAY_OF_MONTH) + ", " + gc.get(Calendar.YEAR); return result; } // "September 23, 2008 12:00 String getPrintableDate(long timestamp) { Date d = new Date(timestamp); java.util.GregorianCalendar gc = new java.util.GregorianCalendar(); gc.setTimeInMillis(timestamp); String result = ""; // result+=d.getMonth()+" "+d.getDate()+" "+d.getYear()+" "+d.getHours()+":"+d.getMinutes(); int min = gc.get(Calendar.MINUTE); String min_str = "" + min; if (min < 10) min_str += "0"; result += monthNameAbbrev[gc.get(Calendar.MONTH)] + " " + gc.get(Calendar.DAY_OF_MONTH) + " " + gc.get(Calendar.YEAR) + " " + gc.get(Calendar.HOUR_OF_DAY) + ":" + min_str; return result; } // Given what cumulative % of the data to show, update the objects and call // redraw void showSnapshotAt(double pct) { Transaction current = getTransactionForPercent(pct); float in = current.cumulative_checkins; float out = current.cumulative_checkouts; current_totals = (int) (in + out) + " Transactions"; current_time = current.getNaturalTimeString(); assignCounts(in, out); timeline.setCurrentPercent(pct); } Transaction getTransactionForPercent(double pct) { if (pct > 1) pct = 1; if (pct < 0) pct = 0; int index = (int) Math.floor(pct * (transactions_all.size() - 1)); return (Transaction) transactions_all.get(index); } // get 'num+1' equally spaced time labels from data set // num+1 so it nincludes first and last sample in array public String[] getTimeLabels(int num) { double pct; String[] labels = new String[num + 1]; for (int n = 0; n <= num; n++) { pct = ((double) n) / num; int index = (int) Math.floor(pct * (transactions_all.size() - 1)); Transaction current = (Transaction) transactions_all.get(index); labels[n] = current.getNaturalTimeString(); // System.out.println(current.itemNumber+" --- "+labels[n]); } Transaction f = (Transaction) transactions_all.get(0); Transaction l = (Transaction) transactions_all.get(transactions_all .size() - 1); // System.out.println("first:"+f.title+"--"+f.itemNumber+" last:"+l.itemNumber); return labels; } float last_x = 0; public void mouseMoved() { if (loaded && timeline.mouseOverGenerally(mouseX, mouseY)) { float snap_test = timeline.getSnapX(mouseX);// see if we snap if (last_x != snap_test || !(timeline.tick_snapping)) { // see if // this x // has not // been // already // snapped // and // plotted showSnapshotAt(timeline.getPercent(mouseX)); if (snap_test > 0) last_x = snap_test; else last_x = mouseX; } redraw(); } } // 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 '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': System.out.println("Setting palette == "+key); palette = Character.getNumericValue(key);//convert from ASCII to number 0-9 colorize(palette); break; 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': break; case 'r': break; case 't': break; case 'd': break; case 'f': fade_screen = !fade_screen; println("Toggled background fading, now = " + fade_screen); break; case 'g': clear_screen = !clear_screen; println("Toggled background clearing, now = " + clear_screen); break; case 'c': break; case 'v': break; case 'b': println("doing random pivot width"); randomPivotWidth(); redraw(); break; case ' ': // randomCounts(); reload(); // redraw(); loop(); break; case 'p': break; } }