26. Dots and Lines

In these notes you will learn:

  • How to represent (x, y) points using a single ArrayList<Point> container.
  • How null can be used with objects.

26.1. Introduction

Lets write a program that draws a dot wherever the user clicks on the screen. Between each dot a line is drawn so that we can see the path of the clicks.

Note

Drawing connected points and lines turns out to be fairly common, and so Processing provides some built-in functions to do it for you: see the documentation for beginShape if you are curious.

26.2. Designing Our Program

Lets spend a moment thinking about the overall structure and operation of our program. Whenever we click on the screen lets have a red dot appear at the click point that is connected by a straight line to the point just before the current one.

If we keep track of the positions of all the click-points, then we can draw the lines between them using the Processing line function.

So this suggests that we ought to use objects to store our (x, y) points. Lets call the class that will create these objects Point:

class Point {
  float x, y;

  // constructor
  Point(float a, float b) {
    x = a;
    y = b;
  }

} // class Point

Now we can make Point objects like this:

Point a = new Point(0, 0);   // a is (0, 0)
Point b = new Point(-2, 5);  // a is (-2, 5)

To store multiple Point objects, we can use an ArrayList:

ArrayList<Point> points = new ArrayList<Point>();

Initially, points is empty, and we can add Points to it like this:

Point a = new Point(0, 0);   // a is (0, 0)
Point b = new Point(-2, 5);  // a is (-2, 5)

points.add(a);
points.add(b);

Points can be added directly without first creating a variable, e.g.:

points.add(new Point(8, 3));   // (8, 3) added to Points
points.add(new Point(0, 1));   // (0, 1) added to Points

With the Point class in hand, lets now write a high-level sketch of the entire program:

ArrayList<Point> points;  // initially has the value null

void setup() {
  size(500, 500);
  smooth();

  points = new ArrayList<Point>();  // makes an empty ArrayList of Points
}

void draw() {
  background(255);

  // draw all the lines

  // draw all the points (on top of the lines)
}

void mousePressed() {
   // add (mouseX, mouseY) to points
}

By using comments like this, we can see the overall structure of our program without yet needing to worry about the low-level details. The next step is to replace those comments with Processing code.

26.3. Filling in the Details

Probably the easiest thing to do first is to fill in mousePressed:

void mousePressed() {
   // add (mouseX, mouseY) to points
}

Adding (mouseX, mouseY) to points requires first creating a Point objects, and then adding it to points with the add function:

void mousePressed() {
   Point p = new Point(mouseX, mouseY);
   points.add(p);
}

Next, lets write the code for drawing all the points. This is not too hard:

void draw() {
  background(255);

  // draw all the lines

  // draw all the points (on top of the lines)
  for (Point p : points) {
    fill(255, 0, 0);
    noStroke();
    ellipse(p.x, p.y, 10, 10);
  }

}

We’ve used a for-each loop to run the code between the { } on each Point object in points (using the variable p to refer to the current object). You can read this for-each loop in English like this: “For each Point p in points, do the following ...”.

Next, we need to draw the lines between the dots. The idea is to draw a line between each pair of adjacent points, where adjacent refers to the order in which the points were clicked on the screen. Fortunately, a for-each loop will process the points in points in the order they were added

So what we can do is use a for-each loop to access every point in points:

for(Point p : points) {
  // ... do something ...
}

Here, p refers to the current point. But to draw a line we need two points, and so we’re going to add a new Point variable called prev that will always refer to the point that was clicked just before p:

Point prev;
for(Point p : points) {
  line(p.x, p.y, prev.x, prev.y);
  prev = p;   // make p refer to the same Point p refers to
}

This almost works, but their is actually major problem: prev has not been given an initial value.

Handling the initial value for prev correctly turns out to be the trickiest part of this program (at least for our particular solution). Since the first point of points has no previous value, there is no previous point to assign to prev.

So what we will do is assign null to prev. Recall that null is a special value that is used with object variables to indicate they are not referring to any object (yet). That makes sense here: there’s is no point before the first one.

Here’s the next draft:

Point prev = null;
for(Point p : points) {
  line(p.x, p.y, prev.x, prev.y);
  prev = p;   // make p refer to the same Point p refers to
}

Unfortunately, this doesn’t work. When prev has the value null, you can’t call prev.x and prev.y because null doesn’t have an x or y value. So we’re going to have to check to see if prev is null before drawing a line:

Point prev = null;
for (Point p : points) {
  if (prev == null) {
    prev = p;
  } else {
    stroke(0);
    line(prev.x, prev.y, p.x, p.y);
    prev = p;
  }
}

The only time prev is null is for the first value of p in the loop. That makes sense: there is no line to draw when have only a single point. So when prev is null, we don’t draw a line and just set prev = p.

For the rest of the points in points, prev will not be null, and so we do draw the line (and also set prev to p).

26.4. Example: Bouncing Points

As a fun example, lets write a new program that is a variation of the above one that allows the points to fall down and bounce on the bottom of the screen. While not terribly practical, it is a nice-looking effect that might give you ideas for other kinds of animation.

We want this new program to work just like the previous one, except that when you press any key suddenly gravity is turned on and the dots all begin to fall downwards.

We saw in earlier notes how to make a ball fall down and bounce on the screen. The basic idea is add two new variables: gravity, to control how fast the ball falls downwards, and elasticity to control how high it bounces:

// ...

float gravity;
float elasticity;

void setup() {
  // ...
  gravity = 0;
  elasticity = 0;
}

These variables are both initially 0 because we don’t want the dots to move until a key pressed:

void keyPressed() {
  gravity = 0.1;
  elasticity = 0.8;
}

keyPressed is automatically called whenever the user presses a key on the keyboard. The particular values for gravity and elasticity were chosen after some experimentation — feel free to change them to get different effects.

Since our points are now going to move, we’re also going to need to add dx and dy to Point:

class Point {
  float x, y;

  float dx, dy;

  // constructor
  Point(float a, float b) {
    x = a;
    y = b;
    dx = 0;
    dy = 0;
  }
} // class Point

Finally, we need to modify draw() to change the position of the points:

void draw() {
  // ...

  // update the dot positions
  for(Point p : points) {
   p.x += p.dx;         // move the point
   p.y += p.dy;

   p.dy += gravity;     // increase acceleration according to gravity

   if (p.y >= 500) {    // check if ball has hit the bottom edge
     p.dy = -(p.dy * elasticity);
     p.y = 499;
   }
  }
}

This is very similar to what we did in the earlier notes on gravity. The main difference is that we are applying gravity to every object in an ArrayList.

26.5. Final Program (no bouncing)

class Point {
  float x, y;

  // constructor
  Point(float a, float b) {
    x = a;
    y = b;
  }
} // class Point

ArrayList<Point> points;

void setup() {
  size(500, 500);
  smooth();

  points = new ArrayList<Point>();
}

void draw() {
  background(255);

  // draw the lines
  Point prev = null;
  for (Point p : points) {
    if (prev == null) {
      prev = p;
    } else {
      stroke(0);
      line(prev.x, prev.y, p.x, p.y);
      prev = p;
    }
  }

  // draw the points
  for (Point p : points) {
    fill(255, 0, 0);
    noStroke();
    ellipse(p.x, p.y, 10, 10);
  }

} // draw

void mousePressed() {
  Point p = new Point(mouseX, mouseY);
  points.add(p);
}

26.6. Final Program (bouncing)

class Point {
  float x;
  float y;

  float dx;
  float dy;

  // constructor
  Point(float a, float b) {
    x = a;
    y = b;
    dx = 0;
    dy = 0;
  }
} // class Point


ArrayList<Point> points;  // initially null

float gravity;
float elasticity;

void setup() {
  size(500, 500);
  smooth();

  points = new ArrayList<Point>();
  gravity = 0;
  elasticity = 0;
}

void draw() {
  background(255);

  // draw the lines
  Point prev = null;
  for (Point p : points) {
    if (prev == null) {
      prev = p;
    }
    else {
      stroke(0);
      line(prev.x, prev.y, p.x, p.y);
      prev = p;
    }
  }

  // draw the points
  for (Point p : points) {
    fill(255, 0, 0);
    noStroke();
    ellipse(p.x, p.y, 10, 10);
  }

  // update the dot positions
  for (Point p : points) {
    p.x += p.dx;         // move the point
    p.y += p.dy;

    p.dy += gravity;     // increase acceleration according to gravity

      if (p.y >= 500) {  // check if ball has hit the bottom edge
      p.dy = -(p.dy * elasticity);
      p.y = 499;
    }
  }
} // draw

void mousePressed() {
  Point p = new Point(mouseX, mouseY);
  points.add(p);
}


void keyPressed() {
  gravity = 0.1;
  elasticity = 0.8;
}