Design of the PLplot C++ Interface

Stream/Object Identity

A C++ class named plstream has been introduced. It's central purpose is provide a specific, object based encapsulation of the concept of a PLplot output stream. Any output produced using a plstream object, will go to the PLplot output stream associated with that object, regardless of what stream may have been active before.

In order to write a multiple output stream PLplot application, a C++ program can declare plstream objects, and invoke drawing methods on those objects, without regard to ordering considerations or other coherency considerations. Although this has obvious simplification benefit even for simple programs, the full benefit is most easily appreciated in the context of Tk extended wish applications in which a plstream can be associated with each plframe.

Namespace Management

The PLplot C API is composed of a set of drawing functions, all prefixed with "pl", in an effort to prevent namespace collision. However, the prefix "pl" is gratuitous, and in particular is unnecessary in a C++ context. The plstream class mirrors most of the PLplot C API, but does so by dropping the "pl" prefix. The plstream class thus serves to collect the PLplot drawing functions into a scope in which collisions with other similarly named functions is not a concern. So, where a C programmer might write:

	plsstrm( 1 );
	plenv( ... );
	plline( ... );
      

The C++ programmer can write:

	plstream p( ... );
	p.env( ... );
	p.line( ... );
      

Is that an important benefit? The utility varies with the number of output streams in use in the program.

plmkstrm() is replaced by object declaration. plsstrm() is replaced by method invocation on the desired output stream object. plgstrm() is rendered irrelevant.

The skeptic may say, "But you have to type the same number of characters! You've replaced 'pl' with 'p.', except it could be worse for a longer object name." True. BUT, in this new scheme, most plots will not be generated by invoking methods on a specific stream object, but rather by deriving from plstream, and invoking methods of "this" object. See the section on derivation below.

Abstraction of Data Layout

The plstream class will provide an abstract interface to the 2-d drawing functions. Instead of forcing the C++ user to organize data in one of a small set of generally brain dead data layouts with poor memory management properties, potentially forcing the C++ user to not use a superior method, or to copy data computed in one layout format to another for plotting (with consequent bug production), the plstream 2-d plotting functions will accept an abstract layout specification. The only thing which is important to the 2-d drawing functions is that the data be "indexable". They should not care about data layout.

Consequently, an abstract class, "Contourable_Data" is provided. This class provides a pure virtual method which accepts indexes, and is to be made to produce a function value for the user's 2-d data field. It is of no concern to PLplot how the user does this. Any mapping between index and data which the user wishes to use, may be used.

This methodology allows the C++ user to compute data using whatever storage mechanism he wants. Then, by deriving a class from PLplot's Contourable_Data abstract class, he can provide a mapping to his own data layout.

Note that this does /not/ mean that the C++ user's internal data layout must be derived from PLplot's Contourable_Data class. Suppose for example that the user data is stored in a C++ "matrix" class. To make this data contourable, the user may define a class which specializes the indexing concept of the PLplot Contourable_Data class to his matrix class. For example:

	class Matrix { ... };
	class Contourable_Matrix : public Contourable_Data {
	Matrix& m;
	public:
	Contourable_Matrix( Matrix& _m ) : m(_m) {}
	PLFLT  operator()( int i, int j ) const { return m(i,j); }
	};

	plstream p( ... );
	Matrix m;
	// Code to fill m with data
	Contourable_Matrix cm(m);
	p.shade( cm, ... );
      

In this way the C++ user is completely freed from the tyranny of moronic data layout constraints imposed by PLplot's C or Fortran API.

Callbacks and Shades

The plstream::plshades method and the other similar methods require callbacks for fill and pltr, mirroring the requirements for plshades. The user may specify their own callbacks or may use the callbacks provided by Plplot. If using Plplot callbacks the user has two options. They may use the appropriate C functions as described in the C API, however this will require direct linkage of the user's executable to the C library as well as the C++ library, which would otherwise not be necessary when using shared libraries. To avoid linking of the C library the user may instead utilise the functions within the plcallback namespace. The plcallback namespace provides fill, tr0, tr1, tr2, and tr2p callbacks which mirror the functionality of the appropriate C functions.

Collapsing the API

Use of abstraction as in C) above will allow a single method in plstream to perform the services of multiple functions in the C API. In those cases where multiple functions were provided with different data layout specifications, but similar functionality, these can all be collapsed into one, through the use of the abstract interface technique described above. Moreover, function name overloading can be used to simplify the namespace for those cases where multiple functions were used to get variations on a basic capability. For example, a single name such as contour or shade can be used for multiple methods taking different argument sets, so that for example, one can make simple plots of rectangular data sets, or more complex generalized coordinate mappings.