// --------------------------------------------------- // University of California Santa Barbara // Media Arts and Technology // MAT 259 | Visualizing Information | Winter 2011 // // Patrick Rudolph // Project 1 | Frequency Mapping // // Main Program // --------------------------------------------------- // --------------------------------------------------- // GLOBAL VARIABLES // --------------------------------------------------- // Style & Layout PFont fTitle,fTopics,fText,fSmall; final color cText = #4D4242; final color cTitle = cText; final color cBG = #FFFFFF; final color cGraph = #8E8E66; final color cGraphD = #27261C; final color cGrid = #F0E6CE; color[] cGraphBG = { #F5F5DC,#F5F5D0,#F5F5C3,#F5F5B7,#F4F5AB }; final int spacing = 2; // spacing between the plots final int spacingIn = 2; // spacing within a plot final int marginTop = 60; final int marginLeft = 90; final int graphW = 850; final int graphH = 100; final String[] years = { "2005","2006","2007","2008","2009","2010" }; final String title = "Hot Topics "+years[0]+" - "+years[years.length-1]; final String toggle1 = "toggle graph style"; final String toggle2 = "toggle color scheme"; final String footer = "MAT259 Winter 2011 | Frequency Mapping | Patrick Rudolph"; // Data Structures int displayMode = 1; // determines which kind of plot is used final int wkInterval = 3; // factor to stretch the graph final int wkMin = 0; final int wkMax = 52*years.length/wkInterval; Topic[] topics = new Topic[5]; // --------------------------------------------------- // INITIALIZING (runs only once) // --------------------------------------------------- void setup() { size( marginLeft+graphW+20 , marginTop+(graphH+spacing)*topics.length+50 ); // initialize fonts and topics fTitle = loadFont("SegoeUI-Bold-30.vlw"); fTopics = loadFont("SegoeUI-Bold-16.vlw"); fText = loadFont("SegoeUI-14.vlw"); fSmall = loadFont("SegoeUI-12.vlw"); topics[0] = new Topic( "Bush", "bush.txt", loadImage("bush.png"), wkMax,wkInterval ); topics[1] = new Topic( "Obama", "obama.txt", loadImage("obama.png"), wkMax,wkInterval ); topics[2] = new Topic( "Financial Crisis","financialcrisis.txt",loadImage("financialcrisis.png"),wkMax,wkInterval ); topics[3] = new Topic( "Global Warming", "globalwarming.txt", loadImage("globalwarming.png"), wkMax,wkInterval ); topics[4] = new Topic( "Apple", "apple.txt", loadImage("apple.png"), wkMax,wkInterval ); smooth(); } // --------------------------------------------------- // DRAWING (runs at framerate, default 60 frames/s) // --------------------------------------------------- void draw() { background(cBG); noStroke(); // draw only once drawTitle(); drawControls(); drawFooter(); drawYearLabels(); // draw for each topic for(int i = 0; i < topics.length; i++) { drawGraph(i); drawGrid(i); switch(displayMode) { case 1: drawDataArea(i); // area diagram break; case 2: drawDataCurve(i); // curve diagram break; case 3: drawDataBars(i,4); // bar chart break; case 4: drawDataBars2(i,8); // bar chart with brightness break; default: drawDataCurve(i); // curve diagram as default break; } drawDataHighlight(i); } drawTopicLabels(); // draw them last because of rotate } // --------------------------------------------------- // DRAWS THE TITLE OF THE SKETCH // --------------------------------------------------- void drawTitle() { // style fill(cTitle); textFont(fTitle); textAlign(LEFT); text(title, 15, 40); // title text } // --------------------------------------------------- // DRAWS THE CONTROLS FOR THE SKETCH // --------------------------------------------------- void drawControls() { fill(cGraph); noStroke(); float x1_2 = width-40; float y1_2 = width-50; float z1_2 = width-50; float x2_2 = 30; float y2_2 = 25; float z2_2 = 35; float x1_1 = width-70; float y1_1 = width-60; float z1_1 = width-60; float x2_1 = 30; float y2_1 = 25; float z2_1 = 35; switch(displayMode) { case 1: triangle(x1_2, x2_2, y1_2, y2_2, z1_2, z2_2); fill(cGraph,50); triangle(x1_1, x2_1, y1_1, y2_1, z1_1, z2_1); break; case 4: triangle(x1_1, x2_1, y1_1, y2_1, z1_1, z2_1); fill(cGraph,50); triangle(x1_2, x2_2, y1_2, y2_2, z1_2, z2_2); break; default: triangle(x1_1, x2_1, y1_1, y2_1, z1_1, z2_1); triangle(x1_2, x2_2, y1_2, y2_2, z1_2, z2_2); break; } // tooltip triangle if(mouseInRect(width-80,20,width-30,40)) { textFont(fSmall); textAlign(CENTER); fill(cGraphBG[0], 170); stroke(cGraph, 130); strokeWeight(1.5); roundedRect(mouseX-55, mouseY-25, 110, 20, 10, 10); fill(cGraphD); noStroke(); text(toggle1, mouseX, mouseY-10); // highlight text } noFill(); stroke(cGraph, 180); strokeWeight(1.5); line(width-100,15,width-100,45); noStroke(); fill(#F4F5AB); rect(width-200,20,10,20); fill(#ADC2F5); rect(width-180,20,10,20); fill(#D9F5AB); rect(width-160,20,10,20); fill(#F5ABAB); rect(width-140,20,10,20); // tooltip color if(mouseInRect(width-205,15,width-125,45)) { textFont(fSmall); textAlign(CENTER); fill(cGraphBG[0], 170); stroke(cGraph, 130); strokeWeight(1.5); roundedRect(mouseX-60, mouseY-25, 120, 20, 10, 10); fill(cGraphD); noStroke(); text(toggle2, mouseX, mouseY-10); // highlight text } } // --------------------------------------------------- // DRAWS THE FOOTER OF THE SKETCH // --------------------------------------------------- void drawFooter() { // style fill(cTitle); textFont(fText); textAlign(RIGHT); text(footer, width-23, height-8); // footer text } // --------------------------------------------------- // DRAWS THE YEAR LABELS BELOW THE PLOTS // --------------------------------------------------- void drawYearLabels() { // style fill(cText, 150); stroke(cText, 100); strokeWeight(1); for(int i = 1; i < years.length; i++) { float x = marginLeft+(graphW*i/years.length)+15; float y = marginTop+(graphH+spacing)*5; text(years[i-1], x-(graphW/years.length)/2+15, y+15); // year labels line(x, y+5, x, y+15); // seperators for year labels } // last year labels (one more then seperators) text(years[years.length-1], marginLeft+graphW+15-graphW/years.length/2+7, marginTop+(graphH+spacing)*5+15); } // --------------------------------------------------- // DRAWS LABELS NEXT TO EACH TOPIC // --------------------------------------------------- void drawTopicLabels() { // style fill(cText, 150); noStroke(); textFont(fTopics); textAlign(CENTER); rectMode(CORNER); translate(100,100); rotate(PI*1.75); for(int i=0; i < topics.length; i++) { if( !mouseInRect(marginLeft, marginTop+getOffset(topics.length-i-1), marginLeft+graphW, marginTop + getOffset(topics.length-i-1) + graphH) ) { text(topics[topics.length-i-1].getName(),-370+i*70, +235-i*70, graphH-20, marginLeft/2); } } } // --------------------------------------------------- // DRAWS THE GRAPH BACKGROUND FOR EACH PLOT // --------------------------------------------------- void drawGraph(int topic) { // style fill(cGraphBG[topic]); noStroke(); rectMode(CORNER); rect(marginLeft, marginTop + getOffset(topic), graphW, graphH); // plot background } // --------------------------------------------------- // DRAWS THE REAL WORLD EVENTS FOR EACH PLOT // --------------------------------------------------- void drawEvents(int topic) { drawEvent("Library slow-down",35,topic); drawEvent("Library slow-down",81,topic); switch(topic) { case 0: // Bush drawEvent("2008 elections",60,topic); break; case 1: // Obama drawEvent("Election",66,topic); drawEvent("Inauguration",71,topic); drawEvent("ARRA",72,topic); drawEvent("Health Care Reform",91,topic); break; case 2: // Financial Crisis drawEvent("Lehmann collapses",63,topic); drawEvent("Obama's stimulus packages",71,topic); break; case 3: // Global Warming drawEvent("Gore: \"An Inconvenient Truth\"",24,topic); drawEvent("Bali Road Map",51,topic); drawEvent("Copenhagen Summit",84,topic); break; case 4: // Apple drawEvent("Release iPod Nano, iPod 4G Color, iPod 5G",11,topic); drawEvent("Release MacBook Pro",20,topic); drawEvent("ReleaseMacBook",26,topic); drawEvent("Announcement iPhone",36,topic); drawEvent("Release iPhone",44,topic); drawEvent("Release iPhone 3G",61,topic); drawEvent("Steve Jobs health problems",70,topic); drawEvent("Steve Jobs returns",80,topic); drawEvent("Release iPad",92,topic); break; } } // --------------------------------------------------- // DRAWS A SINGLE REAL WORLD EVENT // --------------------------------------------------- void drawEvent(String name, int wk, int topic) { float value = topics[topic].getData(wk); //items[topic][wk]; // float x = map(wk, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float y1 = marginTop+getOffset(topic)+spacingIn; float y2 = marginTop+graphH+getOffset(topic)-spacingIn; float y = map(value, topics[topic].getDataMin(), topics[topic].getDataMax(), marginTop+graphH+getOffset(topic)-spacingIn, marginTop+getOffset(topic)+spacingIn); noFill(); stroke(cGraph, 170); strokeWeight(4); line(x,y1+1,x,y2-1); stroke(cBG); strokeWeight(1.3); line(x,y1,x,y2); if( mouseInPlot(x, y, topic) ) { textFont(fSmall); textAlign(CENTER); fill(cGraphBG[topic], 210); stroke(cGraph, 180); strokeWeight(1.5); float w = 40+name.length()*5; roundedRect(x-w/2, y1-25, w, 20, 10, 10); fill(cGraphD); noStroke(); text(name, x, y1-10); // highlight text } } // --------------------------------------------------- // DRAWS THE GRID FOR EACH PLOT // --------------------------------------------------- void drawGrid(int topic) { // style noFill(); stroke(cGrid); strokeWeight(1); for(int i = 1; i < years.length; i++) { float x = marginLeft+(graphW*i/years.length)+15; line(x, marginTop+getOffset(topic), x, marginTop+graphH+getOffset(topic)); // grid lines } } // --------------------------------------------------- // DRAWS THE DATA CURVE FOR EACH PLOT // --------------------------------------------------- void drawDataCurve(int topic) { // style noFill(); stroke(cGraph); strokeWeight(1.4); beginShape(); for(int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float y = map(value, topics[topic].getDataMin(), topics[topic].getDataMax(), marginTop+graphH+getOffset(topic)-spacingIn, marginTop+getOffset(topic)+spacingIn); curveVertex(x, y); // double the curve points for the start and stop // ### is this really needed? ### if ((i == 0) || (i == wkMax-3)) curveVertex(x, y); } endShape(); } // --------------------------------------------------- // DRAWS THE AREA GRAPH FOR EACH TOPIC // --------------------------------------------------- void drawDataArea(int topic) { // style fill(cGraph, 120); noStroke(); beginShape(); for(int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float y = map(value, topics[topic].getDataMin(), topics[topic].getDataMax(), marginTop+graphH+getOffset(topic)-spacingIn, marginTop+getOffset(topic)+spacingIn); curveVertex(x, y); // double the curve points for the start and stop // ### is this really needed? ### if ((i == 0) || (i == wkMax-3)) curveVertex(x, y); } vertex(marginLeft+graphW-9.5,marginTop+graphH+getOffset(topic)-spacingIn); vertex(marginLeft+15,marginTop+graphH+getOffset(topic)-spacingIn); endShape(CLOSE); } // --------------------------------------------------- // DRAWS THE BAR CHART FOR EACH TOPIC // --------------------------------------------------- void drawDataBars(int topic,int barW) { // style fill(cGraph, 180); noStroke(); rectMode(CORNERS); for(int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float y = map(value, topics[topic].getDataMin(), topics[topic].getDataMax(), marginTop+graphH+getOffset(topic)-spacingIn, marginTop+getOffset(topic)+spacingIn); rect(x-barW/2,y,x+barW/2,marginTop+graphH+getOffset(topic)-spacingIn); } } // --------------------------------------------------- // DRAWS THE BAR CHART 2 FOR EACH TOPIC // --------------------------------------------------- void drawDataBars2(int topic, int barW) { // style noStroke(); rectMode(CORNERS); for(int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float percent = norm(value, topics[topic].getDataMin(), topics[topic].getDataMax()); color between = lerpColor(cGraphBG[topic], cGraphD, percent); fill(between); rect(x-barW/2,marginTop+getOffset(topic)+spacingIn,x+barW/2,marginTop+graphH+getOffset(topic)-spacingIn); } } // --------------------------------------------------- // DRAWS THE DATA CURVE MOUSEOVER FOR EACH PLOT // --------------------------------------------------- void drawDataHighlight(int topic) { if(displayMode != 4) { for (int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); float y = map(value, topics[topic].getDataMin(), topics[topic].getDataMax(), marginTop+graphH+getOffset(topic)-spacingIn, marginTop+getOffset(topic)+spacingIn); if( mouseInPlot(x, y, topic) ) { noFill(); stroke(cGraph); strokeWeight(10); point(x, y); // outer circle (dark border) stroke(cGraphBG[topic]); strokeWeight(7); point(x, y); // inner circle (light inside) fill(cGraphBG[topic], 240); stroke(cGraph, 210); strokeWeight(1.5); roundedRect(x-60, y-25, 120, 20, 10, 10); // slightly transparent round-edge-rectangle String txt = (int)value + " items in " + wkInterval + " weeks"; textFont(fSmall); textAlign(CENTER); noStroke(); // text fill(cGraphD); text(txt, x, y-10); // highlight text } } } else { // displayMode == 4 for (int i = 0; i < wkMax-2; i++) { float value = topics[topic].getData(i); float x = map(i, wkMin, wkMax, marginLeft+15, marginLeft+graphW+15); if( mouseInRect(x, marginTop+getOffset(topic)+spacingIn, x+8, marginTop+graphH+getOffset(topic)-spacingIn) ) { fill(cGraphBG[topic], 240); stroke(cGraph, 210); strokeWeight(1.5); roundedRect(mouseX-60, mouseY-25, 120, 20, 10, 10); String txt = (int)value + " items in " + wkInterval + " weeks"; textFont(fSmall); textAlign(CENTER); noStroke(); // text fill(cGraphD); text(txt, mouseX, mouseY-10); // highlight text } } } // mouse over if( mouseInRect(marginLeft, marginTop+getOffset(topic), marginLeft+graphW, marginTop + getOffset(topic) + graphH) ) { image(topics[topic].getImg(),5,marginTop + getOffset(topic)); // mouse over background image drawEvents(topic); } } // --------------------------------------------------- // CALCULATE THE OFFSET FOR THE CURRENT PLOT // --------------------------------------------------- int getOffset(int topic) { return (graphH+spacing)*topic; } // --------------------------------------------------- // IS THE MOUSE OVER THE PLOT? // --------------------------------------------------- boolean mouseInPlot(float x,float y,int topic) { float pos = y - marginTop - (graphH + spacing) * topic; if( (mouseX > x-4) && (mouseX < x+4) && (mouseY > y-pos) && (mouseY < y-pos+graphH) ) return true; return false; } // --------------------------------------------------- // IS THE MOUSE OVER A RECTANGLE? // --------------------------------------------------- boolean mouseInRect(float x1, float y1, float x2, float y2) { if((mouseX > x1) && (mouseX < x2) && (mouseY > y1) && (mouseY < y2)) return true; return false; } // --------------------------------------------------- // DRAW A BORDER AROUND A TEXT // --------------------------------------------------- void textBorder(String txt, float x, float y, color col) { fill(col); text(txt,x+1,y+1); text(txt,x+1,y-1); text(txt,x-1,y+1); text(txt,x-1,y-1); } // --------------------------------------------------- // DRAW A ROUND-EDGED RECTANGLE // --------------------------------------------------- void roundedRect(float x, float y, float w, float h, float rx, float ry) { beginShape(); vertex(x,y+ry); //top of left side bezierVertex(x,y,x,y,x+rx,y); //top left corner vertex(x+w-rx,y); //right of top side bezierVertex(x+w,y,x+w,y,x+w,y+ry); //top right corner vertex(x+w,y+h-ry); //bottom of right side bezierVertex(x+w,y+h,x+w,y+h,x+w-rx,y+h); //bottom right corner vertex(x+rx,y+h); //left of bottom side bezierVertex(x,y+h,x,y+h,x,y+h-ry); //bottom left corner endShape(CLOSE); } // --------------------------------------------------- // EVENTS FOR KEYPRESSED // --------------------------------------------------- void keyPressed() { switch(key) { case '1': displayMode = 1; break; case '2': displayMode = 2; break; case '3': displayMode = 3; break; case '4': displayMode = 4; break; default: break; } } // --------------------------------------------------- // EVENTS FOR MOUSEPRESSED // --------------------------------------------------- void mousePressed() { if( mouseInRect(width-80,20,width-55,40) && displayMode > 1 ) displayMode--; if( mouseInRect(width-55,20,width-30,40) && displayMode < 4 ) displayMode++; if( mouseInRect(width-200,20,width-190,40) ) { cGraphBG[0] = #F5F5DC; cGraphBG[1] = #F5F5D0; cGraphBG[2] = #F5F5C3; cGraphBG[3] = #F5F5B7; cGraphBG[4] = #F4F5AB; } if( mouseInRect(width-180,20,width-170,40) ) { cGraphBG[0] = #DCE4F5; cGraphBG[1] = #D0DBF5; cGraphBG[2] = #C3D2F5; cGraphBG[3] = #B7CAF5; cGraphBG[4] = #ADC2F5; } if( mouseInRect(width-160,20,width-150,40) ) { cGraphBG[0] = #ECF5DC; cGraphBG[1] = #E7F5D0; cGraphBG[2] = #E2F5C3; cGraphBG[3] = #DEF5B7; cGraphBG[4] = #D9F5AB; } if( mouseInRect(width-140,20,width-130,40) ) { cGraphBG[0] = #F5DCDC; cGraphBG[1] = #F5D0D0; cGraphBG[2] = #F5C3C3; cGraphBG[3] = #F5B7B7; cGraphBG[4] = #F5ABAB; } }