Adding a new plot

Before getting started, it is important to know something about VisIt's infrastructure. See these links for more information:

You should first ask yourself whether a new plot is necessary. If you need to transform data but do not need a specialized method for rendering it then you might consider writing an operator instead since it is easier. If you need to both transform the data and provide a new graphical representation for that data then a new plot is probably the way to go.

How plots work

A plot consists of different pieces that each run in one of VisIt's components. Different pieces of a plot are compiled into different plugins:

  • libE : Runs in the compute engine and contains the avtPlot and the filters that implement it. This is where the data processing happens.
  • libV : Runs in the viewer and can do a small amount of data processing but mostly is for rendering the plot's geometry that was produced in the compute engine.
  • libG : Runs in the gui and provides a Qt GUI window that lets you manipulate the plot attributes.
  • libS : Runs in the CLI and provides bindings that let you manipulate the plot attributes from Python.

A plot creates a pipeline in VisIt's compute engine. The pipeline is initially the database on which the plot will operate and any filters that the plot adds to the pipeline. The output of the plot's pipeline is a dataset that gets transmitted to VisIt's viewer. Once the dataset is sent to the viewer, the plot can apply rendering filters that change the representation of the plot's dataset, affecting how it will be drawn. The viewer also executes the portions of a plot that render it into VisIt's OpenGL visualization window.

The gui and cli plot plugins provide mechanisms for setting the plot attributes, which ultimately end up down in the compute engine's pipeline and affect how the data processing is done for the plot's dataset.

Getting started

This hasn't been documented yet, but check out the page for creating an operator here ... they are similar.

The state object

All state in VisIt is transmitted between applications in the form of State objects. A state object is essentially a C++ class that contains fields that describe an object. In the case of plots, the state object contains the plot's attributes, which influence the filters that are applied to the plot and any rendering that takes place.

The state object for a plot is created as with other state objects, by using xmledit to define an XML file that contains the state object's fields. Once the XML file is generated, the xml2atts tool, which gets run as part of the xml2plugin generation script, creates a C++ class that contains the desired fields. The C++ state object class gets compiled as part of the libG, libS, libV, libE plugins that constitute the different plugins that are loaded by VisIt's various components.

The filter

One way to think about plots is as a set of pipeline filters that are executed and associated with a particular method for rendering the resulting data. Not all plots require a new filter to transform the data but it depends on the type of data being processed and how it will be represented graphically. Plots can provide a new avtFilter that take in the data and transform it into a new representation. As an example, VisIt's Scatter plot takes in a mesh with data consisting of 2 or more variables and its filter uses the variables to create a new point-based mesh completely different from the original mesh.

Visit ex network.jpg

The derived avtXXXPlot class

Your derived avtPlot class is a container of sorts for the filters that implements your plot's data processing and sets up the mappers that draw your plot. Your avtPlot class will:

  • Add filters to the VisIt pipeline
  • Accept new plot attributes and use them to set filter properties and rendering attributes
  • Provide behavioral information (e.g. add legends)

Plot types

All of VisIt's plots ultimately derive from the avtPlot class but most derive from specialized subclasses that incorporate logic to perform operations specific to the type of data that the plot represents.

Plot typeWhen to use
avtSurfaceDataPlotWhen you only need to plot external surfaces.
avtVolumeDataPlotWhen you need the plot's 3D, internal data too.
avtImageDataPlotWhen you will create an image-based representation of the plot.
avtLineDataPlotWhen your plot will consist of lines.
avtPointDataPlotWhen your plot will consist of point data.

Important methods

SetAtts

This method sets new plot attributes (state object) into the plot. This method must set the plot's atts member to the new plot attributes. It should also set the plot's needsRecalculation field to determine whether the plot attributes have changed enough such that the plot should be reexecuted on the compute engine. Sometimes plot reexecution is not necessary such as when you are changing a plot attribute that influences how a plot is drawn and won't affect the data in the plot.

   virtual void                SetAtts(const AttributeGroup*);

GetMapper

Return the mapper that will draw the plot.

   virtual avtMapper          *GetMapper(void);

ApplyOperators

This method allows your plot to add filters to the portion of the pipeline that will execute on the compute engine.

   virtual avtDataObject_p     ApplyOperators(avtDataObject_p);

ApplyRenderingTransformation

This method allows your plot to add filters to the portion of the pipeline that will execute on the viewer. This is most often used for operations related to drawing the data such as glyphing.

   virtual avtDataObject_p     ApplyRenderingTransformation(avtDataObject_p);

CustomizeBehavior

Use this method to set plot ordering and add legends, etc.

   virtual void                CustomizeBehavior(void);

CustomizeMapper

Set properties on the mapper that is used to draw the plot. This frequently means using information in the contract to set properties that govern how the plot will be drawn.

   virtual void                CustomizeMapper(avtDataObjectInformation &);

Adding a custom renderer

VTK data is transformed into something viewable by a mapper. A mapper is an object that traverses the VTK data and issues commands needed to draw the data to the graphics card. Most of VisIt's plots use VisIt's vtkVisItOpenGLPolyDataMapper class to convert polydata into a sequence of OpenGL function calls that tell your computer's graphics card how to draw a VTK object. The vtkVisItOpenGLPolyDataMapper class is mainly for sets of polygonal geometry that do not change. If you want to render your plot's resulting data in a custom, or dynamic fashion then you can use a custom renderer within VisIt. VisIt's Label, Molecule, Spreadsheet, and Volume plots use custom renderers to render the VTK data in special, dynamic ways.

In order to add a custom renderer,VisIt plots must return a avtUserDefinedMapper class for their mapper. The avtUserDefinedMapper class is a bridge to a more convenient renderer class that you will provide.

Hooking up a custom renderer in your avtPlot subclass

In order to add a custom renderer to your plot, you must create an instance of avtUserDefinedMapper in your avtXXXXPlot class. The mapper will bridge to your custom renderer class and ultimately make sure that your renderer's Render() method gets called with the VTK data that your plot created.

// avtXXXXPlot.h

// Include your renderer's header file
#include <avtXXXXRenderer.h>

class avtUserDefinedMapper;

class avtXXXXPlot : public avtSurfaceDataPlot
{
public:
    // Methods omitted for brevity

protected:
    avtUserDefinedMapper       *mapper;
    avtXXXXRenderer_p           renderer;

    virtual avtMapper          *GetMapper(void);

    // Other members and method omitted for brevity
};

Here is the code needed to create an instance of avtUserDefinedMapper and associate it with an instance of your custom renderer class. You can copy this code into your new plot's constructor to add a custom renderer.

// avtXXXXPlot.C

avtXXXXPlot::avtXXXXPlot() : avtSurfaceDataPlot()
{
    // Other member initialization ...

    // Create the custom renderer and mapper
    renderer = avtXXXXRenderer::New();
    avtCustomRenderer_p cr;
    CopyTo(cr, renderer);
    mapper = new avtUserDefinedMapper(cr);
}

Here is the code that you need to add to your plot's destructor to make sure that the mapper and renderer are destroyed when your plot is destroyed. Note that the renderer member is a reference pointer so setting it to NULL will cause its reference count to be decremented, leading to the renderer's deletion.

// avtXXXXPlot.C

avtXXXXPlot::~avtXXXXPlot()
{
    // Delete the mapper, in this case the avtUserDefinedMapper
    if (mapper != NULL)
    {
        delete mapper;
        mapper = NULL;
    }
    // This is a ref-pointer so setting it to NULL will decrement the 
    // ref-count to the renderer and delete it when necessary.
    renderer = NULL;

    // Cleanup other members ...
}

Now, you must provide the code to return your user-defined mapper from your plot so it can be used when VisIt needs it. All you have to do is override the GetMapper() method and return a pointer to your plot's instance of avtUserDefinedMapper.

// avtXXXXPlot.C

avtMapper *
avtXXXXPlot::GetMapper(void)
{
    return mapper;
}

We've gone over all of the modifications to avtXXXXPlot that need to be made in order to hook up your renderer. However, there's another topic to discuss if you plan to make your renderer use the plot's state object in order to get some of its settings (e.g. colors, etc.). Each avtPlot subclass implements a SetAtts method that takes a state object as input. Many plots copy the input state object into a member variable that stores the plot attributes so they are available when various plot methods need access to the plot attributes. If you plan to use the state object to control how your renderer draws your plot's data then you should pass the state object along to your renderer too.

// avtXXXXPlot.C

void
avtXXXXPlot::SetAtts(const AttributeGroup *a)
{
    const XXXXAttributes *newAtts = (const XXXXAttributes *)a;

    // Code to use new state object attributes omitted for brevity.

    // Give your renderer the new attributes to influence how it draws
    // the plot data. This assumes that you add a SetAtts method to your
    // custom renderer.
    renderer->SetAtts(*newAtts);
}

The custom renderer

Once you've modified your avtXXXXPlot class so it will use a custom renderer, you'll need to actually define your custom renderer class. You'll need to create new .C and .h files for your custom renderer. Don't forget to add the new .C file to the XXXX.xml file in the sections for additional viewer and engine source files. If you fail to add the new .C file to the XML file then your plugin will may link but it will not load at runtime due to unresolved external symbols.

The new .h file can look like the code below to get started:

// avtXXXXRenderer.h
#ifndef XXXX_RENDERER_H
#define XXXX_RENDERER_H
#include <avtCustomRenderer.h>
#include <XXXXAttributes.h>

class avtXXXXRenderer : public avtCustomRenderer
{
public:
                            avtXXXXRenderer();
    virtual                ~avtXXXXRenderer();
    static avtXXXXRenderer *New(void);

    // Override this method to provide customized rendering
    // functionality.
    virtual void            Render(vtkDataSet *);

    // Override this method so your renderer can release its
    // graphical resources when instructed to do so.
    virtual void            ReleaseGraphicsResources();

    // Provide this method to set your renderer's attributes from
    // avtXXXXPlot::SetAtts so your renderer can use data from
    // the plot's state object.
    void                    SetAtts(const AttributeGroup *);
private:
    // Store the plot attributes so the renderer can use them
    XXXXAttributes          atts;
};

typedef ref_ptr<avtXXXXRenderer> avtXXXXRenderer_p;
#endif

There are several important methods that are overridden from the avtCustomRenderer base class.

MethodDescription
Render

The Render method is called when a VTK dataset needs to be rendered into VisIt's vis window. You'll insert the code for drawing your plot's data into the Render method.

ReleaseGraphicsResources

The ReleaseGraphicsResources method is called when VisIt wants the renderer to release resources such as memory, textures, display lists, etc. When this method is called, the renderer should attempt to free as many of its resources as it can feasibly release.

Multi-block data

Since VisIt can operate on multi-block (domain-decomposed) data, it means that there are potentially N VTK datasets that your renderer will need to render. This means that when the resulting tree of N VTK datasets is sent from the compute engine to the viewer where the data are often rendered, there will be many VTK objects sharing your custom renderer instance. It should be noted that there is 1 custom renderer per avtPlot object and that avtPlot object will sequentially pass the N VTK datasets through the renderer by calling the Render method for each VTK dataset. So, if you plan to create display lists based for the VTK dataset, you will need to associate the display list or other resources with the particular VTK dataset being rendered. This can easily be accomplished by using a map where the map key is the address of the current VTK dataset.

OpenGL vs. Mesa implementations

This changed significantly in 2.0! The directions here apply to 2.0. Directions for 1.12 and earlier were moved to a different page.

VisIt uses OpenGL for on-screen rendering so by calling OpenGL functions in your custom renderer's Render method, your renderer will be able to draw your plot into VisIt's visualization window. VisIt also supports saving off-screen images and rendering off-screen on the compute engine (Scalable Rendering). In order to support off-screen rendering, VisIt uses Mesa versions of the mappers that typically perform rendering.

To support this, all you need to do is make sure that avtGLEWInitializer is among the first includes in the file where you implement your custom Render method:

  #include <avtGLEWInitializer.h>

This causes your plot to use GLEW instead of GL directly. You can further take advantage of this by using GLEW extension queries to ensure you do not use functionality which does not exist on the GPU which VisIt is running on.

Alternate displays

VisIt can use default mappers and custom renderers to use OpenGL to draw into VisIt's visualization window. Sometimes, drawing into a visualization window is not necessarily what you want to do. Suppose you are trying to integrate some alternate data display GUI with your VisIt plot. VisIt supports alternate displays that associate some other type of display window with your VisIt plot. The alternate display can really be anything that can share the Qt event loop so it can be another Qt window, an X11 window, or perhaps even something more elaborate such as a socket to another application.

An alternate display is created by your plot plugin's AlternateDisplay methods. The alternate display is passed around in VisIt as a void* and VisIt treats that pointer as an opaque handle to your alternate display. The handle is passed down to your plugin when VisIt wants to perform certain actions on the alternate display such as showing, hiding, clearing, iconifying, etc. While avtPlot objects may come an go as your plot is cleared and regenerated, the alternate display will persist as long as you have not deleted the plot from the plot list.

See VisIt's Spreadsheet plot for examples of how to create an alternate display.

Alternate display plugin methods

The ViewerPluginInfo class for your plugin needs to implement AlternateDisplay methods in order for it to be able to create an alternate display. Here are the methods that should be implemented:

// Create the alternate display, associate it with a plot, and 
// return a handle to the alternate display. The returned handle
// will be passed to the other alternate display methods to
// identify the alternate display.
virtual void *AlternateDisplayCreate(ViewerPlot *plot);

// Destroy the alternate display pointed to by the dpy handle.
virtual void AlternateDisplayDestroy(void *dpy);

// Clear the alternate display pointed to by the dpy handle. 
// VisIt calls this method when a plot is cleared.
virtual void AlternateDisplayClear(void *dpy);

// Hide the alternate display pointed to by the dpy handle.
virtual void AlternateDisplayHide(void *dpy);

// Show the alternate display pointed to by the dpy handle.
virtual void AlternateDisplayShow(void *dpy);

// Iconify the alternate display pointed to by the dpy handle.
virtual void AlternateDisplayIconify(void *dpy);

// DeIconify the alternate display pointed to by the dpy handle.
virtual void AlternateDisplayDeIconify(void *dpy);

Note that VisIt's XML tools do not yet have a flag for alternate displays to aid in code generation. As such, you will need to add these method declarations to the XXXXViewerPluginInfo class in the generated XXXXPluginInfo.h file. It is recommended that the method definitions are kept in a separate .C file from the rest of the ViewerPluginInfo methods. You'll have to add the separate .C file into the XML file in the viewer section to ensure that the alternate display code gets compiled into the viewer plugin.

Alternate display plugin methods (Qt implementation)

Suppose you have a Qt widget called SpecialViewer that you want to use to use for displaying data in VisIt. The implementation of the alternate display methods will look something like this:

// This method is called when the alternate display gets created
void *
XXXXViewerPluginInfo::AlternateDisplayCreate(ViewerPlot *plot)
{
    void *dpy = 0;

    if(!avtCallback::GetNowinMode())
    {
        // We're not in nowin mode so create the alternate display window
        SpecialViewer *win = new SpecialViewer(0, "SpecialViewer");
        dpy = (void *)win;
    }

    return dpy;
}

//   This method is called when the alternate display must be destroyed.
void 
XXXXViewerPluginInfo::AlternateDisplayDestroy(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        delete v;
    }
}

//   This method is called when the alternate display must be cleared of data.
void 
XXXXViewerPluginInfo::AlternateDisplayClear(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        v->clear();
    }
}

//   This method is called when the alternate display must be hidden.
void 
XXXXViewerPluginInfo::AlternateDisplayHide(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        v->hide();
    }
}

//   This method is called when the alternate display must be shown.
void 
XXXXViewerPluginInfo::AlternateDisplayShow(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        if(v->isMinimized())
            v->showNormal();
        else
            v->show();
        v->raise();
    }
}

//   This method is called when the alternate display must be iconified.
void 
XXXXViewerPluginInfo::AlternateDisplayIconify(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        v->showMinimized();
    }
}

//   This method is called when the alternate display must be de-iconified.
void 
XXXXViewerPluginInfo::AlternateDisplayDeIconify(void *dpy)
{
    if(dpy != 0)
    {
        SpecialViewer *v = (SpecialViewer *)dpy;
        v->showNormal();
    }
}

Updating an alternate display

Alternate displays have no mechanism of their own for obtaining data from VisIt so you will neeed to use a custom renderer that can pass the vtkDataSet to be renderered to your plot's alternate display. This means that every time the visualization window wants to update, for whatever reason, your alternate display will also want to "render". You can, of course, add checks in your alternate display's rendering code to detect whether the input VTK data actually requires the alternate display to be redrawn.

class XXXXAlternateDisplayRenderer : public avtCustomRenderer
{
public:
    XXXXAlternateDisplayRenderer();
    virtual ~XXXXAlternateDisplayRenderer();

    virtual void Render(vtkDataSet *ds);
    virtual void SetAlternateDisplay(void*);

    void SetAtts(const XXXXAttributes &);
private:
    void           *plotDisplay;
    XXXXAttributes  plotAtts;
};

Now for the implementation of the custom renderer that shows how the alternate display gets its data. Since the alternate display will be fed its data by the custom renderer, the renderer must have a SetAlternateDisplay method so it can accept the handle to the alternate display from VisIt. This is necessary because while the alternate display is a long-lived object, existing the entire time the plot exists in the the plot list, the renderer is not so long-lived. VisIt takes care of telling new instances of the custom renderer that it needs to update a specific alternate display.

XXXXAlternateDisplayRenderer::XXXXAlternateDisplayRenderer() : avtCustomRenderer(), plotAtts()
{
    plotDisplay = 0;
}

XXXXAlternateDisplayRenderer::~XXXXAlternateDisplayRenderer()
{
}

// Give this custom renderer object the handle to the alternate display.
void
XXXXAlternateDisplayRenderer::SetAlternateDisplay(void *dpy)
{
    plotDisplay = dpy;
}

void
XXXXAlternateDisplayRenderer::SetAtts(const XXXXAttributes &a
{
    plotAtts = a;
}

Also note that since the avtXXXXPlot class will use a custom renderer, it is recommended that you have separate viewer and engine-specific versions of the custom renderer's Render method. This separation is necessary so as not to introduce GUI toolkit dependencies into the VisIt compute engine via the libE engine plugin. You can do this by putting the Render method into files called XXXXAlternateDisplayRenderer_Engine.C and XXXXAlternateDisplayRenderer_Viewer.C. You'll put the _Engine.C file in the engine-specific files portion of the plot's XML file. You'll put the _Viewer.C file in the viewer-specific files portion of the plot's XML file.

Viewer implementation of the Render method:

// XXXXAlternateDisplayRenderer_Viewer.C
void
XXXXAlternateDisplayRenderer::Render(vtkDataSet *ds)
{
    if(plotDisplay != 0)
    {
        // Assumes the alternate display object has a method called Render
        // can accept a vtkDataSet and the plot attributes. The alternate
        // display's Render method will draw the VTK dataset using
        // whatever API suits the display (e.g. Qt painter calls, X11) 
        ((SpecialViewer *)plotDisplay)->Render(ds, plotAtts);
    }
}

Engine implementation of the Render method:

// XXXXAlternateDisplayRenderer_Engine.C
void
XXXXAlternateDisplayRenderer::Render(vtkDataSet *ds)
{
    // The engine has no alternate displays. No-op.
}

Making a plot GUI window

When you run xml2plugin a basic plot window will be created for your new plot. The files will be called QvisXXXPlotWindow.C and QvisXXXPlotWindow.h and they will be compiled into the libG plugin that VisIt's gui will load on start up. The new window is typically very simple and lists state object field names in one column and widgets capable of setting the values for the fields in the next column. The types of widgets that xml2window uses are determined by the type of the fields that you selected when you created your XML file in xmledit.

It is best to start with the basic implementation of the plot window and adapt it be more aesthetically pleasing or easier to use. You can look at the implementations of the windows for other plots for guidance. It is also helpful to become familiar with programming using Qt.

Implementation tips

  • Don't shadow one of your class members by creating a widget variable with the same name. If you do this when you are creating widgets, you'll crash VisIt's gui because you did not initialize the class member by that name.
  • In UpdateWindow, many widget types require a blockSignals(true) before setting their values or they will end up calling slots attached to them. That's BAD! You can see if you are doing this by using your window with Auto apply turned on.