// column number in data file static final int ID = 0; static final int REVIEW_SCORE = 1; static final int SCIENCE_GPA = 2; static final int CUM_GPA = 3; static final int INTERVIEW_SCORE = 4; static final int PC_HOURS = 5; static final int BCP_GPA = 6; static final int SHADOW_HOURS = 7; static final int APP_STATUS = 8; static final int ELLIPSE_SIZE = 6; HashMap statusColors = new HashMap(); Statistics xStats = new Statistics(); Statistics yStats = new Statistics(); Menu xMenu = new Menu("x", 16, 360, 0); Menu yMenu = new Menu("y", 16, 300, 1); float xAxisMin, xAxisMax; float yAxisMin, yAxisMax; int totalCount; Applicant[] apps; int appCount; FilterButton[] buttons; int buttonCount; String[] menuItems; int menuItemCount; // dashline - {dash length, gap length} float[] dashes = {5, 3}; int xZoomStart, yZoomStart; int xZoomEnd, yZoomEnd; boolean isZoomBox = false; ZoomButton zoomOutButton = new ZoomButton(100, 60); // plot border float plotX1, plotY1; float plotX2, plotY2; PFont plotFont; void setup() { size(1200, 750, P3D); plotX1 = 275; plotX2 = width - 5; plotY1 = 70; plotY2 = height - plotY1 - 75; plotFont = createFont("SansSerif", 13); textFont(plotFont); textMode(SCREEN); smooth(); // fill color hashmap statusColors.put("Program", color(255, 127, 0)); statusColors.put("Accept", color(0,0,0)); statusColors.put("Waitlist", color(1, 133, 113)); statusColors.put("Interview", color(152, 78, 163)); statusColors.put("Deny", color(228, 26, 28)); // menu items array menuItems = new String[8]; menuItems[0] = "ID"; menuItems[1] = "Application Score"; menuItems[2] = "Science GPA"; menuItems[3] = "Cumulative GPA"; menuItems[4] = "Interview Score"; menuItems[5] = "Patient Care Hours"; menuItems[6] = "Bio.Chem.Phys GPA"; menuItems[7] = "Shadow Hours"; menuItemCount = 8; createButtons(); readData(); xStats.calculate(xMenu.getItem()); xAxisMin = xStats.getMin(); xAxisMax = xStats.getMax(); yStats.calculate(yMenu.getItem()); yAxisMin = yStats.getMin(); yAxisMax = yStats.getMax(); } void draw() { background(235); drawXAxis(); drawYAxis(); xMenu.draw(); yMenu.draw(); drawXBoxWhskr(); drawYBoxWhskr(); for (int i = 0; i < appCount; i++) { apps[i].draw(); } for (int i = 0; i < buttonCount; i++) { buttons[i].draw(); } zoomOutButton.draw(); fill(255); stroke(1); rect((plotX1 + plotX2)/2.3, 30, 300, 20); fill(0); textAlign(LEFT, CENTER); text("Click box to filter:", 16, 110); textAlign(CENTER, CENTER); text(menuItems[yMenu.getItem()] + " vs. " + menuItems[xMenu.getItem()], (plotX1 + plotX2)/2.3, 30); textAlign(LEFT, CENTER); text("Data details:", 20, 675); if (isZoomBox) drawZoomBox(); } void mouseReleased() { if (isZoomBox) { if (dist(xZoomStart, yZoomStart, mouseX, mouseY) > 7) { // set new x & y axis int x = restrictMousePlot(mouseX, plotX1, plotX2); int y = restrictMousePlot(mouseY, plotY1, plotY2); float xStart = reverseTX(xZoomStart); float xEnd = reverseTX(x); float yStart = reverseTY(y); float yEnd = reverseTY(yZoomStart); if (xStart < xEnd) { xAxisMin = xStart; xAxisMax = xEnd; } else { xAxisMin = xEnd; xAxisMax = xStart; } if (yStart < yEnd) { yAxisMin = yStart; yAxisMax = yEnd; } else { yAxisMin = yEnd; yAxisMax = yStart; } zoomOutButton.display(); } isZoomBox = false; } } void mousePressed() { // check filter buttons for (int i = 0; i < buttonCount; i++) { if (buttons[i].clicked()) { buttons[i].filter(); break; } } // close menus if opened if (xMenu.isDropDown()) xMenu.closeDropDown(); else if (yMenu.isDropDown()) yMenu.closeDropDown(); // open menus if closed else if (xMenu.clicked()) xMenu.drawDropDown(); else if (yMenu.clicked()) yMenu.drawDropDown(); // set zoom starting points if (isPlotArea(mouseX, mouseY)) { xZoomStart = mouseX; yZoomStart = mouseY; isZoomBox = true; } // zoom out clicked if (zoomOutButton.clicked()) zoomOutButton.reset(); } void drawZoomBox() { int x = restrictMousePlot(mouseX, plotX1, plotX2); int y = restrictMousePlot(mouseY, plotY1, plotY2); dashline(xZoomStart, yZoomStart, x, yZoomStart, dashes); dashline(xZoomStart, yZoomStart, xZoomStart, y, dashes); dashline(x, yZoomStart, x, y, dashes); dashline(xZoomStart, y, x, y, dashes); } int restrictMousePlot(int p, float p1, float p2) { if (p < p1) return (int)p1; if (p > p2) return (int)p2; return p; } void drawXAxis() { stroke(0); strokeWeight(2); fill(0); textSize(13); line(plotX1 - 5, plotY2 + 5, plotX2, plotY2 + 5); float interval = (xAxisMax - xAxisMin) / 6; for (float tick = xAxisMin; tick <= xAxisMax + interval; tick += interval) { if (tick == xAxisMin) { textAlign(LEFT, CENTER); } else if (tick == xAxisMax) { textAlign(RIGHT, CENTER); } else { textAlign(CENTER, CENTER); } float x = TX(tick); text(nf(tick, 1, 2), x, plotY2 + 38); } } void drawYAxis() { stroke(0); strokeWeight(2); fill(0); textSize(13); line(plotX1 - 5, plotY1 - 5, plotX1 - 5, plotY2 + 5); float interval = (yAxisMax - yAxisMin) / 6; for (float tick = yAxisMin; tick <= yAxisMax + interval; tick += interval) { if (tick == yAxisMin) { textAlign(RIGHT); } else if (tick == yAxisMax) { textAlign(RIGHT, TOP); } else { textAlign(RIGHT, CENTER); } float y = TY(tick); text(nf(tick, 1, 2), plotX1 - 33, y); } } void drawXBoxWhskr() { stroke(color(#0000FF)); strokeWeight(2); noFill(); rectMode(CORNERS); float xStart, xEnd; // draw Q1 line xStart = TX(xStats.getMin()); xEnd = TX(xStats.getFirstQtr()); if (xStart < plotX1) xStart = plotX1; if (xEnd > plotX2) xEnd = plotX2; if (xEnd > plotX1 && xStart < plotX2) line(xStart, plotY2 + 20, xEnd, plotY2 + 20); // draw Q2 box xStart = xEnd; xEnd = TX(xStats.getMedian()); if (xStart < plotX1) xStart = plotX1; if (xEnd > plotX2) xEnd = plotX2; if (xEnd > plotX1 && xStart < plotX2) rect(xStart, plotY2 + 15, xEnd, plotY2 + 25); // draw Q3 box xStart = xEnd; xEnd = TX(xStats.getThirdQtr()); if (xStart < plotX1) xStart = plotX1; if (xEnd > plotX2) xEnd = plotX2; if (xEnd > plotX1 && xStart < plotX2) rect(xStart, plotY2 + 15, xEnd, plotY2 + 25); // draw Q4 line xStart = xEnd; xEnd = TX(xStats.getMax()); if (xStart < plotX1) xStart = plotX1; if (xEnd > plotX2) xEnd = plotX2; if (xEnd > plotX1 && xStart < plotX2) line(xStart, plotY2 + 20, xEnd, plotY2 + 20); rectMode(CENTER); } void drawYBoxWhskr() { stroke(color(#0000FF)); strokeWeight(2); noFill(); rectMode(CORNERS); float yStart, yEnd; // draw Q1 line yStart = TY(yStats.getMin()); yEnd = TY(yStats.getFirstQtr()); if (yStart > plotY2) yStart = plotY2; if (yEnd < plotY1) yEnd = plotY1; if (yEnd < plotY2) line(plotX1 - 20, yStart, plotX1 - 20, yEnd); // draw Q2 box yStart = yEnd; yEnd = TY(yStats.getMedian()); if (yStart > plotY2) yStart = plotY2; if (yEnd < plotY1) yEnd = plotY1; if (yEnd < plotY2) rect(plotX1 - 15, yStart, plotX1 - 25, yEnd); // draw Q3 box yStart = yEnd; yEnd = TY(yStats.getThirdQtr()); if (yStart > plotY2) yStart = plotY2; if (yEnd < plotY1) yEnd = plotY1; if (yEnd < plotY2) rect(plotX1 - 15, yStart, plotX1 - 25, yEnd); // draw Q4 line yStart = yEnd; yEnd = TY(yStats.getMax()); if (yStart > plotY2) yStart = plotY2; if (yEnd < plotY1) yEnd = plotY1; if (yEnd < plotY2) line(plotX1 - 20, yStart, plotX1 - 20, yEnd); rectMode(CENTER); } void createButtons() { int count = 0; Iterator i = statusColors.entrySet().iterator(); buttons = new FilterButton[statusColors.size()]; while (i.hasNext()) { Map.Entry me = (Map.Entry)i.next(); buttons[count] = new FilterButton(count, me.getKey().toString()); count++; } buttonCount = count; } void readData() { String[] lines = loadStrings("my_data.tsv"); //read header line parseInfo(lines[0]); apps = new Applicant[totalCount]; for(int i = 1; i < lines.length; i++) { apps[appCount++] = parseApp(lines[i]); } } void parseInfo(String line) { String infoString = line.substring(2); // remove the # String[] infoPieces = split(infoString, ','); totalCount = int(infoPieces[0]); } Applicant parseApp(String line) { String pieces[] = split(line, TAB); int id = int(pieces[ID]); float reviewScore = float(pieces[REVIEW_SCORE]); float scienceGPA = float(pieces[SCIENCE_GPA]); float cumGPA = float(pieces[CUM_GPA]); float interviewScore = float(pieces[INTERVIEW_SCORE]); float pcHours = float(pieces[PC_HOURS]); float bcpGPA = float(pieces[BCP_GPA]); float shadowHours = float(pieces[SHADOW_HOURS]); String appStatus = pieces[APP_STATUS]; return new Applicant(id, reviewScore, scienceGPA, cumGPA, interviewScore, pcHours, bcpGPA, shadowHours, appStatus); } float TX(float x) { return map(x, xAxisMin, xAxisMax, plotX1, plotX2); } float TY(float y) { return map(y, yAxisMax, yAxisMin, plotY1, plotY2); } float reverseTX(float x) { return map(x, plotX1, plotX2, xAxisMin, xAxisMax); } float reverseTY(float y) { return map(y, plotY1, plotY2, yAxisMax, yAxisMin); } boolean isPlotArea(int x, int y) { return (x >= plotX1 && x <= plotX2) && (y >= plotY1 && y <= plotY2); } // dashed line is code written by J David Eisenberg // url: http://www.openprocessing.org/visuals/?visualID=7013 /* * Draw a dashed line with given set of dashes and gap lengths. * x0 starting x-coordinate of line. * y0 starting y-coordinate of line. * x1 ending x-coordinate of line. * y1 ending y-coordinate of line. * spacing array giving lengths of dashes and gaps in pixels; * an array with values {5, 3, 9, 4} will draw a line with a * 5-pixel dash, 3-pixel gap, 9-pixel dash, and 4-pixel gap. * if the array has an odd number of entries, the values are * recycled, so an array of {5, 3, 2} will draw a line with a * 5-pixel dash, 3-pixel gap, 2-pixel dash, 5-pixel gap, * 3-pixel dash, and 2-pixel gap, then repeat. */ void dashline(float x0, float y0, float x1, float y1, float[ ] spacing) { float distance = dist(x0, y0, x1, y1); float [ ] xSpacing = new float[spacing.length]; float [ ] ySpacing = new float[spacing.length]; float drawn = 0.0; // amount of distance drawn if (distance > 0) { int i; boolean drawLine = true; // alternate between dashes and gaps /* Figure out x and y distances for each of the spacing values I decided to trade memory for time; I'd rather allocate a few dozen bytes than have to do a calculation every time I draw. */ for (i = 0; i < spacing.length; i++) { xSpacing[i] = lerp(0, (x1 - x0), spacing[i] / distance); ySpacing[i] = lerp(0, (y1 - y0), spacing[i] / distance); } i = 0; while (drawn < distance) { if (drawLine) { line(x0, y0, x0 + xSpacing[i], y0 + ySpacing[i]); } x0 += xSpacing[i]; y0 += ySpacing[i]; /* Add distance "drawn" by this line or gap */ drawn = drawn + mag(xSpacing[i], ySpacing[i]); i = (i + 1) % spacing.length; // cycle through array drawLine = !drawLine; // switch between dash and gap } } }