// Darren Hardy // MAT 259 - Project One // 10 January 2009 // import java.text.SimpleDateFormat; // global constants final int screenWidth = 1024; final int screenHeight = 768; final int labelSize = 12; final int margin = 100; final int nCategory = 4; final int categoryWidth = floor(float((screenWidth-(2*margin))/nCategory)); final int countThreshold = 1000; final int nLinesPerFrame = 10; final int xLeft = margin; final int xRight = screenWidth-margin; final int xMid = floor(screenWidth/2.0); final int xWidth = xRight-xLeft; final int yTop = margin; final int yBottom = screenHeight-margin-50; final int yMid = floor(screenWidth/2.0); final int yWidth = yBottom-yTop; final boolean do_grid = false; final boolean do_axis = false; final boolean do_table = true; final boolean do_progress = true; final boolean do_xLabels = true; final boolean do_yLabels = true; final color palette[][] = { { color(249,236,195), // Musee de Marrakech color(194,197,145), color(94,112,47), color(30,18,15), color(217,160,54) }, { color(148,228,206), // Musee de lattes color(87,148,152), color(190,0,63), color(8,43,49), color(255,0,80) }, { color(240, 240, 192), // Promise Oneself color(144, 120, 144), color(119, 127, 143), color(55, 59, 66), color(192, 192, 96) }, { color(218,198,129), // Promised me poems color(95,117,79), color(70,100,72), color(43,41,52), color(183,156,67) } }; final String paletteName[] = { "Musee de Marrakech", "Musee de Lattes", "Promise Oneself", "Promised Me Poems" }; final int palleteChoice = 3; final color cTitle = palette[palleteChoice][0]; final color cLabel = palette[palleteChoice][1]; final color cText = palette[palleteChoice][2]; final color cForeground = palette[palleteChoice][1]; final color cBackground = palette[palleteChoice][3]; final color cBold = palette[palleteChoice][4]; // global variables int progressStart = 0; int progressCurrent = 0; int frameNum = 0; float hMin = 100; float hMax = 0; float hMean[] = new float[nCategory]; float hTotal[] = new float[nCategory]; SortedMap histogramMap = new TreeMap(); Date startDate = null; PFont fontLabel = null; PFont fontText = null; PFont fontTitle = null; void setup() { size(screenWidth, screenHeight); // frameRate(1000); // setup fonts and colors fontLabel = loadFont("Label-14.vlw"); fontText = loadFont("Text-12.vlw"); fontTitle = loadFont("Title-24.vlw"); background(cBackground); // write title fill(cTitle); textFont(fontTitle); textAlign(LEFT); text("Library Loans by Media Modernity", xLeft, yTop-30); // write notes fill(cText); textFont(fontText); textAlign(RIGHT); text("Design: Darren Hardy, MAT 259, Winter 2009.\n" + "Fonts: HelveticaNeue. " + "Colors: " + paletteName[palleteChoice] + ", COLOURlovers™. " + "Source: Seattle Public Library.", xRight, screenHeight-25); // draw x-axis labels if (do_xLabels) { final String xLabels[] = { "Book\nSheet Music", "Audio Tape\nMagazine", "Video Tape\nDiskette", "DVD\nCD" }; int x = xLeft + categoryWidth/2; int y = yBottom + 60; fill(cLabel); textFont(fontLabel); textAlign(CENTER); for (int i = 0; i < xLabels.length; i++) { text(xLabels[i], x + (categoryWidth*i), y); } } // draw the grid if (do_grid) { stroke(cForeground, 255/4); line(0, yTop, screenWidth, yTop); // top line(0, yBottom, screenWidth, yBottom); // bottom line(xLeft, 0, xLeft, screenHeight); // left line(xRight, 0, xRight, screenHeight); // right line(0, yMid, screenWidth, yMid); // centerline for (int i = 0; i <= nCategory; i++) { // x axis categories int x = xLeft + (categoryWidth*i); line(x, 0, x, screenHeight); } for (int i = 0; i <= 10; i++) { // y axis gridlines int y = xLeft + i*floor(yWidth/10.0); line(xLeft, y, xRight, y); } } // draw x,y axis if (do_axis) { stroke(cForeground, 255/4); strokeWeight(2); line(xLeft, yBottom, xRight, yBottom); line(xLeft, yTop, xLeft, yBottom); strokeWeight(1); } // read data try { loadHistograms(); } catch (IOException e) { e.printStackTrace(); exit(); } if (do_progress) { progressStart = histogramMap.size(); } if (do_yLabels) { println("drawing ylabels..."); stroke(cForeground,255/5); strokeWeight(1); fill(cText); textFont(fontText); textAlign(RIGHT); float yScale = 0.95/hMax; int n = histogramMap.size(); for (int i = 0; i < nCategory; i++) { hMean[i] = hTotal[i] / n; int y = yTop+2 + (yWidth-floor(yScale*hMean[i]*yWidth)); text(ceil(hMean[i]*100) + "%", xLeft-5, y+4); line(xLeft+2,y,xRight-2,y); } pushMatrix(); translate(xLeft-50, screenHeight/2); fill(cLabel); textFont(fontLabel); textAlign(CENTER); rotate(-HALF_PI); text("% of daily volume", 0, 0); popMatrix(); } println("ready to draw..."); } DateFormat dfParseable = new SimpleDateFormat("yyyy-MM-dd"); DateFormat dfReadable = new SimpleDateFormat("MMMMM d, yyyy"); private void loadHistograms() throws IOException { String fn = "histogram.txt"; //dataPath("histogram.txt"); println("loading histograms from " + fn + "..."); BufferedReader in = null; // open the data file if ((in = createReader(fn)) == null) { throw new IOException("Cannot read fn=" + fn); } // read the file line by line String line = null; while ((line = in.readLine()) != null) { String data[] = splitTokens(line, WHITESPACE + ","); if (line.charAt(0) == '#') { continue; } try { Date dt = dfParseable.parse(data[0]); if (startDate == null || startDate.after(dt)) { startDate = dt; } int n = parseInt(data[data.length-1]); if (n < countThreshold) { println("ignoring outlier data (" + n + "<" + countThreshold + ") for " + dt); continue; } float histogram[] = new float[nCategory]; for (int j = 0; j < nCategory; j++) { histogram[j] = parseFloat(data[j+1]); if (hMax < histogram[j]) { hMax = histogram[j]; } if (hMin > histogram[j]) { hMin = histogram[j]; } hTotal[j] += histogram[j]; } histogramMap.put(dt, histogram); } catch (ParseException e) { println(e); } } int n = histogramMap.size(); println("finished loading " + nfc(n) + " histograms."); } float p() { return (round(1000f*(1f - ((1f * progressCurrent) / progressStart))))/1000f; } int pAlpha() { return 25+floor(p()*75); } void draw() { if (histogramMap.isEmpty()) { println("no more histograms..."); noLoop(); saveFrame("HardyProjectOne_FinalFrame" + (tableVisible ? "_WithTable" : "") + ".png"); return; } frameNum++; Date dateKey = null; for (int i = 0; i < nLinesPerFrame; i++) { if (histogramMap.isEmpty() == false) { // pop next histogram dateKey = (Date)histogramMap.firstKey(); float histogram[] = (float[])histogramMap.get(dateKey); histogramMap.remove(dateKey); progressCurrent = histogramMap.size(); drawHistogram(dateKey, histogram); } } if (do_progress && dateKey != null) { fill(cBackground); noStroke(); rect(0,yTop-26,screenWidth,28); // clear previous date int xProgress = xLeft+floor(xWidth*p()); stroke(cForeground, 255/2); strokeWeight(2); line(xLeft+1, yTop-16, xProgress, yTop-16); fill(cText); textFont(fontText); textAlign(RIGHT); text(dfReadable.format(dateKey), xProgress, yTop-1); if (dateKey.getYear() > startDate.getYear()) { textAlign(LEFT); text(dfReadable.format(startDate), xLeft, yTop-1); } } } void drawHistogram(Date dateKey, float histogram[]) { // draw histogram float yScale = 0.95/hMax; float bScale = 1.25; color cBoldBrighter = color(red(cBold)*bScale, green(cBold)*bScale, blue(cBold)*bScale); stroke(cBoldBrighter, 255/20); strokeWeight(2); strokeJoin(ROUND); strokeCap(ROUND); smooth(); noFill(); beginShape(); curveVertex(xLeft, yBottom); curveVertex(xLeft, yBottom); for (int i = 0; i < 4; i++) { int x = xLeft + categoryWidth*i + categoryWidth/2; int y = yTop+2 + (yWidth-floor(yScale*histogram[i]*yWidth)); curveVertex(x,y); } curveVertex(xRight, yBottom); curveVertex(xRight, yBottom); endShape(); } boolean tableVisible = false; void keyPressed() { if (key == ' ' && do_table && !tableVisible) { fill(cText); textFont(fontText); textAlign(RIGHT); int spacing = 12+5; int y = yTop+2*spacing; int lastM = 1; String lines[] = loadStrings("mediacount.txt"); for (int i = 0; i < lines.length; i++) { String data[] = splitTokens(lines[i], WHITESPACE + ","); int m = parseInt(data[0]); int n = parseInt(data[2]); if (m > lastM) { y += spacing; lastM = m; } text(data[1], xMid-50, y); text(nfc(n,0), xMid+50, y); y += spacing; } tableVisible = true; } }