This package presents MathTools graph-generating machinery. MathTools revolves around generating and formatting text and this package is no different. The assumption here is that a graphing.* implementation produces text content that can be consumed by an external application and turned into a graph. This package, which consists mostly of interfaces, is designed to provide a flexible and generic enough API to allow implementations using whatever technology is desired.

The metaphor used here is of a student (or teacher!) looking at a problem that says 'Graph the following...' and a blank sheet of graph paper. The API provides the 'math' side of the tools and the implementation does the actual work. All components are described in very generic and math-oriented terms, and enough flexibility is present to allow implementations to do exactly what is needed to produce graphs from them.

Graphing Equipment

Renderable and Other Graphing Interfaces

The base graphing interface is Renderable. The Renderable interface is used to describe anything that might appear on a graph. For the purposes of this package rendering means 'drawing on a graph' and has no connection with other Java classes that use the word 'render.' The Renderable interfaces are:

The GraphEngine interface is implementation-specific but not Renderable. GraphEngines produce all of the other components that can be placed on or used for graphing. Specifically it generates instances of Axis, Graphable, and GraphPaper. When graphing, the first order of business is to obtain a GraphEngine for the implementation desired.

The GraphKeys interface is a marker interface for any class designed to provide support for an implementation. By intent, GraphKeys is used to describe a class built to hold static constants; Renderable defines a very generic setParameter(String, Object) method and GraphKeys can be used to hold String keys and Object values. It is not, however, limited to this! GraphKeys can be used to tag any class a GraphEngine might need to provide.

One concept that is presented but not implemented is the Rendering Context. By intent, a Rendering Context holds information useful to a Renderable during the rendering process. Exactly what this information is depends on the implementation itself and usage is not mandatory; rather, it is there if needed. In practice, the Rendering Context is an Object passed to the render() method. The initial context Object is passed to GraphPaper.render() From there, it is assumed that GraphPaper will pass the Object to each Renderable it contains until the process is complete. If an implementation's context needs are not severe, the GraphKeys class can be used as the rendering context.

Concrete Classes

GraphEngineFactory is the only concrete class that is absolutely mandatory for graphing. This class exists to produce GraphEngines tuned to specific implementations. It has a method to enable CapCom's MathClassLoader and another to clear it. Instantiating a GraphEngine requires its fully-qualified class name. This information plus a quick call to getGraphEngine() is all that's needed to start the process.

The RenderEngine class is provided for implementations in which String data simply will not do. A RenderEngine accepts any number of Renderables, holds them in order, and produces an Object[] array of their results when its render() method is called. Instances of this class are available from GraphEngineFactory.

Constraint and the Requirement enum are designed as an aid in specifying conditions, limits, and other constraints on graphs or values. The Requirement enum consists of a collection of 'math words' such as EMPTY, LESS_THAN and INTEGER. These words have no mathematical machinery associated with them other than a String designation for quick creation. The designations for the Requirements above are '{}', '<' and '(Z)' respectively. A Constraint holds a Requirement constant and an Object 'value' to which it is applied. Exactly how this happens is up to the implementation; the equipment is provided for convenience. And, since Requirement is an enum, its values can be used in a switch() block.

RenderableImpl provides a partial implementation of the Renderable interface. It does not define a render() method but it does provide machinery for the others. The class can be named (via a String-argument constructor), it defines the setType() method and provides a protected field to hold its value, and it provides an empty testConstraint() method. It also provides the necessary machinery to set and remove parameters.

The XML Implementation

The graphing.xml.* implementation serves a dual purpose. It demonstrates how an implementation is built, with all critical methods defined in a logical and sensible way. It also serves as a way to render graphs into XML format: useful for storage or persistence. The markup is similar to the simplified XML used by other MathTools; it can be transformed (XSLT) or parsed and manipulated (DOM).

The GraphHandler class extends org.xml.sax.DefaultHandler. It exists to consume the XML markup generated by that implementation and turn it into a GraphPaper instance. This is accomplished by first setting the desired GraphEngine, then adding the GraphHandler as the DefaultHandler for a SAXParser and parsing the XML data. As soon as the parse finishes, a call to GraphHandler's getResult() method returns the freshly-built GraphPaper, ready to render. This functionality is also available from MTXProcessor through its parseGraph() method.

Creating a Graph

Generating a graph is a very straightforward process:

  1. Obtain a reference to GraphEngineFactory. Scripts use MGraph.
  2. If necessary, set the classloader.
  3. Obtain a GraphEngine instance from the desired implementation.
  4. Create and configure a GraphPaper to hold the components.
  5. Create, configure and add the axes.
  6. Obtain a Graphable, configure it and add it. Do this for each graph to appear in the final rendering.
  7. If desired, add any Polygons required.
  8. Add any desired transforms.
  9. render() the GraphPaper and send the result wherever it needs to go.

Since MathTools is text-oriented, the most likely result of rendering will be producing a text file to be fed to the external generator. For implementations with stricter requirements a RenderEngine may be required.

Creating an Implementation

To create an implementation, create classes for all the necessary interfaces, add any extra goodies in a GraphKeys class, and begin creating graphs. Okay. In more detail now...

The Renderable interface is designed to 'factor out' the critical things common to, well, classes that can be rendered to a graph. That is why it exists and the other interfaces extend it. The first step in the new implementation should be a base Renderable class. Or, if the pre-defined behavior is adequate, simply use RenderableImpl as a base class.

The Axis class should come next. It is short and sweet and should be easy to crank out. Extend the base Renderable class and define the Axis-specific methods. Since this class actually WILL appear on a graph, its render() method MUST be defined! The next class to define is Graphable. This is probably the most complex class as it MUST define how to represent expressions and limits. Either method can be defined first, but the render() method should probably be last. Also, take care to define and document any parameters the implementation may need set. The Graphable interface defines five very broad constants, do not be afraid to use them! After the two main 'graph' classes, Polygon should probably come next.

Now implement GraphPaper. All the necessary components are defined. Well, almost. Now is also the time to put together the skeleton of the GraphKeys class, if it is needed. If more than one Renderable class needs more than just the 'standard generic' parameters, or if multiple classes use the same parameters, this is the place to put them! Define just enough of the class to contain what is required to render and leave the rest for later. Finish out GraphPaper and take a break.

Finally, write the GraphEngine class. By now all of the other classes it provides are already done, so finishing it should be simple. This is also a good time to see what parameter keys and values, if any, need to be added to GraphKeys. Once it's done so is the implementation! Package and deploy!

The NO Classes

Now is the time to cover the cox.jmatt.java.MathTools.graphing.noop classes. Not all graphing.* implementations need or will use all of the interfaces provided. The graphing.noop.* package exists to 'fill in the gaps' for such implementations. Any time a GraphEngine is written it should supply non-null instances whenever anything other than GraphKeys is requested. The NO-OP package provides this capacity for implementations that need it.

The NO-OP classes always return false when setting things, and null for render() calls.

Strings or No Strings

Implementations that cannot, will not, do not operate with Strings can still benefit from the XML implementation. After developing the non-String implementation, create another GraphEngine that does use Strings. Each class should be written with a render() method that will generate Strings with enough data to re-create the Objects. Use this class for XML persistence, then write another implementation to convert things back.

The GraphKeys class is invaluable for such two- or three-in-one implementations. One class can be written for each implementation, in which case the constants are named the same but have different values. Or, one overall class can be written with all necessary constants in one single place. This is also where the pKey arguments in GraphEngine and GraphPaper come into their own. They along with the type parameters can be used to pick and choose the output and output type for the various render() methods.