import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.sql.Date; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import processing.core.*; public class FreqMap extends PApplet { public static void main(String args[]) { PApplet.main(new String[] { "--bgcolor=#ffffff" ,"--present", "FreqMap" }); } public class Movie { public String Title; public Date release; public int boxoffice; } public class Book { public String Title; public int count; } public class Font { PFont font; float size; Font(PFont font, float size) { this.font = font; this.size = size; } } Font fontLabelsNum; Font fontLabelsYears; Font fontLabelsMonths; Font fontMenu; Font fontEventInfo; Font fontEventHeading; Font fontCheckoutList; Font fontBig; Font fontLabelCounter; Font fontHeading; DecimalFormat decimal = new DecimalFormat(); DateFormat date = new SimpleDateFormat("d MMM yyyy"); String[] monthNames = (new DateFormatSymbols()).getMonths(); String numStrings[] = { "None", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"}; int windowWidth = 1040; int windowHeight = 880; int yearMax = 2011; int yearMin = 2005; int monthSkip = 4; int monthCount = (yearMax - yearMin) * 12; int graphPosX = 60; int graphPosY = 200; float graphScaleX = 11; int graphSizeX = (int) ( monthCount * graphScaleX ); int graphSizeY = 500; int graphDefaultMinY = 0; int graphDefaultMaxY = 1400; int graphZoomMinY = 0; int graphZoomMaxY = 200; int graphMinY = 0; int graphMaxY = 1400; float graphScaleY = graphSizeY / (float) graphMaxY; int menuPosX = graphPosX + graphSizeX + 50; int menuPosY = graphPosY; int listTitlesPerColumn = 8; int listNumColumns = 2; int listWidth = 500; String filenameMovies = "movies.txt"; String filenameTitles = "titles.txt"; String folderListBook = "list-book/"; String suffixListBook = "-list-book.txt"; String titles[]; String names[]; int colors[]; int[][] values; ArrayList[][] books; boolean selected[]; Movie[] movies; boolean monthHasMovie[] = new boolean[monthCount]; public void setup() { // add scroll wheel handler addMouseWheelListener( new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e) { mouseScrolled(e.getWheelRotation()); } } ); size(windowWidth, windowHeight); smooth(); PFont fontDVSans12 = loadFont("fonts/DejaVuSans-12.vlw"); PFont fontDVSans16 = loadFont("fonts/DejaVuSans-16.vlw"); PFont fontDVSansBold16 = loadFont("fonts/DejaVuSans-Bold-16.vlw"); PFont fontArialMT40 = loadFont("fonts/ArialMT-40.vlw"); PFont fontArialBoldMT40 = loadFont("fonts/Arial-BoldMT-40.vlw"); PFont fontArialBoldMT100 = loadFont("fonts/Arial-BoldMT-100.vlw"); fontLabelsYears = fontLabelsMonths = fontLabelsNum = new Font(fontDVSans12, 12); fontMenu = new Font(fontDVSans12, 12); fontEventInfo = new Font(fontDVSans12, 12); fontEventHeading = new Font(fontDVSansBold16, 16); fontCheckoutList = new Font(fontDVSans12, 12); fontBig = new Font(fontArialBoldMT100, 100); fontLabelCounter = new Font(fontDVSans12, 12); fontHeading = new Font(fontArialBoldMT40, 40); loadMovies(); loadTitles(); loadData(); selected = new boolean[titles.length]; } void loadMovies() { String lines[] = loadStrings(filenameMovies); movies = new Movie[lines.length]; for (int i = 0; i < lines.length; i++) { String[] line = lines[i].split(", "); movies[i] = new Movie(); movies[i].Title = line[0]; movies[i].release = Date.valueOf(line[1]); movies[i].boxoffice = Integer.valueOf(line[2]); int month = (movies[i].release.getYear() + 1900 - yearMin) * 12 + movies[i].release.getMonth(); monthHasMovie[month] = true; } } void loadTitles() { String lines[] = loadStrings(filenameTitles); titles = new String[lines.length]; names = new String[lines.length]; colors = new int[lines.length]; for (int i = 0; i < lines.length; i++) { String[] line = lines[i].split(", "); titles[i] = line[0]; names[i] = line[1]; colors[i] = (int)Long.parseLong(line[2],16); } } void loadData() { values = new int[titles.length][monthCount]; books = (ArrayList[][]) new ArrayList[titles.length][monthCount]; for (int j = 0; j < titles.length; j++) { String filename = folderListBook + titles[j] + suffixListBook; String lines[] = loadStrings(filename); books[j] = (ArrayList[]) new ArrayList[monthCount]; for (int i = 0, k = -1; i < lines.length; i++) { if (lines[i].startsWith("###")) { k++; values[j][k] = 0; books[j][k] = new ArrayList(); continue; } String[] line = lines[i].split(" , "); Book book = new Book(); book.Title = line[0]; book.count = Integer.valueOf(line[1]); books[j][k].add(book); values[j][k] += book.count; } } } public void draw() { background(0xffffffff); int month = monthHover(); drawMenu(); drawGraphArea(); drawLines(); // cover lines outside graph area noStroke(); fill(0xffffffff); rect(graphPosX, 0, graphSizeX, graphPosY); drawCheckoutList(month); int top = (int) (graphPosY + graphSizeY); int bottom = (int) (top + fontLabelsYears.size * 1.4f + fontLabelsMonths.size); if ( month >= 0 && mouseY > top && mouseY < bottom ) { drawGuide(month); drawEvents(month); } } void drawLines() { float x, y; // fills noStroke(); for (int j = 0; j < values.length; j++) { if (selected[j] != true) continue; fill(colors[j] - 0xee000000); beginShape(); for (int i = 0; i < values[j].length; i++) { x = graphPosX + graphScaleX * (i + 0.5f); y = map(values[j][i], graphMinY, graphMaxY, graphPosY + graphSizeY, graphPosY); curveVertex(x, y); if (i == 0 || i == values[j].length - 1) curveVertex(x, y); } x = graphPosX + graphScaleX * (monthCount - 1 + 0.5f); y = graphPosY + graphSizeY; vertex(x, y); x = graphPosX + graphScaleX * 0.5f; vertex(x, y); endShape(CLOSE); } // lines noFill(); strokeWeight(2); for (int j = 0; j < values.length; j++) { if (selected[j] != true) continue; stroke(colors[j]); beginShape(); for (int i = 0; i < values[j].length; i++) { x = graphPosX + graphScaleX * (i + 0.5f); y = map(values[j][i], graphMinY, graphMaxY, graphPosY + graphSizeY, graphPosY); curveVertex(x, y); if (i == 0 || i == values[j].length - 1) curveVertex(x, y); } endShape(); } strokeWeight(1); } void drawMenu() { textFont(fontMenu.font); textAlign(LEFT); float gap = (graphSizeY - fontMenu.size)/ (titles.length - 1); for (int i = 0; i < names.length; i++) { fill(0xff000000); text(names[i], menuPosX, menuPosY + fontMenu.size + i * gap); if (selected[i]) { fill(colors[i]); noStroke(); ellipse(menuPosX - 20, menuPosY + fontMenu.size/2 + i * gap + 2, 10 ,10); } else { //fill(colors[i]); noFill(); //noStroke(); stroke(colors[i]); //ellipse(menuPosX - 20, menuPosY + fontMenu.size/2 + i * gap + 2, 10 ,10); ellipse(menuPosX - 20, menuPosY + fontMenu.size/2 + i * gap + 2, 6, 6); } } int hover = menuItem(); if (hover != -1) { stroke(0xff000000); float y = menuPosY + fontMenu.size + hover * gap + 4; line(menuPosX, y, menuPosX + textWidth(names[hover]), y); } } void drawGraphArea() { // background noStroke(); fill(0x66cceeff); rect(graphPosX, graphPosY, graphSizeX, graphSizeY); stroke(0xff000000); fill(0xff000000); textFont(fontLabelsNum.font); // vertical axis line(graphPosX, graphPosY + graphSizeY, graphPosX, graphPosY); textAlign(RIGHT); int tickSize = 5; for (int tick = graphMinY; tick <= graphMaxY; tick += 200) { float y = map(tick, graphMinY, graphMaxY, graphPosY + graphSizeY, graphPosY); line(graphPosX - tickSize, y, graphPosX, y); text( Integer.toString(tick), graphPosX - 2 * tickSize, y + fontLabelsNum.size / 2 ); } // horizontal axis line(graphPosX, graphPosY + graphSizeY, graphPosX + graphSizeX, graphPosY + graphSizeY); textAlign(CENTER); for (int year = 2005; year < 2011; year++) { float x = graphPosX + (year - 2005) * 12 * graphScaleX; line(x, graphPosY + graphSizeY, x, graphPosY + graphSizeY + fontLabelsYears.size * 1.2f); fill(0xff000000); textFont(fontLabelsYears.font); text(Integer.toString(year), x + 6 * graphScaleX, graphPosY + graphSizeY + fontLabelsYears.size * 1.2f); for (int m = 0; m < 12; m++) { if ( monthHasMovie[(year-yearMin) * 12 + m] ) fill(0xff000000); else fill(0x66000000); textFont(fontLabelsMonths.font); text(monthNames[m].charAt(0), x + m * graphScaleX + graphScaleX * 0.5f, graphPosY + graphSizeY + fontLabelsYears.size * 1.2f + fontLabelsMonths.size * 1.2f); } } float x = graphPosX + (2011 - 2005) * 12 * graphScaleX; line(x, graphPosY + graphSizeY, x, graphPosY + graphSizeY + fontLabelsYears.size * 1.2f); } void drawGuide(int month) { stroke(0xffffffff); float x = graphPosX + month * graphScaleX + graphScaleX * 0.5f; strokeWeight(2); line(x, graphPosY, x, graphPosY + graphSizeY); strokeWeight(1); } void drawEvents(int month) { float x = graphPosX + (month + 0.5f) * graphScaleX; textAlign(CENTER); int count = 0; for (int i = 0; i < movies.length; i++) { int eventMonth = (movies[i].release.getYear() + 1900 - yearMin) * 12 + movies[i].release.getMonth(); if (eventMonth == month) { float y = graphPosY + graphSizeY + fontLabelsYears.size * 6.0f + count * 4 * fontEventInfo.size * 1.2f; fill(0xff000000); textFont(fontEventHeading.font); text(movies[i].Title, x, y); textFont(fontEventInfo.font); text( date.format(movies[i].release), x, y + fontEventInfo.size * 1.2f ); text( "$" + decimal.format(movies[i].boxoffice), x, y + 2 * fontEventInfo.size * 1.2f ); count++; } } if(count > 0) { stroke(0xff000000); strokeWeight(2); noFill(); beginShape(); vertex(x - graphScaleX, graphPosY + graphSizeY + fontLabelsYears.size * 4); vertex(x, graphPosY + graphSizeY + fontLabelsYears.size * 3); vertex(x + graphScaleX, graphPosY + graphSizeY + fontLabelsYears.size * 4); endShape(); strokeWeight(1); } } int dataHover(int month) { if ( month < 0 || mouseY > graphPosY + graphSizeY || mouseY < graphPosY ) return -1; float value = map(mouseY, graphPosY + graphSizeY, graphPosY, graphMinY, graphMaxY); float selectionRange = graphScaleX / graphScaleY; float min = graphMaxY; int index = -1; for (int j = 0; j < titles.length; j++) { if ( selected[j] ) { float dist = Math.abs(value - values[j][month]); if( dist < selectionRange && dist < min ) { min = dist; index = j; } } } return index; } void drawCheckoutList(int month) { int hover = dataHover(month); if (hover < 0) return; drawEvents(month); float x, y; fill(0xff000000); int numTitles = books[hover][month].size(); int checkouts = values[hover][month]; // month line stroke(0xffffffff); x = graphPosX + month * graphScaleX + graphScaleX * 0.5f; strokeWeight(2); line(x, graphPosY, x, graphPosY + graphSizeY); strokeWeight(1); // data point x = graphPosX + (month + 0.5f) * graphScaleX; y = map(values[hover][month], graphMinY, graphMaxY, graphPosY + graphSizeY, graphPosY); stroke(colors[hover]); strokeWeight(10); point(x,y); strokeWeight(1); // checkouts { textAlign(LEFT); textFont(fontLabelCounter.font); x = graphPosX + graphSizeX; y = graphPosY - 110; translate(x, y); rotate((float)-Math.PI/2); if (checkouts == 1) text( "CHECKOUT", 0, 0); else text( "CHECKOUTS", 0, 0); rotate((float)Math.PI/2); translate(-x, -y); textAlign(RIGHT); textFont(fontBig.font); text(checkouts, x - 16, y); } // titles { textAlign(LEFT); textFont(fontLabelCounter.font); x = graphPosX + graphSizeX; y = graphPosY - 15; translate(x, y); rotate((float)-Math.PI/2); if (numTitles == 1) text( "TITLE", 0, 0); else text( "TITLES", 0, 0); rotate((float)Math.PI/2); translate(-x, -y); textAlign(RIGHT); textFont(fontBig.font); text(numTitles, x - 16, y); } float listPosY = graphPosY - listTitlesPerColumn * fontCheckoutList.size * 1.2f - fontCheckoutList.size; // name textAlign(LEFT); textFont(fontHeading.font); text(names[hover], graphPosX, listPosY - 2 * fontCheckoutList.size); // line x = graphPosX; y = listPosY - fontCheckoutList.size; stroke(0xff000000); line(x, y, x + listWidth, y); // checkout list textFont(fontCheckoutList.font); int listGap = 20; int listColumnWidth = ( listWidth - (listNumColumns - 1) * listGap ) / listNumColumns; int numWidth = (int) textWidth("000"); if (numTitles > listTitlesPerColumn * listNumColumns) numTitles = listTitlesPerColumn * listNumColumns; for (int i = 0; i < numTitles; i++) { int count = books[hover][month].get(i).count; // fit title to space available String title = books[hover][month].get(i).Title; while( textWidth(title) > listColumnWidth - numWidth ) title = title.substring(0, title.length()-4).trim() + "..."; x = graphPosX + (int)(i/listTitlesPerColumn) * listColumnWidth + (int)(i/listTitlesPerColumn) * listGap; y = listPosY + fontCheckoutList.size + (i%listTitlesPerColumn) * fontCheckoutList.size * 1.2f; // fade color fill(0xff000000 - 0x01000000 * i * (255 / (listTitlesPerColumn * listNumColumns)) ); textAlign(RIGHT); text( count, x + listColumnWidth, y ); textAlign(LEFT); text( title, x, y ); } } int menuItem() { float gap = (graphSizeY - fontMenu.size ) / (titles.length - 1); int left = menuPosX; int right = windowWidth; int top = (int) (menuPosY - fontMenu.size * 0.5f); int bottom = (int) (menuPosY + graphSizeY + fontMenu.size * 0.5f); if (mouseX > left && mouseX < right && mouseY > top && mouseY < bottom) { int index = (int) ( (mouseY - (menuPosY - fontMenu.size * 0.5f)) / gap ); if (mouseX < menuPosX + textWidth(names[index]) ) { return index; } } return -1; } int monthHover() { int left = graphPosX; int right = graphPosX + graphSizeX; if (mouseX > left && mouseX < right) { return (int) ( (mouseX - graphPosX) / (graphScaleX) ); } return -1; } public void mouseClicked() { int index = menuItem(); if (index != -1) { selected[index] = !selected[index]; } } void mouseScrolled(int delta) { if (mouseX > graphPosX + graphSizeX || mouseY < graphPosY || mouseY > graphPosY + graphSizeY) return; if (delta < 0) { graphMaxY -= 200; if (graphMaxY < graphZoomMaxY) graphMaxY = graphZoomMaxY; } else if (delta > 0) { graphMaxY += 200; if (graphMaxY > graphDefaultMaxY) graphMaxY = graphDefaultMaxY; } graphScaleY = graphSizeY / (float)(graphMaxY - graphMinY); } }