A Processing.js example ( Tears in Darkness )

Posted on March 5th, 2009

Click in the canvas to cry (Firefox & Safari only).
The processing.js visualization code [+]

Processing.js, makes coding graphics for the HTML <canvas> element a dream come true. Casey Reas and Ben Fry created the original library/programming environment to manipulate graphics using Java and has built up a vibrant community with their work. The 2nd revolution however, comes with John Resig's Javascript rendition of those same programming libraries. Because of his port, you can now do things with open technologies such as javascript & html that were once only possible with Flash. I've been exploring with it and this is what I've learned so far.

How was the visualization above created?

Excuse the emo title for the graphic above, but I was going for something simple so that I could quickly learn the system. Animating a tear quickly came to mind as an easy beginner's project. So to begin, let us start with an understanding of what a primitive animation is: A series of pictures rapidly displayed one after another. With Processing.js this notion is realized with the two functions setup() and draw(). You define setup() with the knowledge in mind that Processing.js will automatically call it at the beginning of your visualization to draw the first "picture" of the scene. Following this. it will repeatedly call the draw() function several times per second to generate all of the subsequent "pictures" of your scene.

The snippet below sets up your first "picture" to be 600 pixels wide and 300 pixels high.
    // This function sets up the canvas elment
    void setup() {
      size(600, 300);                              // size( width, height ) - sets the canvas size
      background(0);                               // background ( lightness ) or background ( red, green, blue, alpha ) - sets background color
    }
Creating a tear

In my world, a tear basically consists of a circle that grows as it falls. It gets to a certain size and then it stops and fades away. That is a tear. To represent that, I create an class to store its current position, size, and various other attributes:

   // This class object represents one tear. A global variable called "tears" stores an array of these individual tears
    class Tear{
       int x,y, alpha;
       float size;
       
       // This function is called when a new tear is created from the "new Tear( x, y)" command. You can pass it the x & y position of the new tear
       Tear(int xin, int yin){
          x = xin;                               // sets the x coordinate of the tear
          y = yin;                               // sets the y coordinate of the tear      
          alpha = 255;                           // sets the initial alpha (AKA opacity) of the tear         
          size = 1;                              // sets original tear radius to be 1px
       }
       
       // This function is called to update the tear's attributes
       boolean update(){
          y += 1;                                               // moves the tear down 1px
          alpha -= 5;                                           // decrease tear's opacity by 5
          size += .5;                                           // increase the tear's size by .5px
          if( y > height || alpha < 0){                         // if tear's current height or opacity is out of the visible  range return false
             return false;
          }
          return true;                                          // return true if everything went fine
       }
       
       // draw this tear with its current properties
       void draw(){
          fill(0,60, 100+random(0,100), alpha);        // fill ( red, green, blue, alpha )  - sets fill to these attributes, values between 0 and 255
          ellipse(x,y, size,size);                     // draws a circle
       }

    }

The two major things to notice are 1) the Tear.update() function which changes the tear's properties according to predefined rules and 2) the Tear.draw() function which reads in the current tear's property and does the actual drawing of the tear.

Dealing with many tears

Since our scene will have many tears at once, I'll set up an array of tears to keep track of them all

    Tear[] tears = new Tears[];                    // Create new array to keep track of all the tears
Running the draw() loop

Now that you've got the basic data structures down, we can stick them into the draw() loop to run them. The code below basically loops through all current tears, and updates and draws them.

// This function is automatically called by processing every frame
    void draw() {                                                   
      fill(0, 6);                                  // fill( lightness, alpha ) or fill( red, green, blue, alpha) -  sets fill colors of any shapes drawn hereafter 
      rect(0, 0, width, height);                   // rect ( upper_leftX, upper_leftY, lower_rightX, lower_rightY ) - draws a rectangle, 
      noStroke();                                  //  removes border on shapes drawn after this point

      for( int i=0; i < num_tears; i++){
         if ( tears[i] == null) {
            // Do nothing if current object == null
         }
         else if (tears[i].update() == false){
            // Update the current tear, if return false than set the current object to null
            tears[i] = null;
         }
         else{
            // When tear is not null, and update() wasn't false then draw() it
            tears[i].draw();
         }
      }
mousePressed interactivity

finally I create a little interactivity by listening for the mousePressed event. If someone clicks on the canvas, it will generate another tear object at that mouse location. You'll notice I've augmented the mouseX and mouseY variables to also include how much the user has scrolled the window. The mouseX and mouseY positions are relative to the original position of the canvas element and does not reflect changes to its positions when scrolling.

      if(mousePressed){
         // Add a new tear to the tears array at the current mouse location
         tears[num_tears] = new Tear(mouseX + window.scrollX ,mouseY+window.scrollY);
         num_tears +=1;
      }

    }
How did you get that nice fading effect?

The fading effect of the tears were created by painting a semi-transparent rectangle over the canvas each frame. With each frame these rectangle are painted and eventually cover up the tears.

 void draw() {                                                   
      fill(0, 6);                                  // fill( lightness, alpha ) or fill( red, green, blue, alpha) -  sets fill colors of any shapes drawn hereafter 
      rect(0, 0, width, height);                   // rect ( upper_leftX, upper_leftY, lower_rightX, lower_rightY ) - draws a rectangle, 
Summary

In summary, this animation was built by setting up a Tear class which held attributes like the tear's position. The Tear class had functions to update() its position, and to draw() it. A Tear was generated every time someone clicked on the canvas screen and was stored in an array of Tears. Every time processing.js calls draw(), a loop is executed to go through the list of existing tears to update() their properties, and then to draw() them. Overall, this is fun little system that I'll be using in the future to generate some cool infographics

One response so far

  1. [...] – Change the canvas background to green when the animation starts. – Make the circle twice as big – Change the frame rate and see what happens – Change the delay and see what happens – Make the animation twice as big (800 x 800) – Make the white ring around the circle flash green when the circle is clicked – Make the circle follow your cursor as you click (like in this example) [...]

Leave a reply