Developing a package for mathematical illustration
|
 |
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
| |
|
Let's think about what we'll need to draw this figure.
-
A JPanel to contain the figure elements,
-
the mathematical viewing rectangle,
- a change of coordinates from the mathematical viewing
rectangle to the JPanel's coordinate system,
-
grid
-
axes
-
graph
-
points
-
line
In fact, each of the items above will be an object.
| |
|
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.
| |
|
We will create an extension of JPanel, called
FigurePanel, in a moment. Let's think about what we want out
of it.
- FigurePanelPanel will be an extension of
JPanel onto which we draw our figure.
- Since it holds the pixels on which we will draw, it
makes sense for it to hold the dimensions of the JPanel, the
mathematical BoundingBox and the AffineTransform
that takes mathematical coordinates into pixel coordinates.
- It will also hold the elements that we are going to draw in a
Vector.
| |
| |
FigurePanel's constructor |
|
|
public FigurePanel(double llx, double lly, double urx, double ury) {
bbox = new BoundingBox(llx, lly, urx, ury);
}
| |
| |
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);
}
| |
|
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
| |
|
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; }
}
| |
| |
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.
| |
|
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);
}
}
| |
| |
Let's draw a simple figure |
|
|
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;
}
| |
|
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);
}
}
| |
| |
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
| |
| |
To create a package called figure: |
|
|
- Create a directory (or folder) called figure and move
all relevant .java files into it. Notice
that the directory name has to agree with the name of the package.
- Add a line to the beginning of each .java file before
the class declaration:
package figure;
- Set the CLASSPATH environment variable to point to the
parent directory of figure.
In a bash shell, use export CLASSPATH=..
In a windows terminal window, use set CLASSPATH=..
- Recompile all classes just as before.
- To run the application SimpleFigure, use
java figure.SimpleFigure
| |
|
We can set up our figure package to allow for interactive
diagrams.
LineMover.java
| |
|
Download the figure package and unpack
it using the command tar xzvf figure.tgz
| |
|
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.
|
| |
|
|
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.
|
| |
| |
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.
|
| |
|
Here is an example of how an application, our CubicGraph
from Chapter 3, can be converted to an applet.
|
|
|
You can test the applet using a program called appletviewer:
appletviewer lineMover.html
| |