24. Dots and Lines

In these notes you will learn:

  • How to represent arbitrarily many (x, y) points using an single ArrayList<Point> container object.
  • How null is useful when dealing with objects.

24.1. Introduction

Let’s 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: if you are curious, check out the documentation for beginShape.

24.2. Designing the Program

As always, its a good idea to take a moment to think about the overall structure and operation of the program. Whenever we click on the screen, a we’ll have a red dot appear at the click point. This dot will be connected by a line to the point that was added just before it.

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:

class Point {

    // A point has an x, y position
    float x, y;

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

    void render() {
        fill(255, 0, 0); // red point
        noStroke();

        ellipse(x, y, 10, 10);
    }
}

As we’ve seen before, this now lets us create points like this:

Point a = new Point(0, 0);  // a is (0, 0)
Point b = new point(-2, 5); // b 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); // b is (-2, 5)

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

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

.. code-block:: java

    points.add(new Point(8, 3));
    points.add(new Point(0, 1));

With the Point class in hand, we’re are ready to write a high-level sketch of entire program:

ArrayList<Point> points;  // Initially has value null.

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

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

void draw() {
    background(255);

    // draw all the lines

    // draw all the points
}

void mousePressed() {
    // add (mouseX, mouseY) to the 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 actual Processing code.

Probably the easiest thing to start with is to fill in mousePressed:

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

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

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

Next, lets write the code for drawing all of the points. This isn’t too sticky either:

void draw() {
    background(255);

    // draw all the lines

    // draw all the points
    for (Point p : points) {
        p.render();
    }
}

We’ve used a for-each loop to run the code on each Point object in points (using the variable p) to refer to the current object). Remember, in English, this can be read like this: “For each Point p in points, tell p to render”.

Next, we need to draw the lines between the dots. The idea here 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. That is, consecutive points are adjacent. Luckily, the for-each loop not only goes through all of the points in points, it does so in the order that 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 some magic ...
}

One thing is missing here. The variable p refers to the current point. But to draw a line we need two points, and so we need to also keep track of the point that came before p in the ArrayList. To do this, we’ll declare a variable prev of type Point. We want prev to always be the Point right before p.

Let’s look at an example. Suppose we have populated the ArrayList points with some points so that, conceptually, it looks something like this:

{(12, 4), (2, 56), (0, 0), (4, 10)}

At the first iteration of the for-each loop, p points at (12, 4), at the second, p points at (2, 56), and so on. We’d like prev to behave as follows:

iteration
               p
    1.    {(12, 4), (2, 56), (0, 0), (4, 10)}

            prev       p
    2.    {(12, 4), (2, 56), (0, 0), (4, 10)}

                     prev       p
    3.    {(12, 4), (2, 56), (0, 0), (4, 10)}

                              prev       p
    4.    {(12, 4), (2, 56), (0, 0), (4, 10)}

Then to draw a line we simply use the Processing line() function with the x and y values of p and prev.

To achieve this behaviour in general we can write something like this:

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

This almost works, but there is one major problem. prev has not been given an initial value. If you try to run this, Processing will complain of exactly that.

Handling the initial value for pref correctly, turns out to be the trickiest part of this program. We think of prev as have the previous value. But the first point of points doesn’t have a point that came before it, so we have to value to assign to prev.

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

So we modify our code a little bit:

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

Sadly, this doesn’t work yet. When prev has the value null, it doesn’t make sense to write prev.x or prev.y because null doesn’t have an x or y value. Trying to run the code written above will result in a NullPointerException, which will make Processing hiss at you and stop the program.

So we’re going to have to test whether prev is null or not before we use it:

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

The only time prev is null for the first value of p. After the first iteration prev will not be null, and so a line will be drawn. This corresponds to our idea that there shouldn’t be a line drawn during the first iteration.

This highlights a common use for the null value. We’ve used it here to distinguish between the special case in which there is no previous point, and all of the other cases. There are of course other ways of doing this (can you think of one?), but this way works and is clean.

24.3. Example: Bouncing Points

As a fun example, let’s write a new program that is a variation of the one we just wrote. This program allows the points to fall down and bounce on the the bottom of the screen.

We want this program to work just like the previous one – a click will create a new point and draw a line to the previous point — except that when the user presses any ley on the keyboard suddenly gravity is turned on and 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 was to add two new variables: gravity, to control how fast the ball falls downwards, and elasticity, to control how high it bounces when it hits the ground. We will use the same concept for our program:

// ...

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 is pressed:

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

Since our points are going to move, we’re also going to need to add dx and dy values to Point, and update render() to make use of these:

class Point {
    float x, y;

    float dx, dy;

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

    void render() {
        fill(255, 0, 0);
        noStroke();

        ellipse(x, y, 10, 10);
        x += dx;
        y += dy;

        if (y >= height - 1) {
            dy = -(p.dy * elasticity);
            y = height - 1 - 5;
        }
    }
}

Notice that we don’t use dx. You should try to modify the program to make use of dx.

24.4. 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);
}

24.5. 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;
}