Commit 8504258e authored by Keith Fieldhouse's avatar Keith Fieldhouse
Browse files

Support Stacked Bar Plots

For vtkPlotBar, SetInputArray can be used to identify input
series' beyond index 1.  When this is done, each additional
series will be plotted on top of the previous series as a
stacked bar plot.

To handle this change, vtkPlot now handles multiple labels that
can be associated with each of the plots.  PaintLegend and
GetNearestPoint have been adjusted to deal with this properly.
parent 44ed2a65
......@@ -8,6 +8,7 @@ IF (VTK_USE_RENDERING AND VTK_USE_VIEWS)
TestLinePlot.cxx
TestStackedPlot.cxx
TestBarGraph.cxx
TestStackedBarGraph.cxx
TestContext.cxx
TestGLSL.cxx
TestVector.cxx
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestLinePlot.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 "vtkSmartPointer.h"
#include "vtkChartXY.h"
#include "vtkPlot.h"
#include "vtkPlotBar.h"
#include "vtkAxis.h"
#include "vtkTable.h"
#include "vtkIntArray.h"
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRegressionTestImage.h"
#include "vtkColorSeries.h"
#define VTK_CREATE(type, name) \
vtkSmartPointer<type> name = vtkSmartPointer<type>::New()
#define NUM_MONTHS (12)
static int month[] = {1,2,3,4,5,6,7,8,9,10,11,12};
static int book_2008[] = {5675, 5902, 6388, 5990, 5575, 7393, 9878, 8082, 6417, 5946, 5526, 5166};
static int new_popular_2008[] = {701, 687, 736, 696, 750, 814, 923, 860, 786, 735, 680, 741};
static int periodical_2008[] = {184, 176, 166, 131, 171, 191, 231, 166, 197, 162, 152, 143};
static int audiobook_2008[] = {903, 1038, 987, 1073, 1144, 1203, 1173, 1196, 1213, 1076, 926, 874};
static int video_2008[] = {1524, 1565, 1627, 1445, 1179, 1816, 2293, 1811, 1588, 1561, 1542, 1563};
static int book_2009[] = {6388, 5990, 5575, 9878, 8082, 5675, 7393, 5902, 5526, 5166, 5946, 6417};
static int new_popular_2009[] = {696, 735, 786, 814, 736, 860, 750, 687, 923, 680, 741, 701};
static int periodical_2009[] = {197, 166, 176, 231, 171, 152, 166, 131, 184, 191, 143, 162};
static int audiobook_2009[] = {1213, 1076, 926, 987, 903, 1196, 1073, 1144, 1203, 1038, 874, 1173};
static int video_2009[] = {2293, 1561, 1542, 1627, 1588, 1179, 1563, 1445, 1811, 1565, 1524, 1816};
void build_array(const char *name, vtkIntArray *array, int c_array[])
{
array->SetName(name);
for (int i = 0; i < NUM_MONTHS; i++)
{
array->InsertNextValue(c_array[i]);
}
}
//----------------------------------------------------------------------------
int TestStackedBarGraph( int argc, char * argv [] )
{
// Set up a 2D scene, add an XY chart to it
VTK_CREATE(vtkContextView, view);
view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
view->GetRenderWindow()->SetSize(500, 350);
VTK_CREATE(vtkChartXY, chart);
view->GetScene()->AddItem(chart);
// Create a table with some points in it...
VTK_CREATE(vtkTable, table);
VTK_CREATE(vtkIntArray,arrMonth);
build_array("Month",arrMonth,month);
table->AddColumn(arrMonth);
VTK_CREATE(vtkIntArray,arrBooks2008);
build_array("Books 2008",arrBooks2008,book_2008);
table->AddColumn(arrBooks2008);
VTK_CREATE(vtkIntArray,arrNewPopular2008);
build_array("New / Popular 2008",arrNewPopular2008,new_popular_2008);
table->AddColumn(arrNewPopular2008);
VTK_CREATE(vtkIntArray,arrPeriodical2008);
build_array("Periodical 2008",arrPeriodical2008,periodical_2008);
table->AddColumn(arrPeriodical2008);
VTK_CREATE(vtkIntArray,arrAudiobook2008);
build_array("Audiobook 2008",arrAudiobook2008,audiobook_2008);
table->AddColumn(arrAudiobook2008);
VTK_CREATE(vtkIntArray,arrVideo2008);
build_array("Video 2008",arrVideo2008,video_2008);
table->AddColumn(arrVideo2008);
VTK_CREATE(vtkIntArray,arrBooks2009);
build_array("Books 2009",arrBooks2009,book_2009);
table->AddColumn(arrBooks2009);
VTK_CREATE(vtkIntArray,arrNewPopular2009);
build_array("New / Popular 2009",arrNewPopular2009,new_popular_2009);
table->AddColumn(arrNewPopular2009);
VTK_CREATE(vtkIntArray,arrPeriodical2009);
build_array("Periodical 2009",arrPeriodical2009,periodical_2009);
table->AddColumn(arrPeriodical2009);
VTK_CREATE(vtkIntArray,arrAudiobook2009);
build_array("Audiobook 2009",arrAudiobook2009,audiobook_2009);
table->AddColumn(arrAudiobook2009);
VTK_CREATE(vtkIntArray,arrVideo2009);
build_array("Video 2009",arrVideo2009,video_2009);
table->AddColumn(arrVideo2009);
// Create a color series to use with our stacks.
VTK_CREATE(vtkColorSeries,colorSeries1);
colorSeries1->SetColorScheme(vtkColorSeries::WILD_FLOWER);
// Add multiple line plots, setting the colors etc
vtkPlotBar *bar = 0;
bar = vtkPlotBar::SafeDownCast(chart->AddPlot(vtkChart::BAR));
bar->SetColorSeries(colorSeries1);
bar->SetInput(table, "Month", "Books 2008");
bar->SetInputArray(2,"New / Popular 2008");
bar->SetInputArray(3,"Periodical 2008");
bar->SetInputArray(4,"Audiobook 2008");
bar->SetInputArray(5,"Video 2008");
VTK_CREATE(vtkColorSeries,colorSeries2);
colorSeries2->SetColorScheme(vtkColorSeries::CITRUS);
bar = vtkPlotBar::SafeDownCast(chart->AddPlot(vtkChart::BAR));
bar->SetColorSeries(colorSeries2);
bar->SetInput(table, "Month", "Books 2009");
bar->SetInputArray(2,"New / Popular 2009");
bar->SetInputArray(3,"Periodical 2009");
bar->SetInputArray(4,"Audiobook 2009");
bar->SetInputArray(5,"Video 2009");
chart->SetShowLegend(true);
chart->GetAxis(1)->SetBehavior(1);
chart->GetAxis(1)->SetMaximum(20.0); // Make some room for the legend
chart->GetAxis(1)->SetLabelsVisible(false);
chart->GetAxis(1)->SetTitle("Month");
chart->GetAxis(0)->SetTitle("");
chart->SetTitle("Circulation 2008, 2009");
//Finally render the scene and compare the image to a reference image
view->GetRenderWindow()->SetMultiSamples(0);
int retVal = vtkRegressionTestImageThreshold(view->GetRenderWindow(), 25);
//int retVal = vtkRegressionTestImage(view->GetRenderWindow());
if(retVal == vtkRegressionTester::DO_INTERACTOR)
{
view->GetInteractor()->Initialize();
view->GetInteractor()->Start();
}
return !retVal;
}
......@@ -25,6 +25,7 @@
#include "vtkVector.h"
#include "vtkWeakPointer.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkObjectFactory.h"
......@@ -107,9 +108,10 @@ bool vtkChartLegend::Paint(vtkContext2D *painter)
// metrics, but these could be cached.
for(size_t i = 0; i < this->Storage->ActivePlots.size(); ++i)
{
if (this->Storage->ActivePlots[i]->GetLabel())
vtkStringArray *labels = this->Storage->ActivePlots[i]->GetLabels();
for (size_t l = 0; labels && (l < labels->GetNumberOfTuples()); l++)
{
painter->ComputeStringBounds(this->Storage->ActivePlots[i]->GetLabel(),
painter->ComputeStringBounds(labels->GetValue(l),
stringBounds->GetData());
if (stringBounds[1].X() > maxWidth)
{
......@@ -121,13 +123,21 @@ bool vtkChartLegend::Paint(vtkContext2D *painter)
// Figure out the size of the legend box and store locally.
int padding = 5;
int symbolWidth = 25;
int numLabels = 0;
for(size_t i = 0; i < this->Storage->ActivePlots.size(); ++i)
{
numLabels += this->Storage->ActivePlots[i]->GetNumberOfLabels();
}
vtkVector2f rectCorner(floor(this->Storage->Point.X()-maxWidth)-2*padding-symbolWidth,
floor(this->Storage->Point.Y() -
this->Storage->ActivePlots.size()*(height+padding)) -
numLabels*(height+padding)) -
padding);
vtkVector2f rectDim(ceil(maxWidth)+2*padding+symbolWidth,
ceil((this->Storage->ActivePlots.size())*(height+padding)) +
padding);
ceil((numLabels*(height+padding)) +
padding));
// Now draw a box for the legend.
painter->GetBrush()->SetColor(255, 255, 255, 255);
painter->DrawRect(rectCorner.X(), rectCorner.Y(), rectDim.X(), rectDim.Y());
......@@ -140,7 +150,8 @@ bool vtkChartLegend::Paint(vtkContext2D *painter)
// Draw all of the legend labels and marks
for(size_t i = 0; i < this->Storage->ActivePlots.size(); ++i)
{
if (this->Storage->ActivePlots[i]->GetLabel())
vtkStringArray *labels = this->Storage->ActivePlots[i]->GetLabels();
for (size_t l = 0; labels && (l < labels->GetNumberOfValues()); l++)
{
// This is fairly hackish, but gets the text looking reasonable...
// Calculate a height for a "normal" string, then if this height is greater
......@@ -148,14 +159,14 @@ bool vtkChartLegend::Paint(vtkContext2D *painter)
// base line until better support is in the text rendering code...
// There are still several one pixel glitches, but it looks better than
// using the default vertical alignment. FIXME!
vtkStdString testString = this->Storage->ActivePlots[i]->GetLabel();
vtkStdString testString = labels->GetValue(l);
testString += "T";
painter->ComputeStringBounds(testString, stringBounds->GetData());
painter->DrawString(pos.X(), rect[1] + (baseHeight-stringBounds[1].Y()),
this->Storage->ActivePlots[i]->GetLabel());
labels->GetValue(l));
// Paint the legend mark and increment out y value.
this->Storage->ActivePlots[i]->PaintLegend(painter, rect);
this->Storage->ActivePlots[i]->PaintLegend(painter, rect,l);
rect[1] -= height+padding;
}
}
......
......@@ -1061,12 +1061,13 @@ bool vtkChartXY::LocatePointInPlots(const vtkContextMouseEvent &mouse)
vtkPlot* plot = this->ChartPrivate->PlotCorners[i][j];
if (plot->GetVisible())
{
bool found = plot->GetNearestPoint(position, tolerance, &plotPos);
if (found)
int seriesIndex = plot->GetNearestPoint(position, tolerance, &plotPos);
if (seriesIndex >= 0)
{
const char *label = plot->GetLabel(seriesIndex);
// We found a point, set up the tooltip and return
vtksys_ios::ostringstream ostr;
ostr << plot->GetLabel() << ": " << plotPos.X() << ", " << plotPos.Y();
ostr << label << ": " << plotPos.X() << ", " << plotPos.Y();
this->Tooltip->SetText(ostr.str().c_str());
this->Tooltip->SetPosition(mouse.ScreenPos[0]+2, mouse.ScreenPos[1]+2);
return true;
......
......@@ -23,6 +23,7 @@
#include "vtkIdTypeArray.h"
#include "vtkContextMapper2D.h"
#include "vtkObjectFactory.h"
#include "vtkStringArray.h"
#include "vtkStdString.h"
......@@ -36,7 +37,7 @@ vtkPlot::vtkPlot()
this->Pen = vtkPen::New();
this->Pen->SetWidth(2.0);
this->Brush = vtkBrush::New();
this->Label = NULL;
this->Labels = NULL;
this->UseIndexForXSeries = false;
this->Data = vtkContextMapper2D::New();
this->Selection = NULL;
......@@ -57,6 +58,11 @@ vtkPlot::~vtkPlot()
this->Brush->Delete();
this->Brush = NULL;
}
if (this->Labels)
{
this->Labels->Delete();
this->Labels = NULL;
}
if (this->Data)
{
this->Data->Delete();
......@@ -67,22 +73,22 @@ vtkPlot::~vtkPlot()
this->Selection->Delete();
this->Selection = NULL;
}
this->SetLabel(NULL);
this->SetLabels(NULL);
this->SetXAxis(NULL);
this->SetYAxis(NULL);
}
//-----------------------------------------------------------------------------
bool vtkPlot::PaintLegend(vtkContext2D*, float*)
bool vtkPlot::PaintLegend(vtkContext2D*, float*, int )
{
return false;
}
//-----------------------------------------------------------------------------
bool vtkPlot::GetNearestPoint(const vtkVector2f&, const vtkVector2f&,
int vtkPlot::GetNearestPoint(const vtkVector2f&, const vtkVector2f&,
vtkVector2f*)
{
return false;
return -1;
}
//-----------------------------------------------------------------------------
......@@ -122,29 +128,73 @@ float vtkPlot::GetWidth()
return this->Pen->GetWidth();
}
void vtkPlot::SetLabels(vtkStringArray *labels)
{
if (this->Labels == labels)
{
return;
}
this->Labels = labels;
this->Modified();
}
//-----------------------------------------------------------------------------
const char* vtkPlot::GetLabel()
vtkStringArray *vtkPlot::GetLabels()
{
// If the label string is empty, return the y column name
if (this->Label)
if (this->Labels)
{
return this->Labels;
}
else if (this->AutoLabels)
{
return this->Label;
return this->AutoLabels;
}
else if (this->Data->GetInput() &&
this->Data->GetInputArrayToProcess(1, this->Data->GetInput()))
{
return this->Data->GetInputArrayToProcess(1, this->Data->GetInput())->GetName();
this->AutoLabels = vtkSmartPointer<vtkStringArray>::New();
this->AutoLabels->InsertNextValue(this->Data->GetInputArrayToProcess(1, this->Data->GetInput())->GetName());
return this->AutoLabels;
}
else
{
return NULL;
}
}
//-----------------------------------------------------------------------------
int vtkPlot::GetNumberOfLabels()
{
vtkStringArray *labels = this->GetLabels();
if (labels)
{
return labels->GetNumberOfValues();
}
else
{
return 0;
}
}
//-----------------------------------------------------------------------------
const char *vtkPlot::GetLabel(vtkIdType index)
{
vtkStringArray *labels = this->GetLabels();
if (labels && index >= 0 && index < labels->GetNumberOfValues())
{
return labels->GetValue(index);
}
else
{
return NULL;
}
}
//-----------------------------------------------------------------------------
void vtkPlot::SetInput(vtkTable *table)
{
this->Data->SetInput(table);
this->AutoLabels = 0; // No longer valid
}
//-----------------------------------------------------------------------------
......@@ -165,6 +215,7 @@ void vtkPlot::SetInput(vtkTable *table, const char *xColumn,
this->Data->SetInputArrayToProcess(1, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_ROWS,
yColumn);
this->AutoLabels = 0; // No longer valid
}
//-----------------------------------------------------------------------------
......@@ -188,6 +239,7 @@ void vtkPlot::SetInputArray(int index, const char *name)
this->Data->SetInputArrayToProcess(index, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_ROWS,
name);
this->AutoLabels = 0; // No longer valid
}
//-----------------------------------------------------------------------------
......
......@@ -22,6 +22,7 @@
#define __vtkPlot_h
#include "vtkContextItem.h"
#include "vtkSmartPointer.h" // Needed to hold AutoLabels
class vtkVariant;
class vtkTable;
......@@ -31,6 +32,7 @@ class vtkPen;
class vtkBrush;
class vtkAxis;
class vtkVector2f;
class vtkStringArray;
class VTK_CHARTS_EXPORT vtkPlot : public vtkContextItem
{
......@@ -42,15 +44,19 @@ public:
// Paint legend event for the XY plot, called whenever the legend needs the
// plot items symbol/mark/line drawn. A rect is supplied with the lower left
// corner of the rect (elements 0 and 1) and with width x height (elements 2
// and 3). The plot can choose how to fill the space supplied.
virtual bool PaintLegend(vtkContext2D *painter, float rect[4]);
// and 3). The plot can choose how to fill the space supplied. The index is used
// by Plots that return more than one label.
virtual bool PaintLegend(vtkContext2D *painter, float rect[4],int legendIndex);
//BTX
// Description:
// Function to query a plot for the nearest point to the specified coordinate.
virtual bool GetNearestPoint(const vtkVector2f& point,
// Returns the index of the data series with which the point is associated or
// -1.
virtual int GetNearestPoint(const vtkVector2f& point,
const vtkVector2f& tolerance,
vtkVector2f* location);
vtkVector2f* location
);
virtual bool SelectPoints(const vtkVector2f& min, const vtkVector2f& max);
//ETX
......@@ -81,12 +87,21 @@ public:
vtkGetObjectMacro(Brush, vtkBrush);
// Description:
// Set the plot label.
vtkSetStringMacro(Label);
// Set the plot labels.
void SetLabels(vtkStringArray *labels);
// Description:
// Get the plot label.
const char* GetLabel();
// Get the plot labels.
virtual vtkStringArray *GetLabels();
// Description:
// Get the number of labels associated with this plot.
virtual int GetNumberOfLabels();
// Description:
// Get the label at the specified index.
const char *GetLabel(vtkIdType index);
// Description:
// Get the data object that the plot will draw.
......@@ -158,8 +173,13 @@ protected:
vtkBrush* Brush;
// Description:
// Plot label, used by legend.
char *Label;
// Plot labels, used by legend.
vtkStringArray *Labels;
// Description:
// Holds Labels when they're auto-created
vtkSmartPointer<vtkStringArray> AutoLabels;
// Description:
// Use the Y array index for the X value. If true any X column setting will be
......
......@@ -27,11 +27,265 @@
#include "vtkExecutive.h"
#include "vtkTimeStamp.h"
#include "vtkInformation.h"
#include "vtkSmartPointer.h"
#include "vtkColorSeries.h"
#include "vtkStringArray.h"
#include "vtkObjectFactory.h"
#include "vtkstd/vector"
#include "vtkstd/algorithm"
#include "vtkstd/map"
//-----------------------------------------------------------------------------
namespace {
// Compare the two vectors, in X component only
bool compVector2fX(const vtkVector2f& v1, const vtkVector2f& v2)
{
if (v1.X() < v2.X())
{
return true;
}
else
{
return false;
}
}
// Copy the two arrays into the points array
template<class A>
void CopyToPointsSwitch(vtkPoints2D *points, vtkPoints2D *previous_points, A *a, vtkDataArray *b, int n)
{
switch(b->GetDataType())
{
vtkTemplateMacro(
CopyToPoints(points,previous_points, a, static_cast<VTK_TT*>(b->GetVoidPointer(0)), n));
}
}
// Copy the two arrays into the points array
template<class A, class B>
void CopyToPoints(vtkPoints2D *points, vtkPoints2D *previous_points, A *a, B *b, int n)
{
points->SetNumberOfPoints(n);
for (int i = 0; i < n; ++i)
{
double prev[] = {0.0,0.0};
if (previous_points)
previous_points->GetPoint(i,prev);
points->SetPoint(i, a[i], b[i] + prev[1]);
}
}
// Copy one array into the points array, use the index of that array as x
template<class A>
void CopyToPoints(vtkPoints2D *points, vtkPoints2D *previous_points, A *a, int n)
{
points->SetNumberOfPoints(n);
for (int i = 0; i < n; ++i)
{
double prev[] = {0.0,0.0};
if (previous_points)
previous_points->GetPoint(i,prev);
points->SetPoint(i, i, a[i] + prev[1]);
}
}
} // namespace
//-----------------------------------------------------------------------------
class vtkPlotBarSegment : public vtkObject {
public:
vtkTypeMacro(vtkPlotBarSegment,vtkObject);
static vtkPlotBarSegment *New();
vtkPlotBarSegment()
{
this->Bar = 0;
this->Points = 0;
this->Sorted = false;
this->Previous = 0;
}
void Configure(vtkPlotBar *bar,vtkDataArray *x_array, vtkDataArray *y_array,vtkPlotBarSegment *prev)
{
this->Bar = bar;
this->Sorted = false;
this->Previous = prev;
if (!this->Points)
{
this->Points = vtkSmartPointer<vtkPoints2D>::New();
}
if (x_array)
{
switch (x_array->GetDataType())
{
vtkTemplateMacro(
CopyToPointsSwitch(this->Points,this->Previous ? this->Previous->Points : 0,
static_cast<VTK_TT*>(x_array->GetVoidPointer(0)),
y_array,x_array->GetNumberOfTuples()));
}
}
else
{ // Using Index for X Series
switch (y_array->GetDataType())
{
vtkTemplateMacro(
CopyToPoints(this->Points, this->Previous ? this->Previous->Points : 0,
static_cast<VTK_TT*>(y_array->GetVoidPointer(0)),
y_array->GetNumberOfTuples()));
}
}
}
void Paint(vtkContext2D *painter, vtkPen *pen, vtkBrush *brush, float width, float offset)
{
painter->ApplyPen(pen);
painter->ApplyBrush(brush);
int n = this->Points->GetNumberOfPoints();