Data visualization with Processing, Part 1: An introduction to the language and environment
http://www.ibm.com/developerworks/opensource/library/os-datavis/index.html
Data visualization with Processing, Part 1: An introduction to the language and environment
Summary: 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.
Date: 30 Nov 2010
Level: Intermediate
PDF: A4 and LetterGet Adobe® Reader®
Activity: 3692 views
Comments:
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 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
Now, let's dig into the Processing language to explore its major features en route to developing some interesting applications.
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.
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
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.
Processing includes a large variety of geometric shapes and controls for those shapes. This section introduces some of the basic graphics primitives.
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 );
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.
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);
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
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
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.
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
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
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
With luck, this article has whet your appetite for Processing and helped start your journey with this excellent open source language and environment. The next article will begin 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.
Learn
- At Processing.org, you can download the latest version of the language and environment and get access to a full language reference. You can also learn where Processing is used. See how the Processing language compares to other languages, such as the Java language, as well.
- Processing originated from the Design By Numbers project by John Maeda at the MIT Media Lab. The project was created for visual designers and artists as an introduction to programming. Although the language has some significant differences, you'll find that Design By Numbers' environment heavily influenced that of Processing.
- The Cellular Automata Forest Fire Model and the SIR Model illustrate interesting and complex behavior from a simple set of rules.
- One of the best references for Processing is Processing: A Programming Handbook for Visual Designers and Artists (Casey Reas and Ben Fry, MIT Press, 20047). Also available is Getting Started with Processing (Casey Reas and Ben Fry, O'Reilly Media, 2010).
- To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
- Stay current with developerWorks' Technical events and webcasts.
- Follow developerWorks on Twitter.
- Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
- Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
- The My developerWorks community is an example of a successful general community that covers a wide variety of topics.
- Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
- Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download IBM product evaluation versions or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
- Participate in developerWorks blogs and get involved in the developerWorks community.
M. 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.
developerWorks: Sign in
If you do not have an IBM ID and password, register here.
The first time you sign into developerWorks, a My developerWorks profile is created for you. This profile includes the first name, last name, and display name contained in the profile you created when you registered with My developerWorks. Selected information in your My developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.
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.
All information submitted is secure.
Trademarks | My developerWorks terms and conditions
What's this?
This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.
And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.
What's this?
Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.
(via Instapaper)
Sent from my iPad
No comments:
Post a Comment