This is a crash course in the basic principles and elements of using Processing to visualise data. The student should first download and install Processing. The code in grey boxes can be pasted into the Processing script window and run.

Notes:

  • This page remains a work in progress.
  • The embedded scripts should work in current browsers except Firefox, which has a bug in the stuff that puts Processing online.


Creating primitives

Processing contains some typical primitive geometric objects for drawing, chiefly the point, line, rectangle, and ellipse (circle).

size (310, 110);             	// sets the canvas size to 310 wide and 110 high
point (15, 15);                 // draw point (pixel) at (15,15)
line (30, 20, 80, 90);       	// draw line from (30,20) to (80,90)
rect (120, 20, 50, 70);      	// draw 50 x 70 rectangle starting at (120,20)
ellipse (250, 50, 40, 60);   	// draws a 40 x 60 radius ellipse with centre (250,50)

Note: the grid has been added for reference, and is not reflected in the code above.


Colour and visual properties – stroke, fill and opacity

Stroke and fill functions set the object outline and internal colour properties, either using the 3 argument RBG (red, green, blue) format (0-255 scale) or a single input denoting greyscale. Objects can be made semi-transparent by adding an opacity (alpha) value as a 4th argument after the RGB values.

size (410, 110);
stroke(255, 0, 0);              // red stroke
line (30, 20, 80, 90);
fill(0, 0, 255);                // blue fill
noStroke();                     // remove stroke
rect (120, 20, 50, 70);
noFill();                       // remove fill
stroke(0)                       // black stroke
strokeWeight(3);                // thicker outline
ellipse (250, 50, 40, 60);
noStroke();
fill(50, 100, 200, 126);        // bluish fill with 50% alpha
ellipse (360, 50, 40, 60);
fill(200, 100, 50, 126);        // redish fill with 50% alpha
rect(320, 20, 40, 40);


Data types

Like some other programming languages Processing distinguishes between different data types. The most common basic types are:

  • string (declared ‘String’) – any character or character sequence of alphabetical, numerical or other symbols
  • integer (declared ‘int’) – rounded numbers that cannot perform decimal calculations
  • float (declared ‘float’) – numbers that can perform decimal calculations
  • boolean (declared ‘boolean’) – objects of true/false value used in logical processes

Data can be printed to the Processing console using print and println (print line) commands. For example:

print('Hello');
print(42);
println(3.1415)
println(10 + 4);
println('cats ' + 'and ' + 'dogs ');
println(true);

prints this to the console:

Hello42
3.1415
14
cats and dogs
true

Object variables

Processing is an object oriented language, which means it works by creating and manipulating data objects. These are typically designated a name so their data contents can be easily recalled. To be used in this way object variables must be declared and initialised. Declaration tells Processing that a new object exists with a certain data type. Initialising gives the object its first value and enables it to be used.

int x;                          // declaring
x = 5;                          // initialising (after declaration)
int y = 10;                     // both in 1 line
float half = 0.5;
String vowels = 'aeiou'         // string declaration uses capital 'S'


Common mathematical operators

Processing includes some useful operators for performing simple mathematical functions. These include the typical + – * / operators, but also:

Operator What it does Examples (using x == 4)
++ increase a variable’s current value by 1 with “x++” x becomes 5
+= increase a variable’s current value by n with “x += 2” x becomes 6
– – reduce current object value by 1 with “x- –“, x becomes 3
-= reduce current object value by n with “x-=2” x becomes 2
*= multiply object’s value by n with “x*=2” x becomes 8
/= divide object’s value by n with “x/=4” x becomes 1
% modulo – the remainder after subtraction of a divisor 8%2 == 0 and 12%5 == 2
pow() x to the power of y pow(2,3) == 8
sqrt() square root sqrt(16) == 4

Looping

Looping is a commonly used technique to simplifying repetitive operations. Processing has two methods – the for loop (repeat a specific number of times), and the while loop (repeat until a condition is satisfied)

FOR loop

size (310, 110);
for(int i=0; i<5; i++) {
	line (i*50, 10, i*50 + 10, 90);
}


WHILE loop

These must be carefully constructed to include a failsafe that prevents an infinite loop.

size (310, 110);
int i = 5;
while(i > 0) {
	line (i*50 + 10, 10, i*50, 90);
	i = i-1;
}


Logical processes

Processing uses these operators in logical processes:

&	AND (true if both inputs are true)
|	OR (true if either input is true, or both)
!	NOT (opposite boolean of that input
==	denotes equality
>	greater than
<	less than
<=	less than or equal to

These are used in the if / else statement to create a simple logical algorithm, for example:

if(5 < 2) {
	println('correct');
}
else {
	println('wrong');
}

yields

wrong

Void, setup and draw

Processing uses a 2 main stages to build animated visualisations - a setup stage in which data is variables are typically initialised, data imported and processed, and processes defined; and a draw stage in which the processes are put into operation frame-by-frame. The void operator is used in Processing to specify one of these, or to define a new process. For static visualisations these structures are not necessary.

A simple example of this is:

void setup(){
	size (100, 100);
}
void draw(){
	background(200);
	rect(frameCount%width, 50, 20, 20);
	ellipse(50, (frameCount+75)%height, 20, 20);
}


Notes:

  1. The background() function is used to reset the screen for each new frame. Without this each new frame would also display all previous iterations.
  2. The frameCount variable (the number of the current frame) can produce a useful repeating visual pattern when called in combination with modulo.

Object domains

Objects have limited domains in which they operate, which depend on where they are declared. For instance the variables declared inside loops (e.g. the i integer in the examples) only exist inside the loop statements. This prevents object names clashing and allows the use of generic object naming. For data to be initialised in setup and called (used) in draw its object must be declared as a global variable, i.e. outside of both void components. For example:

float x, y;
String s1, s2;

void setup(){
	size (100, 100);
	s1 = 'A';
	s2 = 'B';
	fill(0);
}
void draw(){
	background(200);
	x = frameCount%width;
	y = (frameCount+75)%height;
	text(s1, x, 50);
	text(s2, 50, y);
}


Arrays

Arrays are fixed-length list objects containing data values of the same data type. They can be single dimensional (a single list) or multi-dimensional (a matrix). This example uses a 2 dimensional array with 10 rows and 2 columns to accomodate coordinate data:

float[][] coords;

void setup(){
	size (200, 100);
	coords = new float[10][2];
	for (int i=0; i < 10; i++){
		coords[i][0] = random(width);  // x values
		coords[i][1] = random(height); // y values
	}
	noStroke();
	fill(0);
}
void draw(){
	background(200);
	for (int i=0; i < 10; i++){
		coords[i][0] = (coords[i][0] + random(-1, 1))%width;
		if(coords[i][0] < 0) coords[i][0] += width;
		coords[i][1] = (coords[i][1] + random(-1, 1))%height;
		if(coords[i][1] < 0) coords[i][1] += height;
		ellipse(coords[i][0], coords[i][1], 5, 5);
	}
}


Notes:

  1. Processing uses zero-based arrays, so array[0] returns the first item.
  2. random() is used here with both 1 and 2 arguments to return customised random numbers to set initial positions and calculate random walks.
  3. Here modulo (e.g. %width) has been combined with logical if(...) statements to make the objects stay inside the box, effectively creating a semi-wrapped environment.
  4. The length of an array (useful for looping) can be obtained with the .length operator - e.g. coords.length in the case above.

ArrayLists

ArrayLists are essentially arrays with flexible lengths and data types. Any values they contain can be added or removed. The script below reproduces the array example above using an ArrayList that has been specified (using a <...> argument) to accept only PVector objects (a handy data type for storing and using spatial information). Note how values are specified and accessed using the .add() and .get() functions.

ArrayList < PVector > coords;

void setup(){
	size (200, 100);
	coords = new ArrayList();
	for (int i=0; i < 10; i++){
		PVector p = new PVector(random(width), random(height));
		coords.add(p);
	}
	noStroke();
	fill(0);
}
void draw(){
	background(200);
	for (int i=0; i < coords.size(); i++){
		coords.get(i).x = (coords.get(i).x + random(-1, 1))%width;
		if(coords.get(i).x < 0) coords.get(i).x = width;
		coords.get(i).y = (coords.get(i).y + random(-1, 1))%height;
		if(coords.get(i).y < 0) coords.get(i).y = height;
		ellipse(coords.get(i).x, coords.get(i).y, 5, 5);
	}
}


Notes:

  1. As you can see PVectors have data slots for an x, y coordinates, accessed e.g. using .x argument. There is also a z coordinate slot, but this can be ignored for 2D projects.
  2. In contrast to .length operators for array lengths, the length of an ArrayList is accessed using the .size() function (function because of the parentheses, which in this case remain empty).
  3. When constraining an ArrayList's data-type with < ... > the standard type spellings do not apply. Instead the following (Title case) versions should be used: 'Float', 'Integer', 'String', 'Boolean'.

Iteration with Arrays and ArrayLists

Arrays and ArrayLists are iterable objects, meaning Processing can access their individual records recursively and more efficiently. So for any Array or Arraylist of uniform data type named e.g. iterable the following loop can be performed:

for (datatype i:iterable) {
	operation 1;
	operation 2;
}

For instance, for an Array/ArrayList of PVectors the following would draw some small squares:

for (PVector p:iterable) rect(p.x, p.y, 5, 5);

Notes:

  • This example also show how loops can be simplified to a single line (without parentheses) for simple operations.

Hashmaps

Processing can also store and recall data by a unique keyword, a useful technique for making Processing objects interact in more specific ways. For information on how to do this see here. In brief, HashMaps are created and populated as follows:

HashMap hm = new HashMap();

hm.put("keyword", data_value);

and their data is accessed using:

hm.get("keyword");

Working with Class Objects

Arrays, ArrayLists and HashMaps are great for storing simple datasets, but to build more advanced models Classes offer more effective ways to manage data and the processes you want to associated with it.

The example below creates a Class called Agent, which contains 2 attributes (a position PVector p and a velocity PVector v), and 2 defined processes (one to move the agent and one to calculate its distance to other agents and draw lines to reflect mutual proximity).

Agent[] agents;

class Agent{
  PVector p,v;
  
  Agent(PVector P){
    this.p = P.get();
    this.v = new PVector();
  }
  
  void move(){
    this.v.add(new PVector(random(-.1, .1), random(-.1, .1)));
    this.v.limit(1);
    this.p.add(v);
    if(this.p.x < 0) this.p.x += width;
    if(this.p.x > width) this.p.x -= width;
    if(this.p.y < 0) this.p.y += height;
    if(this.p.y > height) this.p.y -= height;
  }
  
  void interact(){
    for(Agent a:agents){
      if(a != this){
        float dist = PVector.dist(a.p, this.p);
        stroke(255, 0, 0, 75-dist);
        line(this.p.x, this.p.y, a.p.x, a.p.y);
      }
    }
  }
}

void setup(){
  size (300, 150);
  frameRate(30);
  noStroke();
  fill(0);
  agents = new Agent[12];
  for(int i=0; i<12; i++) {
    PVector P = new PVector(random(width), random(height));
    agents[i] = new Agent(P);
  }
}

void draw(){
  background(200);
  for(Agent a:agents) a.move();
  for(Agent a:agents) a.interact();
  for(Agent a:agents) ellipse(a.p.x, a.p.y, 5, 5);
}


Notes:

  1. New Agents are instantiated by the command Agent(). Note how the instance's initial position P is passed to the Class object inside the parentheses. Any data object can be passed to the Class instance in this way.
  2. The PVector.dist() function is used to calculate the distance between two PVector points.