Qplotter User Manual

Dino Ferrero Merlino

CERN IT/API

Dec 2000


Table of Contents

1. Overview
What is Qplotter?
Motivation
Qt
2. Qplotter at a glance
The simplest Qplotter program
The connection between Qt and Qplotter
Memory management in Qt and Qplotter
Main classes in Qplotter
Generating Postscript output
The IVector class
A more complex example
Using the Viewer
Qplotter Features summary
Page features
Zone features
Curve features
Common features
3. Architecture
Qplotter scene graph
The basic classes of Qplotter scene graph
The Qplotter scene graph in action
Qplotter domain model
Connection between scene graph and domain model
Qplotter systems of coordinates
Qplotter transformations
Physical structure of the package
Dependencies on external packages
4. More on Qplotter classes
Interacting with the page
Managing user interactivity in Qplotter
QpPage user interactivity support
Interacting with the zone
Dealing with the coordinate system
Working with axis
Working with titles
Working with curves
Representations for 1D datasets
Representations for 2D datasets
Using summary info tables
5. Advanced features
Selective zone redrawing
Zooming on a zone
Defining zones with different size in the same page
Working with Qplotter scene graph
A. Supported compilers
B. The Viewer add-on
Introduction
Using the Viewer
C. Adapters
Qplotter adapters to HTL and Gemini
D. The Qplotter tutorial
Qplotter general features
Working with zone limits
Mouse driven interaction
Advanced features
How to run the tutorials
Where to find the tutorials
Building the tutorials
Running the tutorials
Bibliography and Useful Links

Chapter 1. Overview

Table of Contents

What is Qplotter?
Motivation
Qt

What is Qplotter?

Qplotter is a C++ package to produce graphic representation of physics data (such as histograms, scatter plots or curves) both on the screen and as PostScript files. The same package could be used to produce simple 2D drawings (e.g. testbeam setup), but does not provide directly full 3D features such as those of OpenInventor/OpenGL. Qplotter is based on the very popular Qt toolkit and can be freely distributed under the GPL scheme. Users in the High Energy Physics community can think of Qplotter as a replacement of packages such as HIGZ and HPLOT , the graphic subsystem of CERNLIB.

Motivation

The Qplotter project aim is to provide a package that:

  • implements the basic graphics requirements for data presentation

  • implements the HEP specific extensions

  • can be easily extended

  • is based on a simple architecture

  • does not depend on anything but graphics

Several packages such as GNUplot libraries, Qwt etc. partially match this list, but we learned the hard way that reusing a package not designed with such aim is more work that it's worth. We took instead another way: we designed the “proper” architecture and then re-used code from other packages.

Qt

As stated in the section called “What is Qplotter?”, Qplotter is built on top of the Qt toolkit. Qt is a GUI software toolkit written in C++ and fully object-oriented. It is the basis of the popular KDE desktop environment on Linux and is currently supported on Microsoft Windows 95/98/2000, Microsoft Windows NT, Linux, Solaris, HP-UX, Digital UNIX (OSF/1, Tru64), Irix, FreeBSD, BSD/OS, SCO, AIX, IBM OS390 R2. and others. In the unlikely event that Trolltech should become unable to maintain Qt, the last version is legally guaranteed to be released to the free software community, which then will be able to provide continuity.

Figure 1.1. Relations among user applications, QPlotter, Qt and the underlying graphic subsystems

More information on Qt can be found on Trolltech site. A book on Qt [Dalheimer99] is available as well.

Chapter 2. Qplotter at a glance

This chapter provides an overview of Qplotter features. The package comes with a set of example programs and we plan to have a step-by-step tutorial available at some point. Nevertheless this chapter should provide an overview of Qplotter capabilities as well as a first introduction to Qplotter object model.

The simplest Qplotter program

To exploit all Qplotter features, the advanced user should be aware of the basic Qt concepts and how Qplotter fits in the global picture (the less experienced user could rely on a Qt add-on as explained in the section called “Using the Viewer”). The following code represents the minimal Qplotter program based on the general Qt approach to subclass a QWidget to define the main window:

#include <qapplication.h>
#include "Qplotter/qp/qppage.h"

/** The minimum Qplotter program to draw an empty
    page on screen.
*/

const int pixDim=600;

// Subclass Qt QWidget to make our window

class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {page->draw();}
private:
  QpPage  *page;
 protected:
  /// Events processing
  void resizeEvent(QResizeEvent* e){page->resize(e->size());}
};

MyWidget::MyWidget( QWidget *parent, const char *name )
        : QWidget( parent, name )
{
  setGeometry(1,1,pixDim,pixDim);
  page = new QpPage(this,QRect(0,0,pixDim,pixDim),210,210);
  page->createZones(1,1);
  page->getZone(0)->null();
}


int main(int argc, char* argv[]) {
  // QApplication object manages the event loop
  QApplication myapp(argc, argv);
  // Our widget corresponds to a window
  MyWidget myFrame;
  // make the frame visible
  myapp.setMainWidget(&myFrame);
  myFrame.show();
  myFrame.draw();
  // Enter event loop
  myapp.exec();
  return 1;
}

Figure 2.1. Output of the simplest program

This program doesn't do very much, but it shows how to embed the QpPage widget in any Qt program. Nevertheless the window manages resize events and could already produce Postscript output (although we did not exploit this feature).

The connection between Qt and Qplotter

So what is the connection between Qt and Qplotter? If we examine more carefully the program, we notice we've used these Qt classes:

  • A QApplication instance which manages the window event loop

  • A QWidget subclass instance representing the window our page will be placed into

Every Qt application must have an instance of QApplication class to control the event loop and most real-life program will subclass the Qt class QWidget to define the main window. Additional widgets such as buttons etc. could then be placed on such window and using the native slot/signal mechanism the user could model the interaction between GUI and visualisation.

Memory management in Qt and Qplotter

Memory management in C++ is often an error prone task. A recurrent recommendation is to check that any call to operator new() is paired by the corresponding call to delete(). Nevertheless our program creates a QpPage instance without deleting it.


  page = new QpPage(this,QRect(0,0,pixDim,pixDim),210,210);

Did we insert a memory leak? Fortunately not. Both Qt and Qplotter have their “automatic” way of dealing with objects created on the heap (i.e. created by the new() operator). When the QpPage instance is created, it becomes a children of the main widget and is thus automatically destroyed when the main widget is deleted (in our case as soon as the program terminates). So in this case the recommendation is not to delete Qplotter objects to avoid double destruction.

Main classes in Qplotter

The Chapter 3., Architecture will introduce in more details the Qplotter object model, i.e. the set of classes and their relationships. The purpose of this section is just to outline the most important classes from the user's point of view

Figure 2.2. Main QPlotter classes

As we just saw in the previous section, a QpPage object is the “larger” Qplotter entity which can be included in any Qt widget. The QpPage class is basically a container of QpZone instances which are placed according to the number of separate plots we want to have along the horizontal and vertical directions of the page (i.e we want to have 4 zones arranged as 2 zones horizontally and two zones vertically). The QpZone is mainly a container of curves and axis. Each curve can either correspond to a 1D or a 2D dataset (respectively QpCurve1D or QpCurve2D). Several curves can be superimposed on the same zone. So the usual sequence to plot one or more datasets (i.e. histograms or vectors) is to:

  1. Create an instance of QpPage class

  2. Ask the page to create one or several zones (i.e. instances of QpZone class)

  3. Lookup one zone and put a Dataset on it (i.e. an instance of QpCurve1D or QpCurve2D class)

  4. Change zone attributes if necessary

  5. Change curve attributes if necessary

The easiest way to introduce Qplotter main classes is probably to show a program exercising them. The following code creates a page with four zones inside. In each zone we put the same curve using different representations.

On the first zone we set zone and curve attributes as well, so to make the abscissa scale logarithmic and the curve color red.

#include <qapplication.h>
#include "Qplotter/qp/qpplot.h"
#include "Qplotter/qp/qpcurve1D.h"
#include "VectorOfPoints/Vector.h"

int main(int argc, char* argv[]) {
  // Read the dataset from an ASCII file
  AIDAVector vp;
  vp.fromAscii("ex2.dat");
  // Qt QApplication instance
  QApplication myapp(argc, argv);
  // Qplotter simple main widget with a page and some buttons
  QpPlot plot ;
  // We can now stop the application pressing a button
  QObject::connect(&plot, SIGNAL(quit()), &myapp, SLOT(quit()));

  // Retrieve the Qplotter page from the main widget
  QpPage *page = plot.getPage();
  // Create 4 zones
  page->createZones(2,2);

  // Get a pointer to 1st zone
  QpZone *z1 = page->getZone(0);
  // Change a zone attribute
  z1->setXLog(true);
  // Insert a curve in the zone. Default representation is Histogram
  QpCurve1D *qpc = new QpCurve1D( z1, vp);
  // Define curve color as Red
  qpc->setQpPen(new QPen(Qt::red,1));

  // Get a pointer to 2nd zone
  QpZone *z2 = page->getZone(1);
  // Insert a curve in the zone. Representation is Line
  QpCurve1D *qpc2 = new QpCurve1D( z2, vp,QpCurve1D::LINE); 

  // Get a pointer to 3rd zone
  QpZone *z3 = page->getZone(2);
  // Insert two copies of the same  curve in the zone
  // One curve will be a Line, the other a set of error crosses
  new QpCurve1D( z3, vp, QpCurve1D::ERROR); 
  new QpCurve1D( z3, vp, QpCurve1D::LINE); 

  // Get a pointer to 4th zone
  QpZone *z4 = page->getZone(3);
  // Insert a curve in the zone. Representation is Marker+Error cross
  QpCurve1D *qpc4 = new QpCurve1D(z4, vp,QpCurve1D::MARKER_ERROR); 

  // Define our widget as main widget (optional)
  myapp.setMainWidget(&plot);
  // Make the widget visible
  plot.show();
  // Draw the page inside the widget
  plot.draw();
  // Enter event loop
  myapp.exec();
  return 1;
}

 



As you can see instead of sub-classing the QWidget class, we use the QPlot class which is part of Qplotter. This class is an example on how to make a more complex window containing buttons and a QpPage. Notice that curves are inserted with the zone pointer as first parameter, thus making them children of the zone. We can now have a look at program's output:

Figure 2.3. Output of the multiZone program

Thanks to the additional buttons, it is now possible to produce Postscript output, to zoom and un-zoom and to exit the application properly.

Generating Postscript output

Qt provides Postscript output generation as a basic feature. Once the page layout and content have been defined there's just one method to call to produce the Postscript file. If we consider the previous example, instead of writing:


  // Define our widget as main widget (optional)
  myapp.setMainWidget(&plot);
  // Make the widget visible
  plot.show();
  // Draw the page inside the widget
  plot.draw();
  // Enter event loop

it would suffice to write:


  plot.getPage()->print("myfile.ps");

The IVector class

As can be seen in the previous example program, curves are constructed starting from an IVector instance:


  #include "VectorOfPoints/Vector.h"
  ...
  // AIDAVector implements IVector interface
  AIDAVector vp;
  // Read the dataset from an ASCII file
  vp.fromAscii("ex2.dat");
  ...
  // Get a pointer to 1st zone
  QpZone *z1 = page->getZone(0);
  // Insert a curve in the zone. Default representation is Histogram
  QpCurve1D *qpc = new QpCurve1D( z1, vp);
  ...

The AIDAVector class is the entity containing curve's data. Qplotter can query this class to ask for individual points, their coordinates, the associated errors along X and Y coordinate etc. This class is not part of Qplotter (it belongs to a separate package called VectorOfPoints), but for the time being is the only way to define curve data. This will very likely change in the near future, but for HEP applications based on Anaphe libraries, Qplotter provides adapters to convert HTL histograms and Gemini- fit results to IVector objects, as can be seen in this code snippet taken from a Qplotter example program:


#include "HTL/Histograms.h" // Transient histograms.
#include "Qplotter/adapters/HTLAdapter.h"
...
  Histo1D h1d ( "Test of Histo1D",nbx,0.,10. );
  // Capture histogram in a vector
  HTLAdapter hA (h1d);
  ...
  // Show the histogram as Histo+Error
  QpCurve1D *qpc1 = new QpCurve1D(z1, hA.getVect(),QpCurve1D::HISTO_ERROR);  
  ...

More details on adapter will come later on.

A more complex example

In the the section called “Main classes in Qplotter” we saw how to define multiple zones, set logarithmic scale and a few other things. The code presented here will show just one zone, but it will show how to change color and fonts, how to turn on grids, how to put labels and titles on axis and how to get a summary table containing a legend.

#include <qapplication.h>
#include "Qplotter/qp/qpplot.h"
#include "Qplotter/qp/qpcurve1D.h"
#include "Qplotter/qp/qpaxis.h"
#include "VectorOfPoints/Vector.h"

int main(int argc, char* argv[]) {
  AIDAVector vp;
  vp.fromAscii("ex2.dat");
  QApplication myapp(argc, argv);
  QpPlot plot ;
  QObject::connect(&plot, SIGNAL(quit()), &myapp, SLOT(quit()));
  QpZone *z1 = plot.getPage()->getZone(0);
  // Insert one curve for Monte Carlo values
  QpCurve1D *qpc = new QpCurve1D( z1, vp ,QpCurve1D::LINE); 
  // Insert another curve for experimental data
  QpCurve1D *qpce = new QpCurve1D( z1, vp ,QpCurve1D::ERROR); 

  // Color and font management
  // Page children will be drawn in Red using Helvetica font
  plot.getPage()->setQpPen(new QPen(Qt::red,1));
  z1->setQpFont( new QFont( "helvetica", 12 ));
  // Give the curve a name (to appear in legend) and a color
  qpc->setQpPen(new QPen(Qt::red,1));
  qpc->setName(QString("MC"));
  // Give the other curve a name (to appear in legend) and a color
  qpce->setQpPen(new QPen(Qt::blue,1));
  qpce->setName(QString("Data"));

  // Put titles and labels on axis
  z1->setTitle("Luminosity vs. energy");
  z1->getAxis()->getScale(QpQwtScale::Left)->setTitle("Energy");
  z1->getAxis()->getScale(QpQwtScale::Bottom)->setTitle("Luminosity");
  z1->getAxis()->getScale(QpQwtScale::Left)->setLabel("MeV");
  z1->getAxis()->getScale(QpQwtScale::Bottom)->setLabel("pBarn");
    
  // Summary table with a legend for one curve
  SummaryTable *st = new SummaryTable();
  st->append(new StPair(QString("legend"), QString("")));
  qpc->setSummaryTable(st);
  // Summary table with a legend for the other curve
  SummaryTable *ste = new SummaryTable();
  ste->append(new StPair(QString("legend"), QString("")));
  qpce->setSummaryTable(ste);
    
  // Grid management
  z1->enableGridX(true,true);
  z1->enableGridY(true,true);

  plot.show();
  plot.draw();
  myapp.exec();
  return 1;
} 



The output of the program is:

Figure 2.4. Changing more attributes

As we can see, axis are drawn in red, since we redefined the color for the page to be red. This change propagated recursively to all children. On the other hand one of the curve is blue: this is because the curve objects redefines “locally” the color, overriding in this way the settings coming from the parent. Another interesting feature is that we have titles and labels for both axis and a title on the zone. We can see that the grid is turned on both on X and on Y and a legend appeared in the upper right corner.

Using the Viewer

The Viewer is a Qplotter add-on that tries to hide all Qt details and to expose a simpler interface to the unexperienced user. The Viewer could be used, i.e., in a stand-alone C++ program to provide a graphical window which is controlled by simple keyboard input or by external events. The basic features of the viewer are:

  • All Qt details such as the QWidget subclass defining the main window and the QApplication instance managing the event loop are completely hidden.

  • All elements in the page are accessed via abstract interfaces such as IPage, IZone, IDataset (equivalent to curves).

  • Properties on graphic objects are defined using very simple setProperty() methods taking two strings, one to identify the property, the other to define its value.

This is the code of a sample program using the Viewer :

#include <stdio.h>
#include "Qplotter/viewer/viewerFactory.h"
#include "VectorOfPoints/Vector.h"

int main() {
  // Read the dataset from an ASCII file
  AIDAVector vp;
  vp.fromAscii("ex2.dat");
  IViewer *myV = ViewerFactory::Viewer();
  IPage *pg = myV->createPage(1,1);
  IZone *myZ = pg->selectZone(1);
  // Setting min/max on the zone
  myZ->xMinMax(0.,100.);
  myZ->yMinMax(0.,1000.);
  // Changing zone properties
  myZ->setProperty("Option","YAxisLog");
  myZ->setProperty("Option","XAxisLog");
  myZ->setProperty("Option","xAxisGrid");
  myZ->setProperty("Option","yAxisGrid");
  myZ->setProperty("xAxisTitle","Energy");
  myZ->setProperty("yAxisTitle","Frequency");
  myZ->setProperty("xAxisLabel","MeV");
  myZ->setProperty("yAxisLabel","Counts");
  myZ->setProperty("zoneTitle","Fit of a Gaussian Energy Distribution");
  // Inserting two curves
  IDataset *fitCurve =  myZ->addDataset(vp);
  IDataset *histCurve = myZ->addDataset(vp);
  // Changing curve properties
  histCurve->setProperty("Representation","Error");
  histCurve->setProperty("Legend","Data");
  fitCurve->setProperty("Representation","Line");
  fitCurve->setProperty("LineColor","red");
  fitCurve->setProperty("Legend","Fit");
  myV->draw();
  myV->makePS("simpleviewer.eps");  

  // The rest is not important ...
  char tmp[111];
  cout << "Type quit to exit"<< endl;
  do {
    cin >> tmp;
  } while (strcmp(tmp,"quit"));
  delete myV;
  return 1;
}


Despite exercising several Qplotter features, the program is quite easy to understand (and thanks to its use of abstract interfaces it compiles very quickly!). It's worth to mention one Qplotter feature we overlooked so far, the possibility to define minimum and maximum values for X and Y coordinates


  // Setting min/max on the zone
  myZ.xMinMax(0.,100.);
  myZ.yMinMax(0.,1000.);

The same feature is available as a method on the QpZone class. This is the output of the program:

Figure 2.5. Output of the Viewer program

Qplotter Features summary

The last section of this chapter is a summary of all the features the package makes available. The list is further divided according to the major Qplotter entities, i.e. page, zone, curve .

Page features

  • Number of zones and their layout in a simple way.

  • Support for locating coordinates, zooming and picking

  • Draw all the zones in one go or just one zone at a time (e.g. to update only one zone out of several ones).

  • Postscript output

Zone features

  • Empty zones (e.g. as a place-holder)

  • Coordinates management (fixed or variables), minimum/maximum value

  • Title

  • Linear/logarithmic scale on axis

  • Grids on X and Y

  • Zoom/un-zoom on the zone

  • Access axis and then change their properties (such as title, label etc.)

Curve features

  • Different representations available (HISTO, LINE, ERROR, HISTO_ERROR, LINE_ERROR, MARKER, HISTO_MARKER, LINE_MARKER, MARKER_ERROR, SMOOTH, SMOOTH_ERROR, SMOOTH_MARKER , FILLED_HISTO, FILLED_SMOOTH, BOX)

  • Select Marker type and size

  • Legend and summary table (e.g. for statistics or fit parameters).

Common features

  • Font, color, line width and brush re-definable

Chapter 3. Architecture

Qplotter scene graph

In the visualisation jargon, a scene graph is a graph (usually a tree) defining the structure of what's visualised (another term used to define the same entity is display list). The Qplotter scene graph is a tree. Each node of the tree can either be:

  • a QpObject instance

  • a QpNode instance, i.e. a container of QpObject instances.

The basic classes of Qplotter scene graph

QpObject instances have the capability of drawing themselves. QpNode instances usually draw themselves by delegating to the draw() methods of the contained QpObject instances. The relation between these two classes is depicted in the next figure:

Figure 3.1. The basic classes of the Qplotter scene graph

Most Qplotter classes either inherits from QpObject or are QpNode of something else. This is a short list that exemplifies the issue:


class QpPage : public QWidget,  public QpNode<QpZone>
...
// This is a heterogeneous node: element type is the base class, so
// any instance derived from QpObject can be part of a zone scene graph
class QpZone : public QpNode<QpObject>
...
class QpCurve1D : public QpNode<QpObject>
...
class QpPolyShape : public QpObject
class QpPolyLine : public QpPolyShape
...
// This is a homogeneous node: only objects of type QpMarker are allowed
class QpPolyMarker : public QpNode<QpMarker>
class QpMarker : public QpObject
...

So basic object usually inherit from QpObject class, while container objects are QpNode classes. QpNode classes are implemented as list of QpObject, thus the order in the list will reflect the order in drawing. Another distinctive feature of QpNode classes is the fact they can redefine the basic visual attributes such as color, font etc. This means that all elements of a QpNode instance share the same visual attributes, unless they're QpNode instances as well. For instance a QpMarker instance does not have a color of its own: it inherits the color from the QpPolyMarker instance it is part of.

QpObject and QpNode classes include a set of methods which allow to traverse the tree and retrieve the visual properties as defined by the nearest node that redefined them. Other methods allow to retrieve the transformation the object is linked to or the zone it belongs to.

The Qplotter scene graph in action

The Qplotter scene graph is a tree composed of QpObject or QpNode nodes. The order in the tree will define the order of drawing: the tree draws from top to bottom and from left to right (so called “depth first” traversal). This means that it is possible to “bring to front” an object inside a QpNode by moving it first in the list. Similarly it is possible to “bring to back” a whole node by moving it last in the parent's list.

Figure 3.2. An example of Qplotter scene graph

In this case drawing would start from the top node, which would invoke the draw() method of its children from left to right (or better from beginning of the list to the end). Each children will try to draw itself: QpObject instances will really call the underlying drawing facility, while QpNode will “recursively” call the draw methods of their children.

Qplotter domain model

A domain model describes the classes that are specific to the domain the package deals with. Since Qplotter is a package for presentation graphics , most domain classes will deal with the graphical representation of entities ( as curves, axis, legend, markers) or with the layout of the graphic output. Qplotter conceptual model is based on the idea of a page containing zones. The page represent the abstract equivalent of a paper page or a window on the screen. Each zone could be thought as the equivalent of the screen of an instrument such as an oscilloscope: it contains a curve or several correlated curves plus an axis system to intepretate them. The zone can be “decorated” by text (such as labels and titles), legend and so on. Every curve is an individual item and can be represented according to different representations (line, Histo etc.) or visual attributes (color, line width etc.). A relatively detailed domain model of Qplotter is described in the next figure:

Figure 3.3. Qplotter detailed domain model

The role of the QpPage class has been already introduced: it is responsible for “keeping it all together”, that is ensuring that all parts of the scene graph will appear in a coherent way either on paper or on screen. The QpZone class works on a lower level: its purpose is to “keep together” a set of one or more curves plus all the additional information necessary to visualise them. The QpCurve1D and QpCurve2D classes are the container of the graphic objects representing user data. Depending on the representation chosen, they may contain a QpPolymarker (e.g. when MARKER or ERROR representations are used) or a QpPolyline ( LINE or HISTO representation used).If the representation is FILLED a QpPolygon would be used instead .

The QpAxis class is the container of all axes in the zone (up to four so far). Each axe is represented by a QpScale instance. The QpSummaryInfo class is the graphic object wich “glues together” the summary information stored in the QpSummaryTable instances in every zone (this allows for instance to merge statistics from two overlayed histograms in a single table of contents).

Connection between scene graph and domain model

The purpose of this section is somehow to reconcile the domain model (introduced in the section called “ Qplotter domain model ”) with the Qplotter tree. As we saw in the previous sections, most domain classes are at the same time nodes of the tree. Let's see how the tree of a Qplotter page could look like:

Figure 3.4. Qplotter scene graph with domain classes

The process for drawing (or printing) would just be the specialisation of the general algorithm described beforehand. In this case the QpPage instance will ask each of the QpZone instances to draw themselves. Each zone will in turn draw the QpAxis instance and finally the QpCurve one.

Qplotter systems of coordinates

Qplotter works with three systems of coordinates:

  • zone coordinates , i.e. the coordinates of the data. The origin is in the “bottom left” corner of the zone.

  • page coordinates , i.e. the system of coordinates in which zones are placed. It can either be in centimetre or in any arbitrary space (so far is a {0,210},{0,210} space, where 210 is approximately the width in mm of an A4 page). The origin is in the “bottom left” corner of the page.

  • pixel coordinates, i.e. Qt native coordinates. The origin is in the “top left” corner of the pixmap (which means that Y coordinates increase from top to bottom in the pixel space).

User's data are expressed in zone coordinates . Depending on the zone position, the resulting curve must be transformed to page coordinates and finally can be drawn in Qt using pixel coordinates corresponding to the screen area (or the printing device).

Figure 3.5. Qplotter three systems of coordinates

These three systems of coordinates are connected by transformations , which are the subject of the next section.

Qplotter transformations

Transformation classes allow to convert points in one system of coordinates into points in another system of coordinates. Qplotter transformations are instances of the “Strategy Pattern” (see [Gamma95] ) and are associated with each object. This is a sketch of the existing transformation classes:

Figure 3.6. Qplotter transformation classes

QpTransformation

Base class for all transformation strategies

PageStrategy

Transforms from page coordinates to pixel coordinates

ZoneStrategy

Transforms from zone coordinates to pixel coordinates.

ZrBrStrategy

Special zone strategy relative to bottom right corner

ZrBlStrategy

Special zone strategy relative to bottom left corner

ZrTrStrategy

Special zone strategy relative to top right corner

ZrTlStrategy

Special zone strategy relative to top left corner

The instances of the transformations are stored respectively in the QpPage and QpZone instances. Every Qplotter object contains an enumeration that specifies whether it must be drawn in the page coordinates or zone coordinates system (in the latter case the special transformation relative to zone corners can be used as well). This additional degree of flexibility allows e.g. to insert in a zone objects that must be drawn in the page coordinate system.

Physical structure of the package

The Qplotter package is further split in four “components”:

qp

The “kernel”

qwt

The classes coming from the Qwt package

viewer

The QpViewer implementation

adapters

The HTL and Gemini adapters.

Each “component” is packaged is a separate shared library, so that only the part effectively used is taken.

Figure 3.7. Qplotter physical structure

Shared library names reflect the “component” structure. Note that shared libraries names include the version number, but generic names are provided as symbolic links.

[lxplus011] ls -l $LHCXXTOP/specific/redhat61/Qplotter/1.2.0.0/lib
441211 Dec 11 15:44 libqp.1.2.0.0.so
    16 Dec 13 15:45 libqp.so -> libqp.1.2.0.0.so
103362 Dec 11 15:44 libqpadapt.1.2.0.0.so
    21 Dec 13 15:45 libqpadapt.so -> libqpadapt.1.2.0.0.so
 89813 Dec 11 15:44 libqpadapthtl.1.2.0.0.so
    24 Dec 13 15:45 libqpadapthtl.so -> libqpadapthtl.1.2.0.0.so
101333 Dec 11 15:44 libqpqwt.1.2.0.0.so
    19 Dec 13 15:45 libqpqwt.so -> libqpqwt.1.2.0.0.so
221117 Dec 13 15:44 libqpview.1.2.0.0.so
    20 Dec 13 15:45 libqpview.so -> libqpview.1.2.0.0.so

The adapters “component” is actually packaged in two shared libraries, one containing HTL adapters only, the other containing both HTL and Gemini adapters.

Dependencies on external packages

The main dependency of Qplotter is obviously on Qt ,but there are others which are less obvious. Apart from the use of STL in the viewer sub-package, there are dependencies on some other Anaphe packages:

Qt “kernel”

depends on Interfaces package defining the IVector interface. Moreover an implementation of IVector is required to produce a user program, thus another dependency on the VectorOfPoints package.

viewer

The QpViewer implementation depends on Interfaces package defining the IVector, IAnnotation interfaces. Moreover an implementation of those interfaces is required to produce a user program, thus another dependency on AIDA_Annotation, VectorOfPoints packages.

adapters

The package depends on the above mentioned Interfaces , AIDA_Annotation, VectorOfPoints packages as well as on HTL and Gemini.

Chapter 4. More on Qplotter classes

Interacting with the page

The QpPage class is the container of all objects appearing on the screen or printed on paper. Its main tasks are:

  • provide proper placing for the children

  • map to the pixel coordinates system

  • provide access to underlying Qt machinery (QPainter, QPrinter,QPixmap)

The class provides some support for user interaction as well, such as zooming, locating, picking . Before discussing these page features, it's worth introducing the general mechanism provided by Qplotter to deal with user interaction.

Managing user interactivity in Qplotter

Users may interact with Qplotter using the mouse. The most general way to deal with mouse events would be to subclass the QpPage and add code to catch and manage them. To avoid sub-classing even for simple cases, we've devised a system that allows the user to manage mouse events by providing a “client” class “attached” to the page. The implementation is based on the Observer design pattern (see [Gamma95]) and requires two classes:

QpListener

A class that capture mouse events and can notify its “clients”. The QpListener keeps information about the mouse event context and can be queried by clients.

QpEvObserver

A client of a QpListener. The QpEvObserver is notified whenever a mouse event happens and can then execute any action based on the event type and its context.

Users will typically subclass the QpEvObserver to provide the action to execute on notification. The user class will then register with the QpListener, possibly specifying the events it's interested in.

This piece of code illustrates the procedure (more details will be provided in the next sections).

/**
  A small class implementing the locator for this frame.
  Whenever the mouse moves, coordinates are printed
 */
class SmallLocator : public QpEvObserver {
public:
  /// Ask the QpListener to notify ONLY mouse move events
  SmallLocator() : 
    QpEvObserver((QpListener::EventType)(QpListener::Move)) {}
  /// On notification print coordinates
  virtual void update() {
    std::cout << "X Zone coordinate is " << listener->getXz() << std::endl;
    std::cout << "Y Zone coordinate is " << listener->getYz() << std::endl;
  }
  /// Termination
  virtual void end() { listener->removeObserver(this);}
};  

Notice that the object specifies the mouse events it's interested in at construction time and de-register itself at destruction time. The registration must be done by a third party that knows about both the observer and the listener (as explained later on).

The class diagram depicting the relations between the Qplotter classes implementing the observer pattern is the following:

Figure 4.1. Qplotter classes related to the observer pattern

The three subclasses of QpEvObserver are specialisation available directly in Qplotter to implement common operations such as zooming or locating.

QpPage user interactivity support

Each instance of QpPage provides additional objects to support user interaction:

  • A QpListener instance that can be used by any external observer to gather information about the events happening on the page.

  • A QpOutLine instance to allow users to graphically locate a point or select a region in the zones.

  • A QpZoom instance allowing users to implement zoom features by adding a minimum amount of GUI code.

Using this objects, it is possible to implement complex interactive features such as locating, picking and zooming. The next subsections will show how to use those objects to implement useful actions depending on mouse events.

Visualising a cross-hair ruler

We'll modify the program used in the section called “The simplest Qplotter program” to visualise a cross spanning the whole zone whenever the mouse moves on the window (this would help e.g. to locate a point in the plot by looking where the cross ends on the axis).

#include <qapplication.h>
#include "Qplotter/qp/qppage.h"

const int pixDim=600;

// Subclass Qt QWidget to make our window

class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {page->draw();}
private:
  QpPage  *page;
 protected:
  /// Events processing
  void resizeEvent(QResizeEvent* e){page->resize(e->size());}
};

MyWidget::MyWidget( QWidget *parent, const char *name )
        : QWidget( parent, name )
{
  setGeometry(1,1,pixDim,pixDim);
  page = new QpPage(this,QRect(0,0,pixDim,pixDim),210,210);
  page->createZones(1,1);
  page->getZone(0)->null();
  // Activate Cross outline on the page
  page->getOutLine()->setType(QpOutLine::Cross); 
}

int main(int argc, char* argv[]) {
  // QApplication object manages the event loop
  QApplication myapp(argc, argv);
  // Our widget corresponds to a window
  MyWidget myFrame;
  // make the frame visible
  myapp.setMainWidget(&myFrame);
  myFrame.show();
  myFrame.draw();
  // Enter event loop
  myapp.exec();
  return 1;
}

The only difference with respect to the simplest Qplotter program is one line in the constructor of our widget:


  // Activate Cross outline on the page
  page->getOutLine()->setType(QpOutLine::Cross); 

The line retrieves the instance of the QpOutLine class provided by the QpPage object and activates it with a cross outline. From now on whenever the mouse moves over the zone, a cross outline will show the coordinates of the point on the axis.

Change graphic attributes on picking

In this section we'll see some code that “listens” on the left button click event and picks any object below the cursor. If the object is a curve the program toggles its color between black and red.

In order to do so users have to subclass the QpEvObserver class to implement the action to carry out on picking:


class SimplePicker : public QpEvObserver {
public:
  // Keep a pointer to the main widget and subscribe only to left button click events
  SimplePicker(MyWidget *_parent) : 
    QpEvObserver((QpListener::EventType)(QpListener::LeftPressed)) {
      mParent = _parent;
  }
  // On notification execute this
  virtual void update();
  ...
private:
  // Pointer to the class that can actually toggle the color
  MyWidget *mParent;
};  

void SimplePicker::update() {
  // Check we picked something
  if (listener->getPicked() != 0) {
    // If it is a polyline, then toggle the color
    if (QString("QpPolyLine") == listener->getPicked()->isA())
      mParent->toggleColor();
  }
}

We then need to register the locator with the page:


int main(int argc, char* argv[]) {
  ...
  SimplePicker *locator = new SimplePicker(&myFrame);
  locator->lRegister(myFrame.getPage());
  // Enter event loop
  myapp.exec();
  delete locator;
}

Whenever the left button is clicked, the SimplePicker instance is notified and executes its update() method. This in turn invokes MyWidget::toggleColor() method which actually changes the curve color.

Interacting with the zone

The QpZone class represents one area of the page with a “local” system of coordinates. The zone may include axis, one or more curves, text etc., so QpZone is just another container of QpObject or QpNode instances. Zones are usually created by asking a QpPage instance to partition the available space in several areas, e.g. :


  ...
  // Retrieve the Qplotter page from the main widget
  QpPage *page = plot.getPage();
  // Create 4 zones
  page->createZones(2,2);
  ...

This kind of code will create a vector of zones (starting from index 0) which can then be retrieved separately as the following code demonstrates:


  ...
  // Get a pointer to 1st zone 
  QpZone *z1 = page->getZone(0);
  // Get a pointer to 3rd zone 
  QpZone *z3 = page->getZone(2);
  ...

Once such a pointer is available, the user can modify the zone according to its interface. The following sections will explain how to modify the coordinate system, change the axis and define some text in predefined places.

Dealing with the coordinate system

As explained in previous chapters, each zone has its own coordinate system, i.e. the range of values allowed along each dimension. The QpZone class allows to modify the extent of the coordinate system or to fix it at any time. A zone by default has non-fixed coordinates, so as long as new curves are added, the coordinate system will change so that all curves can be drawn. If the coordinate system is fixed by the user, then there are two cases:

  • If the zone contains no curves yet, the first curve will define the limits of the coordinate system

  • If the curve contains already one or more curves, the current coordinate system is frozen

Even if the coordinate system is fixed, the user can override the limits by invoking the QpZone methods updateX() or updateY() to force the new values (this is useful e.g. to implement zooming). The following code snippet shows how to manage zone limits:


void MyWidget::newCurves () {
  AIDAVector vp1,vp2;
  // Read data from file
  vp1.fromAscii("ex2.dat");
  vp2.fromAscii("ex2.dat");
  vp2.mul(2.);
  QpZone *z = page->getZone(0);
  // Insert two curves on the first zone (coordinates not fixed)
  new QpCurve1D( z, vp1 ,QpCurve1D::HISTO); 
  new QpCurve1D( z, vp2 ,QpCurve1D::HISTO); 
  // Insert two curves on the second zone (fixed coordinates)
  z = page->getZone(1);
  z->setFixedCoords(true);
  new QpCurve1D( z, vp1 ,QpCurve1D::HISTO); 
  new QpCurve1D( z, vp2 ,QpCurve1D::HISTO); 
  // Now set arbitrary Y coordinates (fixed)
  z = page->getZone(2);
  z->setFixedCoords(true);
  new QpCurve1D( z, vp1 ,QpCurve1D::HISTO); 
  new QpCurve1D( z, vp2 ,QpCurve1D::HISTO); 
  // Override fixed coordinates
  z->updateY(0,1000.,true);
  // Now set arbitrary X coordinates (not fixed)
  z = page->getZone(3);
  z->updateX(-50,0.);
  new QpCurve1D( z, vp1 ,QpCurve1D::HISTO); 
  new QpCurve1D( z, vp2 ,QpCurve1D::HISTO); 
}

The code reads the content of a curve from file, creates another curve whose points are scaled by two and finally shows both curves in the same zone using different policies to set limits.

Figure 4.2. How limits can change the appearance of the same curves

The same dataset produces different results according to the policy chosen on each zone:

  • Zone 0 with variable limits: Y coordinates stretch to show both curves

  • Zone 1 with fixed limits: Y coordinates are defined by the first curve (the higher curve is clipped).

  • Zone 2 with fixed limits: Y coordinates scale to an arbitrary large interval, so that both curves are “flattened”.

  • Zone 3 with variable limits: X coordinates are first set to an arbitrary range [-50,0], but then automatically stretch to include the tail of the curves around 40.

Working with axis

The QpAxis class is the container of all axis related to a zone. It is a container of QpScale instances, each one representing a potential axis placed on one of the edges { top, bottom, left, right }. Although it is possible to have up to four axis, the { bottom, left } pair is somehow special, since Qplotter assumes these are the axis related to the coordinate system. This means that:

  • { bottom, left } scales are automatically updated whenever the zone limits change

  • logarithmic/linear properties are automatically applied on { bottom, left } scales only.

  • grids are anchored on the divisions along { bottom, left } scales

The appearance of axis can be changed either via QpZone methods (e.g. by changing zone limits or selecting linear/logarithmic scales along X or Y) or directly invoking QpAxis or QpScale methods, as this code fragment shows:


void MyWidget::changeAxis () {
  QpZone *z1 = page->getZone(0);

  // Change axis appearance via QpZone methods
  // Set logarithmic scale on both axis
  z1->setXLog(true);
  z1->setYLog(true);
  // Grid management
  z1->enableGridX(true,true);
  z1->enableGridY(true,true);

  // Change axis directly
  // Get a pointer to the system of axis
  QpAxis *axis = z1->getAxis();
  // Change axis titles and labels
  axis->getScale(QpQwtScale::Left)->setTitle("Energy");
  axis->getScale(QpQwtScale::Bottom)->setTitle("Luminosity");
  axis->getScale(QpQwtScale::Left)->setLabel("MeV");
  axis->getScale(QpQwtScale::Bottom)->setLabel("pBarn");
  // Create a new logarithmic scale on the top 
  axis->addElement(new QpScale(QpQwtScale::Top,z1,false));
  axis->getScale(QpQwtScale::Top)->update(1.,10000000.,false);
  axis->getScale(QpQwtScale::Top)->setLog(true);
  // Create a new linear scale on the right
  axis->addElement(new QpScale(QpQwtScale::Right,z1,false));
  axis->getScale(QpQwtScale::Right)->update(-50.,50,false); 
}

The code first enables logarithmic scales and grids along both X and Y direction to show how QpZone methods can change the way axis are shown. Then a pointer to the axis set is retrieved and titles and labels are set on the { bottom, left } scales (which exist by default as soon as a curve is added to the zone). Finally two additional scales are created on the { top, right } orientations. The output would be something like that:

Figure 4.3. Working with axis

Working with titles

It is possible to define a title associated to the zone. So far the title can only appear on the top of the zone, aligned on the left. The code to set the title is something like that:


  ...
  QpZone *z1 = page->getZone(0);
  z1->setTitle("Luminosity vs. energy");
  ...

Working with curves

The final goal of Qplotter is to show datasets produced by the physics analysis process. The package provides classes to deal with 1D and 2D datasets (respectively QpCurve1D and QpCurve2D). Curves always belongs to a zone, since proper visualisation requires the definition of a surrounding system of coordinates.

Representations for 1D datasets

Several representations are available for 1D datasets, such as:

QpCurve1D::HISTO

Histogram representation

QpCurve1D::LINE

Line representation

QpCurve1D::ERROR

Error bars representation

QpCurve1D::MARKER

Marker representation

QpCurve1D::SMOOTH

Smooth line representation

QpCurve1D::FILLED

Filled area representation

Some of these representations can be combined together to draw, e.g. marker and error bars at the same time. The following code shows how to draw the same dataset with different representations.


void MyWidget::newCurve () {
  QpCurve1D *qpc;
  AIDAVector vp;
  // Read data from file
  vp.fromAscii("ex2.dat");
  // Insert one curve as Line
  QpZone *z1 = page->getZone(0);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::LINE); 
  // Insert one curve as Histo
  z1 = page->getZone(1);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::HISTO); 
  // Insert one curve as error bars
  z1 = page->getZone(2);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::ERROR); 
  // Insert one curve as a set of markers
  z1 = page->getZone(3);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::MARKER); 
  qpc->setSymScale(0.2);
  // Insert one curve as a smooth line
  z1 = page->getZone(4);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::SMOOTH); 
  // Insert one curve as a filled histo
  z1 = page->getZone(5);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::FILLED_HISTO);
  qpc->setQpBrush(new QBrush(Qt::red));
  // Insert one curve as a set of markers plus Histo
  z1 = page->getZone(6);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::MARKER_ERROR); 
  qpc->setSymScale(0.6);
  // Insert one curve as  a filled smooth line
  z1 = page->getZone(7);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::FILLED_SMOOTH); 
  qpc->setQpBrush(new QBrush(Qt::blue));
  // Insert one curve as as Line plus set of markers
  z1 = page->getZone(8);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::LINE_MARKER); 
  qpc->setSymStyle(QpMarker::Cross);
  qpc->setSymScale(0.3);
}

Notice that it is possible to change the color of the filled area, the size or shape of the marker and so on. The output of the program is reproduced in the next figure (the “spike” of the error bar in the middle of the Gaussian is intentional: it shows that asymmetric error bars are supported):

Figure 4.4. Multiple representations of the same curve.

It is worth mentioning there are two ways to deal with the representation of a curve. The first way is to define the representation when the curve is created, as it is done in the code above. The other way is to first create an empty curve and then define the representation when the data are supplied. This might be useful if a “place-holder” for the curve is required before knowing its final representation.


  // Two equivalent ways to deal with representations
  ...
  z1 = page->getZone(8);
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::HISTO); 
  // This is equivalent ...
  qpc = new QpCurve1D(z1);
  ...
  qpc->newCurve(vp, QpCurve1D::HISTO);
  ...

Representations for 2D datasets

So far the only representation available is the BOX one. The following code is an example of the BOX representation: larger values correspond to larger boxes.


void MyWidget::newCurve () {
  AIDAVector vp;
  // Read data from file
  vp.fromAscii("ex2D.dat");
  QpZone *z1 = page->getZone(0);
  // Insert a 2D curve
  qpc = new QpCurve2D( z1, vp ,QpCurve2D::BOX); 
  qpc->setQpBrush(new QBrush(Qt::red));
  qpc->setQpPen(new QPen(Qt::blue,1));
}

Notice that it is possible to change the color of the boxes and to fill them with yet another color. This is the result:

Figure 4.5. BOX representation for 2D datasets

Additional representations for 2D datasets, such as Lego, Contour , Color or Surface will be available in future releases of Qplotter.

Using summary info tables

Summary info tables allows the user to attach textual information and a legend to each individual curve. Using summary tables it is possible to show e.g. histogram statistics, fit parameters or any other information which can be expressed as a pair of {key , value } items. The zone will take care of drawing all summary tables attached to the curves belonging to the zone. This means e.g. that when overlaying two histograms it is possible to see the statistics for both of them.

The SummaryTable class is the container of all pairs {key , value } related to a curve. The drawing algorithm will just show all entries as text, with the key aligned on the left and the value aligned on the right. Special keys such as legend, separator are instead evaluated: the legend key will show the line and/or marker used to draw the curve as the key, while the separator key will result in an horizontal separator in the table.

The following code shows how to deal with summary information. First a SummaryTable instance is created, then it is “populated” with one entry for the maximum value of the curve and another entry for the legend. The SummaryTable instance is finally attached to the curve.


void MyWidget::newCurve () {
  AIDAVector vp1,vp2;
  // Read data from file
  vp1.fromAscii("ex2.dat");
  vp2.fromAscii("ex2.dat");
  vp2.mul(2.);
  QpZone *z1 = page->getZone(0);
  // Insert one curve for experimental data values
  qpc = new QpCurve1D( z1, vp1 ,QpCurve1D::LINE); 
  qpc->setQpPen(new QPen(Qt::red,1));
  SummaryTable* st1 = new SummaryTable;
  st1->append( new StPair( "Max", "100"));
  st1->append( new StPair("legend", "Data"));
  // Attach the SummaryTable to the curve
  qpc->setSummaryTable(st1);
  // Insert one curve for Monte Carlo values
  qpc = new QpCurve1D( z1, vp2 ,QpCurve1D::LINE_MARKER); 
  SummaryTable* st2 = new SummaryTable;
  st2->append( new StPair( "Max", "200"));
  st2->append( new StPair("legend", "Monte Carlo"));
  // Attach the SummaryTable to the curve
  qpc->setSummaryTable(st2);
}

The next figure shows the output of the program.

Figure 4.6. Summary info related to two separate curves

Notice that the legend automatically retrieves the proper line color and marker shape from the curve. Once the summary tables have been attached to curves, it is possible to define on the zone level whether to show only the first table, all tables or none at all, as in the following code:


  QpZone *z1 = page->getZone(0);
  ... 
  QpSummaryInfo* qpi = z1->getSummaryInfo();
  // Only one among these...
  qpi->setVisible(QpSummaryInfo::First); 
  qpi->setVisible(QpSummaryInfo::All); 
  qpi->setVisible(QpSummaryInfo::None); 
  ...

Chapter 5. Advanced features

Selective zone redrawing

The “standard” way of drawing in Qplotter is to draw or redraw the whole page, as in this piece of code:


class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {
    // Full page redraw
    page->draw();
  }
private:
  QpPage  *page;
};

Sometimes it may be useful to redraw a single zone, without any impact on other ones. The QpPage class provides a special method to do so, as exemplified in the following code snippet:


class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {page->redraw();}
  void redrawZone(QpZone *z);
private:
  QpPage  *page;
};

// Selective zone redrawing
void MyWidget::redrawZone(QpZone *z) {
  page->redrawZone(z);
}

/**
  Whenever the mouse is clicked on a zone, the color is toggled between
  red and black and the zone is selectively redrawn
 */

class SmallLocator : public QpEvObserver {
public:
  SmallLocator(MyWidget *_parent);
  /// On notification execute this
  virtual void update();
private:
  MyWidget *mParent;
};  

void SmallLocator::update() {
    // Retrieve zone pointer
    QpZone *z1 = listener->getPickedNode()->getZone();
    // Toggle zone pen color
    if (z1->getQpPen()->color() == Qt::red)
      z1->setQpPen(new QPen(Qt::black,1));
    else
      z1->setQpPen(new QPen(Qt::red,1));
    // Redraw the touched zone only
    mParent->redrawZone(z1);
}

int main(int argc, char* argv[]) {
  MyWidget myFrame;
  ...
  myFrame.draw();
  SmallLocator *locator = new SmallLocator(&myFrame);
  locator->lRegister(myFrame.getPage());
  // Enter event loop
  myapp.exec();
  ...
}

The program attaches a locator to the page and whenever the left button is clicked on a zone it retrieves the zone, toggles its color and redraws only that zone.

Zooming on a zone

Zooming on a zone means changing its limits so that some details are emphasised. Of course “inverse zooming” i.e. moving the plot far away is conceptually possible as well (although less useful). The QpZone class provides methods to implement unlimited zooming/un-zooming features. The first example shows how to use the QpZone::zoom() method that allows to zoom at one point by a scale factor. This is useful e.g. to implement “zoom by 2” like features.


class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {page->draw();}
  void newCurve();
  void zoomBy();
private:
  QpPage  *page;
};

void MyWidget::newCurve () {
  AIDAVector vp;
  // Read data from file
  vp.fromAscii("ex2.dat");
  QpZone *z1 = page->getZone(0);
  z1->setTitle("Original data");
  // Insert one curve 
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::HISTO);
  z1 = page->getZone(1);
  // Insert one curve 
  qpc = new QpCurve1D( z1, vp ,QpCurve1D::HISTO);
}

// Zoom by factor 4
void MyWidget::zoomBy () {
  QpZone *z1 = page->getZone(1);
  z1->setTitle("Zoom by four at (40,0)");
  z1->zoom(40.0,0.0,4.0);
}

int main(int argc, char* argv[]) {
  ...
  myFrame.newCurve();
  // Ask for zooming
  myFrame.zoomBy();
  myFrame.show();
  myFrame.draw();
  // Enter event loop
  myapp.exec();
  ...
}

The program first shows the original curve, then another view of the same curve magnified by four in the right tail area as in the following figure.

Figure 5.1. Zoom one curve by specifying a point and a zoom factor

The whole zooming is carried out by a single method call on the zone.


  // Zoom at point x=40,y=0 by factor 4
  z1->zoom(40.0,0.0,4.0);

Sometimes what is required is to select one subset of the plot and zoom in it. This can easily be done using another method of the QpZone class, that is changing the above mentioned method call to e.g. :


  // Zoom so that the limits becomes [-100,100] on X and [0,120] on Y
  z1->zoom(-100.,0.,100.,120.);

This call would actually request an “inverse zooming”, i.e. the viewpoint moves far away rather than closer by.

Figure 5.2. Zoom one curve by specifying a bounding rectangle

The zone maintains a stack of “zooms”, thus to get back to the previous view it suffices to call the proper method:


  ...
  // Back one zoom level
  z1->unZoom(false);
  // Or straight to the original view
  z1->unZoom(true);
  ...

As explained the section called “QpPage user interactivity support ” the QpPage class provides a QpZoom instance which simplifies the implementation of zooming using additional GUI elements. This is an example of code exploiting this feature:


class MyWidget : public QWidget
{
public:
  MyWidget( QWidget *parent=0, const char *name=0 );
  void draw() {page->draw();page->print("zoom3.eps");}
  void newCurve();
private:
  QpPage  *page;
  QPushButton *zoomButton, *unzoomButton;
protected:
  /// Events processing
  void resizeEvent(QResizeEvent* e){page->resize(e->size());}
};

MyWidget::MyWidget( QWidget *parent, const char *name )
        : QWidget( parent, name )
{
  setGeometry(1,1,pixDim,pixDim);
  page = new QpPage(this,QRect(0,0,pixDim,pixDim),210,210);
  page->createZones(1,1);
  // Create buttons and associate them to QpPage slots
  zoomButton = new QPushButton("Zoom", this);
  QObject::connect(zoomButton, SIGNAL(clicked()), page, SLOT(slotZoom()));
  unzoomButton = new QPushButton("Unzoom", this);
  unzoomButton->setGeometry(110,0,100,30);
  QObject::connect(unzoomButton, SIGNAL(clicked()), page, SLOT(slotUnZoom()));
}

void MyWidget::newCurve () {
  AIDAVector vp;
  // Read data from file
  vp.fromAscii("ex2.dat");
  QpZone *z1 = page->getZone(0);
  // Insert one curve 
  new QpCurve1D( z1, vp ,QpCurve1D::HISTO);
}

int main(int argc, char* argv[]) {
  ...
  // Our widget corresponds to a window
  MyWidget myFrame;
  ...
  // Enter event loop
  myapp.exec();
}

This code allows to zoom by first pressing on the Zoom button and then selecting the zoom area by left clicking and dragging. After the left click, dragging the mouse will show a bounding rectangle on the plot. When the button is released, the bounding rectangle becomes the zoom area and the page automatically zooms on the zone. To un-zoom it suffices to press the Unzoom button and click on the zone to un-zoom.

The GUI buttons are member of the widget and they're created by this code:


MyWidget::MyWidget( QWidget *parent, const char *name )
        : QWidget( parent, name )
{
  ...
  // Create buttons and associate them to QpPage slots
  zoomButton = new QPushButton("Zoom", this);
  QObject::connect(zoomButton, SIGNAL(clicked()), page, SLOT(slotZoom()));
  unzoomButton = new QPushButton("Unzoom", this);
  unzoomButton->setGeometry(110,0,100,30);
  QObject::connect(unzoomButton, SIGNAL(clicked()), page, SLOT(slotUnZoom()));
}

Notice how the clicked button signals are connected to QpPage slots.

Defining zones with different size in the same page

All the examples seen so far assumed that zones were “regularly” placed in the page, i.e. zones had all the same size and did not overlap each other. This is done in all programs by calling the QpPage::createZones(zonesInX,zonesInY) method. This method simply divides the page area in (zonesInX*zonesInY) parts separated by a margin (usually 10% of page width and height respectively).

It is clearly possible to create a page containing zones of different size and ratio, but there's no automatic way to do so (it might eventually be provided using the grid layout concept). Let's assume, for instance, that we want to create a page with:

  • one zone spanning half width and full height of the page, aligned on the left

  • two zones spanning half width and half height of the page, aligned on the right

  • one small zone spanning one tenth of page width and height, aligned on the top right of the first zone.

The next figure shows how such a page would look like:

Figure 5.3. Four “non-regular” zones on the same page

In order to do so we first have to understand how zone coordinates map to page coordinates. The zone space is defined by the limits on the zone, while the page space is defined by the rectangle in the page where the zone is physically placed. The relation between those two rectangles in different spaces are kept in a class named QpMap. Each zone contains two instances of QpMap class holding the information to map points in a zone respectively along the X and Y directions. The ZoneStrategy transformation uses those maps to convert any point in a zone coordinate system to the corresponding point in the page coordinate system.

The QpPage::createZones(zonesInX,zonesInY) method simply defines the maps attached to each zone so that the position of zones on the page respect the required layout, including margins. The same process can be followed “manually” to define zones with arbitrary size and ratio as presented in the following code:


void MyWidget::customZones()
{
  QpMap *xMap;
  QpMap *yMap;
  // Retrieve page size and compute 10% margins
  double pageWidth = page->getWidth();
  double pageHeight = page->getHeight();
  double horizMargin = rint( pageWidth / 10);
  double vertMargin = rint(pageHeight  / 10);

  // The page origin (0,0) is in the bottom left corner

  // First zone: full height, half width
  double zoneWidth = rint((pageWidth - 3*horizMargin) / 2);
  double zoneHeight = rint((pageHeight - 2*vertMargin));
  double xPosition = horizMargin;
  double yPosition = pageHeight - vertMargin - zoneHeight;
  xMap = new QpMap();
  xMap->setContainerRange(xPosition,xPosition+zoneWidth);
  yMap = new QpMap();
  yMap->setContainerRange(yPosition,yPosition+zoneHeight);
  page->addElement(new QpZone(page, xMap, yMap));

  // Second zone: half height, half width, top
  zoneHeight = rint((pageHeight - 3*vertMargin)/2);
  xPosition = horizMargin*2 + zoneWidth;
  yPosition = pageHeight - vertMargin - zoneHeight;
  xMap = new QpMap();
  xMap->setContainerRange(xPosition,xPosition+zoneWidth);
  yMap = new QpMap();
  yMap->setContainerRange(yPosition,yPosition+zoneHeight);
  page->addElement(new QpZone(page, xMap, yMap));

  // Third zone half height, half width, bottom
  yPosition = pageHeight - vertMargin*2 - zoneHeight*2;
  xMap = new QpMap();
  xMap->setContainerRange(xPosition,xPosition+zoneWidth);
  yMap = new QpMap();
  yMap->setContainerRange(yPosition,yPosition+zoneHeight);
  page->addElement(new QpZone(page, xMap, yMap));

  // Fourth small zone (10% of page dimensions) overlaying the first one
  zoneWidth = rint( pageWidth / 10);
  zoneHeight = rint(pageHeight  / 10);
  xPosition = rint((pageWidth - 3*horizMargin) / 2) ;
  yPosition = pageHeight - vertMargin*2 ;
  xMap = new QpMap();
  xMap->setContainerRange(xPosition,xPosition+zoneWidth);
  yMap = new QpMap();
  yMap->setContainerRange(yPosition,yPosition+zoneHeight);
  page->addElement(new QpZone(page, xMap, yMap));
}

This is part of the code used to produce the Figure 5.3.. The code first retrieves the page width and height and computes the 10% horizontal and vertical margins. Then for each zone it computes the extent of the zone (in coordinate space) along the X and Y directions, i.e. the starting point, width and heigh of each zone. Using this information, the program allocates maps for the X and Y directions and gives them as parameter to QpZone constructors.

Working with Qplotter scene graph

As explained in the the section called “ Qplotter scene graph ” , the Qplotter scene graph is a tree of QpObject or QpNode instances. Each QpNode instance is a ordered list of QpObject instances. The order of drawing is in the Qplotter tree is from left to right, so by changing the position of instances in the tree it is possible to change the order of drawing.

In order to explain the details of the management of the scene graph, it's worth to concentrate on a real example: a page divided in two zones, each one containing three overlapping polygons. Using the methods of the QpNode class, the program will change the ordering of objects in one of the zones. At the beginning the scene graph tree for this example is the following:

Figure 5.4. The scene graph at the beginning

The page contains two QpZone instances. Each one contains two QpNode < QpPolyShape > instances: the former embedding a single red polygon, the latter containing two blue polygons. Given the order in the tree, the blue polygons will be drawn after the red one (wich will be on the back). The program modifies the sequence of the QpNode < QpPolyShape > instances, so that the red polygon becomes the last object drawn (as in Figure 5.5.).

Figure 5.5. The scene graph after changing the order in the zone node

The code that creates the scene graph is quite straightforward:


void MyWidget::newPolygons (QpZone *z) {
  z->setXRange(0.0,9.0);
  z->setYRange(0.0,9.0);
  // Insert one polygon
  // Create a QpPolyShape node first (so that we can assign it a pen attribute)
  // The object is a child of the zone (the parent object is the second parameter)
  QpNode<QpPolyShape> *pl=new QpNode<QpPolyShape>(new QString("QpNode<QpPolyShape>"),z,true);
  pl->setQpBrush(new QBrush(Qt::red));
  // Now create a new polygon and insert it in the QpNode
  new QpPolygon(8, polyX1, polyY1, pl, true);
  // Insert Another polygon
  // Create a QpPolyShape node first (so that we can assign it a pen attribute)
  pl=new QpNode<QpPolyShape>(new QString("QpNode<QpPolyShape>"),z,true);
  pl->setQpBrush(new QBrush(Qt::blue));
  // Now create two polygons and insert them in the QpNode
  new QpPolygon(4, polyX2, polyY2, pl, true);
  new QpPolygon(4, polyX3, polyY3, pl, true);
}

After defining the zone limits in the range [ 0,9 ], the code creates a QpNode < QpPolyShape > object, i.e. a container of QpPolyShape objects (such as QpPolygon or QpPolyline ).

The QpNode < QpPolyShape > object becomes a child of the zone (as specified by the last parameter of the constructor). It is important to create a node first, because visual attributes can be assigned only to QpNode instances. In this case the node is assigned a red brush, so that all children are painted with red fill color. Finally the code creates a polygon and set it as the child of the node.

The process (create a node, change brush, create children) is repeated to create the two blue polygons in the first zone. Then the same code is used to create a similar tree in the second zone. Given the creation order, the blue polygons are drawn on top of the red one.

The code to change the order in the tree is something like that:


  ...
  static bool cmpPolygon(QpObject *obj)   { return (obj->isA("QpNode<QpPolyShape>"));   }
  ...
  // Change the order in the list of polygons of the second zone
  QpZone *z = page->getZone(1);
  // Retrieve the first polygon
  QpObject *first = z->searchElement(cmpPolygon);
  // Bring to front the last polygon
  // (i.e. put it as last element of the list of the zone)
  z->addElement(first);
  // Remove the last polygon
  z->setAutoDelete(false);
  z->removeElement(first);
  z->setAutoDelete(true);

The code scans the tree to find the first QpNode < QpPolyShape > object i.e. the container of the red polygon (using the searchElement() method on the zone). The node is “brought to front” by the call to addElement() method and the old instance is removed from the node list using the removeElement() method (the calls to setAutoDelete are used to suspend automatic destruction when an object is removed from the list).

Figure 5.6. Changing the order in the scene graph

As shown in Figure 5.6., the effect of moving the node containing the red polygon to the end of the list is that it now overwrites the blue polygons.

Appendix A. Supported compilers

The list of supported compilers on a given platform follows.

Solaris 2.6

CC 4.2

Solaris 2.7

CC 5.1

Linux

egcs 1.1.2

gcc 2.95.2

Appendix B. The Viewer add-on

The Viewer add-on has been briefly introduced in the section called “Using the Viewer”. It is basically a set of helper classes built on top of the Qplotter kernel in order to hide as many details as possible to the user. The Viewer implements the IViewer interface and is packaged in the QpViewer concrete class.

Introduction

Viewer instances should be created via a factory class and only the IViewer interface methods should be invoked by the user. The following code shows how to create a page with one zone and a curve and print it in a Postscript file:


#include <stdio.h>
#include "Qplotter/viewer/viewerFactory.h"
#include "VectorOfPoints/Vector.h"

int main() {
  // Read the dataset from an ASCII file
  AIDAVector vp;
  vp.fromAscii("ex2.dat");
  IViewer *myV = ViewerFactory::Viewer();
  IPage *pg = myV->createPage(1,1);
  // Inserting one curve
  pg->selectZone(1)->addDataset(vp);
  myV->makePS("simpleviewer.eps");  
}

The ViewerFactory class creates an instance of the concrete class QpViewer and passes it back as a pointer to an IViewer instance. Once such an IViewer instance is created, the interface allows to access the page,zones and curves (Datasets) contained therein.

Using the Viewer

Using the Viewer add-on is much simpler than embedding the Qplotter widgets in a generic Qt program. This is done by exposing only the Qplotter features that are relevant for the end user and by providing a “property based” interface to visual attributes, i.e. the possibility to change such attributes by specifying {Key,Value} pairs. The following code shows how to access the underlying page,zones and curves (Datasets) structure and how to modify visual attribute via properties:


#include <stdio.h>
#include "Qplotter/viewer/viewerFactory.h"
#include "VectorOfPoints/Vector.h"

int main() {
  // Read the dataset from an ASCII file
  AIDAVector vp;
  vp.fromAscii("ex2.dat");
  IViewer *myV = ViewerFactory::Viewer();
  IPage *pg = myV->createPage(1,1);
  IZone *myZ = pg->selectZone(1);
  // Changing zone properties
  myZ->setProperty("Option","YAxisLog");
  // Inserting one curve
  IDataset *histCurve = myZ->addDataset(vp);
  // Changing curve properties
  histCurve->setProperty("LineColor","red");
  ...
}

Notice that page,zones and datasets are passed back as pointer to abstract interfaces (IPage, IZone, IDataset) as well. For a complete example on how to use the Viewer, see the simpleviever and histoFit example programs included in Qplotter distribution.

Appendix C. Adapters

Qplotter adapters to HTL and Gemini

Appendix D. The Qplotter tutorial

Qplotter includes a tutorial, i.e. a set of programs that introduce step by step its features.All programs in the Qplotter tutorial stem from a common ancestor, the simplest Qplotter program, but each one adds a few well defined features which can then be combined in a more complex application. Tutorial programs are grouped in 4 categories:

  • General features

  • Working with zone limits

  • Interaction with the mouse

  • Advanced features

Qplotter general features

simplest

The simplest Qplotter program

onecurve

Showing a 1D curve

onecurve2D

Showing a 2D curve

multirep

Showing a 1D curve using several representations

useaxis

Working with Qplotter axis

summaryt

Working with Qplotter summary tables (e.g. statistics)

Working with zone limits

fourzones

Dealing with zone limits

zoom1

Zooming in a zone by a given factor

zoom2

Zooming out of a zone using a selection rectangle

Mouse driven interaction

interact1

Showing a crosshair cursor on the page

pickcurve

Use picking information to change visual attributes of a curve

zoom3

Zooming in a zone by selecting the area with the mouse

Advanced features

selective

Redraw a single zone in the page

nonregzones

Defining zones with heterogeneous size in the same page

usetree

Working with Qplotter scene graph tree

How to run the tutorials

Where to find the tutorials

Qplotter tutorials are stored in a subdirectory of the CVS checkout. If no access to the CVS repository is available, there's a copy of the checkout installed in the Anaphe shared tree, e.g.:

[lxplus011] ls -l $LHCXXTOP/share/Qplotter/1.3.0.0/Qplotter/tutorial
drwxr-xr-x   2 dinofm   dl           4096 Jan 16 15:16 CVS
-rwxr-xr-x   1 dinofm   dl           3145 Jan 16 09:18 GNUmakefile
-rw-r--r--   1 dinofm   dl           2797 Jan 16 15:18 ex2.dat
-rw-r--r--   1 dinofm   dl           5493 Jan 10 15:59 ex2D.dat
-rwxr-xr-x   1 dinofm   dl           2067 Jan  9 19:34 fourzones.cpp
-rwxr-xr-x   1 dinofm   dl           1112 Dec 22 15:03 interact1.cpp
-rwxr-xr-x   1 dinofm   dl           2505 Jan 10 14:55 multirep.cpp
-rwxr-xr-x   1 dinofm   dl           3598 Jan 12 13:07 nonregzones.cpp
-rwxr-xr-x   1 dinofm   dl           1339 Jan  9 16:15 onecurve.cpp
-rwxr-xr-x   1 dinofm   dl           1390 Jan 10 15:59 onecurve2D.cpp
-rwxr-xr-x   1 dinofm   dl           2554 Jan  9 17:35 pickcurve.cpp
-rwxr-xr-x   1 dinofm   dl           2913 Jan 11 11:36 selective.cpp
-rwxr-xr-x   1 dinofm   dl           1022 Dec 22 15:03 simplest.cpp
-rwxr-xr-x   1 dinofm   dl           1839 Jan 10 17:47 summaryt.cpp
-rwxr-xr-x   1 dinofm   dl           2436 Jan 10 11:22 useaxis.cpp
-rwxr-xr-x   1 dinofm   dl           2821 Jan 15 18:27 usetree.cpp
-rwxr-xr-x   1 dinofm   dl           1607 Jan 11 16:18 zoom1.cpp
-rwxr-xr-x   1 dinofm   dl           1639 Jan 11 16:21 zoom2.cpp
-rwxr-xr-x   1 dinofm   dl           1669 Jan 11 18:42 zoom3.cpp

Building the tutorials

There are gmake target for each tutorial. To build all the tutorials type:

[lxplus011] gmake all

If gmake fails check that the Anaphe environment is properly set, e.g.:

[lxplus011] setenv LHCXX_REL_DIR /afs/cern.ch/sw/lhcxx/specific/redhat61/3.1.0

[lxplus011] setenv LHCXXTOP /afs/cern.ch/sw/lhcxx

[lxplus011] setenv PLATF redhat61

Executables are created in the OS subdirectory, e.g on Linux:

[lxplus011] ls -l Linux |grep rwx
-rwxr-xr-x   1 dinofm   dl          34505 Jan 16 08:56 fourzones
-rwxr-xr-x   1 dinofm   dl          30763 Jan 16 08:56 interact1
-rwxr-xr-x   1 dinofm   dl          35416 Jan 16 08:57 multirep
-rwxr-xr-x   1 dinofm   dl          37684 Jan 16 08:59 nonregzones
-rwxr-xr-x   1 dinofm   dl          32820 Jan 16 08:56 onecurve
-rwxr-xr-x   1 dinofm   dl          34336 Jan 16 08:58 onecurve2D
-rwxr-xr-x   1 dinofm   dl          38647 Jan 16 08:56 pickcurve
-rwxr-xr-x   1 dinofm   dl          38188 Jan 16 08:58 selective
-rwxr-xr-x   1 dinofm   dl          30617 Jan 16 08:56 simplest
-rwxr-xr-x   1 dinofm   dl          40441 Jan 16 08:58 summaryt
-rwxr-xr-x   1 dinofm   dl          35267 Jan 16 08:57 useaxis
-rwxr-xr-x   1 dinofm   dl          55679 Jan 16 08:59 usetree
-rwxr-xr-x   1 dinofm   dl          33664 Jan 16 08:58 zoom1
-rwxr-xr-x   1 dinofm   dl          33761 Jan 16 08:58 zoom2
-rwxr-xr-x   1 dinofm   dl          34501 Jan 16 08:59 zoom3

Running the tutorials

Check that the shared library path includes the Anaphe libraries, e.g.:

[lxplus011] echo $LD_LIBRARY_PATH
/afs/cern.ch/sw/lhcxx/specific/redhat61/3.1.0/lib

Bibliography and Useful Links

Books

[Gamma95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Copyright © 1995. 0-201-63361-2. Addison-Wesley Publishing Company. Design Patterns: Elements of Reusable Object-Oriented Software.

[Dalheimer99] Matthias Kalle Dalheimer. Copyright © 1999. 1-56592-588-2. O'Reilly. Programming with Qt. Writing Portable GUI applications on UNIX and Win32.

Web pages

[Qt Home] Qt Home Page .

[Lizard Home] Lizard Home Page .