VTK/Charts/ChartAPI: Difference between revisions

From KitwarePublic
< VTK‎ | Charts
Jump to navigationJump to search
mNo edit summary
(→‎Newer Screenshot in ParaView: Picture changed, only one plot left.)
 
(20 intermediate revisions by one other user not shown)
Line 1: Line 1:
The diagram below shows the basic relationship between the different classes that make up a chart. For clarity only the composition of a vtkChartXY is shown, but all charts have multiple components used to draw their parts. Each component class that has something rendered to the screen has a function with the following prototype,
The diagram below shows the basic relationship between the different classes that make up a chart. For clarity only the composition of a vtkChartXY is shown, but all charts have multiple components used to draw their parts. Each component class that has something rendered to the screen has a function with the following prototype,


<source lang="cpp">bool Paint(vtk2DPainter *painter);</source>
<source lang="cpp">bool Paint(vtkContext2D *context);</source>


These member functions are called by the chart's RenderOverlay function. The chart manages the order in which the Paint functions are called, and so the grid would be drawn over by the plots, and they in turn would be drawn over by the axes and text.
These member functions are called by the chart's RenderOverlay function. The chart manages the order in which the Paint functions are called, and so the grid would be drawn over by the plots, and they in turn would be drawn over by the axes and text.
Line 21: Line 21:
   // Classes
   // Classes
   vtkProp
   vtkProp
   vtk2DPainter
   vtkChartActor
  vtkContext2D


   vtkChart
   vtkChart
Line 28: Line 29:
   vtkChartPie
   vtkChartPie


  vtkMapper
  vtkChartMapper
   vtkPlot
   vtkPlot
   vtkPlotLine
   vtkPlotLine
Line 49: Line 48:
   arrowhead = none
   arrowhead = none
]
]
   vtkProp -> vtkChart
   vtkProp -> vtkChartActor


   vtkMapper -> vtkChartMapper
   vtkAlgorithm -> vtkContextMapper2D
   vtkChartMapper -> vtkPlot
   vtkContextMapper2D -> vtkPlot
 
  vtkContextProp2D -> vtkChart
  vtkContextProp2D -> vtkPlotGrid
  vtkContextProp2D -> vtkLegend
  vtkContextProp2D -> vtkAxis


   vtkChart -> vtkChartXY
   vtkChart -> vtkChartXY
Line 76: Line 80:
   vtkChartXY -> vtkPlot [headlabel="n" taillabel="1"]
   vtkChartXY -> vtkPlot [headlabel="n" taillabel="1"]
   vtkChartXY -> vtkPlotGrid
   vtkChartXY -> vtkPlotGrid
   vtkChartXY -> vtkAxis
   vtkChartXY -> vtkAxis [headlabel="n" taillabel="1"]
   vtkChartXY -> vtkLegend
   vtkChartXY -> vtkLegend


   vtkChart -> vtk2DPainter
   vtkChartActor -> vtkContext2D
  vtkChartActor -> vtkChart
  qtChartWidget -> vtkChart
  qtChartWidget -> vtkContext2D
  qtChartSVG -> vtkChart
  qtChartSVG -> vtkContext2D
}
}
</graphviz>
</graphviz>


== Common API Elements ==
All drawable elements of a chart use a vtkContext2D to draw. Elements that take a data input inherit from vtkContextMapper2D, and all elements that do not take a data input inherit from vtkContextProp2D. Both of these abstract base classes provide a common API to facilitate efficient rendering.
<source lang="cpp">// Painting functions
bool Paint(vtkContext2D *context);
int GetLayer();
void SetLayer(int layer);
// Properties and settings
bool SetProperty(const char *name, const vtkVariant &value);
bool GetProperty(const char *name, vtkVariant *value);
bool ReadSettings(const vtkSettings &settings);
bool WriteSettings(vtkSettings *settings);</source>
The layer property can be used by the Chart class to sort the order in which elements are drawn, with the grid on the bottom layer, then the line plots, symbols, annotations, axes and the legend on the top layer. The get and set property functions allow for properties of chart objects to be set and get without having to down cast. The read and write settings provide a more general container object for the settings or properties of a chart.
== Chart Settings ==
A simple settings object would facilitate the modification of chart properties using a general container. Using a consistent property naming scheme would allow for settings or themes to be applied across all chart objects. A simple solution to this problem would be an STL map based container, with some helper functions to facilitate default settings and the possibility of inheritance.
<source lang="cpp">class Settings
{
  public:
    SetValue(const vtkStdString &key, const vtkVariant &value);
    vtkVariant GetValue(const vtkStdString &key);
    vtkVariant GetValue(const vtkStdString &key, const vtkVariant &default);
    void BeginGroup(const vtkStdString &groupName);
    void EndGroup();
};</source>
Groups could be delimited using the forward slash character, and settings resemble a directory layout. The begin and end group functions simply push and pop group elements. All keys are unique, and so setting the same key again overwrites the previous value. This allows for a simple implementation of a settings class that has a great deal of flexibility. Internally an STL map is used to store the keys, serialization is quite simple and mapping to XML and other on disk formats should not present significant challenges.
This format would also map very easily to Qt's QSettings object should exchange with Qt be desirable. The following two code snippets would result in the same settings object,
<source lang="cpp">settings->SetValue("Axis/X/Width", 2.0);
settings->BeginGroup("Axis");
settings->BeginGroup("X");
settings->SetValue("Width", 2.0);
settings->EndGroup();
settings->EndGroup();</source>
The major advantage of the more verbose second form is that when the x axis is writing its settings it does not need to be concerned about what groups it might be contained within. It can call settings->SetValue("Width", 2.0); and assume the calling class set the groups up correctly. It can also do the same when reading its settings, calling GetValue("Width") and if the settings object was in the correct group it would return the key "Axis/X/Width".
== Early Screenshot ==
This is a screenshot taken of a graph that contains two data series, each of which are made up of 28 points. There is a line and a point at each data point along with some test text and a transparent rectangle. I have also tested with 1,000,000 data points plotted as a line where the average frame rate (over 1,000 renders) was 93.43 fps on my system. This is without using display lists or vertex buffers at this point.
[[Image:Chart-early.png]]
The chart shows two axes being plotted as lines, a test axis label and tick marks/labels. The data series are plotted in a clipped frame inside the window, which is transformed to the Cartesian coordinates of the data series. This means that when using OpenGL as a backend the graphics card does most of the matrix multiplication for us.
== Newer Screenshot in ParaView ==
Changed I have yet to commit to ParaView (lots of new proxies, one or two leaks, settings need exposing) showing a wavelet source and the new 2D API based charts (right). I promised you a screenshot - so here you go,
[[Image:ParaView2DAPI.png]]


== Rendering Philosophy ==
== Rendering Strategy ==


The base class makes no assumptions about the rendering philosophy employed by any particular chart. In the vtkChartXY I have taken a two pass approach. The data plots are drawn using a view transform according to the extents of the plot set by the vtkAxis objects in x and y. So they can be cached in an OpenGL display list and replayed as the chart changes the range of x and y that are plotted. The plot area is also clipped so that anything outside of the plot area is discarded.
The base class makes no assumptions about the rendering philosophy employed by any particular chart. In the vtkChartXY I have taken a two pass approach. The data plots are drawn using a view transform according to the extents of the plot set by the vtkAxis objects in x and y. So they can be cached in an OpenGL display list and replayed as the chart changes the range of x and y that are plotted. The plot area is also clipped so that anything outside of the plot area is discarded.


The second pass is rendered in device coordinates for the axes, legend etc. Thanks to the way that OpenGL handles line thickness and point size they are not scaled by the model view matrix. Other backends may need to perform the matrix multiplies on the CPU, but this allows the OpenGL backend to take advantage of hardware acceleration.
The second pass is rendered in device coordinates for the axes, legend etc. Thanks to the way that OpenGL handles line thickness and point size they are not scaled by the model view matrix. Other backends may need to perform the matrix multiplies on the CPU, but this allows the OpenGL backend to take advantage of hardware acceleration.

Latest revision as of 15:32, 18 February 2011

The diagram below shows the basic relationship between the different classes that make up a chart. For clarity only the composition of a vtkChartXY is shown, but all charts have multiple components used to draw their parts. Each component class that has something rendered to the screen has a function with the following prototype,

<source lang="cpp">bool Paint(vtkContext2D *context);</source>

These member functions are called by the chart's RenderOverlay function. The chart manages the order in which the Paint functions are called, and so the grid would be drawn over by the plots, and they in turn would be drawn over by the axes and text.

Chart Classes

Common API Elements

All drawable elements of a chart use a vtkContext2D to draw. Elements that take a data input inherit from vtkContextMapper2D, and all elements that do not take a data input inherit from vtkContextProp2D. Both of these abstract base classes provide a common API to facilitate efficient rendering.

<source lang="cpp">// Painting functions bool Paint(vtkContext2D *context); int GetLayer(); void SetLayer(int layer); // Properties and settings bool SetProperty(const char *name, const vtkVariant &value); bool GetProperty(const char *name, vtkVariant *value); bool ReadSettings(const vtkSettings &settings); bool WriteSettings(vtkSettings *settings);</source>

The layer property can be used by the Chart class to sort the order in which elements are drawn, with the grid on the bottom layer, then the line plots, symbols, annotations, axes and the legend on the top layer. The get and set property functions allow for properties of chart objects to be set and get without having to down cast. The read and write settings provide a more general container object for the settings or properties of a chart.

Chart Settings

A simple settings object would facilitate the modification of chart properties using a general container. Using a consistent property naming scheme would allow for settings or themes to be applied across all chart objects. A simple solution to this problem would be an STL map based container, with some helper functions to facilitate default settings and the possibility of inheritance.

<source lang="cpp">class Settings {

 public:
   SetValue(const vtkStdString &key, const vtkVariant &value);
   vtkVariant GetValue(const vtkStdString &key);
   vtkVariant GetValue(const vtkStdString &key, const vtkVariant &default);
   void BeginGroup(const vtkStdString &groupName);
   void EndGroup();

};</source>

Groups could be delimited using the forward slash character, and settings resemble a directory layout. The begin and end group functions simply push and pop group elements. All keys are unique, and so setting the same key again overwrites the previous value. This allows for a simple implementation of a settings class that has a great deal of flexibility. Internally an STL map is used to store the keys, serialization is quite simple and mapping to XML and other on disk formats should not present significant challenges.

This format would also map very easily to Qt's QSettings object should exchange with Qt be desirable. The following two code snippets would result in the same settings object,

<source lang="cpp">settings->SetValue("Axis/X/Width", 2.0); settings->BeginGroup("Axis"); settings->BeginGroup("X"); settings->SetValue("Width", 2.0); settings->EndGroup(); settings->EndGroup();</source>

The major advantage of the more verbose second form is that when the x axis is writing its settings it does not need to be concerned about what groups it might be contained within. It can call settings->SetValue("Width", 2.0); and assume the calling class set the groups up correctly. It can also do the same when reading its settings, calling GetValue("Width") and if the settings object was in the correct group it would return the key "Axis/X/Width".

Early Screenshot

This is a screenshot taken of a graph that contains two data series, each of which are made up of 28 points. There is a line and a point at each data point along with some test text and a transparent rectangle. I have also tested with 1,000,000 data points plotted as a line where the average frame rate (over 1,000 renders) was 93.43 fps on my system. This is without using display lists or vertex buffers at this point.

Chart-early.png

The chart shows two axes being plotted as lines, a test axis label and tick marks/labels. The data series are plotted in a clipped frame inside the window, which is transformed to the Cartesian coordinates of the data series. This means that when using OpenGL as a backend the graphics card does most of the matrix multiplication for us.

Newer Screenshot in ParaView

Changed I have yet to commit to ParaView (lots of new proxies, one or two leaks, settings need exposing) showing a wavelet source and the new 2D API based charts (right). I promised you a screenshot - so here you go,

ParaView2DAPI.png

Rendering Strategy

The base class makes no assumptions about the rendering philosophy employed by any particular chart. In the vtkChartXY I have taken a two pass approach. The data plots are drawn using a view transform according to the extents of the plot set by the vtkAxis objects in x and y. So they can be cached in an OpenGL display list and replayed as the chart changes the range of x and y that are plotted. The plot area is also clipped so that anything outside of the plot area is discarded.

The second pass is rendered in device coordinates for the axes, legend etc. Thanks to the way that OpenGL handles line thickness and point size they are not scaled by the model view matrix. Other backends may need to perform the matrix multiplies on the CPU, but this allows the OpenGL backend to take advantage of hardware acceleration.