Table of Contents
Table of Contents
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.
The Qplotter project aim is to provide a package that:
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.
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.
More information on Qt can be found on Trolltech site. A book on Qt [Dalheimer99] is available as well.
Table of Contents
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.
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;
}
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).
So what is the connection between Qt and Qplotter? If we examine more carefully the program, we notice we've used these Qt classes:
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 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.
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
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:
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:
Thanks to the additional buttons, it is now possible to produce Postscript output, to zoom and un-zoom and to exit the application properly.
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");
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.
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:
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.
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:
Table of Contents
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:
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:
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 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.
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.
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:
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).
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:
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 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).
These three systems of coordinates are connected by transformations , which are the subject of the next section.
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:
Base class for all transformation strategies
Transforms from page coordinates to pixel coordinates
Transforms from zone coordinates to pixel coordinates.
Special zone strategy relative to bottom right corner
Special zone strategy relative to bottom left corner
Special zone strategy relative to top right corner
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.
The Qplotter package is further split in four “components”:
Each “component” is packaged is a separate shared library, so that only the part effectively used is taken.
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.
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:
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.
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.
The package depends on the above mentioned Interfaces , AIDA_Annotation, VectorOfPoints packages as well as on HTL and Gemini.
Table of Contents
The QpPage class is the container of all objects appearing on the screen or printed on paper. Its main tasks are:
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.
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:
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.
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:
The three subclasses of QpEvObserver are specialisation available directly in Qplotter to implement common operations such as zooming or locating.
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.
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.
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.
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.
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:
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.
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.
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:
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:
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.
Several representations are available for 1D datasets, such as:
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):
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); ...
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:
Additional representations for 2D datasets, such as Lego, Contour , Color or Surface will be available in future releases of Qplotter.
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.
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); ...
Table of Contents
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 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.
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.
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.
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:
The next figure shows how such a page would look like:
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.
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:
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.).
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).
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.
The list of supported compilers on a given platform follows.
Table of Contents
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.
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 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.
Table of Contents
Table of Contents
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:
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/tutorialdrwxr-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
There are gmake target for each tutorial. To build all the tutorials type:
[lxplus011] gmake allIf 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