Commit 7ef9d7a4 authored by Zack Galbreath's avatar Zack Galbreath

refactor vtkChartXYZ

It now follows the API of the 2D charts more closely.  vtkChartXYZ is
responsible for the axes, while the subclasses of vtkPlot3D handle
displaying the actual data.  As part of this effort, I've included
optional interactivity into vtkChartXYZ.  This eliminates the need for a
separate vtkInteractiveChartXYZ class.

This change also introduces new functionality.  vtkPlotSurface allows us
to visualize a table as a 3D surface plot.

I've updated vtkScatterPlotMatrix so that it correctly uses the new API
of vtkChartXYZ for animation.

All affected tests were updated as well.
Change-Id: Ic8406c99758a98851949c6153129f7704784e31a
parent f2327f6f
......@@ -16,12 +16,12 @@ set(Module_SRCS
vtkCompositeTransferFunctionItem.cxx
vtkContextPolygon.cxx
vtkControlPointsItem.cxx
vtkInteractiveChartXYZ.cxx
vtkLookupTableItem.cxx
vtkPiecewiseControlPointsItem.cxx
vtkPiecewiseFunctionItem.cxx
vtkPiecewisePointHandleItem.cxx
vtkPlot.cxx
vtkPlot3D.cxx
vtkPlotBar.cxx
vtkPlotGrid.cxx
vtkPlotHistogram2D.cxx
......@@ -29,7 +29,9 @@ set(Module_SRCS
vtkPlotParallelCoordinates.cxx # This adds a vtkInfovisCore dep for one class...
vtkPlotPie.cxx
vtkPlotPoints.cxx
vtkPlotPoints3D.cxx
vtkPlotStacked.cxx
vtkPlotSurface.cxx
vtkScalarsToColorsItem.cxx
vtkScatterPlotMatrix.cxx
)
......
......@@ -41,6 +41,7 @@
TestScientificPlot.cxx
TestStackedBarGraph.cxx
TestStackedPlot.cxx
TestSurfacePlot.cxx
)
# Set the tolerance higher for a few tests that need it
set(TestGLSLError 12)
......
......@@ -17,6 +17,7 @@
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkFloatArray.h"
#include "vtkPlotPoints3D.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
......@@ -51,10 +52,15 @@ int TestChartXYZ(int , char * [])
{
// Now the chart
vtkNew<vtkChartXYZ> chart;
chart->SetAutoRotate(true);
chart->SetFitToScene(false);
chart->SetDecorateAxes(false);
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 300);
view->GetScene()->AddItem(chart.GetPointer());
vtkNew<vtkChartXYZ> chart2;
chart2->SetFitToScene(false);
chart->SetDecorateAxes(false);
view->GetScene()->AddItem(chart2.GetPointer());
chart->SetGeometry(vtkRectf(75.0, 20.0, 250, 260));
......@@ -85,14 +91,14 @@ int TestChartXYZ(int , char * [])
//chart->SetAroundX(true);
// Add the three dimensions we are interested in visualizing.
chart->SetInput(table.GetPointer(), "X Axis", "Sine", "Cosine");
chart->RecalculateBounds();
chart->RecalculateTransform();
vtkNew<vtkPlotPoints3D> plot;
plot->SetInputData(table.GetPointer(), "X Axis", "Sine", "Cosine");
chart->AddPlot(plot.GetPointer());
// We want a duplicate, that does not move.
chart2->SetInput(table.GetPointer(), "X Axis", "Sine", "Cosine");
chart2->RecalculateBounds();
chart2->RecalculateTransform();
vtkNew<vtkPlotPoints3D> plot2;
plot2->SetInputData(table.GetPointer(), "X Axis", "Sine", "Cosine");
chart2->AddPlot(plot2.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
......
......@@ -13,12 +13,13 @@
=========================================================================*/
#include "vtkInteractiveChartXYZ.h"
#include "vtkChartXYZ.h"
#include "vtkContextMouseEvent.h"
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkFloatArray.h"
#include "vtkNew.h"
#include "vtkPlotPoints3D.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
......@@ -30,7 +31,7 @@
int TestInteractiveChartXYZ(int , char * [])
{
// Now the chart
vtkNew<vtkInteractiveChartXYZ> chart;
vtkNew<vtkChartXYZ> chart;
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 300);
view->GetScene()->AddItem(chart.GetPointer());
......@@ -65,9 +66,9 @@ int TestInteractiveChartXYZ(int , char * [])
}
// Add the dimensions we are interested in visualizing.
chart->SetInput(table.GetPointer(), "X Axis", "Sine", "Cosine", "Color");
chart->RecalculateBounds();
chart->RecalculateTransform();
vtkNew<vtkPlotPoints3D> plot;
plot->SetInputData(table.GetPointer(), "X Axis", "Sine", "Cosine", "Color");
chart->AddPlot(plot.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
......
......@@ -74,6 +74,16 @@ int TestScatterPlotMatrix(int, char * [])
matrix->GetMainChart()->SetActionToButton(vtkChart::SELECT_POLYGON,
vtkContextMouseEvent::RIGHT_BUTTON);
// Test animation by releasing a right click on subchart (1,2)
vtkContextMouseEvent mouseEvent;
mouseEvent.SetInteractor(view->GetInteractor());
vtkVector2f pos;
mouseEvent.SetButton(vtkContextMouseEvent::RIGHT_BUTTON);
pos.Set(245, 301);
mouseEvent.SetPos(pos);
matrix->MouseButtonReleaseEvent(mouseEvent);
//Finally render the scene and compare the image to a reference image
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestSurfacePlot.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkChartXYZ.h"
#include "vtkContextMouseEvent.h"
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkFloatArray.h"
#include "vtkNew.h"
#include "vtkPlotSurface.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkTable.h"
#include "vtkRegressionTestImage.h"
#include "vtkUnsignedCharArray.h"
#include "vtkVector.h"
int TestSurfacePlot(int , char * [])
{
vtkNew<vtkChartXYZ> chart;
vtkNew<vtkPlotSurface> plot;
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 300);
view->GetScene()->AddItem(chart.GetPointer());
chart->SetGeometry(vtkRectf(75.0, 20.0, 250, 260));
// Create a surface
vtkNew<vtkTable> table;
float numPoints = 70;
float inc = 9.424778 / (numPoints - 1);
for (float i = 0; i < numPoints; ++i)
{
vtkNew<vtkFloatArray> arr;
table->AddColumn(arr.GetPointer());
}
table->SetNumberOfRows(numPoints);
for (float i = 0; i < numPoints; ++i)
{
float x = i * inc;
for (float j = 0; j < numPoints; ++j)
{
float y = j * inc;
table->SetValue(i, j, sin(sqrt(x*x + y*y)));
}
}
// Set up the surface plot we wish to visualize and add it to the chart.
plot->SetXRange(0, 9.424778);
plot->SetYRange(0, 9.424778);
plot->SetInputData(table.GetPointer());
chart->AddPlot(plot.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
view->GetRenderWindow()->Render();
// rotate
vtkContextMouseEvent mouseEvent;
mouseEvent.SetInteractor(view->GetInteractor());
vtkVector2i pos;
vtkVector2i lastPos;
mouseEvent.SetButton(vtkContextMouseEvent::LEFT_BUTTON);
lastPos.Set(100, 50);
mouseEvent.SetLastScreenPos(lastPos);
pos.Set(150, 100);
mouseEvent.SetScreenPos(pos);
vtkVector2d sP(pos.Cast<double>().GetData());
vtkVector2d lSP(lastPos.Cast<double>().GetData());
vtkVector2d screenPos(mouseEvent.GetScreenPos().Cast<double>().GetData());
vtkVector2d lastScreenPos(mouseEvent.GetLastScreenPos().Cast<double>().GetData());
chart->MouseMoveEvent(mouseEvent);
view->GetInteractor()->Start();
return EXIT_SUCCESS;
}
......@@ -17,66 +17,188 @@
#include "vtkAnnotationLink.h"
#include "vtkAxis.h"
#include "vtkCommand.h"
#include "vtkContext2D.h"
#include "vtkContext3D.h"
#include "vtkContextKeyEvent.h"
#include "vtkContextMouseEvent.h"
#include "vtkContextScene.h"
#include "vtkLookupTable.h"
#include "vtkMath.h"
#include "vtkPen.h"
#include "vtkPlane.h"
#include "vtkPlot3D.h"
#include "vtkTable.h"
#include "vtkFloatArray.h"
#include "vtkTextProperty.h"
#include "vtkTransform.h"
#include "vtkVector.h"
#include "vtkVectorOperators.h"
#include "vtkPen.h"
#include "vtkAnnotationLink.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkIdTypeArray.h"
#include "vtkObjectFactory.h"
#include <vector>
#include <cassert>
using std::vector;
#include <sstream>
vtkStandardNewMacro(vtkChartXYZ)
void vtkChartXYZ::PrintSelf(ostream &os, vtkIndent indent)
//-----------------------------------------------------------------------------
vtkChartXYZ::vtkChartXYZ() : Geometry(0, 0, 10, 10), IsX(false), Angle(0)
{
Superclass::PrintSelf(os, indent);
this->Pen->SetWidth(5);
this->Pen->SetColor(0, 0, 0, 255);
this->AxisPen->SetWidth(1);
this->AxisPen->SetColor(0, 0, 0, 255);
this->Rotation->Identity();
this->Rotation->PostMultiply();
this->Translation->Identity();
this->Translation->PostMultiply();
this->Scale->Identity();
this->Scale->PostMultiply();
this->Interactive = true;
this->SceneWidth = 0;
this->SceneHeight = 0;
this->InitializeAxesBoundaryPoints();
this->AutoRotate = false;
this->DrawAxesDecoration = true;
this->CheckClipping = true;
this->FitToScene = true;
}
//-----------------------------------------------------------------------------
vtkChartXYZ::~vtkChartXYZ()
{
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetAngle(double angle)
{
this->Angle = angle;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetAroundX(bool IsX_)
{
this->IsX = IsX_;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetAutoRotate(bool b)
{
this->AutoRotate = b;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetDecorateAxes(bool b)
{
this->DrawAxesDecoration = b;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetAnnotationLink(vtkAnnotationLink *link)
{
if (this->Link != link)
{
this->Link = link;
this->Modified();
}
}
//-----------------------------------------------------------------------------
vtkAxis * vtkChartXYZ::GetAxis(int axis)
{
assert(axis >= 0 && axis < 3);
return this->Axes[axis].GetPointer();
}
void vtkChartXYZ::Update()
//-----------------------------------------------------------------------------
void vtkChartXYZ::SetGeometry(const vtkRectf &bounds)
{
if (this->Link)
this->Geometry = bounds;
// initialize axes
this->Axes.resize(3);
vtkNew<vtkAxis> x;
this->Axes[0] = x.GetPointer();
x->SetPoint1(vtkVector2f(this->Geometry.GetX(),
this->Geometry.GetY()));
x->SetPoint2(vtkVector2f(this->Geometry.GetX() + this->Geometry.GetWidth(),
this->Geometry.GetY()));
vtkNew<vtkAxis> y;
this->Axes[1] = y.GetPointer();
y->SetPoint1(vtkVector2f(this->Geometry.GetX(),
this->Geometry.GetY()));
y->SetPoint2(vtkVector2f(this->Geometry.GetX(),
this->Geometry.GetY() + this->Geometry.GetHeight()));
// Z is faked, largely to get valid ranges and rounded numbers...
vtkNew<vtkAxis> z;
this->Axes[2] = z.GetPointer();
z->SetPoint1(vtkVector2f(this->Geometry.GetX(),
0));
if (this->IsX)
{
z->SetPoint2(vtkVector2f(this->Geometry.GetX(),
this->Geometry.GetHeight()));
}
else
{
z->SetPoint2(vtkVector2f(this->Geometry.GetX(),
this->Geometry.GetWidth()));
}
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::RecalculateBounds()
{
if (this->Plots.empty())
{
return;
}
vector<vtkVector3f>::const_iterator it = this->Plots[0]->GetPoints().begin();
double bounds[] = { (*it).GetX(), (*it).GetX(),
(*it).GetY(), (*it).GetY(),
(*it).GetZ(), (*it).GetZ()};
// Need to calculate the bounds in three dimensions and set up the axes.
for (unsigned int i = 0; i < this->Plots.size(); ++i)
{
// Copy the row numbers so that we can do the highlight...
if (!this->Points.empty())
vector<vtkVector3f> points = this->Plots[i]->GetPoints();
for (unsigned int j = 0; j < points.size(); ++j)
{
vtkSelection *selection =
vtkSelection::SafeDownCast(this->Link->GetOutputDataObject(2));
if (selection->GetNumberOfNodes())
const vtkVector3f &v = points[j];
for (int i = 0; i < 3; ++i)
{
vtkSelectionNode *node = selection->GetNode(0);
vtkIdTypeArray *idArray =
vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
if (this->SelectedPointsBuildTime > idArray->GetMTime() ||
this->GetMTime() > this->SelectedPointsBuildTime)
if (v[i] < bounds[2 * i])
{
this->SelectedPoints.resize(idArray->GetNumberOfTuples());
for (vtkIdType i = 0; i < idArray->GetNumberOfTuples(); ++i)
{
this->SelectedPoints[i] = this->Points[idArray->GetValue(i)];
}
this->SelectedPointsBuildTime.Modified();
bounds[2 * i] = v[i];
}
else if (v[i] > bounds[2 * i + 1])
{
bounds[2 * i + 1] = v[i];
}
}
}
}
for (int i = 0; i < 3; ++i)
{
this->Axes[i]->SetRange(&bounds[2*i]);
}
// Recalculate transform since axes' ranges were modified
this->RecalculateTransform();
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::PrintSelf(ostream &os, vtkIndent indent)
{
Superclass::PrintSelf(os, indent);
}
//-----------------------------------------------------------------------------
bool vtkChartXYZ::Paint(vtkContext2D *painter)
{
if (!this->Visible || this->Points.size() == 0)
if (!this->Visible)
return false;
// Get the 3D context.
......@@ -85,236 +207,1328 @@ bool vtkChartXYZ::Paint(vtkContext2D *painter)
if (!context)
return false;
this->Update();
this->Update();
// Check if the scene changed size
bool resizeHappened = false;
if (this->FitToScene)
{
resizeHappened = this->CheckForSceneResize();
}
// Calculate the transforms required for the current rotation.
this->CalculateTransforms();
// Draw plots
context->PushMatrix();
context->AppendTransform(this->ContextTransform.GetPointer());
this->PaintChildren(painter);
// Calculate the bounds of the data within the axes
this->ComputeDataBounds();
// Pop the ContextTransform now that we're done drawing data within the axes
context->PopMatrix();
// Draw the axes, tick marks, and labels
this->DrawAxes(context);
if(this->DrawAxesDecoration)
{
this->DetermineWhichAxesToLabel();
this->DrawTickMarks(painter);
this->DrawAxesLabels(painter);
}
// If necessary, rescale the axes so they fits our scene nicely
if (resizeHappened)
{
this->RescaleAxes();
}
this->CheckClipping = false;
return true;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::DrawAxes(vtkContext3D *context)
{
context->PushMatrix();
context->AppendTransform(this->Box.GetPointer());
context->ApplyPen(this->AxisPen.GetPointer());
vtkVector3f box[4];
box[0] = vtkVector3f(0, 0, 0);
box[1] = vtkVector3f(0, 1, 0);
box[2] = vtkVector3f(1, 1, 0);
box[3] = vtkVector3f(1, 0, 0);
context->DrawLine(box[0], box[1]);
context->DrawLine(box[1], box[2]);
context->DrawLine(box[2], box[3]);
context->DrawLine(box[3], box[0]);
for (int i = 0; i < 4; ++i)
{
box[i].SetZ(1);
}
context->DrawLine(box[0], box[1]);
context->DrawLine(box[1], box[2]);
context->DrawLine(box[2], box[3]);
context->DrawLine(box[3], box[0]);
context->DrawLine(vtkVector3f(0, 0, 0), vtkVector3f(0, 0, 1));
context->DrawLine(vtkVector3f(1, 0, 0), vtkVector3f(1, 0, 1));
context->DrawLine(vtkVector3f(0, 1, 0), vtkVector3f(0, 1, 1));
context->DrawLine(vtkVector3f(1, 1, 0), vtkVector3f(1, 1, 1));
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::ComputeDataBounds()
{
double xMin = VTK_DOUBLE_MAX;
double xMax = VTK_DOUBLE_MIN;
double yMin = VTK_DOUBLE_MAX;
double yMax = VTK_DOUBLE_MIN;
float transformedPoint[3];
for (unsigned int i = 0; i < this->Plots.size(); ++i)
{
vtkPlot3D *plot = this->Plots[i];
// examine the eight corners of this plot's bounding cube
for (unsigned int j = 0; j < 8; ++j)
{
float *point = plot->GetDataBounds()[j].GetData();
this->ContextTransform->TransformPoint(
point, transformedPoint);
//plot->GetDataBounds()[j].GetData(), transformedPoint);
if (transformedPoint[0] < xMin)
{
xMin = transformedPoint[0];
}
if (transformedPoint[0] > xMax)
{
xMax = transformedPoint[0];
}
if (transformedPoint[1] < yMin)
{
yMin = transformedPoint[1];
}
if (transformedPoint[1] > yMax)
{
yMax = transformedPoint[1];
}
}
}
this->DataBounds[0] = xMin;
this->DataBounds[1] = yMin;
this->DataBounds[2] = xMax;
this->DataBounds[3] = yMax;
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::DrawAxesLabels(vtkContext2D *painter)
{
vtkContext3D *context = painter->GetContext3D();
// set up text property
vtkNew<vtkTextProperty> textProperties;
textProperties->SetJustificationToCentered();
textProperties->SetVerticalJustificationToCentered();
textProperties->SetColor(0.0, 0.0, 0.0);
textProperties->SetFontFamilyToArial();
textProperties->SetFontSize(14);
painter->ApplyTextProp(textProperties.GetPointer());
// if we're looking directly down any dimension, we shouldn't draw the
// corresponding label
bool shouldDrawAxis[3];
for (int axis = 0; axis < 3; ++axis)
{
shouldDrawAxis[axis] = true;
float start[3] = { 0, 0, 0 };
float end[3] = { 0, 0, 0 };
end[axis] = 1;
this->Box->TransformPoint(start, start);
this->Box->TransformPoint(end, end);
float axisLength = sqrt(
(end[0] - start[0]) * (end[0] - start[0]) +
(end[1] - start[1]) * (end[1] - start[1]));
if (axisLength == 0)
{
shouldDrawAxis[axis] = false;
}
}
float bounds[4];
float xLabelPos[3];
float yLabelPos[3];
float zLabelPos[3];
float offset[2] = {0, 0};
// calculate the pixel coordinates of the lines we wish to label
if (shouldDrawAxis[0])
{
xLabelPos[0] = 0.5;
xLabelPos[1] = this->XAxisToLabel[0];
xLabelPos[2] = this->XAxisToLabel[1];
this->Box->TransformPoint(xLabelPos, xLabelPos);
}
if (shouldDrawAxis[1])
{
yLabelPos[0] = this->YAxisToLabel[0];
yLabelPos[1] = 0.5;
yLabelPos[2] = this->YAxisToLabel[1];
this->Box->TransformPoint(yLabelPos, yLabelPos);
}
if (shouldDrawAxis[2])
{
zLabelPos[0] = this->ZAxisToLabel[0];
zLabelPos[1] = this->ZAxisToLabel[1];
zLabelPos[2] = 0.5;
this->Box->TransformPoint(zLabelPos, zLabelPos);
}
context->PopMatrix();
if (shouldDrawAxis[0])
{
painter->ComputeStringBounds(this->XAxisLabel, bounds);
this->GetOffsetForAxisLabel(0, bounds, offset);
xLabelPos[0] += (offset[0] + this->TickLabelOffset[0][0]);
xLabelPos[1] += (offset[1] + this->TickLabelOffset[0][1]);
painter->DrawString(xLabelPos[0], xLabelPos[1], this->XAxisLabel);
}
if (shouldDrawAxis[1])
{
painter->ComputeStringBounds(this->YAxisLabel, bounds);
offset[0] = 0;
offset[1] = 0;
this->GetOffsetForAxisLabel(1, bounds, offset);
yLabelPos[0] += (offset[0] + this->TickLabelOffset[1][0]);
yLabelPos[1] += (offset[1] + this->TickLabelOffset[1][1]);
painter->DrawString(yLabelPos[0], yLabelPos[1], this->YAxisLabel);
}
if (shouldDrawAxis[2])
{
painter->ComputeStringBounds(this->ZAxisLabel, bounds);
offset[0] = 0;
offset[1] = 0;
this->GetOffsetForAxisLabel(2, bounds, offset);
zLabelPos[0] += (offset[0] + this->TickLabelOffset[2][0]);
zLabelPos[1] += (offset[1] + this->TickLabelOffset[2][1]);
painter->DrawString(zLabelPos[0], zLabelPos[1], this->ZAxisLabel);
}
}
//-----------------------------------------------------------------------------
void vtkChartXYZ::GetOffsetForAxisLabel(int axis, float *bounds,
float *offset)
{
offset[0] = 0;
offset[1] = 0;
switch (this->DirectionToData[axis])
{
// data is to the north
// offset is -y
case 0:
offset[1] = -bounds[3];
break;
// data is northeast
// offset is -x, -y
case 1:
offset[0] = -bounds[2];
offset[1] = -bounds[3];
break;
// data is east
// offset is -x
case 2:
offset[0] = -bounds[2];
break;
// data is southeast
// offset is -x, +y
case 3:
offset[0] = -bounds[2];
offset[1] = bounds[3];
break;
// data is south
// offset is +y
case 4:
offset[1] = bounds[3];
break;
// data is southwest
// offset is +x, +y
case 5:
offset[0] = bounds[2];
offset[1] = bounds[3];
break;
// data is west