Commit 999ed2a6 authored by Joachim Pouderoux's avatar Joachim Pouderoux

Introduce box plot and the corresponding chart.

Change-Id: Ic5c728d45d447fac2678401394717d261dd91d48
parent cb83ede3
......@@ -3,6 +3,7 @@ set(Module_SRCS
vtkAxisExtended.cxx
vtkCategoryLegend.cxx
vtkChart.cxx
vtkChartBox.cxx
vtkChartHistogram2D.cxx
vtkChartLegend.cxx
vtkChartMatrix.cxx
......@@ -25,6 +26,7 @@ set(Module_SRCS
vtkPlot3D.cxx
vtkPlotBag.cxx
vtkPlotBar.cxx
vtkPlotBox.cxx
vtkPlotFunctionalBag.cxx
vtkPlotGrid.cxx
vtkPlotHistogram2D.cxx
......
......@@ -14,6 +14,7 @@ vtk_add_test_cxx(
TestBagPlot.cxx
TestBarGraph.cxx
TestBarGraphHorizontal.cxx
TestBoxPlot.cxx
TestCategoryLegend.cxx
TestColorTransferFunction.cxx,-E80
TestChartDouble.cxx
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestBoxPlot.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 "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkChartBox.h"
#include "vtkPlotBox.h"
#include "vtkTable.h"
#include "vtkLookupTable.h"
#include "vtkIntArray.h"
#include "vtkFloatArray.h"
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkNew.h"
//----------------------------------------------------------------------------
int TestBoxPlot(int , char* [])
{
// Set up a 2D scene, add an XY chart to it
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 400);
view->GetRenderWindow()->SetMultiSamples(0);
vtkNew<vtkChartBox> chart;
view->GetScene()->AddItem(chart.GetPointer());
// Creates a vtkPlotBox input table
// The vtkPlotBox object will display 4 (arbitrary) box plots
int numParam = 4;
vtkNew<vtkTable> inputBoxPlotTable;
for (int i = 0; i < numParam; i++)
{
char num[3];
sprintf(num, "%d", i);
char name[10];
strcpy(name,"Param ");
strcat(name,num);
vtkNew<vtkIntArray> arrIndex;
arrIndex->SetName(name);
inputBoxPlotTable->AddColumn(arrIndex.GetPointer());
}
inputBoxPlotTable->SetNumberOfRows(5);
for (int i = 0; i < numParam; i++)
{
inputBoxPlotTable->SetValue(0, i, i/2); //Q0
inputBoxPlotTable->SetValue(1, i, 2*i + 2 - i); //Q1
inputBoxPlotTable->SetValue(2, i, 2*i + 4); //Q2
inputBoxPlotTable->SetValue(3, i, 2*i + 7); //Q3
inputBoxPlotTable->SetValue(4, i, 2*i + 8); //Q4
}
vtkNew<vtkLookupTable> lookup;
lookup->SetNumberOfColors(5);
lookup->SetRange(0, 4);
lookup->Build();
chart->GetPlot(0)->SetInputData(inputBoxPlotTable.GetPointer());
chart->SetColumnVisibilityAll(true);
chart->SetShowLegend(true);
double rgb[3] = { 1., 1., 0. };
vtkPlotBox::SafeDownCast(chart->GetPlot(0))->SetColumnColor("Param 1", rgb);
// Render the scene
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
view->Render();
view->GetInteractor()->Start();
return EXIT_SUCCESS;
}
......@@ -76,6 +76,7 @@ vtkAxis::vtkAxis()
this->GridVisible = true;
this->LabelsVisible = true;
this->TicksVisible = true;
this->AxisVisible = true;
this->Precision = 2;
this->Notation = vtkAxis::STANDARD_NOTATION;
this->Behavior = vtkAxis::AUTO;
......@@ -295,8 +296,11 @@ bool vtkAxis::Paint(vtkContext2D *painter)
painter->ApplyPen(this->Pen);
// Draw this axis
painter->DrawLine(this->Point1[0], this->Point1[1],
this->Point2[0], this->Point2[1]);
if (this->AxisVisible)
{
painter->DrawLine(this->Point1[0], this->Point1[1],
this->Point2[0], this->Point2[1]);
}
// Draw the axis title if there is one
if (!this->Title.empty())
......
......@@ -277,6 +277,11 @@ public:
vtkSetMacro(TicksVisible, bool);
vtkGetMacro(TicksVisible, bool);
// Description:
// Get/set whether the axis line should be visible.
vtkSetMacro(AxisVisible, bool);
vtkGetMacro(AxisVisible, bool);
// Description:
// Get/set the numerical precision to use, default is 2.
virtual void SetPrecision(int precision);
......@@ -506,6 +511,7 @@ protected:
bool GridVisible; // Whether the grid for the axis should be drawn
bool LabelsVisible; // Should the axis labels be visible
bool TicksVisible; // Should the tick marks be visible.
bool AxisVisible; // Should the axis line be visible.
int Precision; // Numerical precision to use, defaults to 2.
int Notation; // The notation to use (standard, scientific, mixed)
int Behavior; // The behaviour of the axis (auto, fixed, custom).
......
/*=========================================================================
Program: Visualization Toolkit
Module: vtkChartBox.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 "vtkChartBox.h"
#include "vtkAnnotationLink.h"
#include "vtkAxis.h"
#include "vtkBrush.h"
#include "vtkCommand.h"
#include "vtkContext2D.h"
#include "vtkContextScene.h"
#include "vtkContextMapper2D.h"
#include "vtkContextMouseEvent.h"
#include "vtkDataArray.h"
#include "vtkIdTypeArray.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPen.h"
#include "vtkPlotBox.h"
#include "vtkPlotGrid.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkTextProperty.h"
#include "vtkTooltipItem.h"
#include "vtkTransform2D.h"
#include <algorithm>
#include <vector>
// Minimal storage class for STL containers etc.
class vtkChartBox::Private
{
public:
Private()
{
this->Plot = vtkSmartPointer<vtkPlotBox>::New();
this->YAxis->SetPosition(vtkAxis::LEFT);
this->YAxis->SetPoint1(0, 0);
this->YAxis->SetTitle("Y");
}
~Private()
{
}
vtkSmartPointer<vtkPlotBox> Plot;
std::vector<float> XPosition;
vtkNew<vtkTransform2D> Transform;
vtkNew<vtkAxis> YAxis;
vtkNew<vtkPlotGrid> Grid;
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkChartBox);
//-----------------------------------------------------------------------------
vtkChartBox::vtkChartBox()
{
this->Storage = new vtkChartBox::Private;
this->Storage->Plot->SetParent(this);
this->GeometryValid = false;
this->Selection = vtkIdTypeArray::New();
this->SelectedColumn = -1;
this->Storage->Plot->SetSelection(this->Selection);
this->VisibleColumns = vtkStringArray::New();
this->Tooltip = vtkSmartPointer<vtkTooltipItem>::New();
this->Tooltip->SetVisible(false);
this->AddItem(this->Tooltip);
// Set up default mouse button assignments for parallel coordinates.
this->SetActionToButton(vtkChart::PAN, vtkContextMouseEvent::RIGHT_BUTTON);
this->SetActionToButton(vtkChart::SELECT, vtkContextMouseEvent::LEFT_BUTTON);
}
//-----------------------------------------------------------------------------
vtkChartBox::~vtkChartBox()
{
this->Storage->Plot->SetSelection(NULL);
delete this->Storage;
this->Selection->Delete();
this->VisibleColumns->Delete();
}
//-----------------------------------------------------------------------------
void vtkChartBox::Update()
{
vtkTable* table = this->Storage->Plot->GetData()->GetInput();
if (!table)
{
return;
}
if (table->GetMTime() < this->BuildTime && this->MTime < this->BuildTime)
{
return;
}
int nbCols = this->VisibleColumns->GetNumberOfTuples();
this->Storage->XPosition.resize(nbCols);
double grange[2] = { VTK_DOUBLE_MAX, VTK_DOUBLE_MIN };
// Now set up their ranges and locations
for (int i = 0; i < nbCols; ++i)
{
vtkDataArray* array =
vtkDataArray::SafeDownCast(table->GetColumnByName(
this->VisibleColumns->GetValue(i)));
if (array)
{
double range[2];
array->GetRange(range);
if (range[0] < grange[0])
{
grange[0] = range[0];
}
if (range[1] > grange[1])
{
grange[1] = range[1];
}
}
}
this->Storage->YAxis->SetMinimum(grange[0]);
this->Storage->YAxis->SetMaximum(grange[1]);
this->GeometryValid = false;
this->BuildTime.Modified();
}
//-----------------------------------------------------------------------------
bool vtkChartBox::Paint(vtkContext2D *painter)
{
if (this->GetScene()->GetViewWidth() == 0 ||
this->GetScene()->GetViewHeight() == 0 ||
!this->Visible || !this->Storage->Plot->GetVisible() ||
this->VisibleColumns->GetNumberOfTuples() < 1)
{
// The geometry of the chart must be valid before anything can be drawn
return false;
}
this->Update();
this->UpdateGeometry();
// Handle selections
vtkIdTypeArray *idArray = 0;
if (this->AnnotationLink)
{
vtkSelection *selection = this->AnnotationLink->GetCurrentSelection();
if (selection->GetNumberOfNodes() &&
this->AnnotationLink->GetMTime() > this->Storage->Plot->GetMTime())
{
vtkSelectionNode *node = selection->GetNode(0);
idArray = vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
this->Storage->Plot->SetSelection(idArray);
}
}
else
{
vtkDebugMacro("No annotation link set.");
}
painter->PushMatrix();
painter->SetTransform(this->Storage->Transform.GetPointer());
this->Storage->Plot->Paint(painter);
painter->PopMatrix();
this->Storage->YAxis->Paint(painter);
if (this->GetShowLegend())
{
vtkRectf rect;
rect.Set(0, 2, 10, 20);
this->Storage->Plot->PaintLegend(painter, rect, 0);
}
if (this->Tooltip && this->Tooltip->GetVisible())
{
this->Tooltip->Paint(painter);
}
return true;
}
//-----------------------------------------------------------------------------
void vtkChartBox::SetColumnVisibility(const vtkStdString& name,
bool visible)
{
if (visible)
{
for (vtkIdType i = 0; i < this->VisibleColumns->GetNumberOfTuples(); ++i)
{
if (this->VisibleColumns->GetValue(i) == name)
{
// Already there, nothing more needs to be done
return;
}
}
// Add the column to the end of the list
this->VisibleColumns->InsertNextValue(name);
this->Modified();
this->Update();
}
else
{
// Remove the value if present
for (vtkIdType i = 0; i < this->VisibleColumns->GetNumberOfTuples(); ++i)
{
if (this->VisibleColumns->GetValue(i) == name)
{
// Move all the later elements down by one, and reduce the size
while (i < this->VisibleColumns->GetNumberOfTuples()-1)
{
this->VisibleColumns->SetValue(i, this->VisibleColumns->GetValue(i+1));
++i;
}
this->VisibleColumns->SetNumberOfTuples(
this->VisibleColumns->GetNumberOfTuples()-1);
if (this->SelectedColumn >= this->VisibleColumns->GetNumberOfTuples())
{
this->SelectedColumn = -1;
}
this->Modified();
this->Update();
return;
}
}
}
}
//-----------------------------------------------------------------------------
void vtkChartBox::SetColumnVisibilityAll(bool visible)
{
// We always need to clear the current visible columns.
this->VisibleColumns->SetNumberOfTuples(0);
this->SelectedColumn = -1;
if (visible)
{
vtkTable *table = this->GetPlot(0)->GetInput();
for (vtkIdType i = 0; i < table->GetNumberOfColumns(); ++i)
{
this->SetColumnVisibility(table->GetColumnName(i), visible);
}
}
}
//-----------------------------------------------------------------------------
bool vtkChartBox::GetColumnVisibility(const vtkStdString& name)
{
for (vtkIdType i = 0; i < this->VisibleColumns->GetNumberOfTuples(); ++i)
{
if (this->VisibleColumns->GetValue(i) == name)
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
vtkIdType vtkChartBox::GetNumberOfVisibleColumns()
{
return this->VisibleColumns->GetNumberOfTuples();
}
//-----------------------------------------------------------------------------
vtkAxis* vtkChartBox::GetYAxis()
{
return this->Storage->YAxis.GetPointer();
}
//-----------------------------------------------------------------------------
void vtkChartBox::SetPlot(vtkPlotBox *plot)
{
this->Storage->Plot = plot;
this->Storage->Plot->SetParent(this);
}
//-----------------------------------------------------------------------------
vtkPlot* vtkChartBox::GetPlot(vtkIdType)
{
return this->Storage->Plot;
}
//-----------------------------------------------------------------------------
vtkIdType vtkChartBox::GetNumberOfPlots()
{
return 1;
}
//-----------------------------------------------------------------------------
float vtkChartBox::GetXPosition(int index)
{
return (index < static_cast<int>(this->Storage->XPosition.size())) ?
this->Storage->XPosition[index] : 0;
}
//-----------------------------------------------------------------------------
void vtkChartBox::UpdateGeometry()
{
vtkVector2i geometry(this->GetScene()->GetViewWidth(),
this->GetScene()->GetViewHeight());
if (geometry.GetX() != this->Geometry[0] ||
geometry.GetY() != this->Geometry[1] || !this->GeometryValid)
{
// Take up the entire window right now, this could be made configurable
this->SetGeometry(geometry.GetData());
this->SetBorders(40, 30, 0, 20);
// Iterate through the axes and set them up to span the chart area.
int xStep = (this->Point2[0] - this->Point1[0]) /
(static_cast<int>(this->Storage->XPosition.size()));
int x = this->Point1[0] + (xStep / 2);
for (size_t i = 0; i < this->Storage->XPosition.size(); ++i)
{
this->Storage->XPosition[i] = x;
x += xStep;
}
vtkAxis* axis = this->Storage->YAxis.GetPointer();
axis->SetPoint1(40, this->Point1[1]);
axis->SetPoint2(40, this->Point2[1]);
if (axis->GetBehavior() == 0)
{
axis->AutoScale();
}
axis->Update();
this->GeometryValid = true;
// Cause the plot transform to be recalculated if necessary
this->CalculatePlotTransform();
if (this->VisibleColumns->GetNumberOfValues() > 1)
{
this->Storage->Plot->SetBoxWidth(0.5f *
(this->GetXPosition(1) - this->GetXPosition(0)));
}
this->Storage->Plot->Update();
}
}
//-----------------------------------------------------------------------------
void vtkChartBox::CalculatePlotTransform()
{
// In the case of box plots everything is plotted in a normalized
// system, where the range is from 0.0 to 1.0 in the y axis, and in screen
// coordinates along the x axis.
vtkAxis* axis = this->Storage->YAxis.GetPointer();
float *min = axis->GetPoint1();
float *max = axis->GetPoint2();
float yScale = 1.0f / (max[1] - min[1]);
this->Storage->Transform->Identity();
this->Storage->Transform->Translate(0, axis->GetPoint1()[1]);
// Get the scale for the plot area from the x and y axes
this->Storage->Transform->Scale(1.0, 1.0 / yScale);
}
//-----------------------------------------------------------------------------
bool vtkChartBox::Hit(const vtkContextMouseEvent &mouse)
{
vtkVector2i pos(mouse.GetScreenPos());
float width = this->Storage->Plot->GetBoxWidth() / 2.f;
return
pos[0] > this->Point1[0] - width &&
pos[0] < this->Point2[0] + width &&
pos[1] > this->Point1[1] &&
pos[1] < this->Point2[1];
}
//-----------------------------------------------------------------------------
bool vtkChartBox::MouseMoveEvent(const vtkContextMouseEvent &mouse)
{
if (mouse.GetButton() == this->Actions.Pan() && this->SelectedColumn >= 0)
{
if (this->Tooltip)
{
this->Tooltip->SetVisible(false);
}
// Move the axis in x
float deltaX = mouse.GetScenePos().GetX() - mouse.GetLastScenePos().GetX();
this->Storage->XPosition[this->SelectedColumn] += deltaX;
float selX = this->Storage->XPosition[this->SelectedColumn];
int nbCols = static_cast<int>(this->Storage->XPosition.size());
int left = this->SelectedColumn - 1;
int right = this->SelectedColumn + 1;
float width = this->Storage->Plot->GetBoxWidth() * 0.5f;
if (left >= 0 && (selX - width) < this->Storage->XPosition[left])
{
this->SwapAxes(this->SelectedColumn, this->SelectedColumn - 1);
this->SelectedColumn--;
}
else if (right < nbCols && (selX + width) > this->Storage->XPosition[right])
{
this->SwapAxes(this->SelectedColumn, this->SelectedColumn + 1);
this->SelectedColumn++;
}
this->Scene->SetDirty(true);
}
if (mouse.GetButton() == vtkContextMouseEvent::NO_BUTTON)
{
this->Scene->SetDirty(true);
if (this->Tooltip)
{
this->Tooltip->SetVisible(this->LocatePointInPlots(mouse));
}
}
return true;
}
//-----------------------------------------------------------------------------
bool vtkChartBox::MouseButtonPressEvent(const vtkContextMouseEvent& mouse)
{
if (mouse.GetButton() == this->Actions.Pan())
{
// Select an axis if we are within range
if (mouse.GetScenePos()[1] > this->Point1[1] &&
mouse.GetScenePos()[1] < this->Point2[1])
{
// Iterate over the axes, see if we are within 10 pixels of an axis
for (size_t i = 0; i < this->Storage->XPosition.size(); ++i)
{
float selX = this->Storage->XPosition[i];
float width = this->Storage->Plot->GetBoxWidth() / 2.f;
if (selX - width < mouse.GetScenePos()[0] &&
selX + width > mouse.GetScenePos()[0])
{
this->SelectedColumn = static_cast<int>(i);
this->Scene->SetDirty(true);
return true;
}
}
}
this->SelectedColumn = -1;
this->Scene->SetDirty(true);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool vtkChartBox::MouseButtonReleaseEvent(const vtkContextMouseEvent& mouse)
{
this->SelectedColumn = -1;
if (mouse.GetButton() == this->Actions.Select())
{
if (this->SelectedColumn >= 0)
{
if (this->AnnotationLink)
{