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
[...] – 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) [...]