This page tries to explain how to quickly and (as) correctly (as possible) use Processing sketches on webpages. The information is based on the work done by the processing.js group.
This information on this page reflects the best of my knowledge anno November 2010, and processing.js becomes more and more complete, so it is possible that the information on this page changes over time. Should you spot any incompletenesses or blatant mistakes, please contact me at pomax at nihongoresources.com, with the obvious substitutions in place to make it a legal email address.
The "Processing" language (also referred to as "P5") is a programming language with a focus on data visualisation. Of course, "data" is a loose concept, and Processing can be used for anything from drawing a few lines and circles on a screen, to full blown interactive animations. In fact, a basic Processing program is two lines of code, and will already play an animation:
1
2
void setup() { size(..., ...); }
void draw() {}
Of course this program doesn't show you anything, because all it does is set up the visual context to have a certain width and height (indicated in the setup method as the size(...,...) instruction) and then calls draw() every few milliseconds. Of course, draw() is empty, so it won't actually show you anything. A more useful minimal program would be a "hello world" program, but I hate those because they only show a programming language can write text, and that's pretty much the least interesting feature of any programming language. Instead let's look at a minimal program that makes sense for a data visualisation language:
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
float framerate = 24; // our "sketch" will have a framerate of 24 frames per second.
int ball_x; // ball administration: x coordinate
int ball_y; // ball administration: y coordinate
int ball_radius = 20; // ball administration: ball radius
void setup() {
size(200,200); // set draw area size
frameRate(framerate); // set animation framerate
ball_x = width/2; // set the initial ball coordinates
ball_y = ball_radius; // set the initial ball coordinates
stroke(#003300); // set the default shape outline colour
fill(#0000FF); // set the default shape fill colour
}
void draw() {
// compute the ball height for this frame
float bounce_height = height/2 * abs(sin(PI*frameCount/framerate));
// because the top of the screen is 0, and the bottom is "height",
float ball_height = height - (bounce_height+ball_radius);
// clear the drawing area
background(#FFFFEE);
// set the new ball y position
ball_y = (int) (ball_height);
// draw the ball
ellipse(ball_x,ball_y,ball_radius,ball_radius);
}
This looks a bit long for a minimal program, but then again, this actually does something: it shows us a ball that bounces up and down, taking one second for each bounce. It shows a few aspects of Processing too: every variable is strongly typed. So you have to indicate what you'll be using a variable for, and you can choose from:
And of course there are also the typical complex data types:
You'll see why this last one turns out to be really useful later on.
Coming back to the minimally functional example of a Processing program, or "sketch", there are also some examples of Processing' own API at work. The following methods are native Processing calls:
The Processing API is in fact quite expansive (See http://processing.org/reference for the full list), but it can't cover everything. Luckily it supports object oriented programming, so that our previous example can also be written as an object oriented sketch:
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
Bouncer bouncer;
void setup() {
size(200,200);
frameRate(24);
stroke(#003300);
fill(#0000FF);
bouncer = new Ball(width/2,20,20);
}
void draw() {
bouncer.computeNextStep(width, height, frameRate);
background(#FFFFEE);
bouncer.draw();
}
interface Bouncer {
void computeNextStep(int width, int height, float framerate);
void draw();
}
class Ball implements Bouncer
{
int x,y,radius;
int step = 0;
Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.radius = r;
}
void computeNextStep(int sketch_width, int sketch_height, float frame_rate) {
step++;
float sin_value = abs(sin(PI*step/(float)frame_rate));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + radius);
y = (int) (ball_height);
}
void draw() {
ellipse(x,y,radius,radius);
}
}
Instead of doing everything in the draw() function, the object oriented approach tucks all the code that relates to computing the ball's position in the definition for what we consider a "Ball". To be good object oriented programmers, we've also said that things that are a Ball are also a Bouncer, and this lets us extend our sketch very easily to instead of a bouncing ball, have a bouncing box by keeping almost everything the same, and adding a new class Box that's a Bouncer:
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
void setup() {
...
bouncer = new Box(width/2,20,20,20);
}
class Box implements Bouncer
{
int x,y,w,h;
int step=0;
Box(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void computeNextStep(int sketch_width, int sketch_height, float frame_rate) {
step++;
float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate)));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + h);
y = (int) (ball_height);
}
void draw() {
rect(x,y,w,h);
}
All of a sudden we have a bouncing box, that starts from a falling position instead of from the ground, and we didn't have to modify the master draw() function for it! In fact, let's just use a group of bouncing things:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Bouncer[] bouncer = new Bouncer[3];
void setup() {
...
bouncer[0] = new Ball(width/3-20,20,20);
bouncer[1] = new Box(width/2-10,20,20,20);
bouncer[2] = new Ball((2*width/3)+20,20,20);
}
void draw() {
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].computeNextStep(width, height, frameRate);
}
background(#FFFFEE);
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].draw();
}
}
...
Fantastic, two bouncing balls and a bouncing box, bouncing counter-point to each other. But it's not very interactive yet. Let's change it so that we can "hang on" to bouncing things until we let go of them again. Processing allows interaction with the keyboard and mouse, using what are known as "event handlers", methods that Processing automatically calls for you when you use the keyboard or mouse. In this case we care about mouse interaction, so we'll look at mousePressed and mouseReleased events:
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
void mousePressed() {
for(int b=0, end=bouncer.length; b<end;b++) {
if(bouncer[b].mouseOver(mouseX, mouseY)) {
bouncer[b].mousePressed();
}
}
}
void mouseReleased() {
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].mouseReleased();
}
}
abstract class Bouncer
{
int x, y;
boolean canmove = true;
int step = 0;
abstract void computeNextStep(int width, int height, float framerate);
abstract void draw();
abstract boolean mouseOver(int mx, int my);
void mousePressed() { canmove = false; }
void mouseReleased() { canmove = true; }
}
class Ball extends Bouncer
{
int radius;
Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.radius = r;
}
void computeNextStep(int sketch_width, int sketch_height, float frame_rate) {
if(canmove) {
step = (int)((step+1) % frame_rate);
float sin_value = abs(sin(PI*step/(float)frame_rate));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + radius);
y = (int) (ball_height);
}
}
void draw() { ellipse(x,y,radius,radius); }
boolean mouseOver(int mx, int my) {
return sqrt((x-mx)*(x-mx) + (y-my)*(y-my)) <= radius;
}
class Box extends Bouncer
{
int w,h;
int step=0;
Box(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void computeNextStep(int sketch_width, int sketch_height, float frame_rate) {
if(canmove) {
step = (int)((step+1) % frame_rate);
float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate)));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + h);
y = (int) (ball_height);
}
}
void draw() { rect(x,y-h/2,w,h); }
boolean mouseOver(int mx, int my) {
return x<=mx && mx<=x+w && (y-h/2)<=my && my<=(y+h/2);
}
}
Because the Ball and Box classes will do the same thing on mouse interaction, the interface Bouncer has been changed to an actual class too, to take care of some of the shared functionality. Now if you click on anything that's a Bouncer it'll stop moving until you let it go. Let's go one step further an just allow us to move the bouncing things around, too.
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
Bouncer[] bouncer = new Bouncer[3];
void setup() {
size(200,200);
frameRate(24);
stroke(#003300);
fill(#0000FF);
bouncer[0] = new Ball(width/3-20,20,20);
bouncer[1] = new Box(width/2-10,20,20,20);
bouncer[2] = new Ball((2*width/3)+20,20,20);
}
void draw() {
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].computeNextStep(width, height, frameRate);
}
background(#FFFFEE);
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].draw();
}
}
void mousePressed() {
for(int b=0, end=bouncer.length; b<end;b++) {
if(bouncer[b].mouseOver(mouseX, mouseY)) {
bouncer[b].mousePressed();
}
}
}
void mouseReleased() {
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].mouseReleased();
}
}
void mouseDragged() {
for(int b=0, end=bouncer.length; b<end;b++) {
bouncer[b].mouseDragged(mouseX, mouseY);
}
}
abstract class Bouncer
{
int x, y;
boolean canmove = true;
int step = 0;
int xoffset = 0;
int yoffset = 0;
void computeNextStep(int width, int height, float framerate) {
if(canmove) {
reallyComputeNextStep(width, height, framerate);
}
}
abstract void reallyComputeNextStep(int width, int height, float framerate);
abstract void draw();
abstract boolean mouseOver(int mx, int my);
void mousePressed() {
canmove = false;
}
void mouseReleased() {
canmove = true;
x += xoffset;
y += yoffset;
xoffset = 0;
yoffset = 0;
}
void mouseDragged(int mx, int my) {
if(!canmove) {
xoffset = mx-x;
yoffset = my-y;
}
}
}
class Ball extends Bouncer
{
int radius;
Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.radius = r;
}
void reallyComputeNextStep(int sketch_width, int sketch_height, float frame_rate) {
step = (int)((step+1) % frame_rate);
float sin_value = abs(sin(PI*step/(float)frame_rate));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + radius);
y = (int) (ball_height);
}
void draw() { ellipse(x+xoffset,y+yoffset,radius,radius); }
boolean mouseOver(int mx, int my) {
return sqrt((x-mx)*(x-mx) + (y-my)*(y-my)) <= radius;
}
}
class Box extends Bouncer
{
int w,h;
int step=0;
Box(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void reallyComputeNextStep(int sketch_width, int sketch_height, float frame_rate) {
step = (int)((step+1) % frame_rate);
float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate)));
float bounce_height = sketch_height/2 * sin_value;
float ball_height = sketch_height - (bounce_height + h);
y = (int) (ball_height);
}
void draw() { rect(x+xoffset,(y-h/2)+yoffset,w,h); }
boolean mouseOver(int mx, int my) {
return x<=mx && mx<=x+w && (y-h/2)<=my && my<=(y+h/2);
}
}
And with that, on to the original topic of this article: using Processing on web pages
The great thing about Processing is that it can be used on webpages. Traditionally, you would convert your sketch to a java applet, and embed that on a page, but the processing.js project has changed this: you can now use your sketches directly without turning it into an applet at all. In the same way that you include a javascript file, or a CSS stylesheet, you can simply link to your sketch and magic happens.
Let's say we save the previous sketch, with the bouncing and the mouse interaction, as "mysketch.pde", and we want to show this on a webpage. Using processing.js, this is a trivially simple trick:
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="processing.js"></script>
</head>
<body>
<canvas id="mysketch" data-processing-sources="mysketch.pde"></canvas>
</body>
</html>
And we're done. In fact, I went ahead and made sure this page already uses processing.js, and that there is a mysketch.pde to load. If you click on the play control for the sketch above, it will start running, and do exactly what you would expect it to do based on what it does when you run it from the Processing environment.
While not recommended, you can also put your sketch directly on a page, much like how you can put javascript or CSS styles directly on a page. However, in order for processing.js to properly load your code, you'll need some extra help in the form of the "init.js" file that is included with the processing.js examples archive from the processing.js downloads page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="processing.js"></script>
</head>
<body>
<script type="application/processing" target="mysketch">
void setup() { ... }
void draw() { ... }
class SomeClass {
...
}
</script>
<canvas id="mysketch"></canvas>
</body>
</html>
There are several reasons for why this is not a very good practice, but the most important one is that this way you can't be sure whether or not you've written a bug-free sketch. By writing it directly on the page, you might in fact have written some buggy code, which you will then find is terribly hard to debug because the browser is not equiped with a debugger for Processing code. In fact, processing.js rewrites your sketch into pure javascript (using some terribly clever tricks that we won't go into), so even if when it tells you where the error is in a javascript debugger, it will tell you where it went wrong in the converted javascript object, not the actual place in your source code. So keep it as a separate file, and make sure to test your code in the Processing IDE!
Just running a sketch on a page is fine, but the benefit of a webpage is that it offers the kind of user interaction that you can't get from within the sketch itself. Pretty buttons, text areas that fold away, pop up, etc. make it very attractive to have your sketch do all the animation graphicsy work, but have all the information about the sketch, as well as controls for it, on the webpage. This is entirely possible - in fact, the "stop" and "play" buttons for the sketch above are examples of javascript telling the sketch what to do. Because a webpage offers more than a plain sketch can, processing.js has a few special tricks up its sleeve, so that you can get the most out of your creative work. Arguably the most important of these is the "get the sketch" function:
1
var mysketch = Processing.getInstanceById('mysketchid');
This is the pivotal function on the javascript side of things. It gives you a direct reference to your sketch, and everything in it, so that you can call any function and examine and modify any variable that is in the sketch.
Let's say we have the following sketch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void setup() {
size(200,200);
noLoop(); // turn off animation, since we won't need it
stroke(#FFEE88);
fill(#FFEE88);
background(#000033);
text("",0,0); // force Processing to load a font
textSize(24); // set the font size to something big
}
void draw() { }
void drawText(String t)
{
background(#000033);
// get the width for the text
float twidth = textWidth(t);
// place the text centered on the drawing area
text(t, (width - twidth)/2, height/2);
}
We can make this sketch draw a different text based on some text we have on our webpage, using javascript. This is in fact really easy: first, let's save this processing code as mysketch2.pde, and load it onto a page in the same way as earlier in the article. Then, we use javascript to ask for our sketch instance, after which we call the "drawText" function with some text that we get from the web page that the sketch is running on:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="processing.js"></script>
<script type="text/javascript">
function drawSomeText(id) {
var pjs = Processing.getInstanceById(id);
var text = document.getElementById('inputtext').value;
pjs.drawText(text); }
</script>
</head>
<body>
<canvas id="mysketch2" data-processing-sources="mysketch2.pde"></canvas>
<input type="textfield" value="my text" id="inputtext">
<button type="button" onclick="drawSomeText('mysketch2')">place</button>
</body>
</html>
This has the same effect as the sketch that's running below. Simply fill in a bit of text, and hit the ▶ button to see the sketch render it on the drawing area.
So far so good, but what if we also want to make Processing code call javascript? In order for us to so, while making sure the sketch keeps running both on your page an in the Processing environment, we have to do a bit more work
You can't just stick plain javascript in your sketch and hope everything goes well because it's "on a web page". A better approach is to neatly separate your sketch and your on-page javascript, and ensure that whatever you want to do on your page runs through a javascript function. How do we do that? Let's say we have the following page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="processing.js"></script>
</head>
<body>
<canvas id="mysketch3" data-processing-sources="mysketch3.pde"></canvas>
<div id="coordinates">
x/y: <input type="textfield" id="xcoord">/<input type="textfield" id="ycoord">
</div>
</body>
</html>
And say we have the following sketch, which we'll save as mysketch3.pde:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void setup() {
size(200,200);
stroke(255);
background(0);
noLoop();
}
void draw() {
fill(0,0,0,40);
rect(-1,-1,width+2,height+2);
}
void mouseMoved() {
line(mouseX,0,mouseX,height);
line(0,mouseY,width,mouseY);
redraw();
}
As an exercise, let's try to get the mouse coordinates from that mouseMoved event onto the page. This means we'll need to define a javascript function that will do this for us:
1
2
3
4
function showXYCoordinates(x, y) {
document.getElementById('xcoord').value = x;
document.getElementById('ycoord').value = x;
}
And we'll need to make the sketch know that there is such a thing as javascript, and that it has this function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface JavaScript {
void showXYCoordinates(int x, int y);
}
void bindJavascript(JavaScript js) {
javascript = js;
}
JavaScript javascript;
void setup() { ...}
void draw() { ... }
void mouseMoved() {
...
if(javascript!=null){
javascript.showXYCoordinates(mouseX, mouseY);
}
}
What we've done is we've told Processing: "There are things that follow the JavaScript interface. This means that whatever else they can do, they'll have a function called "showXYCoordinates" and that function takes two arguments, both of type int." - of course, the sketch will not magically know our on-page javascript, so we also define a function that lets us tell the sketch what actually counts as javascript, which is what the bindJavascript(...) function is for.
This leaves us with needing to make sure that we really do tell the sketch what Javascript is, which we'll do as follows on the page:
1
2
3
4
5
6
7
8
9
10
11
12
var bound = false;
function bindJavascript() {
var pjs = Processing.getInstanceById('mysketch3');
if(pjs!=null) {
pjs.bindJavascript(this);
bound = true;
}
if(!bound) setTimeout(bindJavascript, 250);
}
bindJavascript();
This defines a function that checks whether processing.js has loaded our sketch yet. If not, it tries again 250ms later. If the sketch is loaded, the reference to the sketch is used to tell it what "javascript" should be. This is achieved by virtue of processing.js trusting that you wrote valid code. As such, as long as you're on the javascript side of things, you can pass the sketch whatever you like, and the sketch will trust that it conforms to what the method says the type should be. In this case we pass the javascript "this" value (which refers to the global javascript environment for the current window), and tell processing "this thing conforms to your JavaScript interface, so it has loads of things but the only thing you need to be concerned about is whether or not it has a showXYCoordinates(int, int) function, which it does."
So, our final sketch behaves like the sketch below, and our final page source looks like the following:
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="processing.js"></script>
<script type="text/javascript">
var bound = false;
function bindJavascript() {
var pjs = Processing.getInstanceById('mysketch3');
if(pjs!=null) {
pjs.bindJavascript(this);
bound = true; }
if(!bound) setTimeout(bindJavascript, 250);
}
bindJavascript();
function showXYCoordinates(x, y) {
document.getElementById('xcoord').value = x;
document.getElementById('ycoord').value = y;
}
</script>
</head>
<body>
<canvas id="mysketch3" data-processing-sources="mysketch3.pde"></canvas>
<div id="coordinates">
x/y: <input type="textfield" id="xcoord">/<input type="textfield" id="ycoord">
</div>
</body>
</html>
This leaves us with one last topic that we need to cover, simply because it's so common: using an AJAX approach to get some data, and then passing that data, properly formatted, to your sketch.
One interesting thing that processing.js allows us to do is to create objects as we defined them in our sketch, in javascript, and then hand them over to the sketch to deal with. To give an example, let's use the following sketch, which lets you draw points by clicking with the mouse, and links up all the points with lines:
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
ArrayList points;
ArrayList getPoints() { return points; }
void setup() {
size(200,200);
points = new ArrayList();
noLoop();
stroke(255,0,0);
fill(255);
}
void draw() {
background(200,200,255);
for(int p=0, end=points.size(); p < end; p++) {
Point pt = (Point) points.get(p);
if(p < end-1) {
Point next = (Point) points.get(p+1);
line(pt.x,pt.y,next.x,next.y); }
pt.draw(); }
}
void mouseClicked() {
points.add(new Point(mouseX,mouseY));
redraw();
}
class Point {
int x,y;
Point(int x, int y) { this.x=x; this.y=y; }
void draw() { ellipse(x,y,10,10); }
}
We can also make javascript place some points for us, automatically, by using the following javascript:
1
2
3
4
5
6
7
8
9
function loadPoints(id, button) {
var pjs = Processing.getInstanceById(id);
var points = pjs.getPoints();
points.add(new pjs.Point(10,10));
points.add(new pjs.Point(10,190));
points.add(new pjs.Point(190,190));
points.add(new pjs.Point(190,10));
pjs.draw();
}
Because processing.js turns the sketch into actual javascript, every class we defined in it can be created using new pjs.ClassName(arguments,...).
Another way to get data from javascript into a sketch is by way of a JSON description. JSON is particulary interesting because javascript can read a JSON text string and immediately turn it into a full-fledged javascript object. Let's look at our point-drawing sketch again, this time with a dedicated "addPoint" method:
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
ArrayList points;
void setup() {
size(200,200);
points = new ArrayList();
}
void draw() {
background(200,200,255);
for(int p=0, end=points.size(); p<end; p++) {
Point pt = (Point) points.get(p);
if(p<end-1) {
Point next = (Point) points.get(p+1);
line(pt.x,pt.y,next.x,next.y); }
pt.draw(); }
}
void mouseClicked() {
addPoint(mouseX,mouseY);
}
Point addPoint(int x, int y) {
Point pt = new Point(x,y);
points.add(pt);
return pt;
}
class Point {
int x,y;
Point(int x, int y) { this.x=x; this.y=y; }
void draw() {
stroke(255,0,0);
fill(255);
ellipse(x,y,10,10);
}
}
Now what if -- instead of using mouse clicks, or predetermined javascript -- we want to load points based on some data on a remote server? We change our web page so that it can deal with JSON data from a remote server, and then make our javascript tell the sketch what to do:
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Processing Page</title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="processing.js"></script>
<script type="text/javascript">
function drawPoints(id) {
var pjs = Processing.getInstanceById(id);
var json = $.get("http://thatfunkysite.com/serving/json.asp");
var data = eval("("+json+")");
if(data) {
// we know the JSON is an array of points, called "points"
for(p=0, end=data.points.length; p<end; p++) {
var point = data.points[p];
pjs.addPoint(point.x, point.y);
}
}
}
</script>
</head>
<body>
<canvas id="mysketch5" data-processing-sources="mysketch5.pde"></canvas>
<div id="controller"><button type="button" id="loadPoints" onclick="loadPoints('mysketch5')"></div>
</body>
</html>
The JSON that gets loaded in the example sketch in this section is the following:
{ 'points' : [ {'x': 10 , 'y': 10}, {'x': 190 , 'y': 10}, {'x': 190 , 'y': 190}, {'x': 10, 'y': 190} ] }
Another favourite is the XML document. This is where the Processing data type "XMLElement" comes into play. Rather than trying to use javascript to parse the XML, we pass the xml data straight on to our sketch, which will know exactly what to do with it:
1
2
3
4
5
6
function loadPoints(id, button) {
button.disabled = "disabled";
var pjs = Processing.getInstanceById(id);
var xml = $.get("http://thatfunkysite.com/serving/getxml.php");
pjs.buildFromXML(xml);
}
And the handling in our sketch will look like this:
1
2
3
4
5
6
7
8
9
10
11
void buildFromXML(String xml) {
XMLElement data = new XMLElement(xml);
XMLElement[] xmlpoints = data.getChildren();
for(int p=0, end=xmlpoints.length; p<end; p++) {
XMLElement xmlpoint = xmlpoints[p];
int x = xmlpoint.getIntAttribute("x");
int y = xmlpoint.getIntAttribute("y");
points.add(new Point(x, y));
}
redraw();
}
If the xml thus loaded looks like the following snippet, the sketch will behave in the way the sketch below does:
1
2
3
4
5
6
7
<xml>
<point x='10' y='10'/>
<point x='190' y='50'/>
<point x='10' y='90'/>
<point x='190' y='140'/>
<point x='10' y='190'/>
</xml>
A special kind of XML document is the SVG Scalable Vector Graphics document. What's special about it is that it already represents visual data, and Processing can load SVG XML natively.
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
XMLElement svgxml;
PShape ps;
int x=0;
int y=0;
int xoffset=0;
int yoffset=0;
int xmark=0;
int ymark=0;
void setup() {
size(200,200);
noLoop();
}
void draw() {
background(255);
stroke(0);
for(int px=0; px<width; px+=10) { line(px,0,0,px); }
for(int py=0; py<height; py+=10) { line(width,py,0,py+height); }
stroke(#000066);
for(int py=0; py<height;py+=10) { line(0,py,width,py); }
if(ps!=null) { shape(ps,x-xoffset,y-yoffset,200,200); }
}
void mousePressed() {
xmark = mouseX;
ymark = mouseY;
}
void mouseDragged() {
xoffset = xmark-mouseX;
yoffset = ymark-mouseY;
redraw();
}
void mouseReleased() {
if(xoffset!=0 || yoffset!=0) {
x -= xoffset;
y -= yoffset;
xoffset=0;
yoffset=0;
} else {
XMLElement path = svgxml.getChild(0);
path.setAttribute("fill",getNextColor());
bindSVG(svgxml);
}
}
String[] colors = {"#f0f8ff","#faebd7","#00ffff","...", ..., ... };
int color_len = colors.length;
int current_color = 0;
String getNextColor() {
current_color = (current_color+1)%color_len;
return colors[current_color];
}
void buildFromSVG(String svg) {
svgxml = new XMLElement(svg);
bindSVG(svgxml);
}
void bindSVG(XMLElement svg) {
ps = new PShapeSVG(svgxml);
redraw();
}
This sketch will load the following SVG code, and allows you to click on the shape to change its color, or click-drag it to move it around:
1
2
3
<svg width='200' height='200'>
<path d='M 10 10 L 190 10 L 140 100 L 190 190 L 10 190 L 50 100 L 10 10 M 100 80 L 140 30 L 50 30 L 100 80 M 100 120 L 50 170 L 140 170 Z'/>
</svg>
One last thing you may want to pass to a sketch is a real javascript object. Say we have a physics library that we want to take advantage of in our sketch, and for Processing it's a .jar file, but there's a javascript version too. This thing lets us create a javascript "Physics" object that we can call functions on for computing momentum and force transfer when we make two things hit each other. Since processing.js does not support .jar libraries (because it's precompiled binary java class code, and pjs cannot unpack a jar file, reverse engineer the .java source, transform it to Processing API calls, then confirm all calls are supported) we'll use this convenient javascript library. But how to do this cleanly?
We have to do three things:
So let's get cracking! First we determine which functions in the javascript Physics object we actually make use of. We could write an interface that has correct method signatures for every function in the library, but this would be overkill. After reviewing our code we see that we actually only make use of three of the objects functions: collide_objects(forcevector1, forcevector2, collisionangle), get_force_vector(startpoint, distancevector, accelleration) and get_trajectory(startpoint, objectmass, initialvector, gravityvector, gravitystrength). Let's build our interface:
1
2
3
4
5
6
7
8
9
10
11
interface Physics
{
// collision modifies the two force vectors in place. Nothing is returned.
void collide_objects(float[] forcevector1, float[] forcevector2, float collisionangle);
// get force vector returns a force vector
float[] get_force_vector(float[] startpoint, float[] distancevector, float accelleration);
// trajectory calculation returns a curve, represented as a lest of 2D coordinates
float[][] get_trajectory(float[] startpoint, float objectmass, float[] initialvector, float[] gravityvector, float gravitystrength);
}
First job done. Now to bind javascript. We've already looked at how to do this, so just follow the standard procedure and job's a good'n. In addition to whatever javascript functions you want to expose, however, we must add one more:
1
2
3
4
5
interface JavaScript {
...
Physics buildPhysicsObject([properly typed variables to match the javascript constructor]);
...
}
Finally, we now make use of our purely javascript library in our js file(s):
1
2
3
4
function buildPhysicsObject(x, y, someothervar)
{
return new Physics(x,y,someothervar);
}
This last step seems silly, but we have to delegate the task of building a javascript object to javascript. If we tried to do this inside our sketch, processing.js will not complain, but Processing will. To make matters worse, it also obscures the point of failure: if something goes wrong in the sketch, did it go wrong because processing.js has a bug, or because you were sloppy with javascript inside a Processing sketch? Golden rule: keep the different languages separated as much as possible.
We're almost set to use our javascript library for Processing purposes now. The only thing left is to create these objects and then use them in our sketch:
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
46
47
class MassBody
{
// a class for bodies with mass that travel with some speed and are located at some x/y position
float[] forceVector = new float[2]; // 2 dimensional vector;
int XDIM = 0; // index constant
int YDIM = 1; // index constant
float x = 0; // position
float y = 0; // position
MassBody(float x, float y) { forceVector[XDIM] = 0; forceVector[YDIM] = 0; this.x=x; this.y=y; }
void draw() { /* draws the body */ }
float[] getForce() { return forceVector; }
void impartForce(float[] stimulus) { forceVector[XDIM] += stimulus[XDIM]; forceVector[YDIM] += stimulus[YDIM]; }
void updatePosition() { x+=...; y+=...; }
}
MassBody body1 = new MassBody(...);
MassBody body2 = new MassBody(...);
Phsyics physicsObject
void bindJavascript(JavaScript js) {
javascript = js;
int x = 0;
float y = 9;
String[] somevalues = {"scalar", "vector", "tensor"};
physicsObject = javascript.buildPhysicsObject(x,y,somevalues);
}
void setup()
{
...
}
void draw()
{
// if the two bodies collide, compute the resultant forces
if(body1.collides(body2))
{
// forces are recomputed in place
float angle_12 = body1.getAngleTo(body2);
physicsObject.collide_objects(body1.getForce(), body2.getForce(), angle_12);
body1.updatePosition();
body2.updatePosition();
}
body1.draw();
body2.draw();
}
And we're done!
As last point of business, you can of course also use Processing.js as a pure graphics library, by invoking it for some canvas and then calling Processing API calls directly. Let's jump right in! The following code is what you would write on-page, although of course as ever it's far better practice to link to your source files, so you should really place what's in the script tags in a file myPjsSketch.js and link to it using src="..." instead.
The following code is split up into multiple (numbered) sections.
First, bind a new Processing instance to the indicated canvas.
1
2
3
4
5
<canvas id="glibcanvas"></canvas>
<script type="text/javascript">
(function() {
var canvas = document.getElementById('glibcanvas');
var pjs = new Processing(canvas);
The setup() function defines the entry point for our sketch. It sizes the canvas, and tells it not to animate by default.
1
2
3
4
5
6
7
8
9
// let's write a sketch
var value = 0;
// Definition for the initial entry point
pjs.setup = function() {
pjs.size(200,200);
// we want to turn off animation, because this is a demo page and it
// would use cpu while not being looked at. Only draw on mousemoves
pjs.noLoop();
}
The draw() function defines the main draw function, which is called whenever a frame update is requested (either because the sketch is looping, or because redraw() is called manually)
1
2
3
4
5
6
7
8
9
10
11
12
13
// Draw a "sine wave" using two bezier curves, with an undulating amplitude.
pjs.draw = function() {
// partially clear, by overlaying a semi-transparent rect
// with background color
pjs.noStroke();
pjs.fill(255,75);
pjs.rect(0,0,200,200);
// draw the "sine wave"
pjs.stroke(100,100,200);
pjs.noFill();
pjs.bezier(0,100, 33,100+value, 66,100+value, 100,100);
pjs.bezier(100,100, 133,100+-value, 166,100+-value, 200,100);
}
The mouseMoved() function is a Processing event handler that is triggered by the mouse moving around over the canvas. It is far more convenient to use the built-in event handler than to write our own.
1
2
3
4
pjs.mouseMoved = function() {
value = ( pjs.mouseY-100);
pjs.redraw();
}
This is all we need in our sketch, so there's only one call left to make:
1
2
3
4
// Finally, calling setup() will kickstart the sketch
pjs.setup();
}());
</script>
So let's see that in action - the code that we just ran through is supposed to draw a sinewave, with an amplitude that depends on where the mouse is on the canvas. If all went well, it will look like this:
This guide was brought to you by Mike "Pomax" Kamermans.
For more information, you can visit the following websites, or visit us in the #processing.js IRC channel on irc.mozilla.org.