Data visualization with Processing, Part 1: An introduction to the language and environment

Building graphical applications and applications that present complex data can be difficult. Although many graphical libraries exist, they cater to advanced users or present non-trivial APIs. The Processing language and environment solves this problem by creating a portable environment and language for graphical presentation. Processing makes it simple to build applications that present static data, dynamic data (such as animations), or interactive data. This first article in the series explores building applications for visualization — in particular, simulations for life sciences.

Share:

M. Tim Jones, Independent author, .

author photo - M. Tim JonesM. Tim Jones is an embedded firmware architect and the author of "Artificial Intelligence: A Systems Approach," "GNU/Linux Application Programming" (now in its second edition), "AI Application Programming" (in its second edition), and "BSD Sockets Programming from a Multilanguage Perspective." His engineering background ranges from the development of kernels for geosynchronous spacecraft to embedded systems architecture and networking protocols development. he is a platform architect with Intel and author in Longmont, Colo.



30 November 2010

Also available in Japanese

Although many open source projects are driven to build alternatives for existing applications, there are numerous projects that represent new and innovative works. Processing is one of those programs. Processing began at the Massachusetts Institute of Technology's (MIT's) Media Lab in 2001, created by Ben Fry and Casey Reas, though it has contributions from Carnegie Mellon; the University of California, Los Angeles; Miami University; and others.

The original goal for Processing was the development of a graphical sketchbook and environment that could be used to teach (graphically) the fundamentals of computer science. It has since evolved into an environment that can be used to create professional works for graphical visualization. A community has grown around it, building libraries that evolve the language and environment for animation, visualization, vision, network programming, and many other applications. What you'll find here is that Processing is a great environment for data visualization, with a simple interface, a powerful language, and a rich set of mechanisms for data and application export.

Processing runs on GNU/Linux®, as well as Mac OS X and Windows®, and it supports the ability to export images to a variety of formats. For dynamic applications, it's even possible to export Processing applications as Java™ applets for use in Web environments.

This first article begins with an exploration of the Processing IDE, then reviews some of the first aspects of the Processing language. It then looks at some of the key graphical primitives and finally explores a couple of applications that take advantage of them.

The origins of Processing

Processing began as a simplified programming language that could be used to teach computer programming. The ideas started in the Design By Numbers project at the MIT Media Lab (John Maeda), whose goal was to teach programming through the development of visual applications. Although it could be used by beginning programmers, the project was also intended for artists and visual designers. See Resources for more information on Processing and its descendants.

Processing environment

The first step is to install the Processing environment. Go to Processing.org, click Download Processing, and select your operating system. Note that the examples in which article use V1.2.1 of Processing. Once the gzipped tarball is downloaded, expand it using tar xvfz processing-1.2.1.tgz, for example.

You'll also need to ensure that you have Java technology available. On Ubuntu, simply type sudo apt-get install openjdk-6-jdk.

When installation is complete, go to the processing-1.2.1 directory created by the earlier tarball and test it by typing ./processing.

This should bring up the Processing Development Environment (PDE, or Processing IDE), shown in Figure 1. The large portion of the window is the text editor. If you type the two lines shown in the figure and click Run (the triangle in the upper left), a window appears that is a result of the simple program (or sketch, in Processing lingo) that you entered. Clicking Stop (the square in the upper left) causes the program to exit and the window to disappear.

Figure 1. The PDE and Results window
Screenshot of the Processing Development Environment results window. On the left side is a menu at the top with a code window below and a debug window below that. To the right is a small sketch window with a diagonal line drawn from uper left to lower right.

Now, let's dig into the Processing language to explore its major features en route to developing some interesting applications.


Processing language

Processing is written in the Java programming language, and it's also the language to which Processing is closest in the language tree. So, if you understand C or the Java language, Processing will be simple to learn. It does make some simplifications in the way the programs are constructed. Processing doesn't include some of the more advanced features of the Java language, but many of these features are integrated into Processing, so it's not necessary for you to understand them.

The reason the Java language was chosen is that Processing applications are translated into Java code for execution. Choosing the Java paradigm simplifies this translation and makes it simple and straightforward to develop and execute visual programs. For a comparison of the Processing language and the Java language, check out Resources.


Graphics environment

As you saw in Figure 1, developing in Processing involves the PDE and display window. The coordinate system, for 2-D graphics, is shown in Figure 2. The size keyword defines the dimensions of the display window in pixels and is commonly the first step in a Processing application.

Figure 2. Coordinates of a 2-D display window
Diagram showing how the coordinates in the code map to the window

As shown in Figure 2, the size keyword specifies the X and Y coordinate extremes of the display window. The line keyword draws a line between two pixels in the display (in the form x1, y1 to x2, y2). Note that drawing off screen (outside of the bounds defined by size) is not illegal, but simply ignored.

This article doesn't explore it, but size accepts an optional third argument for mode. mode defines the rendering engine to be used and supports PDF (for rendering directly to an Adobe® PDF document), OPENGL (to exploit an available Open-GL graphics adapter), P3D (for fast 3-D rendering), and others. The default is JAVA2D, which is best for high-quality 2-D imaging.

Now, let's explore some of the basic graphics primitives before digging into a couple of sample applications.


Graphics primitives

Processing includes a large variety of geometric shapes and controls for those shapes. This section introduces some of the basic graphics primitives.

Background and colors

The background function is used to set the color of the display window. This function can use a variety of different parameters (to define a gray value, or a Red-Green-Blue [RGB] color). The code segment in Listing 1 generates the output shown in Figure 3, cell a.

Listing 1. Using the Background function
size(100, 100);
background( 0, 128, 0 );

Drawing pixels

You can also draw individual pixels using the set function. This function takes the x,y coordinate within the display window and a third argument for the color. Processing also has a type called color through which you can define the color used for a particular operation. In this case, you create a color instance and use it to set a particular pixel within the display window (see Listing 2 and Figure 3, cell b).

Listing 2. Setting pixels and colors
size(100, 100);
for (int x = 0 ; x < 100 ; x++) {
  for (int y = 0 ; y < 100 ; y++) {
    color c = color( x*2, y*2, 128 );
    set(x, y, c);
  }
}

You can use a get operation to read the color of a given pixel in the display. Although set is simple, it's not the fastest way to manipulate the display. For faster access, use the pixels array (in concert with the loadPixels and updatePixels functions), instead.

Drawing shapes

It's also simple to draw shapes within Processing using single functions. To set the color that's used when drawing shapes, you use the stroke function. This function can take a single gray parameter or three parameters for RGB. You can also define the color that's used to fill the shape with the fill command.

Listing 3 shows how to draw lines, rectangles, circles (using the ellipse), and ellipses. The line function takes four arguments, representing the points between which a line is drawn. The rect function draws a rectangle, with the first two points defining the location and the next two defining width and height, respectively. The ellipse function also takes four arguments, defining the location and width/height. When width and height are equal, the shape is a circle. You can also tailor ellipses using the ellipseMode function, which specifies whether the x,y location represents the corner (CORNER) or the center of the ellipse (CENTER). See Figure 3, cell C.

Listing 3. Lines and shapes
size(100, 100);
stroke(0, 128, 0);
line(10, 10, 90, 90);

fill(20, 50, 150);
rect(30, 30, 60, 40);

fill(190, 0, 30);
ellipse(30, 70, 20, 20);

fill(0, 150, 90);
ellipse(70, 30, 30, 20);

Drawing quadrilaterals

You can easily draw four-sided polygons in Processing with quad. The quadrilateral takes eight arguments representing the four points of the quadrilateral. The example in Listing 4 creates 10 random quadrilaterals (where the points must be in clockwise or counter-clockwise order. This code also creates a random grayscale color for each quad.

Listing 4. Drawing quadrilaterals
size(100, 100);

for (int i = 0 ; i < 10 ; i++) {

  int x1 = (int)random(50);
  int y1 = (int)random(50);
  int x2 = (int)random(50) + 50;
  int y2 = (int)random(50);
  int x3 = (int)random(50) + 50;
  int y3 = (int)random(50) + 50;
  int x4 = (int)random(50);
  int y4 = (int)random(50) + 50;

  fill( color((int)random(255) ) );

  quad( x1, y1, x2, y2, x3, y3, x4, y4 );

}
Figure 3. Graphics output for listings 1 through 4
Cell A shows a solid green square. Cell B is a rainbow gradient from the lower left corner to the upper right. Cell C shows a diagonal line, a blue box, a green elipse and a red circle. Cell D shows a number of overlapping polygons in various shades of gray.

Numerous other shapes exist, as do controls for line width and smoothness of image. Figure 4 shows the quad function example from Listing 4 with a call to smooth. This function provides anti-aliasing of the edges and improves the quality of the image at the cost of speed.

Figure 4. Using the smooth function
Screenshot shows the gray-shaded polygons with crisp lines and smooth shading

Structure of a Processing application

So far, you've explored the Processing language in a collection of simple scripts — unstructured code that provided simple elements of an application. Processing applications have a structure, which is important when developing graphical applications that run continuously and alter the display window over time (for example, for animation). Two of the important functions in this context are setup and draw.

The setup function is used for initialization and is executed once by the Processing run time. Typically, the setup function contains the size function (to define the bounds of the window) as well as initialization of variables that are used during operation. The Processing runtime continuously executes the draw. Each time the draw function finishes, a new frame is drawn to the display window, and the draw function is invoked again. The default draw rate is 60 frames per second, although you can alter this rate by calling the frameRate function.

You can also control when frames are drawn using noLoop and draw. The noLoop function causes drawing to stop and can be restarted using the loop function. You can control when draw is called by calling redraw.

Now that you know how to develop a Processing application, let's look at a simple example that demonstrates the use of text.

Using text

Processing also supports text, both within the display window and in the form of a console for debugging. To use text within the display window, you need a font. The first step is to create a font (which you do using the Tools option of the PDE). After selecting a font to create, this font file (VLW) will show up in the project's ./data subdirectory. You can then load this file using the loadFont function, and then define it as the default using textFont. Both of these steps are shown in Figure 5 within the setup function. Note also that you slow the frame rate down to one frame per second (because that's the frequency at which updates naturally occur).

The draw function demonstrates a number of other functions you've not yet seen. First are the time functions, which return the hour, minute, and second of the clock. Note that there are additional functions that return the year, month, and day. With the time data stored, you then create a string using the nf function, which converts numbers into strings. To add some variety to your clock, manipulate the colors of the background and clock using background and fill functions. The background ranges from 255 (white) to 137 (light gray). The fill function, which colors the text, ranges from 100 (light gray) to 218 (near black). With the colors set, the text function emits your time string to the display window at the defined coordinates. You also emit the string to the console using the println function (see the bottom left of Figure 5).

Figure 5. Using text within a Processing application
Screenshot shows the IDE running a program which updates a clock in the rendering window.

Building simple applications

Now, let's look at a couple of simulations built with Processing. The first is an implementation of a 2-D cellular automaton that implements the forest-fire model. This model, from Chopard and Droz's "Cellular Automata Modeling of Physical Systems," provides a simple system that illustrates the growth of trees in a grid and the spread of fire resulting from a lightning strike. This simulation consists of a simple set of rules that are defined as:

  • On an empty site (brown), a tree grows with probability pGrowth.
  • A tree becomes a burning tree (red) if at least one of its neighbors is burning.
  • A burning tree (red) becomes an empty site (brown).
  • A tree without any burning neighbors becomes a burning tree with probability pBurn. This occurs, for example, as a result of a lightning strike.

These rules are encoded in the update function (see Listing 5), which iterates through the 2-D space to determine how states transition per the defined rules. Note that the 2-D space is actually 3-D because you maintain two copies of the space — one for the current iteration and one for the last. You do this to avoid corrupting the space with changes. The space then becomes a display space (what is displayed) and a compute space (application of the rules). The spaces swap over each generation.

For the most part, this application uses very little of Processing's graphics keywords. A few colors are defined for the space, stroke is used to change colors, and point is used to draw a pixel. Using the Processing model, the draw function calls update to apply the rules; upon return, draw emits the updated space to the display window.

Listing 5. Cellular Automata Forest Fire Model
int[][][] pix = new int[2][400][400];
int toDraw = 0;

int tree = 0;
int burningTree = 1;
int emptySite = 2;

int x_limit = 400;
int y_limit = 400;

color brown = color(80, 50, 10); // brown
color red   = color(255, 0, 0); // red;
color green = color(0, 255, 0); // green

float pGrowth = 0.01;
float pBurn = 0.00006;


boolean prob( float p )
{
  if (random(0, 1) < p) return true;
  else return false;
}


void setup()
{
  size(x_limit, y_limit);
  frameRate(60);
  
  /* Initialize to all empty sites */
  for (int x = 0 ; x < x_limit ; x++) {
    for (int y = 0 ; y < y_limit ; y++) {
      pix[toDraw][x][y] = emptySite;
    }
  }
  
}


void draw()
{
  update();
  
  for (int x = 0 ; x < x_limit ; x++) {
    for (int y = 0 ; y < y_limit ; y++) {

      if        (pix[toDraw][x][y] == tree) {
        stroke( green );
      } else if (pix[toDraw][x][y] == burningTree) {
        stroke( red );
      } else stroke( brown );

      point( x, y );

    }
  }
 
  toDraw = (toDraw == 0) ? 1 : 0;
}


void update()
{
  int x, y, dx, dy, cell, chg, burningTreeCount;
  int toCompute = (toDraw == 0) ? 1 : 0;

  for (x = 1 ; x < x_limit-1 ; x++) {
    for (y = 1 ; y < y_limit-1 ; y++) {

      cell = pix[toDraw][x][y];

      // Survey area for burning trees
      burningTreeCount = 0;
      for (dx = -1 ; dx < 2 ; dx++) {
        for (dy = -1 ; dy < 2 ; dy++) {
          if ((dx == 0) && (dy == 0)) continue;
          else if (pix[toDraw][x+dx][y+dy] == burningTree) burningTreeCount++;
        }
      }

      // Determine next state
      if      (cell == burningTree) chg = emptySite;
      else if ((cell == emptySite) && (prob(pGrowth))) chg = tree;
      else if ((cell == tree) && (prob(pBurn))) chg = burningTree;
      else if ((cell == tree) && (burningTreeCount > 0)) chg = burningTree;
      else chg = cell;
      pix[toCompute][x][y] = chg;
    }
  }
}

Figure 6 shows the iteration of the Cellular Automata Forest Fire Model, skipping appropriately to show the effect of the rule set. Time 0 consists solely of empty space into which trees grow. At time 40, you can begin to see fires burning that eventually take over the space. At around time 100, tree growth is more visible, but at time 120, more fires begin and the process cycles.

Figure 6. Output from the Cellular Automata Forest Fire Model
Screenshot shows 16 boxes depicting the output of the program at 10-second intervals

The Susceptible/Infected/Recovered Model

The Susceptible/Infected/Recovered (SIR) Model simulates the spread of a disease within a hospital. Like the forest-fire model, SIR is implemented by a simple set of rules but adds complex and interesting behavior. In this model, you have a grid of beds filled by patients. At time 0, all patients are susceptible to the new disease, meaning that they've never had the disease and may, therefore, contract it. If one of the patients in the four N/S/E/W neighborhoods has the disease, then the patient becomes infected with a probability of tau. An infected patient remains sick for K days, during which time the patient may infect other patients. After K days, the patient recovers and is now resistant to the disease.

As with the previous example, the setup function initializes the hospital to all susceptible patients, except for the patient in the center who is infected. In this implementation, 0 is susceptible, 1-K is infected, and -1 is recovered. The draw function emits the geometry to the display window, and update implements the SIR rules. As before, a 3D array is used to maintain the current and working geometries. Listing 6 shows the code.

Listing 6. The SIR Model in Processing
int[][][] beds = new int[2][200][200];
int toDraw = 0;

int x_limit = 200;
int y_limit = 200;

color brown = color(80, 50, 10); // brown
color red = color(255, 0, 0); // red;
color green = color(0, 255, 0); // green

int susceptible = 0;
int recovered = -1;

float tau = 0.2;
int k = 4;

boolean prob( float p )
{
  if (random(0, 1) < p) return true;
  else return false;
}


void setup()
{
  size(x_limit, y_limit);
  frameRate(50);
  
  for (int x = 0 ; x < x_limit ; x++) {
    for (int y = 0 ; y < y_limit ; y++) {
      beds[toDraw][x][y] = susceptible;
    }
  }
  beds[toDraw][100][100] = 1;
}


void draw()
{
  update();
  
  for (int x = 0 ; x < x_limit ; x++) {
    for (int y = 0 ; y < y_limit ; y++) {

      if (beds[toDraw][x][y] == recovered) stroke( brown );
      else if (beds[toDraw][x][y] == susceptible) stroke( green );
      else if (beds[toDraw][x][y] < k) stroke( red );

      point( x, y );

    }
  }
 
  toDraw = (toDraw == 0) ? 1 : 0;
}

boolean sick( int patient )
{
  if ((patient > 0) && (patient < k)) return true;
  return false;  
}

void update()
{
  int x, y, cell;
  int toCompute = (toDraw == 0) ? 1 : 0;

  for (x = 1 ; x < x_limit-1 ; x++) {
    for (y = 1 ; y < y_limit-1 ; y++) {

      cell = beds[toDraw][x][y];

      if (cell == k) cell = recovered;
      else if (sick(cell)) cell++;
      else if (cell == susceptible) {
        if (sick(beds[toDraw][x][y-1]) || sick(beds[toDraw][x][y+1]) ||
            sick(beds[toDraw][x-1][y]) || sick(beds[toDraw][x+1][y])) {
          if (prob(tau)) cell = 1;
        }
      }

      beds[toCompute][x][y] = cell;

    }
  }
}

The output of the SIR Model in Processing is shown in Figure 7. Note here that green pixels represent susceptible patients, red show the sick patients, and brown are the recovered patients. Given that the sickness lasts for four days and neighboring patients have a 20-percent chance of catching the disease, the disease randomly spreads throughout the hospital, infecting many but leaving islands of uninfected patients.

Figure 7. Output of the SIR Model in Processing
Screenshot shows a green field with colored pixels depicting the SIR model results

Going further

With luck, this article has whet your appetite for Processing and helped start your journey with this excellent open source language and environment. Part 2 of this series begins to explore some of the more advanced features of Processing and provide additional applications. In particular, it will look at object-oriented programming, image processing, particle swarms, and how to export your application as a Java applet.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=592211
ArticleTitle=Data visualization with Processing, Part 1: An introduction to the language and environment
publish-date=11302010