Graph of a cubic 

Graph of a cubic 



Breaking it into objects 

Let's think about what we'll need to draw this figure.
Moreover, it's easy to imagine that we want to reuse some of these features, the grid and graph in particular, in other illustrations. This thought is a hint to make separate objects out of them. Then we will be able to insert them into other illustrations later on. In fact, each of the items above will be an object. 

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. 

CubicPanel's fields 

We will create an extension of JPanel, called CubicPanel, in a moment. Let's think about what we want out of it.
Grid grid; CubicFunction function; AffineTransform transform; int width = 0, height = 0; BoundingBox bbox; 

CubicPanel's constructor 

CubicPanel's constructor will just instantiate the fields we need. public CubicPanel(double llx, double lly, double urx, double ury) { setBackground(Color.white); bbox = new BoundingBox(llx, lly, urx, ury); grid = new Grid(this); function = new CubicFunction(this); } Notice that we pass a reference to the CubicPanel to the constructors for the grid and graph. We'll see why this is presently. The AffineTransform is not instantiated in the constructor for two reasons.


CubicPanel'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(); Graphics2D g = (Graphics2D) gfx; if (size.width != width  size.height != height) { 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); } grid.plot(g); function.plot(g); } 

Two other methods 

When the grid and graph are plotting, they will need to know the BoundingBox and the AffineTransform. We'll include methods for the grid and graph to retrieve these objects from the CubicPanel when they are drawn. public BoundingBox getBoundingBox() { return bbox; } public AffineTransform getTransform() { return transform; } 

Grid 

Here is Grid.java: public class Grid { CubicPanel panel; public Grid(CubicPanel cp) { panel = cp; } public void plot(Graphics2D g) { BoundingBox bbox = panel.getBoundingBox(); 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); } AffineTransform transform = panel.getTransform(); 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. 

CubicFunction.java 

Here is CubicFunction.java: public class CubicFunction { CubicPanel panel; public CubicFunction(CubicPanel cp) { panel = cp; } public void plot(Graphics2D g) { BoundingBox bbox = panel.getBoundingBox(); AffineTransform transform = panel.getTransform(); int steps = 50; double stepsize = (bbox.urx  bbox.llx)/steps; GeneralPath path = new GeneralPath(); path.moveTo((float) bbox.llx, (float) valueAt(bbox.llx)); for (int i = 1; i <= steps; i++) { double x = bbox.llx + i*stepsize; path.lineTo((float) x, (float) valueAt(x)); } g.setPaint(Color.black); g.draw(transform.createTransformedShape(path)); } public double valueAt(double x) { return x*x*x  x; } } 

And all together 


Abstract classes 

Now that we've made the grid and graph reusable, we'd like to take this just a bit further.


More abstract classes 

We will define an abstract class called Plotable that models the behavior of a figure element that can be plotted on a FigurePanel. Here is Plotable.java import java.awt.*; public abstract class Plotable { FigurePanel panel; Color color = Color.black; public void setFigurePanel(FigurePanel fp) { panel = fp; } public void setColor(Color c) { color = c; } public abstract void plot(Graphics2D g); } This looks something like a typical class: there are instance fields and two methods, setFigurePanel and setColor, that are defined. Notice that the method plot is not defined and that it is declared to be abstract. This is allowed as long as the class is declared to be abstract. This is exactly how we think about our figure elements: they can be given a color and put in a FigurePanel, but they differ in how they are plotted. Abstract classes can not be instantiated but they can be subclassed by a class that defined any abstract methods. 

Our new Grid class 

public class Grid extends Plotable { public void plot(Graphics2D g) { BoundingBox bbox = panel.getBoundingBox(); 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); AffineTransform transform = panel.getTransform(); g.draw(transform.createTransformedShape(path)); } } Notice that all we have done to define this class is make explicit how the grid should be plotted. 

Axes 

Since we may wish to plot axes in some figures, let's build a class Axes that also extends Plotable. import java.awt.*; import java.awt.geom.*; public class Axes extends Plotable { public void plot(Graphics2D g) { BoundingBox bbox = panel.getBoundingBox(); GeneralPath path = new GeneralPath(); path.moveTo((float) bbox.llx, 0); path.lineTo((float) bbox.urx, 0); path.moveTo(0, (float) bbox.lly); path.lineTo(0, (float) bbox.ury); g.setPaint(color); AffineTransform transform = panel.getTransform(); g.draw(transform.createTransformedShape(path)); } } 

and a class for points 

Here is a class GraphicalPoint that allows us to plot points. Besides defining plot, it allows us to specify how large the point should be and a style, either a circle or square, for drawing the point. 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; Mover mover; 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) { Point2D.Double point = new Point2D.Double(x, y); panel.getTransform().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); } } 

FigurePanel.java 

Now that we have a reasonable collection of Plotables, let's look at what modifications we need to make in CubicPanel to obtain a more general FigurePanel. First, we would not like to be able to add an arbitrary number and type of elements into the diagram. Therefore, we will store the Plotables in a Vector and include a method add by which a Plotable may be added to the FigurePanel. 

An illustration 


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 SimpleFigure BoundingBox GraphicalPoint Plotable 

To create a package called figure: 



Interfaces 

When building a general class to graph functions, we need to be able to specify the particular function we are interested in. In other words, we need to pass a method to the graphing class. We can do this using an interface. package figure; public interface Function { public double valueAt(double x, double[] params); } The point is that if we know a class implements the Function interface, we can be sure that it has a method with the signature: public double valueAt(double x, double[] params); We include double[] params in the argument list so that we can pass parameters to the function along with the argument x. 

Implementing an interface 

Here is an implementation of Function: public class Cubic implements Function { public double valueAt(double x, double[] params) { return x*x*x  x; } } 

Building a graphing class 

Let's now construct the class in our figure package that will allow us to graph functions. To construct an instance, we will need to give it an implementation of Function to build the graph. public class GraphicalFunction extends Plotable { Function function; double[] params; Stroke stroke; int steps = 50; 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 plot(Graphics2D g) { BoundingBox bbox = panel.getBoundingBox(); AffineTransform transform = panel.getTransform(); double stepsize = (bbox.urx  bbox.llx)/steps; GeneralPath path = new GeneralPath(); path.moveTo((float) bbox.llx, (float) function.valueAt(bbox.llx, params)); for (int i = 1; i <= steps; i++) { double x = bbox.llx + i*stepsize; path.lineTo((float) x, (float) function.valueAt(x, params)); } g.setPaint(color); Stroke savedStroke = g.getStroke(); g.setStroke(stroke); g.draw(transform.createTransformedShape(path)); g.setStroke(savedStroke); } } 

How to use it 


Interactivity 

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

Get the figure package 

Download the figure package and unpack it using the command tar xzvf figure.tgz 