JCM Version 1.0
This page discusses the more useful Java classes in the package edu.hws.jcm.draw, which is part of the JCM project. The full API can be found in the JavaDoc documentation.
A DisplayCanvas offers a surface on which graphical objects such as graphs and parametric curves can be drawn. A DisplayCanvas works together with one or more CoordinateRects. A CoordinateRect sets up a two-dimensional coordinate system on the canvas or on a rectangular area within the canvas. Graphical objects that are to be displayed on a canvas belong to subclasses of the abstract class Drawable. A Drawable objects is added to a CoordinateRect, and uses the coordinate system defined by that CoordinateRect. The DisplayCanvas class is designed so that if there is just one CoordinateRect that is to fill the whole canvas, then you can more or less ignore the CoordinateRect and think of the canvas itself as having a coordinate system.
The default constructor for the DisplayCanvas class makes a canvas that has a white background and that initially has no associated CoordinateRects. Since DisplayCanvas is a subclass of the standard AWT class Canvas, you can use the setBackground(Color) method to set the background color for the canvas. You can use the add(Drawable) method to add a Drawable to the canvas. The Drawable is actually added to a CoordinateRect. If the canvas does not yet have a CoordinateRect, one is created that fills the entire canvas and the Drawable is added to that; otherwise, the Drawable is added to the first CoordinateRect in the canvas. If you are working with multiple CoordinateRects in the same canvas (which is rather unusual), you can use add(Drawable,int) to add a Drawable to a particular CoordinateRect. The second argument specifies which CoordinateRect to use: 0 for the first CoordinateRect that was added to the canvas, 1 for the second, and so on. You can also add a Drawable directly to a CoordinateRect. The getCoordinateRect() method returns the first CoordinateRect in the canvas. If one does not already exist, it creates one and returns it. The getCoordinateRect(int) method returns a specified CoordinateRect. You can add CoordinateRects explicitly to a canvas, although you would usually do this only if you want to use several CoordinateRects. In that case, you need to specify the area that will be occupied by the CoordinateRect. The method addNewCoordinateRect(double hmin, double hmax, double vmin, double vmax) can be used to do this. See the JavaDoc documentation for details.
The Drawables in a DisplayCanvas can be InputObjects and Computables, which must be set up with a Controller. However, the DisplayCanvas itself implements both the InputObject and Computable interfaces. Adding a DisplayCanvas to a Controller is equivalent to adding each of the InputObjects and Computables that it contains to the Controller. This is done automatically if you use JCMPanels. Alternatively, of course, you could set up the InputObjects and Computables by hand.
There are several options that you can turn on or off for a DisplayCanvas. The method setUseOffscreenCanvas(boolean) controls whether or not an off-screen copy of the canvas is maintained. This is on by default, but for simple, static displays you can turn it off and save some memory. Use the method setHandleMouseZooms(boolean) to turn the mouse zoom feature on or off. This is off by default. If it is turned on, then the user can "zoom" a CoordinateRect in or out by clicking, shift-clicking, or clicking-and-dragging on the canvas. (This feature should really be implemented using an invisible Drawable, in the same way that the Panner class works, but it's an old implementation that I haven't bothered to change yet.)
A DisplayCanvas implements the ErrorReporter interface, so it can be used as an error reporter by a Controller. You can also display an error message directly on the canvas by calling the setErrorMessage(Controller,String) method. The first parameter, which usually indicates the Controller that is setting the error message, can be safely set to null. Usually, the error message will be cleared automatically, but if you run across a situation in which it doesn't clear itself in a timely way, you can call clearErrorMessage().
A CoordinateRect works with a DisplayCanvas to allow the graphical display of objects of type Drawable. Its purpose is to lay out a two-dimensional coordinate system on all or part of the canvas. In many cases, you can just work with the canvas and let the CoordinateRect class work automatically in the background, but there are some cases where you need to work with CoordinateRects. The default constructor creates a CoordinateRect with horizontal coordinates ranging from -5 to 5 and vertical coordinates ranging from -5 to 5. There is another constructor which takes the limits -- xmin, xmax, ymin, and ymax -- as parameters. You can set the limits later by calling setLimits(double,double,double,double) or setLimits(double). You can discover the current limits by calling getLimits(), which returns an array of four double's. There are various methods for zooming and otherwise changing the limits, although you don't usually need to call them directly.
One peculiar feature of CoordinateRects is that the coordinate limits, xmin to xmax and ymin to ymax, do not quite cover the whole rect, in general. There is, by default, a five-pixel gap around the outside of the CoordinateRect that has coordinates outside this range. You can set the size of this gap by calling the setGap(int) method. If a set of axes is added to the CoordinatRect, it extends from xmin to xmax and from ymin to ymax, so there is a gap between the endpoints of the axes and the boundary of the rect. A Graph1D, on the other hand, extends all the way from one side of the rect to the other. Ordinarily this is a good idea.
It's possible to create Value objects that represent the xmin, xmax, ymin, and ymax values of the CoordinateRect. Call the getValueObject(int) method with the parameter equal to one of the constants CoordinateRect.XMIN, CoordinateRect.XMAX, CoordinateRect.YMIN, or CoordinateRect.YMAX. You might use the xmin and xmax value objects as minimum and maximum Values on a VariableSlider, for example, if the slider is supposed to represent a valid x-coordinate on the CoordinateRect. If you do this, you need to make sure that a Controller is notified when the limits on the CoordinateRect change. Call the method setOnChange(Controller) to tell the CoordinateRect to call the compute() method of the Controller when its limits change. (Note that this is not done automatically by the gatherInputs() method in class JCMPanel. You have to do if by hand if you use any Value objects from the CoordinateRect.)
The range of values on a CoordinateRect can be controlled by a LimitControlPanel. The LimitControlPanel is a Panel that contains input boxes for the xmin, xmax, ymin, and ymax values. It can contain other controls, such as buttons for zooming in or out. The controls can be arranged into either one column or two columns. The possible buttons are indicated by the constants SET_LIMITS, EQUALIZE, ZOOM_IN, ZOOM_OUT, SAVE, and RESTORE in the LimitControlPanel class. You can "or" several of these constants together to indicate a set of buttons. The default constructor produces a LimitControlPanel that contains a single "Set Limits" button and in which the controls are arranged in one column. The constructor LimitControlPanel(int,boolean) lets you specify a set of buttons as the first parameter. The second parameter is set to true to get two columns of controls, false to get one column. The input boxes for xmin, xmax, ymin, and ymax are, by default, labeled "xmin", "xmax", "ymin", and "ymax", but there is another constructor that you can use to set other labels.
You can add control buttons to a LimitControlPanel after it is created by calling addButton(int), where the parameter specifies a set of one or more buttons. It's possible to add other components to a LimitControlPanel, which can be unrelated to its limit control function. Call addComponent(Component) to add a single component, or call addComponentPair(Component,Component) to add a pair of components that you would like to be in the same row of a two-column layout. If the component is a VariableInput, then the name of the VariableInput will be used as a label for the input box. The standard components in a LimitControlPanel work independently of Controllers. However, if you add other components, you will have to add them to Controllers and set Controllers to respond to them. This is never done automatically, even if you build an interface from JCMPanels.
Calling the addCoords(CoordinateRect) method will associate the CoordinateRect with the LimitControlPanel. Calling the addCoords(DisplayCanvas) method will associate the first (and likely only) CoordinateRect in the DisplayCanvas with the LimitControlPanel. When a CoordinateRect is associated with a LimitControlPanel, the limits on the CoordinateRect and the limits on the LimitControlPanel are kept in sync. You can associate several CoordinteRects with the LimitControlPanel, and they will all stay in sync.
The limits that have been input by the user are applied when the user presses return in one of the input boxes or when the user clicks the "Set Limits" button. It is possible that the user's input contains errors, and the LimitControlPanel needs a place to report such errors. An error reporter -- usually a DisplayCanvas -- can be set by calling the setErrorReporter(ErrorReporter) method. If no error reporter has been set, then an error message is simply printed to standard output, which is almost never satisfactory.
The Drawable objects in a CoordinateRect are drawn from front to back in the same order in which they were added to the CoordinateRect.
abstract class Drawable
Drawable is an abstract class that defines the type of object that can be added to CoordinateRects and displayed on DisplayCanvasses. A subclass must provide a definition for the abstract method draw(Graphics,boolean). The first parameter is the Graphics context that the object will use to draw itself. The boolean parameter is true if the limits or size of the CoordinateRect that contains the Drawable object have changed since the last time the object was displayed.
Drawable defines the method setVisible(boolean) which can be used to hide or show the object. If an object is not visible, then its draw() method is not called, so the Drawable doesn't have to check its visibility before displaying itself.
The Drawable class has protected instance variables canvas and coords which record the DisplayCanvas and CoordinateRect that contain the Drawable. This data is set automatically when the Drawable is added to a CoordinateRect. The CoordinateRect does this by calling the Drawable method setOwnerData(DisplayCanvas,CoordinateRect).
class Axes, class Grid, and class DrawBorder
These three classes define "passive" Drawables. That is, they are neither InputObjects or Computables. You can just create the object, add it to a CoordinateRect or DisplayCanvas, and forget about it. All these objects are somewhat configurable.
A Grid displays as a regular grid of horizontal and vertical lines. The line spacing is determined automatically. You can change the color of a Grid by calling its setColor(Color) method. If a Grid is used, it would ordinarily be the first thing added to the CoordinateRect.
A DrawBorder appears as a border around the edges of the CoordinateRect. By default, the border is two pixels wide, but you can change this with the setWidth(int) method. The color, which is black by default, can be changed with the setColor(Color) method. The border should ordinarily be the last thing that is added to a CoordinateRect.
Axes are a little more complicated. An Axes object appears as a set of horizontal and vertical axes. The axes have labeled tick marks which are computed automatically (not always as well as I would like). If the actual axis does not lie within the CoordinateRect -- because zero is not in the range of values -- then, by default, the axis is drawn in a lighter color along an edge of the CoordinateRect. (However, you can force the axis to always be drawn in the center of the CoordinateRect or to always be drawn along an edge. See the JavaDoc documentation.) You can set the regular color and the lighter color used for the axes by calling setAxesColor(Color) and setLightAxesColor(Color).
A Graph1D draws the graph of a function of one variable. The function can be given in the constructor, Graph1D(Function), and can be set or changed later by calling the setFunction(Function) method. If the function is null, nothing is drawn. The default constructor creates a Graph1D with null function. You can set the color of the graph, which by default is magenta, by calling the setColor(Color) method.
A ParametricCurve draws the ParametricCurve (x(t),y(t)) determined by two functions of one variable. The minimum and maximum values of the independent variable t can be specified as Value objects. You could, for example, use VariableInputs as the Values. (The "minimum" doesn't actually have to be less than the "maximum".) The number of intervals into which the range of t values is divided can also be specified by a Value object. The graph is drawn by finding the points (x(ti),y(ti)) for endpoints ti of the subintervals, and connecting these points with lines (with some heuristics to try to handle discontinuities.) The default constructor creates a ParametricCurve with nothing to draw. The constructorParametricCurve(Function x, Function y, Value tmin, Value tmax, Value intervals)
provides all the data explicitly. The constructor ParametricCurve(Function,Function) uses the constants -5 and 5 as the limits and uses 200 subintervals. (These default values are also used if tmin, tmax, or intervals are null.)
The data for the curve can be changed later by calling the methods setFunctions(Function,Function), setXFunction(Function), setYFunction(Function), setLimits(Value,Value), setTMin(Value), setTMax(Value), and setIntervals(Value). The color of the curve, which is magenta by default, can be changed by calling setColor(Color).
A VectorField is generally displayed as a "direction field" of equal length arrows. (The display style can be changed. See the JavaDoc documentation.) The lengths of the arrows are given by a vector field defined by two functions of two variables, (dx(x,y),dy(x,y)). These can be specified in the constructor VectorField(Function,Function). They can be set or changed later by calling the methods setFunctions(Function,Function), setXFunction(Function), and setYFunction(Function). The color of the arrows can be set by calling the setColor(Color) method. The color is lightGray by default. The arrows are drawn at a rectangular grid of points. By default, the number of pixels between rows and between columns is 30. You can change this by calling setPixelSpacing(int). The value is clamped to the range 5 to 200 (although only values from about 10 to 50 really make sense).
A ScatterPlot is a Drawable that can draw a scatter plot of pairs of numbers (x,y), where the numbers are taken from a table. The table is a member of the class edu.hws.jcm.awt.DataTableInput. The numbers x and y can be taken directly from two columns of the table. They can also be computed using "column expressions", that is expressions that can use the column names from the table as variables. The expressions for x and y are evaluated once for each row in the table to get the data that is to be plotted. In addition, a regression line for the plotted data can be drawn. (This line can be turned off if you want, by calling setShowRegressionLine(false).)
class DrawGeometric, class TangentLine, and class Crosshair
A DrawGeometric is a geometric figure such as a line or circle or rectangle whose position/size/slope/radius can be given by Value objects. A DrawGeometric is a Computable, and it can change when the Value objects that it depends on change. Since there are a lot of different shapes, this is a fairly complicated class. The shape types are specified by constants such as RECT_ABSOLUTE, OVAL_RELATIVE, and INFINITE_LINE_ABSOLUTE. Shapes come in two general categories: absolute and relative. A RECT_ABSOLUTE, for example, is determined by two points (x1,y1) and (x2,y2) which are corners of the rectangle. A RECT_RELATIVE is determined by a point (x1,y1), which gives one corner of the rectangle and two addition numbers that specify the width and height of the rectangle. An INFINITE_LINE_ABSOLUTE goes through two points, (x1,y1) and (x2,y2), while an INFINITE_LINE_RELATIVE goes is determined by four values x1, y1, dx, and dy and goes through (x1,y1) and (x1+dx,y1+dy). This is complicated even more because in the case of a relative shape, it's possible to specify the relative offsets dx and dy as integer numbers of pixels instead of as x- and y-values. See the JavaDoc documentation for full information.
In the constructorDrawGeometric(int shape, Value x1, Value y1, Value x2, Value y2)
shape is one of the shape constants such as DrawGeometric.RECT_ABSOLUTE. x1 and y1 specify one point on the shape. x2 and y2 specify either another point or the relative offsets, depending on whether the shape is absolute or relative. Another constructorDrawGeometric(int shape, Value x1, Value y1, int dx, int dy)
which should only be used with relative shapes, uses dx and dy to specify pixel offsets. There are methods for setting or changing all these values later. If any of the values are null or undefined, then nothing is drawn.
The color of a shape can be set by calling setColor(Color). The default is black. The thickness of the line that is used to draw the shape can be set by calling setLineWidth(int). The default is 1. By default, only the outlines of ovals and rects are drawn. However, by setting a fill color, you can arrange for the interior to be filled in. Do this by calling setFillColor(Color). The default value is null, which means that an unfilled shape is drawn. Note that the fill color can be different from the color of the outline.
Crosshair and TangentLine are subclasses of DrawGeometric that make it convenient to set up certain types of shapes. A Crosshair is a 15-pixel by 15-pixel cross. If the constructor Crosshair(Value,Value) is used, then the values give the x- and y-coordinates where the cross is drawn. If the constructor Crosshair(Value x, Function f) is used, then the crosshair is drawn at the point (x,f(x)), so that it is constrained to lie on the graph of the function. A TangentLine is an infinite line that is tangent to the graph of a function at a specified point. The constructor Crosshair(Value x, Function f) specifies the function and the x-coordinate of the point of tangency.
A DrawString is a string of text that is drawn at a specified point on a DisplayCanvas. The text can be more than one line long, with lines separated by newline characters, '\n'. The text can include #'s, which will be replaced by the values of Value objects. (This works in much the same way as DisplayLabels.) A simple DisplayString, with no #'s can be created with the constructor DrawString(String). This string will appear in the top left corner of the CoordinateRect. You can specify a different position. Use the constructor DrawString(String,int) where the second parameter is one of the constants TOP_LEFT, TOP_CENTER, ..., which are defined in the DrawString class. If the DisplayString contains #'s which are to be substituted by Values, you can provide the Values in an array in the constructor DrawString(String,int,Value). It is also possible to position the string at an (x,y) point given by two Value objects. See the JavaDoc documentation for details.
You can set the color of the string with the setColor(Color) method. The default is black. Set the font with setFont(Font). If no font is specified, then the font of the DisplayCanvas is used. Set the maximum desired length for numbers embedded in the string by calling setNumSize(int). The default is 12.
class MouseTracker, class DraggablePoint, and class Panner
These classes provide for mouse interaction with a DisplayCanvas. A MouseTracker object has two associated Variables representing an x- and a y-coordinate. It has no visible representation on the screen. The values of these variables change when the user clicks or clicks-and-drags on the CoordinateRect that contains the MouseTracker. The variables can be retrieved by calling the getXVar() and getYVar() methods. You could use these Variables, for example, as Values that determine the corners of a DrawGeometric or the position of a DrawString. (These Variables implement the Tieable interface, so they can be synced with other Value objects, such as VariableSliders.) You can call setListenForDrags(false) to turn off the response of the MouseTracker to drags. Then the MouseTracker will only change when the user clicks the mouse. If you call setUndefinedWhenNotDragging(true) then the values of the associated variables will be undefined (Double.NaN) when the user is not dragging the mouse.
A MouseTracker is an InputObject, which has to be set up with a Controller. If you are using automatic setup (JCMPanels and mainPanel.gatherInputs()), you should add the MouseTracker to a Controller and you should call setOnUserAction(Controller) to tell the MouseTracker to notify the Controller when the user interacts with the MouseTracker. (If the DisplayCanvas that contains the MouseTracker is added to a Controller, you don't have to add the MouseTracker itself.)
A DraggablePoint is similar to a MouseTracker, except that it appears as a visible disk that the user can drag. It also has getXVar() and getYVar() to retrieve the associated Variables, and these Variables implement the Tieable interface. It is an InputObject which needs to be set up with a Controller.
Several options can be set for a DraggablePoint. In particular, you can call clampY(Value) to force the point to move along the horizontal line whose y-coordinate is given by the Value, and you can use clampY(Function) to force the point to move along the graph of a function. You can also constrain the horizontal motion of the point, although you can't constrain it both horizontally and vertically at the same time. See the JavaDoc documentation for more deatails.
A Panner provides a different type of mouse interaction. When the user right-clicks-and-drags on a CoordinateRect, the Panner will respond by dragging the CoordinateRect around on the xy-plane. All you have to do to enable panning is to add a "new Panner()" to a DisplayCanvas to directly to a CoordinateRect.