VTK/Marks

From KitwarePublic
Jump to navigationJump to search

Overview

Marks can be thought of as highly-configurable vectorized graphics items, or graphics item factories. They are a mid-level API built on more basic rendering primitives like lines, circles, etc. You want to use marks to make it easy to configure a set of primitives, where each primitive's properties will change depending on the item.

Protovis is an API description for marks with a reference implementation in javascript. Take a look at the protovis site for details on their design decisions and features.

In VTK, we want to explore a similar interface for marks in C++. The following is a possible technology stack for using marks in VTK. Qt could be incorporated as an alternative to OpenGL for publication-quality images.

Marks.png

Initial Prototype

The purpose of this prototype was to see how closely C++ code can resemble Protovis Javascript code. The properties are defined as boost::function instances. boost::lambda expressions (lines using the special _index and _d variables) may be used to specify functions. Alternately, free/static functions or function objects may be used. The bottom images show the results from both pieces of code.

Protovis C++
var data = pv.range(10).map(function(d) { return Math.random() + .1; });
  std::vector<double> vec;
  for (int i = 0; i < 10; ++i)
    {
    vec.push_back(vtkMath::Random() + 0.1);
    }
  VectorStorage* s = new VectorStorage(vec);
  Datum data(s);
// Sizing and scales.
var w = 400,
    h = 250,
    x = pv.Scale.linear(0, 1.1).range(0, w),
    y = pv.Scale.ordinal(pv.range(10)).splitBanded(0, h, 4/5);
  // Sizing and scales.
  int w = 400;
  int h = 250;
  LinearScale x(0, 1.1);
  x.range(0, w);
  OrdinalScale y(range(10));
  y.splitBanded(0, h, 4.0/5);
// The root panel.
var vis = new pv.Panel()
    .width(w)
    .height(h)
    .bottom(20)
    .left(20)
    .right(10)
    .top(5);
  // The root panel.
  Panel vis;
  vis.width(w);
  vis.height(h);
  vis.bottom(20);
  vis.left(20);
  vis.right(10);
  vis.top(5);
// The bars.
var bar = vis.add(pv.Bar)
    .data(data)
    .top(function() y(this.index))
    .height(y.range().band)
    .left(0)
    .width(x);
  // The bars.
  Bar* bar = vis.addBar();
  bar->data(data);
  bar->top(bind(y, _index));
  bar->height(y.band());
  bar->left(0);
  bar->width(x);
// The value label.
bar.anchor("right").add(pv.Label)
    .textStyle("white")
    .text(function(d) d.toFixed(1));
  // The value label.
  Label* label = bar->anchor("right")->addLabel();
  label->textStyle(vtkColor4d(1, 1, 1, 1));
  label->text(bind(toFixed, _d, 1));
// The variable label.
bar.anchor("left").add(pv.Label)
    .textMargin(5)
    .textAlign("right")
    .text(function() "ABCDEFGHIJK".charAt(this.index));
  // The variable label.
  Label* label2 = bar->anchor("left")->addLabel();
  label2->textMargin(5);
  label2->textAlign(vtkStdString("right"));
  label2->text(bind(charAt, "ABCDEFGHIJK", _index));
// X-axis ticks.
vis.add(pv.Rule)
    .data(x.ticks(5))
    .left(x)
    .strokeStyle(function(d) d ? "rgba(255,255,255,.3)" : "#000")
  .add(pv.Rule)
    .bottom(0)
    .height(5)
    .strokeStyle("#000")
  .anchor("bottom").add(pv.Label)
    .text(x.tickFormat);
  // X-axis ticks.
  Rule* rule = vis.addRule();
  rule->data(x.ticks(5));
  rule->left(x);
  rule->strokeStyle(if_then_else_return(_d[0] > 0, vtkColor4d(1, 1, 1, 0.3), vtkColor4d(0, 0, 0, 1)));

  Rule* rule2 = rule->addRule();
  rule2->bottom(0);
  rule2->height(5);
  rule2->strokeStyle(vtkColor4d(0, 0, 0, 1));

  Label* l = rule2->anchor("bottom")->addLabel();
  l->text(bind(toFixed, _d, 1));
// Render the scene.
vis.render();
// Render the scene.
vis.render();
ProtovisBarChart.png MarksBarChart.png

Fleshing Out the vtkMark API

The fundamental type would be vtkMark, the superclass of all mark types. Its interface would define how programmers would work with all marks in VTK. The following sections enumerate some possible use cases with code snippets showing what functionality the feature would allow.

Possible Supported Parameter Types

Description Example Code
Basic types (double, int, string, vtkVariant)
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", 10);
Data object
vtkDirectedGraph* g = vtkDirectedGraph::New();
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("data", g);
Pipeline output port
vtkRandomGraphSource* s = vtkRandomGraphSource::New();
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("data", s->GetOutputPort());
vtkArray
vtkDenseArray<double>* a = vtkDenseArray<double>::New();
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", a);
vtkAbstractArray
vtkDoubleArray* a = vtkDoubleArray::New();
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", a);
Data object + field type + array name
vtkTable* t = vtkTable::New();
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", t, vtkDataObject::ROW, "columnName");
Data object + field type + array name + component index
vtkTable* t = vtkTable::New();
vtkDoubleArray* loc = vtkDoubleArray::New();
loc->SetNumberOfComponents(2);
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("left", t, vtkDataObject::ROW, "location", 0);
m->SetParameter("bottom", t, vtkDataObject::ROW, "location", 1);
Function pointer
double MySize(vtkMark* m, int i) { return 10*i; }
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", &MySize);
Functor (i.e. struct type with operator()) - requires SetParameter to be templated.
struct MySize {
  double operator()(vtkMark* m, int i) { return 10*i; }
}
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", MySize());
Lambda function (boost::lambda)
Parameter strategies
class vtkDoubleParameter {
  virtual double Get(vtkMark* m, int i) = 0;
}

class MySize : public vtkDoubleParameter {
protected:
  double Get(vtkMark* m, int i) { return 10*i; }
}
//...
vtkDotMark* m = vtkDotMark::New();
m->SetParameter("size", MySize());
Override virtual methods in mark
class vtkDotMark : public vtkMark {
protected:
  virtual double GetSize(int i) { return 10; }
}

class MyMark : public vtkDotMark {
protected:
  double GetSize(int i) { return 10*i; }
}
//...
MyMark* m = MyMark::New();
Inherit from parent mark
vtkDotMark* parent = vtkDotMark::New();
parent->SetParameter("size", 10);
vtkDotMark* child = vtkDotMark::New();
parent->Add(child); // Child has size 10


Possible Parameter Storage and Access

Named member variables

class vtkDotMark {
public:
  ParamType GetSize() { return this->Size; }
  void SetSize(ParamType p) { this->Size = p; }
private:
  ParamType Size;
}

Named member variables with import/export to name/value map

class vtkMark {
public:
  ParamType GetParameter(string key)
  { this->ExportSettings()->GetParameter(key); }
  void SetParameter(string key, ParamType p)
  { this->ImportSettings(this->ExportSettings()->SetParameter(key, p)); }
  virtual void ImportSettings(vtkSettings* s) { }
  virtual vtkSettings* ExportSettings() { return vtkSettings::New(); }
}

class vtkDotMark {
public:
  ParamType GetSize() { return this->Size; }
  void SetSize(ParamType p) { this->Size = p; }
  virtual vtkSettings* ExportSettings()
  { return this->Parent->ExportSettings()->SetParameter("size", this->Size); }
  virutal void ImportSettings(vtkSettings* s)
  { this->Size = s->GetParameter("size"); }
private:
  ParamType Size;
}

Generic name/value map

class vtkMark {
public:
  ParamType GetParameter(string key)
  { return this->Parameters[key].Valid ? this->Parameters[key] : this->Parent->GetParameter(key); }
  void SetParameter(string key, ParamType p)
  { this->Parameters[key] = p; }
private:
  map<string, ParamType> Parameters;
}

Generic name/value map with setter/getter syntactic sugar for discoverability

class vtkDotMark : public vtkMark {
public:
  ParamType GetSize() { return this->GetParameter("size"); }
  void SetSize(ParamType p) { this->SetParameter("size", p); }
}

vtkInformation/value map

The idea here is that valid parameter names/types/bounds are queryable at runtime, instead of in an opaque map.

class vtkMark {
public:
  // Keys to describe parameters
  static vtkInformationIntegerKey* PARAMETER_TYPE();
  static vtkInformationStringKey* PARAMETER_NAME();
  static vtkInformationIntegerKey* PARAMETER_NUMBER_OF_COMPONENTS();
  static vtkInformationIntegerVectorKey* PARAMETER_INTEGER_BOUNDS();
  static vtkInformationDoubleVectorKey* PARAMETER_REAL_BOUNDS();

  int GetNumberOfParameters() { return this->ParameterInfo->size(); }
  vtkInformation* GetParameterInfo(int i) { return this->ParameterInfo[i]; }
  int GetParameterHandle(const char* name)
  { /* search ParameterInfo for name, return index */ }
  void SetParameter(int handle, ParamType p)
  { this->ParameterValues[handle] = p; }

protected:
  virtual void SetupParameters() = 0;
  vector<vtkInformation*> ParameterInfo;
  vector<ParamType> ParameterValues;
}

class vtkDotMark : public vtkMark {
protected:
  void SetupParameters()
  {
    vtkInformation* sizeInfo = vtkInformation::New();
    double sizeBounds[2] = { 0., VTK_DOUBLE_MAX };
    sizeInfo->Set( vtkPointMark::PARAMETER_NAME(), "size" );
    sizeInfo->Set( vtkPointMark::PARAMETER_TYPE(), VTK_DOUBLE );
    sizeInfo->Set( vtkPointMark::PARAMETER_NUMBER_OF_COMPONENTS(), 1 );
    sizeInfo->Set( vtkPointMark::PARAMETER_REAL_BOUNDS(), sizeBounds, 2 );
    this->ParameterInfo.push_back(sizeInfo);
    this->ParameterValues.push_back(10);
  }
}

Possible Other Features

  • Declarative API
  • Iterator support