By Ben Fry
Book Price: $39.99 USD
£24.99 GBP
PDF Price: $31.99
Cover | Table of Contents | Colophon
line(15, 25, 70, 90);
size(400, 400); background(192, 64, 0); stroke(255); line(150, 25, 270, 350);
saveFrame( ) function. Adding
saveFrame( ) at the end of draw( ) will produce a numbered sequence of
TIFF-format images of the program's output, named screen-0001.tif, screen-0002.tif, and so on. A new file will
be saved each time draw( ) runs.
Watch out because this can quickly fill your sketch folder with
hundreds of files. You can also specify your own name and file type
for the file to be saved with a command like:saveFrame("output.png")#s (hash marks) where the numbers should be
placed:saveFrame("output-####.png");stroke(
), line( ), and background( ), as well as others that have not
yet been covered. To see how a function works, select its name, and then
right-click and choose Find in Reference from the pop-up menu (Find in
Reference can also be found beneath the Help menu). That will open the
reference for that function in your default web browser.size( ) command also sets
the global variables width and
height. For objects whose size is
dependent on the screen, always use the width and height variables instead of a number (this
prevents problems when the size( )
line is altered):size(400, 400); // The wrong way to specify the middle of the screen ellipse(200, 200, 50, 50); // Always the middle, no matter how the size( ) line changes ellipse(width/2, height/2, 50, 50);
size(
) command specified only a width and height for the new
window. An optional parameter to the size(
) method specifies how graphics are rendered. A renderer handles how the Processing API is
implemented for a particular output method (whether the screen, or a
screen driven by a high-end graphics card, or a PDF file). Several
renderers are included with Processing, and each has a unique
function. At the risk of getting too far into the specifics, here are
examples of how to specify them with the loadStrings( ), loadBytes( )split( )for( ), if
(item[i].startsWith( ))min( ), max( ), abs(
)map( ), beginShape( ), endShape( )fill( ), strokeWeight( ), smooth( )mouseMoved( ), mouseDragged( ), keyPressed( )import processing.xml.*;
import command. In Processing, this line
also determines what code is packaged with a sketch when it is
exported as an applet or application.xml:XMLElement xml = new XMLElement(this, "sites.xml");
xml variable can now be
manipulated as necessary to read the contents. The full example can be
seen in the reference for its class, XMLElementPImage mapImage;
void setup( ) {
size(640, 400);
mapImage = loadImage("map.png");
}
void draw( ) {
background(255);
image(mapImage, 0, 0);
}loadImage(
), convey the purpose of the calls in simple language. What
you may need to get used to is dividing your code into functions such
as setup( ) and PImage mapImage;
void setup( ) {
size(640, 400);
mapImage = loadImage("map.png");
}
void draw( ) {
background(255);
image(mapImage, 0, 0);
}loadImage(
), convey the purpose of the calls in simple language. What
you may need to get used to is dividing your code into functions such
as setup( ) and draw( ), which determine how the code is
handled. After clicking the Run button, the setup( ) method executes once. After
setup( ) has completed, the
draw( ) method runs repeatedly. Use
the setup( ) method to load images,
fonts, and set initial values for variables. The draw( ) method runs at 60 frames per second
(or slower if it takes longer than 1/60th of a second to run the code
inside the draw( ) method); it can
be used to update the screen to show animation or respond to mouse
movement and other types of input.loadImage( ) function reads an image from
the data folder (URLs or absolute paths also work). The PImage class is a container for image data,
and the Table class is just two
pages of code, and we'll get into its function later. In the meantime,
suffice it to say that it reads a file as a grid of rows and columns.
The class has methods to get an int,
float, or String for a specific row and column. To get
float values, for instance, use the following format:table.getFloat(row, column)
locationTable and
use the locationTable.getFloat( )
function to read each location's coordinates (x and y
values).PImage mapImage;Table locationTable; int rowCount; void setup( ) { size(640, 400); mapImage = loadImage("map.png"); // Make a data table from a file that contains // the coordinates of each state. locationTable = new Table("locations.tsv"); // The row count will be used a lot, so store it globally. rowCount = locationTable.getRowCount( ); } void draw( ) { background(255); image(mapImage, 0, 0);
Table object and load the data from a file
called random.tsv, available at
http://benfry.com/writing/map/random.tsv.for loop to walk through each line of the data
table and check to see whether each value is bigger than the maximum
found so far, or smaller than the minimum. To begin, the dataMin variable is set to MAX_FLOAT, a built-in value for the maximum
possible float value. This ensures
that dataMin will be replaced with
the first value found in the table. The same is done for dataMax, by setting it to MIN_FLOAT. Using 0 instead of MIN_FLOAT and MAX_FLOAT will not work in cases where the
minimum value in the data set is a positive number (e.g., 2.4) or the
maximum is a negative number (e.g., −3.75).PImage mapImage;
Table locationTable;
int rowCount;
Table dataTable;
float dataMin = MAX_FLOAT;
float dataMax = MIN_FLOAT;
void setup( ) {
size(640, 400);
mapImage = loadImage("map.png");
locationTable = new Table("locations.tsv");
rowCount = locationTable.getRowCount( );
// Read the data table.
dataTable = new Table("random.tsv");
// Find the minimum and maximum values.
for (int row = 0; row < rowCount; row++) {
float value = dataTable.getFloat(row, 1);
if (value > dataMax) {
dataMax = value;
}
if (value < dataMin) {
dataMin = value;
}
}
}drawData( ) function
is introduced, which takes x and y coordinates as parameters, along with
an abbreviation for a state. The drawData(
) function grabs the float value from column 1 based on a
state abbreviation (which can be found in column 0).map( ) function, but you don't have
to use ellipses or colors to plot your data points. You could draw an
image at each location, varying its size based on the data. Or some
points could be hidden or reorganize themselves in various ways. The
points might refer to anything from chain coffee shops per capita to
poverty levels in each state.PImage mapImage;
Table nameTable;
int currentRow = −1;
PrintWriter writer;
void setup( ) {
size(640, 400);
mapImage = loadImage("map.png");
nameTable = new Table("names.tsv");
writer = createWriter("locations.tsv");
cursor(CROSS); // make easier to pinpoint a location
println("Click the mouse to begin.");
}
void draw( ) {
image(mapImage, 0, 0);
}
void mousePressed( ) {
if (currentRow != −1) {
String abbrev = nameTable.getRowName(currentRow);
writer.println(abbrev + "\t" + mouseX + "\t" + mouseY);
}
currentRow++;
if (currentRow == nameTable.getRowCount( )) {
// Close the file and finish.
writer.flush( );
writer.close( );
exit( );
} else {
// Ask for the next coordinate.
String name = nameTable.getString(currentRow, 1);
println("Choose location for " + name + ".");
}
}Table class from the previous chapter:float values, making it more efficient than
the previous version, which simply converted the data whenever getString( ), getFloat( ), or getInt( ) were used.FloatTable class has
methods for calculating the min and max for the rows and columns. These
methods are worth discussing because they are important in later code.
The following example calculates the minimum value for a column
(comments denote important portions of the code): float getColumnMax(int col) {
// Set the value of m arbitrarily high, so the first value
// found will be set as the maximum.
float m = MIN_FLOAT;
// Loop through each row.
for (int row = 0; row < rowCount; row++) {
// Only consider valid data elements (see later text).
if (isValid(row, col)) {
// Finally, check to see if the value
// is greater than the maximum found so far.
if (data[row][col] > m) {
m = data[row][col];
}
}
}
return m;
}isValid( ) method is
important because most data sets have incomplete data. In the milk-tea-coffee.tsv file, all of the data is
valid, but in most data sets (including others used in this chapter),
missing values require extra consideration.FloatTable data;
float dataMin, dataMax;
void setup( ) {
data = new FloatTable("milk-tea-coffee.tsv");
dataMin = 0;
dataMax = data.getTableMax( );
}dataMin value would produce a chart that
looked as though the beverage history included periods of no (or nearly
no) tea consumption in the U.S. In addition, if the value is 6, it's
important that the relative difference seen by the viewer is not just
0.9, but that it shows the full range from 0 up to 5.1 and how it
compares to a value of 6.plotX1, plotY1, plotX2, and plotY2 variables define the corners of the
plot. To provide a nice margin on the left, set plotX1 to 50, and then set the plotX2 coordinate by subtracting this value
from width. This keeps the two sides
even, and requires only a single change to adjust the position of both.
The same technique is used for the vertical location of the
plot:FloatTable data; float dataMin, dataMax;float plotX1, plotY1; float plotX2, plotY2; int yearMin, yearMax; int[] years; void setup( ) { size(720, 405); data = new FloatTable("milk-tea-coffee.tsv"); years = int(data.getRowNames( )); yearMin = years[0]; yearMax = years[years.length - 1]; dataMin = 0; dataMax = data.getTableMax( ); // Corners of the plotted time series plotX1 = 50; plotX2 = width - plotX1; plotY1 = 60; plotY2 = height - plotY1; smooth( ); }
draw( ) method that
sets the background to a light gray and draws a filled white rectangle
for the plotting area. That will make the plot stand out against the
background, rather than a color behind the plot itself—which can muddy
its appearance.rect( ) function normally
takes the form rect(x, y, width,
height), but rectMode(CORNERS) changes the parameters to
rect(left, top, right, bottom), which
is useful because our plot's shape is defined by the corners. Like other
methods that affect drawing properties, such as fill( ) and stroke(
), rectMode( ) affects all
geometry that is drawn after it until the next time rectMode( ) is called:void draw( ) {
background(224);
// Show the plot area as a white box.
fill(255);
rectMode(CORNERS);
noStroke( );
rect(plotX1, plotY1, plotX2, plotY2);
strokeWeight(5);
// Draw the data for the first column.
stroke(#5679C1);
drawDataPoints(0);
}
// Draw the data as a series of points.
void drawDataPoints(int col) {
int rowCount = data.getRowCount( );
for (int row = 0; row < rowCount; row++) {
if (data.isValid(row, col)) {
float value = data.getFloat(row, col);
float x = map(years[row], yearMin, yearMax, plotX1, plotX2);
float y = map(value, dataMin, dataMax, plotY2, plotY1);
point(x, y);
}
}
}draw( ) method to write the name of the column
with the text( ) method:FloatTable data; float dataMin, dataMax; float plotX1, plotY1; float plotX2, plotY2;int currentColumn = 0; int columnCount; int yearMin, yearMax; int[] years; PFont plotFont; void setup( ) { size(720, 405); data = new FloatTable("milk-tea-coffee.tsv"); columnCount = data.getColumnCount( ); years = int(data.getRowNames( )); yearMin = years[0]; yearMax = years[years.length - 1]; dataMin = 0; dataMax = data.getTableMax( ); // Corners of the plotted time series plotX1 = 50; plotX2 = width - plotX1; plotY1 = 60; plotY2 = height - plotY1; plotFont = createFont("SansSerif", 20); textFont(plotFont); smooth( ); } void draw( ) { background(224); // Show the plot area as a white box. fill(255); rectMode(CORNERS); noStroke( ); rect(plotX1, plotY1, plotX2, plotY2); // Draw the title of the current plot. fill(0); textSize(20); String title = data.getColumnName(currentColumn); text(title, plotX1, plotY1 - 10); stroke(#5679C1); strokeWeight(5); drawDataPoints(currentColumn); }
text( ) line draws the text
10 pixels above plotY1, which
represents the top of the plot, and the drawDataPoints( ) line uses currentColumn instead of just 0. Results are
shown in .
createFont( ) function is
used to create a font from one of the built-in typefaces. The built-in
typefaces are Serif, SansSerif, Monospaced, Dialog, and DialogInput; they map to the default fonts on
each operating system. On Mac OS X, for instance, SansSerif maps to Lucida Sans, whereas on
Windows it maps to Arial. The default fonts are useful when you don't
want to deal with the Create Font tool, but the font choices are not
particularly inspiring, and they don't guarantee consistent output
across different operating systems. For instance, making pixel-level
decisions with a built-in font is a bad idea because the shaping and
spacing of the characters can be significantly different on other
operating systems.yearInterval variable to the beginning of
the code before setup( ):int yearInterval = 10;
void drawYearLabels( ) {
fill(0);
textSize(10);
textAlign(CENTER, TOP);
for (int row = 0; row < rowCount; row++) {
if (years[row] % yearInterval == 0) {
float x = map(years[row], yearMin, yearMax, plotX1, plotX2);
text(years[row], x, plotY2 + 10);
}
}
}