Kitware Source Feature Aricle: July 2010

New Chart API in VTK

One of the first tasks I was given after starting at Kitware was to implement a new API to render charts in VTK. There were various motivations for this, and the project grew in scope after discussions and close collaboration with developers from Sandia National Laboratories. There were several elements to the project, and development is still ongoing. These include:

  • Abstracted 2D API for rendering
  • Canvas API for handling objects and mouse interaction
  • Chart API, using the previous two APIs

All of the rendering in the new charts is now done in VTK, using OpenGL. This is a departure from the previous generation of the charts that used Qt to do all rendering. The use of the OpenGL backend is abstracted through a 2D API, thus allowing for the implementation of other backends in the future, such as Qt, SVG, PDF. The majority of this work is in the Charts subdirectory of the VTK source tree, and can be used in both VTK and ParaView.

Development of the 2D API
The starting point of this work was to promote 2D rendering/data to be a first class citizen in VTK. New classes were added to Common to support 2D data and transformations - vtkPoints2D, vtkMatrix3x3, and vtkTransform2D. The majority of the new classes were added to a new VTK kit called Charts, some of these classes may migrate to more appropriate locations in the future.

2D API

The vtkContext2D class provides the abstracted 2D rendering API, and uses an implementation of the vtkContextDevice2D class to perform all low level rendering. This allows for rendering functions in the charts to make calls such as,

painter->ApplyPen(this->Pen);
painter->DrawPoly(this->Points);

The first line applies the Pen for the object (affecting rendering properties such as color, line thickness, line type), and the second draws a polyline between the x, y coordinates supplied in the Points object (an instance of vtkPoints2D). In the case of the vtkOpenGLContextDevice2D class, the second line executes the following snippet of OpenGL code,

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, f);
glDrawArrays(GL_LINE_STRIP, 0, n);
glDisableClientState(GL_VERTEX_ARRAY);

This means that most rendering code for charts need only be written once, with multiple implementations of the abstract vtkContextDevice2D class being possible. I hope to add a second implementation more suited to producing publication quality charts in the near future.

The Scene API
The second major part of new API added for the charts was the canvas API, which facilitates object persistence and mouse interaction. The canvas API draws inspiration from the Graphics View framework in Qt, and as you can see in the diagram has a similar class/inheritance hierarchy.

Scene API

Any graphical item that will be rendered by the scene must inherit from vtkContextItem. This class has several virtual functions that can be implemented in derived classes to render the item and control interaction with it. The only pure virtual function is the Paint function that has the following prototype:

virtual bool Paint(vtkContext2D *painter) = 0;

In order to handle mouse interactions several other virtual functions come into play, such as Hit which lets the scene know if a particular mouse event has hit the item, and the mouse event virtual functions that can be overridden to respond to mouse events.

The scene manages the items, but it needs a vtkContextView in order to render. The current implementation of vtkContextView contains a vtkContextScene, to which the user can add items. The TestContext unit test shows a simple example of an item being derived, and then displayed in a scene that is rendered by the context view. A snippet of code to set up and use the vtkContextView is shown below:

vtkSmartPointer view =
  vtkSmartPointer::New();
view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
view->GetRenderWindow()->SetSize(800, 600);
vtkSmartPointer test =
  vtkSmartPointer::New();
view->GetScene()->AddItem(test);
view->GetRenderWindow()->Render();

Chart API
The chart API builds on the new 2D and canvas APIs to provide a simple API that has no additional dependencies. It was written to be scalable, internally using templated code to pack coordinates in such a way that rendering is batched, and data is not copied more than necessary.

A base vtkChart class derives from vtkContextItem, and implements API common to most 2D charts. A further specialization was implemented in vtkChartXY, in which the common XY chart was implemented. This class can be instantiated and added to a scene. Until some plots are added nothing will be displayed on screen.

Most of the currently implemented chart API uses vtkChartXY to manage the plots, axes, legends and other associated objects. The TestLinePlot unit test (available in the Testing/Cxx subdirectory of Charts) shows the simplest way to construct and use a line chart. Below is an example taken from the code:

vtkSmartPointer<vtkContextView> view =
         vtkSmartPointer<vtkContextView>::New();
  view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
  view->GetRenderWindow()->SetSize(400, 300);
  vtkSmartPointer<vtkChartXY> chart =
vtkSmartPointer<vtkChartXY>::New();
  view->GetScene()->AddItem(chart);

  // Create a table with some points in it...
  vtkSmartPointer<vtkTable> table =
     vtkSmartPointer<vtkTable>::New();
  vtkSmartPointer<vtkFloatArray> arrX =
vtkSmartPointer<vtkFloatArray>::New();
  arrX->SetName("X Axis");
  table->AddColumn(arrX);
  vtkSmartPointer<vtkFloatArray> arrC =
vtkSmartPointer<vtkFloatArray>::New();
   arrC->SetName("Cosine");
   table->AddColumn(arrC);
   vtkSmartPointer<vtkFloatArray> arrS =
vtkSmartPointer<vtkFloatArray>::New();
   arrS->SetName("Sine");
   table->AddColumn(arrS);
   vtkSmartPointer<vtkFloatArray> arrS2 =
vtkSmartPointer<vtkFloatArray>::New();
   arrS2->SetName("Sine2");
   table->AddColumn(arrS2);
   // Test charting with a few more points...
   int numPoints = 69;
   float inc = 7.5 / (numPoints-1);
   table->SetNumberOfRows(numPoints);
   for (int i = 0; i < numPoints; ++i)
     {
     table->SetValue(i, 0, i * inc);
     table->SetValue(i, 1, cos(i * inc) + 0.0);
     table->SetValue(i, 2, sin(i * inc) + 0.0);
     table->SetValue(i, 3, sin(i * inc) + 0.5);
     }

   // Add multiple line plots, setting the colors etc
   vtkPlot *line = chart->AddPlot(vtkChart::LINE);
   line->SetInput(table, 0, 1);
   line->SetColor(0, 255, 0, 255);
   line->SetWidth(1.0);
   line = chart->AddPlot(vtkChart::LINE);
   line->SetInput(table, 0, 2);
   line->SetColor(255, 0, 0, 255);
   line->SetWidth(5.0);
   line = chart->AddPlot(vtkChart::LINE);
   line->SetInput(table, 0, 3);
   line->SetColor(0, 0, 255, 255);
   line->SetWidth(4.0);

This API is also wrapped in Python, and an equivalent example in Python would be:

   view = vtk.vtkContextView()
   view.GetRenderer().SetBackground(1.0,1.0,1.0)
   view.GetRenderWindow().SetSize(400,300)
   chart = vtk.vtkChartXY()
   view.GetScene().AddItem(chart)

   # Create a table with some points in it
   table = vtk.vtkTable()
   arrX = vtk.vtkFloatArray()
   arrX.SetName("X Axis")
   arrC = vtk.vtkFloatArray()
   arrC.SetName("Cosine")
   arrS = vtk.vtkFloatArray()
   arrS.SetName("Sine")
   arrS2 = vtk.vtkFloatArray()
   arrS2.SetName("Sine2")
   numPoints = 69
   inc = 7.5 / (numPoints - 1)
   for i in range(0,numPoints):
     arrX.InsertNextValue(i*inc)
     arrC.InsertNextValue(math.cos(i * inc) + 0.0)
     arrS.InsertNextValue(math.sin(i * inc) + 0.0)
     arrS2.InsertNextValue(math.sin(i * inc) + 0.5)

   table.AddColumn(arrX)
   table.AddColumn(arrC)
   table.AddColumn(arrS)
   table.AddColumn(arrS2)

   # Now add the line plots with appropriate colors
   line = chart.AddPlot(0)
   line.SetInput(table,0,1)
   line.SetColor(0,255,0,255)
   line.SetWidth(1.0)
   line = chart.AddPlot(0)
   line.SetInput(table,0,2)
   line.SetColor(255,0,0,255);
   line.SetWidth(5.0)
   line = chart.AddPlot(0)
   line.SetInput(table,0,3)
   line.SetColor(0,0,255,255);
   line.SetWidth(4.0)

Paraview Chart 1

The charts are also exposed in ParaView 3.8.0, and so can be experimented with from the ParaView GUI. In early tests they have proven to be much more scalable, and use the GPU to perform Cartesian transformations in the plot window. There are three types of plots currently available – points, lines and bars. The line plots also use the same marks implemented by the points. The charts feature mouse over tooltips displaying the value of the nearest point, as well as early support for selection in the point and line plots. More recently support for multiple axes (up to four) was added, and will be included in the next VTK release.

Support for parallel coordinates was added as part of a comparative visualization project led by Dr. Berk Geveci. Development of this new chart type in VTK/ParaView led to support for selection linking in ParaView between multiple views, which could also be used in VTK applications.

Paraview Chart 2

The user can left click and drag on any of the axes in order to select lines that lie in a chosen range, narrowing down their choices by selecting ranges on multiple axes.

Paraview Chart 3

Charts in ParaView
The new charts described previously are exposed in ParaView, and a large amount of development and testing was done to satisfy the needs of ParaView users. ParaView 3.8.0 is the first release to feature these new charts. From the ParaView GUI you can select what data to plot using the filters, principally in the “Data Analysis” submenu.

Data Analysis Submenu

The ParaView screenshot shows the “can.ex2” dataset available in the ParaViewData repository, plotting some points over time and a histogram of displacement. Some of my latest changes have enabled selection linking for line charts too, allowing you to select rows in the spreadsheet view and see the points highlighted in the chart.

Conclusions
This new API in VTK (and functionality exposed in ParaView) represents a significant amount of new work. A large portion of this work was supported by the Sandia InfoVis and ParaView projects. Going forward, the implementation of a publication quality context device, and the addition of further chart types are on the agenda. It will also be important to ensure that the rendering remains efficient as more features are added.

Acknowledgements
The author would like to thank Tim Shead and Brian Wylie for their help and support in developing these new features. This work was partially funded by Sandia National Laboratories as part of the Titan Toolkit (titan.sandia.gov) and ParaView (paraview.org) projects.

Marcus Hanwell  Marcus Hanwell is an R&D engineer in the scientific visualization team at Kitware, Inc. He joined the company in October 2009, and has a background in open source, Physics and Chemistry. He spends most of his time working with Sandia on VTK, Titan and ParaView.