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.
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:
Axis
classes can also represent grids, scale boxes, and other such entities.Axis
and Graphable
instances
as well as methods to set the physical size and dimension of the graph. It also has a generic transformGraph()
method which implementations
can use to apply universal transforms.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.
GraphEngineFactory
is the only concrete class that is absolutely mandatory for graphing. This class exists to produce GraphEngine
s
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 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.
Generating a graph is a very straightforward process:
GraphEngineFactory
. Scripts use MGraph.GraphEngine
instance from the desired implementation.GraphPaper
to hold the components.Graphable
, configure it and add it. Do this for each graph to appear in the final rendering.Polygon
s required.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.
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!
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.
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.