Java Graph Applet evolution

Evolution of a Simple Applet...

...into something more complex? (that's progress?)


What I will be presenting in this page is an example of transforming a basic applet into something that has more features and is structured in a way to make it easy (hopefully) to modify, expand, and re-use.

The initial basic applet is a simple function you can plot. The code comes directly from (and therefore is copyright to) Sun. It's a very simple program that plots a function to the screen but it has no scaling or unit markings. It looks like this:
import java.awt.Graphics;

public class GraphApplet extends java.applet.Applet {
    double f(double x) {
        return (Math.cos(x/5) + Math.sin(x/7) + 2) * size().height / 4;
    }

    public void paint(Graphics g) {
        for (int x = 0 ; x < size().width ; x++) {
            g.drawLine(x, (int)f(x), x + 1, (int)f(x + 1));
        }
    }
}

and it produces an result of:

Now let's take this program and make a very simple change to it that seperates the code into 2 parts. Part 1 will be the setup and initialization part, part 2 will contain the function and graphing part. I'll show you the code and include the applet, then on the other side of it explain more what changes I made to the code. I will be using some color coding to show major changes or features.
File: GraphAppletv2.java
import java.applet.*;
import java.awt.*;

public class GraphAppletv2 extends java.applet.Applet {

  public void init(){
    Canvas funcShow = new DrawFuncv2();   // Canvas with the function

    setLayout(new BorderLayout());     // one of a handfull of layouts
    add("Center", funcShow);
  }

}
File: DrawFuncv2.java
import java.applet.*;
import java.awt.*;

public class DrawFuncv2 extends Canvas {
    double f(double x) {
        return (Math.cos(x/5) + Math.sin(x/7) + 2) * size().height / 4;
    }

    public void paint(Graphics g) {
        for (int x = 0 ; x < size().width ; x++) {
            g.drawLine(x, (int)f(x), x + 1, (int)f(x + 1));
        }
    }
}
and it produces an result of:

Ok, first a word on the file structure and compiling this code with javac. I tend to use UNIX based machines, either Linux or Irix is I can. On these machines using the current javac you will need to have a seperate file for each class. So in the above example note that I havelisted two files as being viewed, GraphAppletv2.java and DrawFuncv2.java. My compile command is going to be something like:

javac GraphAppletv2.java DrawFuncv2.java

You can simply see that the file DrawFuncv2.java is my original GraphApplet.java with a new name and now extends Canvas, not extends Applet. I then created a new GraphAppletv2.java (I am using the v* notation to keep the classes straight for all the applet tags in this file) that now has an init() method init to creat a instance of the DrawFuncv2 class and set the Layouts.

The three lines in the init() method of GraphAppletv2.java create first an instance of the DrawFuncv2 class, then define a type of layout that will be used for the various components we will add and then add those components. In this case we have only one component, so you may wonder what the point of a lyout manager is. Indeed this is why the original code was as simple as it was. You'll notice that these two program look exactly the same when they run. The difference is that the second is more flexiable if we want to add in the future more elements.

Let's go ahead and make those changes next. I am going to add a field and a slider bar. I will then have the slider bar update the value in the field as it is moved. Again I will show the code and the applet (with color coding of major changes) and then on the other side try and go into more detail of the mess I am making of this once simple program.
File: GraphAppletv3.java
import java.applet.*;
import java.awt.*;

public class GraphAppletv3 extends java.applet.Applet {
Label v1;

  public void init(){
    Canvas funcShow = new DrawFuncv3();   // Canvas with the function 
    Panel conPanel = new Panel();         // Panel to hold GUI elements
    v1 = new Label("0");                  // A new label, initialized to 0
    
    // Set the conPanel layout and add two elements to it
    conPanel.setLayout(new BorderLayout());
    conPanel.add("Center", v1);
    conPanel.add("South", new Scrollbar(Scrollbar.HORIZONTAL, 1, 0, 1, 10));

    setLayout(new BorderLayout());     // one of a handfull of layouts
    add("Center", funcShow);
    add("South", conPanel);
  }

  public boolean handleEvent(Event evt) {
    if (evt.target instanceof Scrollbar) {
        int v=((Scrollbar)evt.target).getValue();
        v1.setText(String.valueOf(v));
    }
    return true;
  }
}
File: DrawFuncv3.java
import java.applet.*;
import java.awt.*;

public class DrawFuncv3 extends Canvas {
    double f(double x) {
        return (Math.cos(x/5) + Math.sin(x/7) + 2) * size().height / 4;
    }

    public void paint(Graphics g) {
        for (int x = 0 ; x < size().width ; x++) {
            g.drawLine(x, (int)f(x), x + 1, (int)f(x + 1));
        }
    }
}
and it produces an result of:

Ok, what have I done here. Note first that DrawFuncv3.java is the same as DrawFuncv2.java except for the name change to stay consistant in this example. The changes to GraphAppletv3.java from the previous version have all been color coded red.

So, let's talk about the mess I have made now. Not that first I have added a class variable to my code called v1. This is of type lable and is class level because I want to use it in both methods of the GraphAppletv3.java file. Next I will make an new Panel and create the v1 Label. After that note that I set the layout type of the conPanel Panel. Notice how that is done with conPanel.setLayout.... You will want to get used to this " . " style notation. I add now the Label v1 to the center and a scrollbar to the bottom. We will be nesting layouts in this example, this is the way you will create more complex GUI's in your programs. There is not alot of low level control to the GUI placement (not easy ones anyway) so at the begining you will need to learn the layout types and play with nesting them to get the interface you want. Last we set the layout for the whole applet and add the Canvas funcShow and the Panel conPanel.

Ok, next let's try and attach the number value from the scrollbar not only to this field but to the function we are graphing to see if we can create a link between the scrollbar and the graphic. Again I will show the code and running program and then explain what is going on.

NOTE: There is an error in this approach (in the following applet), but I want to demonstrate the error, so if it seems to not be working, don't panic, read on. ;)


File: GraphAppletv4.java
import java.applet.*;
import java.awt.*;

public class GraphAppletv4 extends java.applet.Applet {
Label v1;

  public void init(){
    Canvas funcShow = new DrawFuncv4();   // Canvas with the function
    Panel conPanel = new Panel();         // Panel to hold GUI elements
    v1 = new Label("0");                  // A new label, initialized to 0

    // Set the conPanel layout and add two elements to it
    conPanel.setLayout(new BorderLayout());
    conPanel.add("Center", v1);
    conPanel.add("South", new Scrollbar(Scrollbar.HORIZONTAL, 1, 0, 1, 10));

    setLayout(new BorderLayout());     // one of a handfull of layouts
    add("Center", funcShow);
    add("South", conPanel);
  }

  public boolean handleEvent(Event evt) {
    if (evt.target instanceof Scrollbar) {
        int v=((Scrollbar)evt.target).getValue();
        DrawFuncv4.i=((Scrollbar)evt.target).getValue();
        v1.setText(String.valueOf(v));
	
    }
    return true;
  }
}
File: DrawFuncv4.java
import java.applet.*;
import java.awt.*;

public class DrawFuncv4 extends Canvas { 
static int i;

    double f(double x) {
        return (Math.cos(x*i/5) + Math.sin(x*i/7) + 2) * size().height / 4;
    }

    public void paint(Graphics g) {
        for (int x = 0 ; x < size().width ; x++) {
            g.drawLine(x, (int)f(x), x + 1, (int)f(x + 1));
        }
    }
}
and it produces an result of:

Ok, the error should be easy to see. Our graph is not updating as we change the value in the scrollbar. If you change the value, try hiding the window or iconizing it then bringing it back. The graph will most likely draw now. Our graph is not being updated as we change the value. So how do we fix this? Well, first what did we do to make this connection at all. Ok, look at the code which is colored red to see what I added.

The part added to GraphAppletv4.java is easy to spot. A reference to the variable DrawFuncv4.i, note how this is done by referencing the class the a period and the variable name. As I mentioned before, this " . " notation will be important for a programmer to get comfortable reading and understanding.

In DrawFuncv4.java I added a static int i. This must be static because we don't want to be changing between calls. That is, once it is set to 5 we want it to stay five for the life of the class unless we decide to change it. I then simply modified the equation so we could visually see the change.

Ok, let's try and fix this update part so it updates on it's own. Once again I will show the code and the running applet and then explain what is occuring. This code will also have an aspect of it that we will need to fix and again I will explain on the other side of the code.
File: GraphAppletv5.java
import java.applet.*;
import java.awt.*;

public class GraphAppletv5 extends java.applet.Applet implements Runnable {
Label v1;
Thread runner;
DrawFuncv5 funcShow = new DrawFuncv5();       // Canvas with the function

  public void start() {
    if (runner == null) {
      runner = new Thread(this);
      runner.start();
    }
  }

  public void stop() {
    if (runner != null) { 
      runner.stop();
      runner = null;
    }
  }

  public void run() {
    while (true) {
      funcShow.reset();
      try { Thread.sleep(1000); }
      catch (InterruptedException e) { }
    }
  }

  public void init(){
    Panel conPanel = new Panel();         // Panel to hold GUI elements
    v1 = new Label("0");                  // A new label, initialized to 0

    // Set the conPanel layout and add two elements to it
    conPanel.setLayout(new BorderLayout());
    conPanel.add("Center", v1);
    conPanel.add("South", new Scrollbar(Scrollbar.HORIZONTAL, 1, 0, 1, 10));

    setLayout(new BorderLayout());     // one of a handfull of layouts
    add("Center", funcShow);
    add("South", conPanel);
  }

  public boolean handleEvent(Event evt) {
    if (evt.target instanceof Scrollbar) {
        int v=((Scrollbar)evt.target).getValue();
        DrawFuncv5.i=((Scrollbar)evt.target).getValue();
        v1.setText(String.valueOf(v));
    }
    return true;
  }
}

File: DrawFuncv5.java
import java.applet.*;
import java.awt.*;

public class DrawFuncv5 extends Canvas {
static int i;

    public void reset() {
      repaint();
    }

    double f(double x) {
        return (Math.cos(x*i/5) + Math.sin(x*i/7) + 2) * size().height / 4;
    }

    public void paint(Graphics g) {
        for (int x = 0 ; x < size().width ; x++) {
            g.drawLine(x, (int)f(x), x + 1, (int)f(x + 1));
        }
    }
}

and it produces an result of:

Ok, right off you see the that the flicker in this applet that we don't want to have. We will have to do something about this. Double buffering comes to mind. Also I had to implement threads in this program, so there is alot of changes to go over. Note that one of them is potentially an error in the previous 4 instances of this code. I will review this and make a report on what I find out.

...to be continued...