Back to TOC SOFTWARE TOOLS


Scientific Plotting with OWL

Gualtiero Chiaia

Sometimes, the easiest way to make a tool more powerful is to make it simpler.


Introduction

Like many professionals involved in technical research, I frequently need to produce x-y plots. In my case, for instance, I need to display x-y spectra representing energy distributions of photoemitted electrons. But the basic graphics functions I need are probably very similar from one field of research to the next. In most cases I need to compare one spectrum to another on the same plot; to perform mathematical operations on the data; to print, load, and save the results; and finally, to export the resulting multiplot to a dedicated graphics tool. Since I am using Windows, the latter operation can be easily carried out by copying the plot to the Windows clipboard, so I add clipboard access to my basic list of needs.

Although the software market is clogged with programs for displaying data in all the possible formats and styles, most of these programs are unsatisfactory for my purposes. The main problem is that these packages simply try to do too much. As a result, they are too big, rigid, not at all user friendly, and too slow in performing simple operations. My solution has been to develop a software tool to perform all the data analysis I routinely use in my job.

In this article I show how to build a custom plotting application using Borland's Object Window Library (OWL). One of the main benefits of OWL in this application is its Turbo Device Context (TDC) class, which encapsulates all the functionalities for graphical representation, both of text and geometrical figures, in a device-independent way. All drawing routines are performed on a device context; i.e., a spectrum is plotted on the screen, printed on paper, or saved to the clipboard, depending on which instance of the TDC class is selected. The beauty of this approach is that you can create a customizable application with the low-level flexibility of C++, and let OWL handle much of the burden of managing multiple graphics contexts. I've found it to be a very effective combination.

I don't have room to go into all the features of this application, which is called Prisma. The full source code and{short description of image} executable are available on the CUJ ftp server (see 3 for details). A sample plot is shown in Figure 1. I discuss here some of the most significant parts of Prisma: plotting to the screen and the printer, and to the clipboard for export to other graphical tools.

The SpettroPlot Class

Class SpettroPlot (Listing 1) is the interface object for producing the physical plot on the chosen device: screen, printer or metafile (clipboard). The comments accompanying the data members pretty much explain their purpose, but some could use a little elaboration: sx and sy are the respective x and y scale factors, that is, the ratios of the physical size of the device (in pixels) to the real-world plot coordinates. For example, in my case:

sx = device width in pixels
     --------------------------------------------
     Energy in electron volts
sy = device height in pixels
     ----------------------------------------------
     Intensity in counts per second

All but two of the data members are private, as required by the principle of information hiding. The exceptions are TColor *colore, and the structure tbzoom, which I have made public for the sake of efficiency.

The class constructor takes two parameters: a pointer to the client window (father) which is stored into parent, and a pointer (ls) to a list of spectra, which it stores in member ls1.

Some of the class member functions are simple utility functions, and the accompanying comments ought to explain well enough what they do. GetClientSize does just as its name suggests; it retrieves size information from the client window and stores it in data members w and h whenever the size of the client window is changed (WM_SIZE).

The more interesting member functions are as follows:

Note that all the member functions that actually draw on the given device take a pointer to a TDC class as a parameter. For example, take a look at member function Plot (Listing 2) . When it's time for Plot to draw on the display device, it does so by calling a member function of the TDC class (via the call DC->Ellipse(p,delta)). The TDC class, and its derived classes, encapsulate all the common functionality to produce a graphical representation, independently of the physical device to which this representation is directed. Therefore, by means of several public member functions, a TDC object can draw lines, text, and geometrical figures; customize the pen attributes as well as fonts and brushes; store the coordinate metrics of a given device area (for example, pixels on the screen, inches on the printer, or mm on a plotter device, etc.). Particularly important, a TDC-derived class can be associated with a device which is a file (TMetafileDC), or the memory itself (TMemoryDC). This last feature enables an application to export graphical images for outside use.

Plotting On the Screen

Member function Plot deserves a closer look, since it is one of the most important functions in the class. I give a brief description here of how it works.

The first thing Plot does is draw the axes, tick marks, labels, and captions via a call to Axes. Then the outer for loop scans through all the spectra belonging to the list ls1. (Remember that one of the design criteria for this plotting tool was that it should be able to superimpose more than one spectrum on the display device.) A spectrum is basically a sequence of Cartesian coordinates, stored as an object of a class Spettro. This class holds a pointer to the first x-y element (first) of type Couple, which is an x-y couple of float plus a pointer to the next Couple. This linked list structure enables a dynamic list of elements to be built, with a flexible total number of x-y coordinates for each spectrum.

For each spectrum, Plot determines the size of the dots to be drawn (by calling ReturnCircle(j)) and the number of x-y couples (GiveElement(j)->NumCouples()) in the spectrum. Dot size and number of couples are stored in the variables c and ncp respectively.

Next Plot chooses the current spectrum's pen color and brush color from a palette (colore[j]), and selects them into the device context. Plot allows one of the spectra to be marked, in which case a predefined color is chosen instead.

Finally, for all x-y coordinates in the current spectrum, Plot draws an ellipse (actually a circle) at the position p=(x,y) with size delta. This occurs within the inner for loop; the pointer running scans through the spectrum from first to last element. For each element, the scale factors (sx,sy) and plot origin (ox,oy) are used to convert running->x and running->y into physical device coordinates, stored into p.

Printing

Printing in OWL is relatively straightforward. First, the application must create an object of class TPrinter, representing the printing device, and an object derived from class TPrintout (an abstract class) to hold the information to be printed. The application will then invoke the TPrinter member function Print with a TPrintout object as a parameter. Print will call the TPrintout member function PrintPage for each page to be printed.

Refer to Listing 3 for a more detailed look at how printing occurs. The highest level function involved in printing is MsFilePrint, a member function of the client window TWndw. This member function is called whenever the user selects the Print item of the File menu [1]. If the TPrinter object exists, MsFilePrint constructs a TWndwPrintout object. The constructor for TWndPrintout (the second function shown in Listing 3) doesn't do much; it just stores a pointer to the client window and passes the title on to the base class. MsFilePrint then passes this object as a parameter to the TPrinter member function Print.

As stated earlier, Print will call TWndPrintout member function PrintPage for every page in the document. PrintPage is the third function shown in Listing 3. The first statement in PrintPage chooses a mapping mode between the client window and the printer page. In this case the mapping is of type MM_ISOTROPIC, to preserve the x-y ratio of the screen plot. DC is a member of TWndwPrintout that points to a TPrintDC object.

The next three statements set the conversion ratio between the client window area and the page size. SetWindowExt sets the client area to be mapped onto the device viewport; SetViewportExt sets the page size as determined by PageSize, which is a member of TWndPrintout.

The last statement in PrintPage causes a page to be printed, via a call to TWndw::Paint.

Paint is shown at the bottom of Listing 3. It is invoked not only when the screen needs repainting, but also for printing. Paint acts on the device context object which it receives as a parameter, independently of which kind of TDC derived class it is. In this case Paint simply calls the SpettroPlot::Plot(TDC *dc). Thus, the same Plot routine as described above for plotting on the screen is also used here to print, without the need to be changed.

Saving to the Clipboard

The clipboard device is not conceptually different than the printer or the screen. The OWL library supplies TDC derived classes which support the clipboard as a device context element: TMetaFileDC and TMemoryDC. The windows metafile format is particularly useful because another graphical tool can "un-group" the image elements (dots, lines, text, etc.). This was the requirement of my application.

Listing 4 shows the message-response function MsEditCopyClipboard. This function is declared with the same parameters as MsFilePrint described above. And in an analogous fashion, MsEditCopyClipboard is called whenever the user chooses the Copy to the Clipboard item of the Edit menu, or clicks on the left bar icon showing a photographic camera.

Accessing the clipboard is not much different than accessing the printer, except that it is first necessary to open a file for temporary storage of data. This is done in the first five lines of Listing 4: a file name is created by concatenating "\\prisma.wmf" to the current directory path retrieved from the father of the client window. The concatenated name is then passed to the constructor of the TMetaFileDC class and the required memory space is allocated.

The next part of MsEditCopyClipboard is very similar to MsFilePrint. The mapping mode is set (in this case, MM_TEXT, so that a dot on the screen corresponds to a dot on the metafile device), a conversion ratio and destination device area as well. MsEditCopyClipboard uses the same Paint technique as MsFilePrint to "plot" an image on the metafile; once again the SpettroPlot::Plot member function is indirectly called.

Once a replica of the screen has been copied to the metafile, the metafile is closed via a call to TMetaFileDC::Close. A copy of the metafile also exists in memory, and Close passes a pointer to this copy to the constructor TMetaFilePict. As a result, the variable mfp winds up with a pointer to a TMetaFilePict object.

Finally, MsEditCopyClipboard constructs a clipboard object. The clipboard is then emptied and the metafile picture streamed to the TClipboard object. At the end the clipboard is closed, all the allocated pointers deleted, and a dummy value (1) returned.

At the completion of MsEditCopyClipboard a screen plot resides on the clipboard and a windows metafile (prisma.wmf) resides in the same directory as the executable prisma.exe.

Conclusion

Programmers often find themselves in a difficult place because they have contradictory needs: On the one hand, they want to have powerful tools — tools that are far too complex for one person to develop; on the other hand, programmers want tools that are customizable and programmable in a familiar language (such as C or C++). Application frameworks such as OWL alleviate this situation by making it possible to create tools which are both robust and programmer-friendly. o

Acknowledgments

In developing the complete program I am particularly indebted to Dave Hale of the Colorado School of Mines, who made his library of mathematical routines available on the net, at the address: <hilbert.mines.colorado.edu:pub/cwpcodes/cwp.su.all.26.tar.Z>.

Notes

[1] Actually, it's a little more complicated than that. The Decorated Frame Window, which hosts both the menu bar and the client window TWndw, forwards a message to TWndw via a call to TWindow::Postmessage(UINT). TWndw looks through its response table and calls the MsFilePrint function. The parameters WPARAM and LPARAM encode the Windows message that was sent to TWndw by the parent decorated frame window.

A book I found useful in this project was Object-Oriented Programming with Borland C++ 4, by Clayton Walnum, Que Corporation, 1994.

Gualtiero Chiaia has a degree in Nuclear Engineering and a Ph.D. in Physics from Politecnico di Milano. He has done research on electron spectroscopy, electronic structure of materials, and material and surface science. He has been interested in computer programming since 1980. He may be reached at chiaia@axp7000.cdc.polimi.it.