1. Visualizations, Part 2

In this notebook, we continue to look at methods of displaying information visually. See Part 1

1.1 Preliminaries

To explore these ideas, we need to introduce a new construct in Processing: the array.

1.1.1 Arrays

Arrays are contiguous blocks of memory used to store similar things. Effectively this allows you to have variables that you can reference by a number.

You can define a variable to hold an unspecified number of integers with:

int [] values = new int[] { 1, 2, 3 };

You can also assign the variable with an array of values at the same time. We use the new keyword to create the array that holds the elements 1, 2, and 3:

int [] values = new int[] { 1, 2, 3 };

To refer to the first and second items, we use:

values[0]
values[i]

Let's see the array in use.

1.2 Albers Conic Map Projection

In the last lecture, we used a SVG image of the united states.

Here, we use a meta-command to download the file again:

In [ ]:
%download http://upload.wikimedia.org/wikipedia/commons/archive/3/32/20091105194402%21Blank_US_Map.svg

And a meta-command to rename the file:

In [ ]:
! mv 20091105194402%21Blank_US_Map.svg usa-wikipedia.svg

In this example, we want to use the states as before, but also be able to plot longitude and latitude on the map.

Since the map was just a picture (and not drawn) I wasn't sure what coordinate system it was in.

After a bit of trial and error, I found that the Albers conic projection was a good fit.

In this example, I plot some longitude and latitude points, and kept adjusting the xoffset, xscale, yoffset, and yscale until the points lined up pretty well. Normally, we would know these values, but the wikipedia map did not provide those.

If you click, it will plot those points, and some cites.

Notice that the albers function returns an array containing the x and the y values:

In [88]:
PShape usa;
PShape michigan;
PShape ohio;

void setup() {
    size(959, 593);  
    background(255);
    usa = loadShape("usa-wikipedia.svg");
    michigan = usa.getChild("MI");
    ohio = usa.getChild("OH");
}

void draw() { 
    // Draw the full map
    shape(usa, 0, 0);  
    noLoop();
}

float[] albers(lat, lng) {
    lat0 =  23.0 * (PI/180);   // Latitude_Of_Origin
    lng0 = -96.0 * (PI/180);   // Central_Meridian
    phi1 =  30.0 * (PI/180);   // Standard_Parallel_1
    phi2 =  50.0 * (PI/180);   // Standard_Parallel_2

    n = 0.5 * (sin(phi1) + sin(phi2));
    c = cos(phi1);
    C = c * c + 2 * n * sin(phi1);
    p0 = sqrt(C - 2 * n * sin(lat0)) / n;
    theta = n * (lng * PI/180 - lng0);
    p = sqrt(C - 2 * n * sin(lat * PI/180)) / n;
    x = p * sin(theta);
    y = p0 - p * cos(theta);
    return new float[2] { x, y };
}

void plot(lat, lon, c) {
    // Values to scale the lat, lon to fit on the USA map:
    xoffset = 485;
    xscale = 1245;
    yoffset = 630;    
    yscale = 1250;
    
    float[2] xy = albers(lat, lon);
    fill(c);
    ellipse(xoffset + xy[0] * xscale, yoffset - xy[1] * yscale, 10, 10);
}

void mousePressed() {
    for (lat = 30; lat <= 50; lat += 5) {
        for (lon = 70; lon <= 130; lon += 5) {
            plot(lat, -lon, color(255));
        }
    }
    // Some Cites:
    plot(39.790942, -86.147685, color(255, 0, 128));
    plot(47.042418, -122.893077, color(255, 128, 0));
    plot(30.4518, -84.27277, color(255, 0, 0));
    plot(44.323535, -69.765261, color(255, 0, 255));
    plot(33.448457, -112.073844, color(255, 255, 0));
}
Sketch #82:

Sketch #82 state: Loading...

You can use this to overlay data, such as population density, locations of events, etc. You might want to use the 4th element of color(), the alpha value, to make the colors transparent.

1.3 Genomics

Here is a diagram style that is used a lot in genomic and biological data:

http://circos.ca/intro/genomic_data/

How could we make such a chart?

First, let's make some tests using different curves.

I found that the bezier, with control points at the center looks pretty good:

In [98]:
noFill();
stroke(255, 0, 0);
bezier(10, 10, 50, 50, 50, 50, 90, 10);
Sketch #92:

Sketch #92 state: Loading...

For this example, we'll reuse the rotateAround functions from the last assignment. This is used to find the starting and stopping points around a center.

Then, we draw a bezier curve between them:

In [109]:
int rotateAroundX(int x1, int y1, int length, int angle) {
    return x1 + length * cos(angle);
}

int rotateAroundY(int x1, int y1, int length, int angle) {
    return y1 - length * sin(angle);
}

void setup() {
    size(300, 300);
    background();
}

void draw() {
    // Center:
    cx = width/2;
    cy = height/2;
    
    // Random degree, converted to radians:
    p1 = random(360) * PI/180;
    // Random degree, converted to radians:
    p2 = random(360) * PI/180;

    // Find where p1 would be:
    x1 = rotateAroundX(cx, cy, width/2, p1);
    y1 = rotateAroundY(cx, cy, width/2, p1);
    
    // Find where p2 would be:
    x2 = rotateAroundX(cx, cy, width/2, p2);
    y2 = rotateAroundY(cx, cy, width/2, p2);
    
    // Connect p1 to p2:
    noFill();
    stroke(random(255), random(255), random(255));
    bezier(x1, y1, cx, cy, cx, cy, x2, y2);
}
Sketch #103:

Sketch #103 state: Loading...

What could you use this to represent?

1.4 Relationships

In this example, we construct three arrays:

  • name - to hold the name of a person
  • from - to hold the index of the to-person
  • to - to hold the index of the from-person

The idea is we are representing information about a relationship. For example, it might represent a tweet (from one person, sent to (or mentioning) another).

In [1]:
int rotateAroundX(int x1, int y1, int length, int angle) {
    return x1 + length * cos(angle);
}

int rotateAroundY(int x1, int y1, int length, int angle) {
    return y1 - length * sin(angle);
}

void setup() {
    size(300, 300);
    background();
}

void draw() {
    // Center:
    cx = width/2;
    cy = height/2;
    
    int[] from = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] to =   new int[] {1, 3, 3, 6, 3, 1, 2, 1, 2, 3 };
    string[] name = new string[] {"Helen", 
                                  "Mary",
                                  "Teyvonia",
                                  "Glenda",
                                  "Bertrude",
                                  "Elvie",
                                  "Sarah",
                                  "Mary",
                                  "Trisha",
                                  "Karen" };

    // First, let's draw the names around the circle:
    for (int i = 0; i < 10; i++) {
        p1 = from[i] * 36 * PI/180;
        p2 = to[i] * 36 * PI/180;
    
        x1 = rotateAroundX(cx, cy, width/2, p1);
        y1 = rotateAroundY(cx, cy, width/2, p1);
        fill(0);
        text(name[i], x1, y1);
    }
    // Next, we connect up the people:
    for (int i = 0; i < 10; i++) {
        p1 = from[i] * 36 * PI/180;
        p2 = to[i] * 36 * PI/180;

        // Find where p1 would be:
        x1 = rotateAroundX(cx, cy, width/2, p1);
        y1 = rotateAroundY(cx, cy, width/2, p1);

        // Find where p2 would be:
        x2 = rotateAroundX(cx, cy, width/2, p2);
        y2 = rotateAroundY(cx, cy, width/2, p2);

        
        // Connect p1 to p2:
        noFill();
        strokeWeight(random(5));
        stroke(random(255), random(255), random(255));
        bezier(x1, y1, cx, cy, cx, cy, x2, y2);
    }
    noLoop();
}
Sketch #1:

Sketch #1 state: Loading...

In this example, I randomly drew the connecting line in a random color and weight. That information could also be stored in an array.

1.5 Heatmaps

The idea of a heatmap is to show intensity as a visual representation, often color. Here we see the number of discussions between two people represented as a heatmap. Why is the diagonal white?