/**** The Evolution of Privacy on Facebook http://mattmckeon.com/facebook-privacy/ ***/ ////////////////////////////////////////////////////////////////////////////////////// // CONSTANTS color SLICE_FILL = color(59, 89, 152); color SLICE_STROKE = color(255); color SLICE_TIC = color(255); color SLICE_AXIS = color(192); color AUDIENCE_STROKE = color(76,57,52); color AUDIENCE_FILL = color(255); color YEAR_LABEL_FILL = color(192); color YEAR_LABEL_FILL_ACTIVE = color(128); // Classes of information String[] INFOS = {"Name", "Picture", "Gender", "Other Profile Data", "Birthday", "Friends", "Contact Info", "Networks", "Wall posts", "Photos", "Likes"}; // Audience names String[] AUDIENCES = {{"You", "Friends", "Network", "All Facebook Users", "The Entire Internet"}, // 2005 {"You", "Friends", "Network", "All Facebook Users", "The Entire Internet"}, // 2006 {"You", "Friends", "Network", "All Facebook Users", "The Entire Internet"}, // 2007 {"You", "Friends", "FoF", "All Facebook Users", "The Entire Internet"}, // 2009 (Nov) {"You", "Friends", "FoF", "All Facebook Users", "The Entire Internet"}, // 2009 (Dec) {"You", "Friends", "FoF", "All Facebook Users", "The Entire Internet"} // 2010 (Apr) }; // Times String[] TIMES = {"2005", "2006", "2007", "2009 (Nov)", "2009 (Dec)", "2010 (Apr)"}; // Sizes of audiences [TIMES.length x AUDIENCES.length] int[][] AUDIENCESIZES = {{1, 40, 1000, 5000000,1000000000}, // 2005 {1, 70, 10000, 12000000, 1100000000}, // 2006 {1, 100, 15000, 50000000, 1300000000}, // 2007 {1, 130, 5650, 350000000, 1800000000}, // 2009 (Nov) {1, 130, 5650, 390000000, 1800000000}, // 2009 (Dec) {1, 130, 5650, 400000000, 1800000000}, // 2010 (Apr) }; // Labels for audience sizes [TIMES.length x AUDIENCES.length] String[][] AUDIENCESIZELABELS = {{"1", "40", "1K", "5M", "1B"}, // 2005 {"1", "70", "10K", "12M", "1.1B"}, // 2006 {"1", "100", "15K", "50M", "1.3B"}, // 2007 {"1", "130", "8.5K", "350M", "1.8B"}, // 2009 (Nov) {"1", "130", "8.5K", "390M", "1.8B"}, // 2009 (Dec) {"1", "130", "8.5K", "400M", "1.8B"}, // 2010 (Apr) }; // Degree of visibility of various classes of personal info. [TIMES.length x INFOS.length] // Each value is an index into AUDIENCES int[][] VISIBILITIES = {{3, 3, 3, 1, 1, 2, 1, 3, 2, 2, -1}, // 2005 {3, 3, 3, 1, 1, 2, 1, 3, 2, 2, -1}, // 2006 {3, 3, 3, 2, 2, 2, 1, 3, 2, 2, -1}, // 2007 {4, 4, 4, 2, 2, 3, 1, 4, 2, 2, 2}, // 2009 (Nov) {4, 4, 4, 2, 2, 4, 1, 4, 2, 2, 4}, // 2009 (Dec) {4, 4, 4, 4, 2, 4, 1, 4, 4, 4, 4} // 2010 (Apr) }; // Min and max values from AUDIENCESIZES float MIN_VALUE = 1; float MAX_VALUE = 1800000000; int[] CANVAS_DIM = {810, 670}; // Canvas size int[] VIS_DIM = {530, 530}; // The visualization size int[] ORIGIN = {315, 327}; // The origin of the visualization PFont FONT = loadFont("luxisr.svg"); String LABEL_SLICE = "Wall posts"; // Name of the info slice where the audience labels are drawn. // Font sizes for various labels int SLICE_LABEL_SIZE = 13; int AUDIENCE_LABEL_SIZE = 10; int LEGEND_LABEL_SIZE = 10; int LEGEND_SIZE = 14; int YEAR_LABEL_SIZE = 40; // Global animation vars int current_time = 0; int step = 5, c = 0; // Visualization elements ArrayList audiences, slices, timeLabels; LicenseButton licenseButton; boolean redrawAll = true; ////////////////////////////////////////////////////////////////////////////////////// // UTILITIES FOR POLAR COORDINATE MATH float[] toXY(float r, float theta) { float[] xy = {r*cos(radians(theta)), r*sin(radians(theta))}; return xy; } void vertexXY(float r, float theta) { float[] xy = toXY(r, theta); vertex(xy[0], xy[1]); } void bezierVertexXY(float c1r, float c1theta, float c2r, float c2theta, float r, float theta) { float[][] xy = {toXY(c1r, c1theta), toXY(c2r, c2theta), toXY(r, theta)}; bezierVertex(xy[0][0], xy[0][1], xy[1][0], xy[1][1], xy[2][0], xy[2][1]); } void bezierXY(float p1r, float p1theta, float c1r, float c1theta, float c2r, float c2theta, float p2r, float p2theta) { float[][] xy = {toXY(p1r, p1theta), toXY(c1r, c1theta), toXY(c2r, c2theta), toXY(p2r, p2theta)}; bezier(xy[0][0], xy[0][1], xy[1][0], xy[1][1], xy[2][0], xy[2][1], xy[3][0], xy[3][1]); } void lineXY(float r1, float theta1, float r2, float theta2) { float[] xy1 = toXY(r1, theta1); float[] xy2 = toXY(r2, theta2); line(xy1[0], xy1[1], xy2[0], xy2[1]); } ////////////////////////////////////////////////////////////////////////////////////// // Scale function used for the audience sizes float scaledValue(float v) { return log(v) + 1; } ////////////////////////////////////////////////////////////////////////////////////// // SETUP AND MAIN LOOP // Setup the Processing Canvas void setup() { size(CANVAS_DIM[0], CANVAS_DIM[1]); strokeWeight(1); frameRate( 15 ); slices = new ArrayList(); for (int i=0; i=0; i--) { ((Audience)audiences.get(i)).drawMe(); } for (int i=slices.size()-1; i>=0; i--) { ((Slice)slices.get(i)).drawMe(); } popMatrix(); drawLegend(); licenseButton.drawMe(); noFill(); stroke(192); rect(0, 0, CANVAS_DIM[0]-1, CANVAS_DIM[1]-1); if (c < step) { c = c+1; } else { redrawAll = false; } } ////////////////////////////////////////////////////////////////////////////////////// // USER INTERACTION void mousePressed() { if (c < step) { return; } for (int i=0; i= x) && (x_ < x+w) && (y_ >= y) && (y_ < y+h); } void drawMe() { fill(textColor); textFont(FONT, textHeight); text(txt, x, y); } } // The clickable year labels class TimeLabel { String txt; float x, y; TimeLabel(String txt_, float x_, float y_) { txt = txt_; x = x_; y = y_; } boolean contains(float x_, float y_) { float h = getCurrentHeight(); float w = FONT.width(txt) * h; return (x_ >= x) && (x_ < x+w) && (y_ >= y) && (y_ < y+h) } boolean isCurrent() { return txt == TIMES[current_time]; } float getCurrentHeight() { if (isCurrent()) { return YEAR_LABEL_SIZE / 2; } else { return YEAR_LABEL_SIZE / 3; } } void drawMe() { float labelHeight = getCurrentHeight(); if (isCurrent()) { fill(YEAR_LABEL_FILL_ACTIVE); } else { fill(YEAR_LABEL_FILL); } textFont(FONT, labelHeight); text(txt, x, y-labelHeight/2); } } // Audience rings class Audience { String[] names; float[] radii; color fillColor; Audience(String[] nms, float[] r) { names = nms; radii = r; fillColor = null; } void drawMe() { if (fillColor) { fill(fillColor); stroke(AUDIENCE_STROKE); } else { noFill(); } float rNow = radii[current_time]; int n = INFOS.length; float delta = 360.0/n; if (fillColor) { beginShape(); vertexXY(rNow*.9, 0); for (int i=0; i 90 && theta+delta/2 < 270) { float angle = theta+delta/2; float xy[] = toXY(scaledValue(MAX_VALUE/2)/2 + txtWidth, angle); translate(xy[0], xy[1]); rotate(PI + radians(angle)); } else { float xy[] = toXY(scaledValue(MAX_VALUE/2)/2, theta+delta/2); translate(xy[0], xy[1]); rotate(radians(theta+delta/2)); } strokeWeight(1/scaleXY); if (rNow < 0) { fill(YEAR_LABEL_FILL); } else { fill(SLICE_FILL); } text(name, 0,-5*labelHeight/8); popMatrix(); if (rNow < 0) // We're at a time before this feature was available on Facebook. { return; } if (current_time > 0) { rNow = radii[current_time-1] + c*(radii[current_time] - radii[current_time-1])/step; } stroke(SLICE_STROKE); // The fill portion of the wedge beginShape(); vertexXY(scaledValue(MIN_VALUE)/2, theta); vertexXY(rNow*.9, theta); bezierVertexXY(rNow, theta+delta*.1, rNow, theta+delta*.9, rNow*.9, theta+delta); vertexXY(scaledValue(MIN_VALUE)/2, theta+delta); vertexXY(scaledValue(MIN_VALUE)/2, theta); endShape(); // Tic marks for the wedge at the audience levels labelHeight = AUDIENCE_LABEL_SIZE/scaleXY; textFont(FONT, labelHeight); stroke(SLICE_TIC); for (int i=0; i 0 ? scaledValue(AUDIENCESIZES[current_time-1][i])/2 : 0; if (ticr <= rNow) { bezierXY(ticr*.9, theta, ticr, theta+delta*.1, ticr, theta+delta*.9, ticr*.9, theta+delta); } // Draw audience labels if we're the label slice if (name == LABEL_SLICE) { pushMatrix(); float txtWidth = FONT.width(AUDIENCES[current_time][i])*labelHeight; if (i==0) { translate(0, -3*labelHeight/4); } else { float fudge = .91; if (i == AUDIENCES[current_time].length-1) { fudge = .925; } float xy = toXY(ticr*fudge, theta+delta/2); translate(xy[0], xy[1]); rotate(PI/2+radians(theta+delta/2)); } if (ticr*.9 <= rNow) { fill(SLICE_TIC); } else { fill(AUDIENCE_STROKE); } text(AUDIENCES[current_time][i], -txtWidth/2, 0); popMatrix(); } } } }