Developing a package for mathematical illustration


Creating figures
Packages
Applets

Based on notes Developing a package for mathematical illustration
ADDANCHOR cubic FIRSTANCHOR Graph of a function
There are some basic elements here: a grid, axes, a graph, points and a line. Our goal is to develop some classes that can be easily re-used to create illustrations like this.

Figure API ANCHOR

Let's think about what we'll need to draw this figure.

In fact, each of the items above will be an object. ANCHOR BoundingBox

The viewing rectangle will be important for setting up the change of coordinates and for drawing both the grid and the graph. We will create a class BoundingBox to help us pass the information around easily.

public class BoundingBox {
    public double llx, lly, urx, ury;
    public BoundingBox(double lowerLeftX, double lowerLeftY,
                       double upperRightX, double upperRightY) {
        llx = lowerLeftX;  lly = lowerLeftY;
        urx = upperRightX; ury = upperRightY;
    }
}

Notice that all a BoundingBox does is store information. ANCHOR FigurePanel

We will create an extension of JPanel, called FigurePanel, in a moment. Let's think about what we want out of it.

ANCHOR FigurePanel's constructor

    public FigurePanel(double llx, double lly, double urx, double ury) {
        bbox = new BoundingBox(llx, lly, urx, ury);
    }
ANCHOR FigurePanel's paintComponent method

Here we will simply determine the AffineTransform and ask the grid and graph to draw themselves on the Graphics2D.

    public void paintComponent(Graphics gfx) {
        super.paintComponent(gfx);
        Rectangle size = getBounds();
        if (size.width != width || size.height != height
	    || transform == null) {
            width = size.width;  height = size.height;
            float ratioX = (float) (width / (bbox.urx - bbox.llx));
            float ratioY = (float) (-height/(bbox.ury - bbox.lly));
            transform = new AffineTransform();
            transform.scale(ratioX, ratioY);
            transform.translate(-bbox.llx, -bbox.ury);
	}	
        Graphics2D g = (Graphics2D) gfx;
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			   RenderingHints.VALUE_ANTIALIAS_ON);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
			   RenderingHints.VALUE_STROKE_PURE);
        for (int i = 0; i < plotables.size(); i++)
            ((Plotable) plotables.elementAt(i)).plot(g, bbox, transform);
    }
ANCHOR What is a plotable?

The graphical elements will simply contain code for drawing themselves. We will have them extend a class called Plotable.

Axes
GraphicalEllipse
GraphicalFunction
GraphicalLine
GraphicalPoint
Grid
ANCHOR Plotable

import java.awt.*;
import java.awt.geom.*;

public abstract class Plotable {
    Color color = Color.black;
    public abstract void plot(Graphics2D g, BoundingBox bbox, 
			      AffineTransform transform);
    public void setColor(Color c) {  color = c; }
}
ANCHOR How do we extend Plotable?

Here is Grid.java:

import java.awt.*;
import java.awt.geom.*;

public class Grid extends Plotable {
    public void plot(Graphics2D g, BoundingBox bbox,
		     AffineTransform transform) {
        GeneralPath path = new GeneralPath();
        int minX = (int) Math.floor(Math.min(bbox.llx, bbox.urx));
        int maxX = (int) Math.ceil(Math.max(bbox.llx, bbox.urx));
        for (int x = minX; x <= maxX; x++) {
            path.moveTo(x, (float) bbox.lly);
            path.lineTo(x, (float) bbox.ury);
        }
        int minY = (int) Math.floor(Math.min(bbox.lly, bbox.ury));
        int maxY = (int) Math.ceil(Math.max(bbox.lly, bbox.ury));
        for (int y = minY; y <= maxY; y++) {
            path.moveTo((float) bbox.llx, y);
            path.lineTo((float) bbox.urx, y);
        }
        g.setPaint(color);
        g.draw(transform.createTransformedShape(path));
    }
}

Notice that this arrangement allows us to concentrate on drawing the grid in this class without worrying about the other details. ANCHOR GraphicalPoint.java

Here is CubicPoint.java:

import java.awt.*;
import java.awt.geom.*;

public class GraphicalPoint extends Plotable implements Moveable {
    public static final int CIRCLE = 0;
    public static final int SQUARE = 1;
    int style = CIRCLE;
    double size = 2;
    public double x, y;
    Shape shape;
    
    public GraphicalPoint(double xp, double yp) {
	x = xp;  y = yp;
    }

    public void setPoint(double xp, double yp) {
	x = xp;  y = yp;
    }

    public void setSize(double s) { size = s; }
    
    public void setStyle(int s) { style = s; }

    public void plot(Graphics2D g, BoundingBox bbox,
		     AffineTransform transform) {
	Point2D.Double point = new Point2D.Double(x, y);
	transform.transform(point, point);
	if (style == CIRCLE) 
	    shape = 
		new Ellipse2D.Double(point.x - size, point.y - size,
				    2*size, 2*size);
	else shape = 
		new Rectangle2D.Double(point.x - size, point.y - size,
				    2*size, 2*size);
	g.setPaint(color);
	g.fill(shape);
	g.setPaint(Color.black);
	g.draw(shape);
    }
}
ANCHOR Let's draw a simple figure ANCHOR Graphing functions

We first need to model a function

public interface Function {
    public double valueAt(double x, double[] params);
}

For instance, here is the method in an implementation of Function.

    public double valueAt(double x, double[] params) {
	return x*x*x+params[0]*x;
    }
ANCHOR GraphicalFunction

import java.awt.*;
import java.awt.geom.*;

public class GraphicalFunction extends Plotable {
    Function function;
    Stroke stroke;
    int steps = 50;
    double[] params;
    double x0, x1;
    boolean domainSet = false;

    public GraphicalFunction(Function f, double[] p) {
        function = f;  params = p;
        stroke = new BasicStroke(1f);
    }

    public void setSteps(int s) { steps = s; }

    public void setStroke(Stroke s) { stroke = s; }

    public void setDomain(double xp0, double xp1) {
	domainSet = true;
	x0 = xp0;  x1 = xp1;
    }

    public void plot(Graphics2D g, BoundingBox bbox,
		     AffineTransform transform) {
	if (!domainSet) {
	    x0 = bbox.llx;  x1 = bbox.urx;
	}
        double stepsize = (x1 - x0)/steps;
        GeneralPath path = new GeneralPath();
	Point2D.Float point = 
	    new Point2D.Float((float) x0, (float) function.valueAt(x0, params));
	transform.transform(point, point);
        path.moveTo(point.x, point.y);
        for (int i = 1; i <= steps; i++) {
            double x = x0 + i*stepsize;
	    point = new Point2D.Float((float) x,
				      (float) function.valueAt(x, params));
	    transform.transform(point, point);
            path.lineTo(point.x, point.y);
	}
        g.setPaint(color);
        Stroke savedStroke = g.getStroke();
	g.setStroke(stroke);
	g.draw(path);
        g.setStroke(savedStroke);
    }
}
ANCHOR Let's add a function ADDANCHOR package ANCHOR Putting everything into a package

By now, we have quite a few classes that work together to create illustrations. Let's see how to bundle them together into a package.

Our classes are:

    Axes		FigurePanel	Grid		GraphicalPoint
    BoundingBox		GraphicalPoint	Plotable
ANCHOR To create a package called figure:

ANCHOR Interactivity

We can set up our figure package to allow for interactive diagrams.

LineMover.java ANCHOR Get the figure package

Download the figure package and unpack it using the command tar xzvf figure.tgz ADDANCHOR applets ANCHOR Applets An example

Note:
  • Applets are embedded in another program, typically a web browser, and distributed through a web server.

  • The range of operations applets can perform is somewhat restricted. File operations and printing are not allowed until the applet is digitally signed.
ANCHOR Writing applets

Note:
  • Applets can created by writing a subclass of JApplet, which is itself a subclass of Panel. The application running the applet, usually a web browser, will provide a Frame in which the applet will reside.

  • Components are generally added to the JApplet rather than painting directly onto it.

  • Applications "stand alone". The execution of the code clearly begins with public static void main(String[] args).

  • It may not be immediately clear where the execution of an applet begins. This is because the applet is being run by another program, such as a web browser.
ANCHOR Converting applications to applets

Note:
  • Typically speaking, we do not write a constructor for a JApplet nor we do not instantiate it explicitly. The web browser will do this.

  • The initialization is performed by overriding the method public void init().

  • Converting an application to an applet is usually as simple as moving the content in the application's main method to the applet's init method.
ANCHOR An example

Here is an example of how an application, our CubicGraph from Chapter 3, can be converted to an applet.

CubicGraph.java
CubicGraphApplet.java
ANCHOR Another example

LineMoverApplet.java
ANCHOR Preparing the HTML file

 lineMover.html 
LASTANCHOR Testing the applet

You can test the applet using a program called appletviewer:

    appletviewer lineMover.html