[Title to Be Determined] – Daniil and María

For this project, we chose to expand on the idea of the butterfly animation that María worked on last week. With it, we decided to create a visualization for the world’s birth and death rates.

The Data

Initially, we thought it would be fascinating to use real-time data and found a website that simulates births and deaths around the globe by the second. However, without an API, we didn’t know how to extract the data.

Thus, we chose to use information from a database and came across The World Factbook (TWF), published by the CIA. This publication, available online and already updated for 2017, includes lists of the world’s countries by descending birth and death rates (per 1,000 persons).

Python code to organize TWF’s data and generate a text file (which can be used by Processing):

def convertTxt(txtFile):
 txtData = open(txtFile,'r')
 txtText = txtData.read()
 txtArray = []
 txtArray2 = txtText.splitlines()
 for i in range(0, len(txtArray2)):
 tempArray = txtArray2[i].split(" ")
 ranking = tempArray.pop(0)
 for j in range(0, len(tempArray)):
 tempArray[j].strip()
 if tempArray[j] != "" and tempArray[j] != " ":
 stringArray = tempArray[j].split(", ")
 firstWordArray = stringArray[0].split()
 firstWord = " ".join(firstWordArray)
 first = stringArray.pop(0)
 stringArray.insert(0, firstWord)
 if len(stringArray) == 2:
 stringArray.reverse()
 string = " ".join(stringArray)
 else:
 string = ", ".join(stringArray)
 txtArray.append(string)
 return txtArray

def combineTxt(txtBirth, txtDeath):
 birthArray = convertTxt(txtBirth)
 deathArray = convertTxt(txtDeath)
 completeArray = []
 for i in range(0, len(birthArray)):
 if i%2 == 0:
 for j in range(0, len(deathArray)):
 if deathArray[j] == birthArray[i]:
 completeArray.append(birthArray[i])
 completeArray.append(birthArray[i+1])
 completeArray.append(deathArray[j+1])
 return completeArray

def writeTxt(txtBirth, txtDeath, txtNew):
 array = combineTxt(txtBirth, txtDeath)
 string = "_".join(array)
 newFile = open(txtNew,"w")
 newFile.write(string) 
 newFile.close()

writeTxt("rawdata_2054.txt", "rawdata_2066.txt", "birthDeath.txt")

Text file that the previous code generated (such that it shows country_birthRate_deathRate):

Niger_44.80_12.10_Mali_44.40_12.60_Uganda_43.40_10.40_Zambia_41.80_12.40_Burundi_41.70_9.00_Burkina Faso_41.60_11.50_Malawi_41.30_8.10_Somalia_40.00_13.30_Angola_38.60_11.30_Mozambique_38.30_11.90_Afghanistan_38.30_13.70_Nigeria_37.30_12.70_Ethiopia_36.90_7.90_Sierra Leone_36.70_10.60_South Sudan_36.20_8.00_Chad_36.10_14.00_Tanzania_36.00_7.80_Cameroon_35.80_9.80_Benin_35.50_8.00_Guinea_35.40_9.20_Republic of the Congo_35.10_9.70_Central African Republic_34.70_13.50_Gabon_34.30_13.10_Democratic Republic of the Congo_34.20_9.90_Senegal_34.00_8.30_Liberia_33.90_9.50_Timor-Leste_33.80_6.00_Togo_33.70_7.10_Sao Tome and Principe_33.30_7.00_Rwanda_33.30_8.80_Guinea-Bissau_32.90_14.10_Equatorial Guinea_32.80_8.00_Gaza Strip_32.30_3.20_Madagascar_32.10_6.70_Zimbabwe_31.90_9.90_Iraq_30.90_3.80_Mauritania_30.90_8.10_Ghana_30.80_7.10_Egypt_30.30_4.70_Eritrea_30.10_7.30_The Gambia_30.10_7.10_Western Sahara_29.80_8.20_Yemen_29.20_6.10_Sudan_28.50_7.50_Cote d'Ivoire_28.20_9.50_Namibia_27.90_8.10_Comoros_26.90_7.40_West Bank_26.70_3.50_Jordan_25.50_3.80_Solomon Islands_25.30_3.80_Kenya_25.10_6.80_Lesotho_25.10_14.90_Marshall Islands_25.00_4.20_Vanuatu_24.50_4.10_Guatemala_24.50_4.70_Nauru_24.40_5.90_Oman_24.30_3.30_Swaziland_24.30_13.40_Belize_24.30_6.00_Papua New Guinea_24.00_6.50_Philippines_24.00_6.10_Laos_23.90_7.50_Tajikistan_23.80_6.10_Tuvalu_23.80_8.60_Djibouti_23.60_7.60_Cambodia_23.40_7.60_Haiti_23.30_7.70_Algeria_23.00_4.30_American Samoa_22.90_4.80_Honduras_22.80_5.20_Tonga_22.60_4.90_Kyrgyzstan_22.60_6.60_Bolivia_22.40_6.50_Pakistan_22.30_6.40_Syria_21.70_4.00_Kiribati_21.30_7.10_Botswana_20.70_13.30_Samoa_20.60_5.30_South Africa_20.50_9.60_Federated States of Micronesia_20.30_4.20_Cabo Verde_20.20_6.10_Nepal_19.90_5.70_Kuwait_19.60_2.20_Mongolia_19.60_6.30_Malaysia_19.40_5.10_India_19.30_7.30_Turkmenistan_19.30_6.10_Venezuela_19.20_5.20_Bangladesh_19.00_5.30_Fiji_19.00_6.10_Kazakhstan_18.70_8.20_Dominican Republic_18.60_4.60_Mexico_18.50_5.30_Saudi Arabia_18.40_3.30_Israel_18.30_5.20_Burma_18.20_7.90_Ecuador_18.20_5.10_Panama_18.10_4.90_Jamaica_18.00_6.70_Morocco_18.00_4.80_Peru_18.00_6.00_Nicaragua_17.90_5.10_Iran_17.80_5.90_Libya_17.80_3.60_Bhutan_17.50_6.60_Brunei_17.20_3.60_Northern Mariana Islands_17.20_3.80_Argentina_17.00_7.50_Uzbekistan_16.90_5.30_Guam_16.70_5.20_Paraguay_16.50_4.70_Tunisia_16.40_6.00_Indonesia_16.40_6.40_Colombia_16.30_5.40_El Salvador_16.30_5.70_Azerbaijan_16.20_7.10_Turkey_16.00_5.90_Suriname_16.00_6.10_Maldives_16.00_3.90_Antigua and Barbuda_15.80_5.70_Grenada_15.80_8.10_Costa Rica_15.70_4.60_Turks and Caicos Islands_15.70_3.20_Vietnam_15.70_5.90_Guyana_15.50_7.40_Sri Lanka_15.50_6.20_The Bahamas_15.40_7.10_United Arab Emirates_15.30_2.00_New Caledonia_15.20_5.60_Dominica_15.20_7.90_French Polynesia_15.00_5.10_North Korea_14.60_9.30_Ireland_14.50_6.50_Lebanon_14.40_4.90_Greenland_14.40_8.60_Brazil_14.30_6.60_Cook Islands_14.10_8.30_Gibraltar_14.10_8.40_Faroe Islands_14.00_8.70_Seychelles_13.90_6.90_Curacao_13.80_8.30_Iceland_13.80_6.30_Chile_13.70_6.10_Bahrain_13.50_2.70_Saint Lucia_13.50_7.60_Saint Vincent and the Grenadines_13.40_7.30_Saint Kitts and Nevis_13.30_7.10_New Zealand_13.30_7.40_Wallis and Futuna_13.30_5.20_Armenia_13.30_9.40_Mauritius_13.10_7.00_Trinidad and Tobago_13.10_8.70_Albania_13.10_6.70_Sint Maarten_13.10_5.00_Uruguay_13.00_9.40_Anguilla_12.70_4.60_Aruba_12.50_8.30_Georgia_12.50_10.90_United States_12.50_8.20_China_12.40_7.70_France_12.30_9.30_Norway_12.20_8.10_Australia_12.10_7.20_Jersey_12.10_7.70_United Kingdom_12.10_9.40_Cayman Islands_12.10_5.70_Sweden_12.00_9.40_Barbados_11.80_8.50_Moldova_11.80_12.60_Macedonia_11.50_9.10_Belgium_11.40_9.70_Luxembourg_11.40_7.30_Cyprus_11.40_6.70_Russia_11.30_13.60_Bermuda_11.30_8.40_Palau_11.20_8.00_Thailand_11.10_7.90_British Virgin Islands_11.00_5.10_Isle of Man_11.00_10.10_Montserrat_11.00_6.30_Netherlands_10.90_8.80_Falkland Islands (Islas Malvinas)_10.90_4.90_Cuba_10.80_8.60_Puerto Rico_10.80_8.80_Finland_10.70_9.90_Ukraine_10.50_14.40_Belarus_10.50_13.30_Switzerland_10.50_8.20_Liechtenstein_10.40_7.30_Denmark_10.40_10.30_Estonia_10.30_12.50_Canada_10.30_8.50_Montenegro_10.20_9.60_Virgin Islands_10.20_8.90_Malta_10.10_9.20_Lithuania_10.00_14.50_Latvia_9.90_14.40_Slovakia_9.80_9.80_Guernsey_9.80_8.90_Saint Helena, Ascension, and Tristan da Cunha_9.70_7.70_Qatar_9.70_1.50_Poland_9.60_10.30_Czechia_9.50_10.40_Austria_9.50_9.50_Spain_9.40_9.10_Hong Kong_9.10_7.20_Hungary_9.10_12.80_Portugal_9.10_11.10_Romania_9.00_11.90_Croatia_9.00_12.10_Serbia_9.00_13.60_Bulgaria_8.80_14.50_Bosnia and Herzegovina_8.80_9.90_Macau_8.80_4.40_Italy_8.70_10.30_San Marino_8.60_8.60_Germany_8.50_11.60_Greece_8.50_11.20_Taiwan_8.40_7.30_South Korea_8.40_5.80_Singapore_8.40_3.50_Slovenia_8.30_11.50_Andorra_7.80_7.10_Japan_7.80_9.60_Saint Pierre and Miquelon_7.20_9.80_Monaco_6.60_9.60

The Visualization

Having obtained the data we needed, we adapted the butterfly code from María’s previous assignment such that it responds to each country’s birth and death rates.

We wanted to allow the user to select a country from a drop-down menu. In return, the sketch generates as many butterflies as the birth rate minus the deat rate in the chosen country (thus, a representation of its population´s growth rate) during 2016. Each butterfly flutters for some time and then “dies” (begins to fall and eventually disappears). If the total number of butterflies that are “alive” at any time is less than the country’s growth rate, then more butterflies – as many as needed to maintain the population – are generated (“born”) to repopulate the space. Once the country is changed on the menu,  the butterfly births and deaths adjust to the new population.

Processing sketch (this is actually a mash-up of three sketches – the butterfly class, the drop-down menu and the text file “reader”, and the sketch that runs the code):

import controlP5.*;

PFont font;

String data[];

ControlP5 cp5;
ControlFont cf1;
DropdownList d1;
int country;

ArrayList<Butterfly> bfs = new ArrayList <Butterfly>();
PImage bf1, bf2, bf3, bf4, bf5, bf6, bf7, bf8, bf9;
int widthLimit;
int heightLimit;
int counter;
int index;
int rate;
boolean start;

void setup(){
 size(900, 900);
 font = createFont("BebasNeue Regular.ttf", 30);
 loadImages();
 start = false;
 
 String[] txtFile = loadStrings("birthDeath.txt");
 data = split(txtFile[0], '_');
 
 cp5 = new ControlP5(this);
 d1 = cp5.addDropdownList("Country")
 .setPosition(100, 110)
 .setSize(200, 200); 
 customize(d1);
 
 counter = 0;
 index = -1;
 widthLimit = 250;
 heightLimit = 250;
 for (int i = 0; i < rate; i++){
 Butterfly b = new Butterfly();
 bfs.add(b);
 };
}

int getRate(int country){
 int indexCountry = country*3; //countries from 0 to 225
 int rate = Math.round(float(data[indexCountry + 1])) - Math.round(float(data[indexCountry + 2]));
 if(rate < 0){
 rate = 0;
 };
 return rate;
}

void customize(DropdownList ddl) {
 ddl.setBackgroundColor(color(190));
 ddl.setItemHeight(20);
 ddl.setBarHeight(15);
 ddl.getCaptionLabel().set("Countries");
 for (int i = 0;i < data.length/3; i++) {
 ddl.addItem(data[i*3], i);
 }
 ddl.setColorBackground(color(60));
 ddl.setColorActive(color(255, 128));
}

void controlEvent(ControlEvent theEvent) {
 if (theEvent.isController()) {
 index = int(theEvent.getController().getValue()); 
 rate = getRate(index);
 index = index*3;
 println(rate);
 }
}

void loadImages(){
 bf1 = loadImage("bf1.png");
 bf2 = loadImage("bf2.png");
 bf3 = loadImage("bf3.png");
 bf4 = loadImage("bf4.png");
 bf5 = loadImage("bf5.png");
 bf6 = loadImage("bf6.png");
 bf7 = loadImage("bf7.png");
 bf8 = loadImage("bf8.png");
 bf9 = loadImage("bf9.png");
}

class Butterfly{
 int X;
 int Y;
 int FRAME;
 int DIRECTION;
 int HORIZONTAL;
 int VERTICAL;
 int SPEED;
 int CENTERX;
 int CENTERY;
 int centerCounter;
 int centerCounterLimit;
 float alpha;
 
 Butterfly(){
 FRAME = int(random(1, 10));
 SPEED = int(random(10, 20));
 DIRECTION = int(random(0, 2));
 X = DIRECTION*width;
 Y = int(random(heightLimit, height-heightLimit));
 CENTERX = int(random(widthLimit, width-widthLimit));
 CENTERY = int(random(heightLimit, height-heightLimit));
 HORIZONTAL = (CENTERX - X)/SPEED;
 VERTICAL = (CENTERY - Y)/SPEED;
 alpha = 255;
 centerCounter = 0;
 centerCounterLimit = int(random(100, 300));
 }
 
 float fly(){
 tint(255, alpha);
 pushMatrix();
 scale(0.75);
 scale((float)Math.pow(-1, DIRECTION), 1);
 switch(FRAME){
 case 1: image(bf1, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf1.width)), Y);
 break;
 case 2: image(bf2, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf2.width)), Y);
 break;
 case 3: image(bf3, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf3.width)), Y);
 break;
 case 4: image(bf4, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf4.width)), Y);
 break;
 case 5: image(bf5, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf5.width)), Y);
 break;
 case 6: image(bf6, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf6.width)), Y);
 break;
 case 7: image(bf7, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf7.width)), Y);
 break;
 case 8: image(bf8, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf8.width)), Y);
 break;
 case 9: image(bf9, ((float)Math.pow(-1, DIRECTION))*(X + (DIRECTION*bf9.width)), Y);
 break;
 };
 popMatrix();
 if((DIRECTION == 0 && X >= CENTERX) || (DIRECTION == 1 && X <= CENTERX)){
 HORIZONTAL = 0;
 VERTICAL = 0;
 centerCounter++;
 };
 if(centerCounter > centerCounterLimit){
 HORIZONTAL = 0;
 VERTICAL = (height - CENTERY)/SPEED;
 alpha -= 1;
 };
 if(counter%7 == 0){
 if(FRAME == 9){
 FRAME = 1;
 }
 else{
 FRAME++;
 };
 };
 if(counter%SPEED == 0){
 X += HORIZONTAL;
 Y += VERTICAL;
 };
 return alpha;
 }
 }

void draw(){
 background(255);
 textFont(font);
 fill(0, 120);
 if(start == false){
 d1.hide();
 textAlign(CENTER);
 text("This is a data visualization of the world's 2016 birth and death rates for each country.", width/2, height/30*14);
 text("The data was taken from the CIA's 'The World Factbook'.", width/2, height/30*15);
 fill(0);
 text("Click to continue.", width/2, height/30*18);
 if(mousePressed){
 start = true;
 }
 }
 else{
 d1.show();
 fill(0, 120);
 textAlign(LEFT);
 if(index == -1){
 text("Choose a country ", 100, 100);
 }
 else{
 text("Country: "+data[index], 100, 100);
 textAlign(RIGHT);
 text("Birth rate: "+data[index+1], width - 100, height - 133);
 text("Death rate: "+data[index+2], width - 100, height - 100);
 textAlign(LEFT);
 };
 
 counter++;
 if(bfs.size() < rate){
 for(int i = 0; i < rate - bfs.size(); i++){
 Butterfly a = new Butterfly();
 bfs.add(a);
 };
 };
 for (int i = 0; i < bfs.size(); i++){
 Butterfly b = bfs.get(i);
 float opacity = b.fly();
 if(opacity < 0){
 bfs.remove(b);
 };
 }; 
 };
}

There was nothing particularly challenging about this project, however, it still took us time to figure out how to connect the text with the butterfly reproduction (generation) rate AND the menu at the same time.

Video of running sketch (a few countries were chosen to show the differences in the amount of butterflies):

1 thought on “[Title to Be Determined] – Daniil and María”

Comments are closed.