Final Project

How to play: In order to win the game, the player must move the polar bear to its home (the iceberg) while avoiding the obstacles. The polar bear can be moved in four directions using the arrow keys. When the polar bear comes into contact with an obstacle, the number of lives will decrease and the background will slightly turn blue (representing ice melting). There’s a total of 60 lives to start with, and there’s also a timer that counts down from a minute. If the number of lives or time runs out before the polar bear reaches home, then the game is over.

final project
// variables for images
var bear; 
var home; 
var factory;
var fire;
var bottle;

//variable for bear & obstacles
var bearx = 90;
var beary = 550;
var fires = [];
var factories = [];
var bottles = [];

// variables for background color
var colorR = 255;
var colorG = 253;
var colorB = 240;
var stepR;
var stepG;
var stepB;

// variable for timer
var timer = 60;

// variable for lives
var lives = 60; 

// variables for winning
var victory = false;
var angle = 0;

function preload() {
    bear = loadImage("https://i.imgur.com/P6jBMLN.png");
    home = loadImage("https://i.imgur.com/GZBJAQt.png");
    factory = loadImage("https://i.imgur.com/8uJDtMZ.png");
    fire = loadImage("https://i.imgur.com/P8Og7bN.png");
    bottle = loadImage("https://i.imgur.com/Q3Qch7G.png");
}

function setup() {
    createCanvas(500, 600);
    imageMode(CENTER);
    home.resize(170, 0);
    bear.resize(100, 0);
    fire.resize(60, 0);
    factory.resize(60, 0);
    bottle.resize(60, 0);
    // get dimensions of images
    print("fire width" + " " + fire.width);
    print("fire height" + " " + fire.height); 
    print("bear width" + " " + bear.width);
    print("bear height" + " " + bear.height);
    print("factory width" + " " + factory.width);
    print("factory height" + " " + factory.height); 
    print("bottle width" + " " + bottle.width);
    print("bottle height" + " " + bottle.height); 
    
    // make obstacles 
    for (var i = 0; i < 3; i ++) {
        var startposition = random(0, width/5)*5;
        var startspeed = random(0, 3);
        fires.push(makeFire(startposition, 0, startspeed));
    }

    for (var i = 0; i < 3; i ++) {
        var startposition = random(0, width/5)*5;
        var startspeed = random(0, 3);
        factories.push(makeFactory(startposition, 0, startspeed));   
    }

    for (var i = 0; i < 3; i ++) {
        var startposition = random(0, width/5)*5;
        var startspeed = random(0, 3);
        bottles.push(makeBottle(startposition, 0, startspeed));   
    }
}

function draw() {
    background(colorR, colorG, colorB); // initial cream color

    // game over melted ice will be blue => (177, 224, 230)
    stepR = (255 - 177) / 60;
    stepG = (253 - 224) / 60;
    stepB = (240 - 230) / 60;

    // timer 
    textSize(20);
    textAlign(CENTER);
    text("Timer: " + timer, 430, 580);
    text("Lives: " + lives, 330, 580);
        
    if (frameCount % 60 == 0 & timer > 0) {
        timer -= 1; // counts down 
    } 
    if (timer == 0) { // if times run out, game ends
        gameover();
    }

    image(home, width/2, 70); 
    image(bear, bearx, beary);

    // controlling bear's position through arrow keys 
    if (keyIsPressed & victory == false) {
        if (keyCode === LEFT_ARROW && bearx >= 50) {
            bearx -= 2;
        }
        if (keyCode === RIGHT_ARROW & bearx <= width - 50) {
            bearx += 2;
        }
        if (keyCode === UP_ARROW & beary >= 28.5) {
            beary -= 2;
        }
        if (keyCode === DOWN_ARROW & beary <= height - 28.5) {
            beary += 2;
        }
    }
    
    if (bearx >= width/2 - 20 & bearx <= width/2 + 20 && beary >= 50 && beary <= 90) { // if bear reaches home
        win();
    }

    obstacles(fires, 44);
    obstacles(factories, 35);
    obstacles(bottles, 26.5);
}

// function to show & update obstacles + interaction 
function obstacles(array, size) {
    for (var i = 0; i < array.length; i++) {
        array[i].show();
        array[i].update();
        if ((bearx >= array[i].x - 80 & bearx <= array[i].x + 80) && (beary >= array[i].y - size && beary <= array[i].y + size)) {
            if (colorR >= 177 || colorG >= 224 || colorB >= 230) {
                colorR -= stepR;
                colorG -= stepG;
                colorB -= stepB;
                lives -= 1;
            } else {
                gameover(); break;
            }
        } 
    }
}


// fire object
function makeFire(fx, fy, fspeed) {
    var fireobject = {x: fx, y: fy, speed: fspeed, show: fireShow, update: fireUpdate};
    return fireobject;
}

function fireShow() {
    image(fire, this.x, this.y);
}

function fireUpdate() {
    this.y += this.speed;
    if (this.y - 44 >= height) {
        this.y = -44;
    }
}

// factory object 
function makeFactory(facx, facy, facspeed) {
    var factoryobject = {x: facx, y: facy, speed: facspeed, show: factoryShow, update: factoryUpdate};
    return factoryobject;
}

function factoryShow() {
    image(factory, this.x, this.y);
}

function factoryUpdate() {
    this.y += this.speed;
    if (this.y - 35 >= height) {
        this.y = -35;
    }
}

// bottle object
function makeBottle(bx, by, bspeed) {
    var bottleobject = {x: bx, y: by, speed: bspeed, show: bottleShow, update: bottleUpdate};
    return bottleobject;
}

function bottleShow() {
    image(bottle, this.x, this.y);
}

function bottleUpdate() {
    this.y += this.speed;
    if (this.y - 26.5 >= height) {
        this.y = -26.5;
    }
}

// game over function
function gameover() {
    background(212, 36, 17); // red background if game over
    textSize(45);
    text("GAME OVER!", width/2, height/2);
    //stopping everything else
    fires = [];
    factories = [];
    bottles = [];
    victory = true;
    noLoop();
}

// you win function 
function win() {
    background(149, 217, 143); // green background if win
    textSize(45);
    text("YOU WIN!", width/2, height/2);
    // rotating the bear if win
    push();
    translate(width/2, 200);
    rotate(radians(angle));
    imageMode(CENTER);
    image(bear, 0, 0);
    pop();
    angle += 5;
    //stopping everything else
    fires = [];
    factories = [];
    bottles = [];
    victory = true;
}

Since the theme of our final project was climate change, I wanted to incorporate different causes of climate change through the form of an interactive game where the polar bear symbolizes the victims of global warming. The polar bear has to avoid obstacles including pollution from a factory, plastic waste in the ocean, and forest fires, in order to safely reach home. It’s difficult to win the game, which symbolizes how we’ve neglected this issue for so long that it’s now quite impossible to reverse the impact.

If I had more time to work on this project, I might add positive objects such as renewable energy sources that the bear can touch and will increase the number of lives or the remaining time, representing potential actions to reverse or reduce climate change.

Project 11: Generative Landscape

generative landscape
var wood; // wooden table
var belt = []; // conveyer belt
var sushi = [];
var plate = [];
var sushiTypes = [];
var terrain = [];
var noiseParam = 0;
var noiseStep = 0.01;
var sky;
var clouds = [];

var sushiLinks = [
    "https://i.imgur.com/fm2adto.png",
    "https://i.imgur.com/Q2z9Ki8.png",
    "https://i.imgur.com/tUeehNx.png",
    "https://i.imgur.com/H2lTaNf.png",
    "https://i.imgur.com/t7TbPiI.png",
    "https://i.imgur.com/dNH5jvD.png",
    "https://i.imgur.com/YJ7h1Hl.png",
    "https://i.imgur.com/Hu1TVEI.png",
    "https://i.imgur.com/ZyAzhTq.png",
    "https://i.imgur.com/X8sFOwk.png",
    "https://i.imgur.com/t3pzPkC.png",
    ]

function preload() {
    wood = loadImage("https://i.imgur.com/5cpfZzh.png");
    belt = loadImage("https://i.imgur.com/s1SR1ru.png");
    sky = loadImage("https://i.imgur.com/qvwpqNr.png");
    
    for (var i = 0; i < 11; i ++) {
        var sushiImage;
        sushiImage = loadImage(sushiLinks[i]);
        sushiTypes.push(sushiImage);
    }
}

function setup() {
    createCanvas(450, 400);
    imageMode(CENTER);
    wood.resize(450, 0);
    belt.resize(450, 0);
    sky.resize(450, 0);

    // make collection of sushi
    for (var i = 0; i < 5; i++) {
        var sx = random(width);
        sushi[i] = makeSushi(sx);
    }

    // make background terrain
    for (var i = 0; i <= width; i ++) {
        var n = noise(noiseParam);
        var value = map(n, 0, 1, 0, height);
        terrain.push(value);
        noiseParam += noiseStep;
    }

    // make clouds
    for (var i = 0; i < 5; i ++) {
        var cloudx = random(width);
        var cloudy = random(height);
        clouds[i] = makeClouds(cloudx, cloudy);
    }

}


function draw() {
    image(sky, width/2, height/2);
    drawTerrain();
    image(wood, width/2, 250);
    image(belt, width/2, 250);

    updateAndDisplaySushi();
    removeSushiThatHaveSlippedOutOfView();
    addNewSushiWithSomeRandomProbability(); 

    updateAndDisplayClouds();
    removeCloudsThatHaveSlippedOutOfView();
    addNewCloudsWithSomeRandomProbability(); 
}

// all cloud related functions

function updateAndDisplayClouds() {
    for (var i = 0; i < clouds.length; i ++) {
        clouds[i].move();
        clouds[i].display();
    }
}

function removeCloudsThatHaveSlippedOutOfView() {
    var cloudsToKeep = [];
    for (var i = 0; i < clouds.length; i++){
        if (clouds[i].x + 60 > 0) {
            cloudsToKeep.push(clouds[i]);
        }
    }
    clouds = cloudsToKeep;
}

function addNewCloudsWithSomeRandomProbability() {
    var newCloudLikelihood = 0.01; 
    if (random(0,1) < newCloudLikelihood) {
        var newcloudX = random(width);
        var newcloudY = random(200);
        clouds.push(makeClouds(newcloudX, newcloudY));
    }
}

function makeClouds(CLOUDX, CLOUDY) {
    var c = {x: CLOUDX,
             y: CLOUDY,
             speed: -2,
             move: cloudMove,
             display: cloudDisplay}
    return c;
}

function cloudMove() {
    this.x += this.speed;
}

function cloudDisplay() {
    fill(255, 254, 246); // cream color
    noStroke();
    ellipse(this.x, this.y - 5, 60, 50);
    ellipse(this.x - 20, this.y + 10, 60, 50);
    ellipse(this.x + 15, this.y - 5, 70, 50);
    ellipse(this.x + 5, this.y + 20, 60, 50);
    ellipse(this.x + 30, this.y + 10, 80, 50);
}

// all terrain functions

function drawTerrain() {
    fill(73, 133, 115); 
    noStroke();
    beginShape();
    vertex(0, height);
    for (i = 0; i <= width/5 + 1; i += 1) {
        vertex(i*5, terrain[i]);
        vertex((i+1)*5, terrain[i+1]);
    }
    vertex(width, height);
    endShape();

    // make terrain continuous
    terrain.shift();
    var n = noise(noiseParam);
    var value = map(n, 0, 1, 0, height);
    terrain.push(value);
    noiseParam += noiseStep;

}

// all sushi related functions

function updateAndDisplaySushi() {
    for (var i = 0; i < sushi.length; i ++) {
        sushi[i].move();
        sushi[i].display();
    }
}

function removeSushiThatHaveSlippedOutOfView() {
    var sushiToKeep = [];
    for (var i = 0; i < sushi.length; i++){
        if (sushi[i].x + sushi[i].breadth > 0) {
            sushiToKeep.push(sushi[i]);
        }
    }
    sushi = sushiToKeep;
}

function addNewSushiWithSomeRandomProbability() {
    var newSushiLikelihood = 0.007; 
    if (random(0,1) < newSushiLikelihood) {
        sushi.push(makeSushi(450));
    }
}


function makeSushi(birthLocationX) {
    var s = {x: birthLocationX,
                breadth: 50,
                speed: -1,
                sushiType: random(sushiTypes),
                move: sushiMove,
                display: sushiDisplay}
    return s;
}


function sushiMove() {
    this.x += this.speed;
}

function sushiDisplay() {
    image(this.sushiType, this.x, 310, this.breadth+10, this.breadth);
}

For this project, I decided to create a sushi conveyer belt that’s located outdoors. I started off by drawing all the different types of sushi that’ll be randomized in the landscape. I also drew the conveyer belt and the wooden table. Then I implemented the background which is the sky with a randomized terrain/hill and clouds. I struggled a bit with creating an array of objects using images, so I couldn’t quite figure how to make the sushi not overlap each other. I also tried to create plates under the sushi, but I ended up removing them because they weren’t quite matching up with the sushi.

This is all the variations of the sushi:

Looking Outwards 11: Societal Impacts of Digital Art

In Sebastian Smee’s article, “Beeple’s digital ‘artwork’ sold for more than any painting by Titian or Raphael. But as art, it’s a great big zero.“, discusses the recent uprising of NFTs. An NFT is a digital file that allows people to bid and claim ownership over it. The article criticizes how the value of NFTs is not from the work itself but rather from market manipulation. For instance, graphic designer Beeple recently sold his work “Everydays: The First 5000 Days” for an insanely high price of $69.3 million when the piece was actually just a collage of colorful images. This goes to show the extent to which people are willing to go for the hype and how drastically the market for artwork has changed.

“Everydays: The First 5000 Days” is a digital file by the artist Beeple, who has been posting images every day since 2007. (Christie’s Images Limited 2020)

Project 10: Sonic Story

CLICK ON CANVAS TO START!

 
sonic story wordpress
var pan1; // pan
var pan2; // pan with flame
var egg1; // whole egg
var egg2; // cracked egg 
var egg3; // raw egg
var egg4; // cooked egg 
var bacon1; // raw bacon
var bacon2; // cooked bacon
var plate; 

var pansound;
var stove;
var fridge;
var crackegg;
var fryegg;
var bacon;
var tada;

var counter = false;
var count = 0;

function preload() {
    // images
    pan1 = loadImage("https://i.imgur.com/k8FOFKN.png");
    pan2 = loadImage("https://i.imgur.com/A5D1YD7.png");
    egg1 = loadImage("https://i.imgur.com/bTLCEPN.png");
    egg2 = loadImage("https://i.imgur.com/W6aWzCk.png");
    egg3 = loadImage("https://i.imgur.com/ptjZjee.png");
    egg4 = loadImage("https://i.imgur.com/f0iTWaq.png");
    bacon1 = loadImage("https://i.imgur.com/JllJSKp.png");
    bacon2 = loadImage("https://i.imgur.com/KaqJr4l.png");
    plate = loadImage("https://i.imgur.com/OKZWSey.png");

    // sounds 
    pansound = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/pansound.wav");
    stove = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/stove.wav");
    fridge = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/fridge.wav");
    crackegg = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/crackegg.wav");
    fryegg = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/fryegg.wav");
    bacon = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/bacon.wav");
    tada = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/tada.wav");
}


function setup() {
    createCanvas(400, 400);
    frameRate(1);
    pan1.resize(200, 0);
    pan2.resize(200, 0);
    egg1.resize(40, 0);
    egg2.resize(80, 0);
    egg3.resize(80, 0);
    egg4.resize(80, 0);
    bacon1.resize(40, 0);
    bacon2.resize(90, 0);
    plate.resize(200, 0);
    useSound();
}

function soundSetup() { 
    pansound.setVolume(5);
    stove.setVolume(5);
    fridge.setVolume(5);
    crackegg.setVolume(5);
    fryegg.setVolume(5);
    bacon.setVolume(5);
    tada.setVolume(5);
}


function draw() {
    background(247, 220, 224); // light pink background 
    // story line: this is basically a short clip of making breakfast. There are a lot of kitchen and cooking sounds incorporated to show each step of frying an egg and bacon. 

    if (counter == true) {
        count += 1;
    }

    // taking out pan 
    if (count > 0 & count <= 2) {
        image(pan1, 110, 230);
        pansound.play();
    }

    // turn on stove 
    if (count >= 2 & count < 4) {
        pansound.stop();
        image(pan2, 110, 230); // switch to pan with flames 
        stove.play();
    }

    // taking out egg
    if (count >= 4 & count < 5) {
        stove.stop();
        image(pan2, 110, 230);
        image(egg1, 170, 200);
        fridge.play();
    }

    // cracking egg 
    if (count >= 5 & count < 6) {
        fridge.stop();
        image(pan2, 110, 230);
        image(egg2, 150, 200);
        crackegg.play();

    }

    // raw egg in pan 
    if (count >= 6 & count < 9) {
        crackegg.stop();
        image(pan2, 110, 230);
        image(egg3, 155, 285);
        fryegg.play();
    }

    // egg sizzling -> cooked 
    if (count >= 9 & count < 12) {
        image(pan2, 110, 230);
        image(egg4, 155, 285);
    }

    // taking out bacon 
    if (count >= 12 & count < 13) {
        fryegg.stop();
        fridge.play();
        image(pan2, 110, 230);
        image(bacon1, 170, 200);
    }

    // bacon sizzling -> cooked
    if (count >= 13 & count < 15) {
        bacon.play();
        image(pan2, 110, 230);
        image(bacon2, 150, 285);
    }

    // bacon in plate w/ egg + TADA 
    if (count >= 15 & count < 16) {
        bacon.stop();
        tada.play();
        image(plate, 100, 230);
        image(bacon2, 130, 260);
        image(egg4, 180, 260);
    }
}

function mousePressed() {
    counter = true; // starts counting once click screen 
}

My sonic story is quite simple. It basically shows a breakfast of eggs and bacon being made. I drew all the graphics for the food, pan, flame, and plate. I used sounds like the fridge opening to represent taking out the ingredients. Other sounds like cracking the egg and the bacon sizzling are all to represent the cooking.

Project 09: Computational Portrait

portrait
var img;

function preload() {
    img = loadImage("https://i.imgur.com/TWN7sA6.jpeg");
}

function setup() {
    createCanvas(300, 300);
    frameRate(60);
    noStroke();
    img.resize(width, height);
    img.loadPixels();
}

function draw() {
    fill(0);
    text("keep mouse pressed for different effect", 50, 10);

    if(mouseIsPressed) { // while mouse is pressed, generate random squares 
        let pix_x = floor(random(img.width));
        let pix_y = floor(random(img.height));
        let pix = img.get(pix_x, pix_y);
        fill(pix);
        var square_x = random(width);
        var square_y = random(height);
        let mapX = map(mouseX, 0, width, 0, 20);
        square(square_x, square_y, mapX);
    } else { // otherwise, use circles 
        let x = floor(random(img.width));
        let y = floor(random(img.height));
        let pix = img.get(x, y);
        fill(pix);
        circle(x, y, random(15)); //diameter random 
    }
}

I used an image that I found on imgur by Mihaela Noroc, a Romanian photographer. I chose this particular image because it involved a lot of vibrant colors, which I thought would be fun to work with. Here are some screenshots of my portrait:

Looking Outwards 09: A Focus on Women and Non-binary Practitioners in Computational Art

Demonstration of all the possible transformations of “Augmented Hand Series”

The “Augmented Hand Series” is a real-time interactive software system that presents playful transformations of participants’ hands. The project was developed in 2013-2014 by Chris Sugrue, Golan Levin, and Kyle McDonald. Chris Sugrue, currently based in Paris, is an artist and programmer developing interactive installations, audio-visual performances, and experimental interfaces. She received her Masters degree in Fine Arts in Design and Technology from Parsons School of Design. Since then, she worked as a creative engineer, a lead developer, and also as a teaching artist. She enjoys experimenting with technology in playful and curious ways. The “Augmented Hand Series” is one great example. The project is presented as a box that allows visitors to insert their hands, then a screen will display a “reimagined” version of their hand. For instance, there may be an extra finger on their hand or a finger is relocated to the other side of the palm. I find this project to be extremely interesting because it provides such a dynamic and fun way for visitors to interact with the software. The resulting behavior of the displayed hand depends on both the visitor and the algorithm. I really admire that dynamic aspect of this series.

How the visitors interact with the project

Looking Outwards 08: The Creative Practice of an Individual

Eyeo 2019 – Mike Tucker from Eyeo Festival on Vimeo.

The speaker that I chose for this week’s LO is Mike Tucker. I watched the talk he gave “NEW DOORS OF PERCEPTION: EXPLORATIONS INTO MIXED REALITY” at Eyeo 2019. Mike Tucker is an Interactive Director, Designer & Developer, currently based in New York. He studied Graphic Design at Virginia Commonwealth University. For the last 5 years, Mike has been working at Magic Leap, a company focused on developing the future of Spatial Computing. His work mainly involves interactive arts and music, and his hope is to “encourage the next wave of spatial designers to question our expectations of media, and the opportunity we all have in designing a mixed reality future.”

At Eyeo, he talked about how he started his journey from graphic design to web design and then to VR and interactive art. His most recent work is Tónandi (means “tone spirit” in Icelandic), which is an interactive audio-visual experience where participants are within a virtual environment with a unique soundscape. The soundscape adapts to the current environment and changes based on the participant’s eyes, hands, and movement.

Tónandi in action

I really love this project because I think it really breaks the expectations of how a medium should be experienced. The way that sound is represented visually and the ability for people to touch and interact with “sound” is so incredible. It’s also very admirable because Mike talks about how they started from prototypes and experimented with different elements before developing the final product. It’s quite amazing to see his early explorations worked towards his current works. Furthermore, he broke down the different aspects of how they created perception and illusion, which includes head tracking, controllers, spatial sound, touch, eye gaze, and environment.

Mike presented his work by documenting the stages of development. He used a combination of images and videos to show his past projects and also various explorations. He also showed other creators’ projects that served as inspiration. It’s very interesting to see how the technology improved over the years. I can definitely learn from him by doing a better job of documenting my work process.

Project 07: Composition with Curves

curves
var points = 100;

function setup() {
    createCanvas(400, 400);
}

function draw() {    
    background(map(mouseX, 0, width, 50, 0)); // background color goes from grey -> black as mouse moves to the right
    translate(width/2, height/2); // move origin to center
    hypotrochoid(); // draw the curve   
}

function hypotrochoid() {
    // https://mathworld.wolfram.com/Hypotrochoid.html
    var x; 
    var y;
    var a = map(mouseX, 0, width, 50, 80); // radius of fixed circle
    var b = map(mouseY, 0, height, 0.1, 1); // radius of circle rolling around fixed circle
    var h = mouseX/2; // distance from center of interior circle
    strokeWeight(map(mouseX, 0, width, 0.1, 3)); // stroke weight increases as mouse moves to the right
    stroke(mouseX, 100, mouseY);
    noFill();
    beginShape();
    for (var i = 0; i <= points; i ++) {
        var t = map(i, 0, points, 0, TWO_PI);
        x = (a - b) * cos(t) + (h * cos(((a - b)/b) * t));
        y = (a - b) * sin(t) - (h * sin(((a - b)/b) * t));
        vertex(x, y);
    }
    endShape();
}


I started the project by just browsing all the different curves in mathworld and coding them to see how they look on the canvas. I ended up settling on the Hypotrochoid curve. The sample curve on the website actually didn’t seem very interesting, but once I allowed the parameters to change based on the mouse’s position, the curve had a lot more details and got much more complicated. It was quite incredible to see how the curve changes as the mouse cross the canvas. Here are some screenshots of the different looks:

The changes are quite drastic, so it’s interesting that they were all produced by the same equation.

Looking Outwards 7: Information Visualization

“The Rhythm of Food” showing the seasonality of Apricot

The project that I find incredibly intriguing is “The Rhythm of Food” created by teams at the Good News Lab and Truth & Beauty. The goal of the project is to investigate the seasonal patterns in food searches. Using data from 12 years of weekly Google Trends, the creators developed a radial “year clock” chart with various segments indicating the search interests of hundred of dishes and ingredients. The results are featured on a website that highlights the ongoing trends in the current month. The website also allows people to dig into the data themselves and explore the seasonality of various food. The project was built using ES2015, webpack, react, Material UI, and d3 v4. I really enjoy this project because it uses something that we need every day, food, to reveal all sorts of cultural and social phenomena. For instance, the clock shows that searches for “gefilte fish” peak around mid-March because it’s a traditional appetizer for Passover. It’s even more interesting because it explores how the seasonality of food varies across the world. Another aspect that I admire about “The Rhythm of Food” is the visual design. Moritz Stefaner mentioned that he wanted to find “unique, intriguing, expressive and elegant visual devices to bring out the most interesting aspects of the data.” This was accomplished very successfully through the intricate designs of the clock and the cohesive color scheme.

Animation showing how the clock works

Project 06 – Abstract Clock

clock
var x = [];
var y = [];
var sec; // second
var minutes; // minute
var hr; // hour

// every second, coffee fills up a little bit in a cup and coffee mug gets larger
// every minute, coffee completely fills up a cup
// every hour, the background color darkens

function setup() {
    createCanvas(450, 300);
}

// each cup of coffee represents one minute 

function draw() {
    var hr = hour();
    background(255-(hr*5), 255-(hr*5), 255-(hr*3)); // background color gets darker by the hour

    minutes = minute();
    sec = second();

    for(var row = 0; row < 6; row +=1 ) {
        for(var col = 0; col < 10; col += 1) {
            fill(250, 247, 222); // cream color for mug
            var current = row*10+col; // current mug
            mug(40+(40*col), 43+(40*row), current); // draw the mug
            }
        }
}

function mug(x, y, cur) { // function to draw the mug
    // body of the mug
    var frac = 1;
    if (cur < minutes) {
        fill(117, 72, 50); // brown
    } else if (cur == minutes) {
        frac = sec/60;
        fill(250+(117-250)*frac, 247+(72-247)*frac, 222+(50-222)*frac); // cream -> brown
    } else {
        return;
    }

    push();

    translate(x, y);
    scale(frac); // every second, the mug gets bigger

    ellipse(0, 0, 30, 7); // top of mug
    beginShape();
    curveVertex(-15, 0);
    curveVertex(-15, 0);
    curveVertex(-14, 10);
    curveVertex(-10, 25);
    curveVertex(-5, 28);
    curveVertex(0, 28);
    curveVertex(5, 28);
    curveVertex(10, 25);
    curveVertex(14, 10);
    curveVertex(15, 0);
    curveVertex(15, 0);
    endShape();

    // mug handle
    noFill(); 
    strokeWeight(1.5);
    beginShape();
    curveVertex(12, 20);
    curveVertex(12, 20);
    curveVertex(18, 13);
    curveVertex(15, 5);
    curveVertex(14, 8);
    curveVertex(14, 8);
    endShape();
    strokeWeight(1);

    pop();
}

The idea behind this project stemmed from my love for coffee. My time is basically measured by cups of coffee, which inspired me to create a canvas full of coffee mugs. The hours of each day are represented by the darkening of the background. The minutes of each hour are represented by the sixty mugs. The seconds of each minute are represented by the size and color of the mugs (filling up the mugs).

My initial sketch:

Sketch

I found this project quite challenging. I realized that executing a concept in code can be a lot more difficult than expected.