Code in Visuals

Ruby, Rails, Data Visualization

Olympics Medals vs GDP Visualization With Paper.js

Having done few projects with Processing.js, I was looking for next opportunity to use Paper.js for a decent sized project and up came the olympics visualization challenge courtesy of folks from Visualizing.org The goal of this visualization is to map how in the last 6 summer olympics countries with total medals compare in terms of GDP per capita. Let’s briefly go over the process I used for building this visualization.

Data collection

I decided to just copy paste total medals tally from London2012.com website for each of last 6 summer olympics into a csv and added extra column with corresponding GDP per capita for the country(from Worldbank.org)

App

I am using a sinatra app to read in the csv and return it in json format, so the index endpoint just renders the html whereas /countries endpoint returns the json data for that specific event year.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Index page
get '/' do
  File.read(File.join('public', 'paper.html'))
end

#Parse csv data
get '/countries' do
  content_type :json
  countries = []
  year = params[:year] || 2004
  CSV.foreach("#{year}.csv") do |line|
    country = {}
    country['name'] = line[0]
    country['gold'] = line[1].to_i
    country['silver'] = line[2].to_i
    country['bronze'] = line[3].to_i
    country['gdp'] = line[4].to_i
    countries << country
  end
  countries.to_json
end

Building the visualization

There’s 2 ways to write paperjs code: using their own paperscript library, which is easier to get started with as lot of defaults like views, project and event handler hooks are provided. But if you have a more complex visualization with DOM elements that you want interact with using javascript, then it’s better to build it using straignt up javascript and define and hook the project, view and handlers via javascript directly. As this visualization is fairly simple, I chose to stick with paperscript.

    Here’s the basic pseudocode for the paperscript code:
  1. Load the olympics data in json format for each year, store them as OlympicEvent object in array
  2. Each of those countries are stored as Node object with necessary attributes like medal counts, gdp per capita, country name and so forth.
  3. In initial state, draw overview of each olympic event(total medals, participating countries, medal winning countries..)
  4. Draw the interactive state, where each node(country) for each event is represented by a circle, size of which represents total medals, and y-position in the 2D space represents the GDP per capita in that year. This is done in onMouseClick() event handler, so first time you click on the canvas, initial state gets replaced by interactive state.
  5. To achieve some level of interaction, implement onMouseMove() handler where we check if cursor is over any node(country) and illuminate that country(while seting opacity to half for rest of the nodes)
  6. Finally clicking inside a node shows a history of that particular country, displaying the difference in GDP per capita and medals count in succesive events. This is obviously in the onMouseClick() event where initialState is false already.

Since x-axis maps to year of the olympic event, I set aside a width of about 170px(just through trial and error) for each year to allow nodes to space out and not be colliding with each other. Here’s the rather naive/simple collision detection I used, where node is moved n units to the right if colliding with another node:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    //Checks if given point and radius will collide with any of already drawn circles
    //returns tolerance(distance) if collision, or false if no collision
    detectCollision = function(countries, point, size) {
      var closeCircle;
      //utilizes Point#isClose() method from Paperjs
      closeCircle = _.find(countries, function(c){ return point.isClose(c.circle.position, size + c.radius); });
      if(closeCircle)
        return closeCircle.radius/2;
      else
        return false;
    }

    //Here's where the function is used
    //Moves nodes to its proper position based in GDP value
    explodeNodes = function() {
      var colliding;
      // var allNodes = _.map(olympicEvents, function(event){ return event.nodes; });
      // allNodes = _.flatten(allNodes);
      _.each(olympicEvents, function(e) {
        computeInfo(e.nodes, e.year);
        var drawnCountries = [];
        _.each(e.nodes, function(n) {
            yPoint = 650 - (n.gdp / 100);
            if (yPoint < 50) {
              yPoint = 50;
            }
            n.circle.position.x = e.xPosition;
            n.circle.position.y = yPoint;
            //detect if its colliding with another circle
            var colliding = false;
            var counter = 0;
            do {
              colliding = detectCollision(drawnCountries, n.circle.position, n.radius);
              if(colliding) {
                n.circle.position.x += 2*colliding;
              }
              counter++;
            } while (colliding || (counter <= 4)) //If if its still colliding after 4 tries, tough luck
            n.infoText.position = n.circle.position + [n.radius, 0];
            n.circle.visible = true;
            drawnCountries.push(n);
        });
      });
    }

I did use underscore.js rather heavily, mainly for routines like map, each and find. The end code is still quite verbose, mainly due to repetitive tasks like drawing labels in different places and so forth, which can be DRYed up a bit. Overall I liked working with Paperjs and getting used to its API, its somewhat similar to Processing API which is what I am most familiar with. I have put the whole source code, including the sinatra app up in github: https://github.com/nandayadav/olympics_visualization And here’s the endproduct: Olympics GDP vs Medals Visualization