- Two Strategies
- Strategy 1: Deriving the x and y positions from the index
- Review: Arranging items in a line
- Deriving the column (x position)
- Deriving the row (y position)
- Varying the items
- Using a function to draw each cell
- Strategy 2: Nested loops
- Drawing different items at different grid positions
- Extracting grid() into a function
Two Strategies
There are two strategies for arranging items in a grid:
- Use a single loop to iterate over the number of items; derive the x and y position from the item number.
- Use a nested loop to iterate over the x positions and, for each x, iterate over the y positions (left sketches). If the item number is required, derive it from the values of x and y (right sketch).
Strategy 1: Deriving the x and y positions from the index
Review: Arranging items in a line
This builds on Arranging a Line of Items. Here is a sketch from that page:
void setup() {
size(600, 600);
background(100);
}
void draw() {
for (int i = 0; i < 30; i++) {
int x = 30 + 50 * i;
circle(x, 100, 40);
}
}
The shape position is derived from the loop index, using the function used here.
Note: From here on, the definition of setup()
is not shown. Each of the following code samples assumes that the sketch also contains a setup()
function:
void setup() {
size(600, 600);
background(100);
}
Derive y
from i
as well.
void draw() {
for (int i = 0; i < 30; i++) {
float x = 30 + 50 * i;
float y = 30 + 10 * i;
circle(x, y, 40);
}
}
Deriving the column (x position)
Let’s start with the angled line from above, and just look at the x position.
void draw() {
for (int i = 0; i < 30; i++) {
float x = 30 + 50 * i;
float y = 30 + 50 * i;
circle(x, y, 20);
}
}
Instead of a diagonal line, where each x is greater than the previous x and each y is greater than the previous y, we would like to arrange the items on a grid. Here, each item is labelled with its index number. The x value increases from items 0 to 4, and then it resets back to the left side and increases again to 9, and so on.
We can use the modulo (remainder) operator %
to chop it into pieces. For an input sequence i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 etc., the modulo operator i % 5 produces 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, etc. The sketch then multiples this by 50 to make the result more visible: 0, 50, 100, 150, 200, 0, 50, 100, 150, 200, 0, 50, etc.
Note: A mnemonic for %
is that it is like division /
except that its value is what’s left over (the °
s).
void draw() {
for (int i = 0; i < 20; i++) {
int column = i % 5;
float x = 30 + 50 * column;
float y = 30 + 50 * i;
circle(x, y, 20);
}
}
Deriving the row (y position)
Now let’s compute the y position. Since there are five items per row, the column is just the number of the item divided by five, and rounded down. For an input sequence i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 etc., dividing by 5 produces 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, etc. Putting this into an int type variable produces 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, etc.
void draw() {
for (int i = 0; i < 20; i++) {
int row = i/5;
float x = 30 + 50 * i;
float y = 30 + 50 * row;
circle(x, y, 20);
}
}
Putting this together:
void draw() {
for (int i = 0; i < 20; i++) {
int row = i / 5;
int column = i % 5;
float x = 30 + 50 * column;
float y = 30 + 50 * row;
circle(x, y, 20);
}
}
Varying the items
We can use the row and column in other ways. This sketch gives each circle a size that is a function of its row and column.
void draw() {
background(100);
for (int i = 0; i < 30; i++) {
int row = i / 5;
int column = i % 5;
float x = 30 + 50 * column;
float y = 40 + 50 * row;
float size = map(row - column, 0, 10, 10, 50);
circle(x, y, size);
}
}
Using a function to draw each cell
Instead of calling circle()
, we can use our own function.
void draw() {
background(100);
for (int i = 0; i < 30; i++) {
int row = i / 5;
int column = i % 5;
float x = 30 + 50 * column;
float y = 40 + 50 * row;
myShape(x, y, row, column);
}
}
void myShape(float x, float y, int row, int column) {
float size = map(row - column, 0, 10, 10, 50);
circle(x, y, size);
}
The following code replaces just the implementation of the myShape
function. The code in draw()
remains exactly the same.
void myShape(float x, float y, int row, int column) {
float size = map(row - column, 0, 10, 30, 40);
circle(x, y, size / 2);
square(x, y, size);
circle(x + size, y, size / 2);
circle(x, y + size, size / 2);
circle(x + size, y + size, size / 2);
}
Strategy 2: Nested loops
An alternative to deriving the row and column from the linear index i
, is to use a nested loop (one loop inside of another loop) to step through the x and y values, or the row and column numbers, directly.
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 50 * column;
float y = 40 + 50 * row;
circle(x, y, 20);
}
}
}
If we need i
(the index number), we can derive it from the row and column numbers.
void setup() {
size(600, 600);
background(100);
}
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
int i = 5 * row + column;
float x = 30 + 50 * column;
float y = 40 + 50 * row;
fill(255);
circle(x, y, 30);
fill(0);
text(i, x, y);
}
}
}
Or we can initialize it 0, and increment it each time through the (inner) loop, to keep count of how many items we've drawn.
void draw() {
int i = 0;
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 50 * column;
float y = 40 + 50 * row;
i++;
fill(255);
circle(x, y, 30);
fill(0);
text(i, x, y);
}
}
}
Drawing different items at different grid positions
Above, the same shape is drawn at each grid position. We will add a second function, that draws a second shape. First, rename myShape
to decoratedSquare
. This doesn’t change the behavior of the sketch, but it will make it more readable once we add the second shape, since then we can use descriptive names instead of decoratedSquare1
and decoratedSquare2
.
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
decoratedSquare(x, y);
}
}
}
void decoratedSquare(float x, float y) {
push();
translate(x, y);
circle(0, 0, 25);
square(0, 0, 50);
circle(50, 0, 25);
circle(0, 50, 25);
circle(50, 50, 25);
pop();
}
Add a second drawing function circles()
and call it instead.
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
circles(x, y);
}
}
}
void circles(float x, float y) {
push();
translate(x, y);
fill(255); // white
circle(0, 10, 25);
fill(color(#F8FC05)); // yellow
circle(10, 10, 25);
fill(color(#4D980C)); // green
circle(10, 0, 25);
pop();
}
Now, we can call circles()
for some cells and decoratedSquare()
for others.
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
if ((row + column) % 3 > 0) {
circles(x, y);
} else {
decoratedSquare(x, y);
}
}
}
}
void decoratedSquare(float x, float y) {
push();
translate(x, y);
circle(0, 0, 25);
square(0, 0, 50);
circle(50, 0, 25);
circle(0, 50, 25);
circle(50, 50, 25);
pop();
}
void circles(float x, float y) {
push();
translate(x, y);
fill(255); // white
circle(0, 10, 25);
fill(color(#F8FC05)); // yellow
circle(10, 10, 25);
fill(color(#4D980C)); // green
circle(10, 0, 25);
pop();
}
Animate the circles, by incorporating data from millis()
. Note that you need to use a decimal 500.0 in the division to keep dx smooth.
void circles(float x, float y) {
push();
translate(x, y);
float dx = map(cos(millis() / 500.0), -1, 1, 0, 10);
float size = 25;
fill(255); // white
circle(0, dx, size);
fill(color(#F8FC05)); // yellow
circle(dx, dx, size);
fill(color(#4D980C)); // green
circle(dx, 0, size);
pop();
}
Extracting grid()
into a function
Let’s extract the code that draws the grid into its own function, so that we can call it several times in order to create several grids.
void draw() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
circles(x, y);
}
}
}
⇒
void draw() {
grid();
}
void grid() {
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 6; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
circles(x, y);
}
}
}
Change rows
and columns
from literal numbers 5
to parameters. This allows the function that calls grid()
to specify the number of rows and columns.
void draw() {
grid(5, 6);
}
void grid(int rows, int columns) {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
circles(x, y);
}
}
}
Call grid()
twice, to draw two grids of different sizes.
void draw() {
grid(5, 6);
translate(width / 2, height / 2);
grid(3, 4);
}
void grid(int rows, int columns) {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
float x = 30 + 100 * column;
float y = 40 + 100 * row;
circles(x, y);
}
}
}
Original p5.js version ©2020–2022 by Oliver Steele. Updated for Processing by Margaret Minsky.