mirror of
https://github.com/OpenFOAM/ThirdParty-6.git
synced 2025-12-08 06:57:43 +00:00
2344 lines
74 KiB
C++
2344 lines
74 KiB
C++
/*=========================================================================
|
|
|
|
Program: Visualization Toolkit
|
|
Module: vtkChartXY.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 "vtkChartXY.h"
|
|
|
|
#include "vtkContext2D.h"
|
|
#include "vtkPen.h"
|
|
#include "vtkBrush.h"
|
|
#include "vtkColorSeries.h"
|
|
#include "vtkChartSelectionHelper.h"
|
|
|
|
#include "vtkMath.h"
|
|
#include "vtkTransform2D.h"
|
|
#include "vtkContextScene.h"
|
|
#include "vtkContextMouseEvent.h"
|
|
#include "vtkContextKeyEvent.h"
|
|
#include "vtkContextTransform.h"
|
|
#include "vtkContextClip.h"
|
|
#include "vtkPoints2D.h"
|
|
#include "vtkVector.h"
|
|
#include "vtkVectorOperators.h"
|
|
|
|
#include "vtkPlotArea.h"
|
|
#include "vtkPlotBar.h"
|
|
#include "vtkPlotBag.h"
|
|
#include "vtkPlotFunctionalBag.h"
|
|
#include "vtkPlotStacked.h"
|
|
#include "vtkPlotLine.h"
|
|
#include "vtkPlotPoints.h"
|
|
#include "vtkContextMapper2D.h"
|
|
|
|
#include "vtkAxis.h"
|
|
#include "vtkPlotGrid.h"
|
|
#include "vtkChartLegend.h"
|
|
#include "vtkTooltipItem.h"
|
|
|
|
#include "vtkDataSetAttributes.h"
|
|
#include "vtkTable.h"
|
|
#include "vtkIdTypeArray.h"
|
|
|
|
#include "vtkAnnotationLink.h"
|
|
#include "vtkSelection.h"
|
|
#include "vtkSelectionNode.h"
|
|
#include "vtkSmartPointer.h"
|
|
|
|
#include "vtkObjectFactory.h"
|
|
#include "vtkCommand.h"
|
|
|
|
#include "vtkStdString.h"
|
|
#include "vtkTextProperty.h"
|
|
|
|
#include "vtkDataArray.h"
|
|
#include "vtkStringArray.h"
|
|
|
|
// My STL containers
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class vtkChartXYPrivate
|
|
{
|
|
public:
|
|
vtkChartXYPrivate()
|
|
{
|
|
this->Colors = vtkSmartPointer<vtkColorSeries>::New();
|
|
this->Clip = vtkSmartPointer<vtkContextClip>::New();
|
|
this->Borders[0] = 60;
|
|
this->Borders[1] = 50;
|
|
this->Borders[2] = 20;
|
|
this->Borders[3] = 20;
|
|
}
|
|
vtkPlot* GetPlotByColumn(vtkIdType columnId)
|
|
{
|
|
std::vector<vtkPlot*>::iterator it =
|
|
this->plots.begin();
|
|
for ( ; it != this->plots.end(); ++it)
|
|
{
|
|
vtkPlot* plot = *it;
|
|
vtkTable* table = plot->GetInput();
|
|
const int idx = 1; // column
|
|
if (table &&
|
|
table->GetColumn(columnId) ==
|
|
plot->GetData()->GetInputAbstractArrayToProcess(idx, table))
|
|
{
|
|
return plot;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::vector<vtkPlot *> plots; // Charts can contain multiple plots of data
|
|
std::vector<vtkContextTransform *> PlotCorners; // Stored by corner...
|
|
std::vector<vtkAxis *> axes; // Charts can contain multiple axes
|
|
vtkSmartPointer<vtkColorSeries> Colors; // Colors in the chart
|
|
vtkSmartPointer<vtkContextClip> Clip; // Colors in the chart
|
|
int Borders[4];
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkStandardNewMacro(vtkChartXY);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkChartXY::vtkChartXY()
|
|
{
|
|
this->ChartPrivate = new vtkChartXYPrivate;
|
|
|
|
this->AutoAxes = true;
|
|
this->HiddenAxisBorder = 20;
|
|
|
|
// The plots are drawn in a clipped, transformed area.
|
|
this->AddItem(this->ChartPrivate->Clip);
|
|
|
|
// The grid is drawn first in this clipped, transformed area.
|
|
vtkPlotGrid *grid1 = vtkPlotGrid::New();
|
|
this->ChartPrivate->Clip->AddItem(grid1);
|
|
grid1->Delete();
|
|
|
|
// The second grid for the far side/top axis
|
|
vtkPlotGrid *grid2 = vtkPlotGrid::New();
|
|
this->ChartPrivate->Clip->AddItem(grid2);
|
|
grid2->Delete();
|
|
|
|
// Set up the bottom-left transform, the rest are often not required (set up
|
|
// on demand if used later). Add it as a child item, rendered automatically.
|
|
vtkSmartPointer<vtkContextTransform> corner =
|
|
vtkSmartPointer<vtkContextTransform>::New();
|
|
this->ChartPrivate->PlotCorners.push_back(corner);
|
|
this->ChartPrivate->Clip->AddItem(corner); // Child list maintains ownership.
|
|
|
|
// Next is the axes
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
this->ChartPrivate->axes.push_back(vtkAxis::New());
|
|
// By default just show the left and bottom axes
|
|
this->ChartPrivate->axes.back()->SetVisible(i < 2 ? true : false);
|
|
this->AttachAxisRangeListener(this->ChartPrivate->axes.back());
|
|
this->AddItem(this->ChartPrivate->axes.back());
|
|
}
|
|
this->ChartPrivate->axes[vtkAxis::LEFT]->SetPosition(vtkAxis::LEFT);
|
|
this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetPosition(vtkAxis::BOTTOM);
|
|
this->ChartPrivate->axes[vtkAxis::RIGHT]->SetPosition(vtkAxis::RIGHT);
|
|
this->ChartPrivate->axes[vtkAxis::TOP]->SetPosition(vtkAxis::TOP);
|
|
|
|
// Set up the x and y axes - should be configured based on data
|
|
this->ChartPrivate->axes[vtkAxis::LEFT]->SetTitle("Y Axis");
|
|
this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetTitle("X Axis");
|
|
|
|
grid1->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
|
|
grid1->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
|
|
grid2->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
|
|
grid2->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
|
|
|
|
// Then the legend is drawn
|
|
this->Legend = vtkSmartPointer<vtkChartLegend>::New();
|
|
this->Legend->SetChart(this);
|
|
this->Legend->SetVisible(false);
|
|
this->AddItem(this->Legend);
|
|
|
|
this->PlotTransformValid = false;
|
|
|
|
this->DrawBox = false;
|
|
this->DrawSelectionPolygon = false;
|
|
this->DrawNearestPoint = false;
|
|
this->DrawAxesAtOrigin = false;
|
|
this->BarWidthFraction = 0.8f;
|
|
|
|
this->Tooltip = vtkSmartPointer<vtkTooltipItem>::New();
|
|
this->Tooltip->SetVisible(false);
|
|
this->AddItem(this->Tooltip);
|
|
this->LayoutChanged = true;
|
|
|
|
this->ForceAxesToBounds = false;
|
|
this->ZoomWithMouseWheel = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkChartXY::~vtkChartXY()
|
|
{
|
|
for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
this->ChartPrivate->plots[i]->Delete();
|
|
}
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
this->ChartPrivate->axes[i]->Delete();
|
|
}
|
|
delete this->ChartPrivate;
|
|
this->ChartPrivate = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::Update()
|
|
{
|
|
// Perform any necessary updates that are not graphical
|
|
// Update the plots if necessary
|
|
for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
this->ChartPrivate->plots[i]->Update();
|
|
}
|
|
this->Legend->Update();
|
|
|
|
// Update the selections if necessary.
|
|
if (this->AnnotationLink)
|
|
{
|
|
this->AnnotationLink->Update();
|
|
vtkSelection *selection =
|
|
vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
|
|
// Two major selection methods - row based or plot based.
|
|
if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
|
|
{
|
|
vtkSelectionNode *node = selection->GetNumberOfNodes() > 0?
|
|
selection->GetNode(0) : NULL;
|
|
vtkIdTypeArray *idArray = node?
|
|
vtkIdTypeArray::SafeDownCast(node->GetSelectionList()) : NULL;
|
|
std::vector<vtkPlot*>::iterator it =
|
|
this->ChartPrivate->plots.begin();
|
|
for ( ; it != this->ChartPrivate->plots.end(); ++it)
|
|
{
|
|
// Use the first selection node for all plots to select the rows.
|
|
(*it)->SetSelection(idArray);
|
|
}
|
|
}
|
|
else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
|
|
{
|
|
for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
|
|
{
|
|
vtkSelectionNode *node = selection->GetNode(i);
|
|
vtkIdTypeArray *idArray =
|
|
vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
|
|
vtkPlot *selectionPlot =
|
|
vtkPlot::SafeDownCast(node->GetProperties()->Get(vtkSelectionNode::PROP()));
|
|
// Now iterate through the plots to update selection data
|
|
std::vector<vtkPlot*>::iterator it =
|
|
this->ChartPrivate->plots.begin();
|
|
for ( ; it != this->ChartPrivate->plots.end(); ++it)
|
|
{
|
|
if (selectionPlot == *it)
|
|
{
|
|
(*it)->SetSelection(idArray);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
|
|
{
|
|
// Retrieve all the selected plots
|
|
std::vector<vtkPlot*> selectedPlots;
|
|
for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
|
|
{
|
|
vtkSelectionNode *node = selection->GetNode(i);
|
|
vtkIdTypeArray *selectedColumns =
|
|
vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
|
|
vtkIdType* ptr = reinterpret_cast<vtkIdType*>(selectedColumns->GetVoidPointer(0));
|
|
for (vtkIdType j = 0; j < selectedColumns->GetNumberOfTuples(); ++j)
|
|
{
|
|
vtkPlot* selectedPlot = this->ChartPrivate->GetPlotByColumn(ptr[j]);
|
|
if (selectedPlot)
|
|
{
|
|
selectedPlots.push_back(selectedPlot);
|
|
}
|
|
}
|
|
}
|
|
// Now iterate through the plots to update selection data
|
|
std::vector<vtkPlot*>::iterator it =
|
|
this->ChartPrivate->plots.begin();
|
|
for ( ; it != this->ChartPrivate->plots.end(); ++it)
|
|
{
|
|
vtkPlot* plot = *it;
|
|
vtkIdTypeArray* plotSelection = 0;
|
|
bool ownPlotSelection = false;
|
|
bool isSelected =
|
|
std::find(selectedPlots.begin(), selectedPlots.end(), plot) !=
|
|
selectedPlots.end();
|
|
if (isSelected)
|
|
{
|
|
static int idx = 1; // y
|
|
vtkAbstractArray* column = plot->GetData()->GetInputAbstractArrayToProcess(
|
|
idx, plot->GetInput());
|
|
plotSelection = plot->GetSelection();
|
|
if (!plotSelection || plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
|
|
{
|
|
plotSelection = vtkIdTypeArray::New();
|
|
ownPlotSelection = true;
|
|
for (vtkIdType j = 0; j < column->GetNumberOfTuples(); ++j)
|
|
{
|
|
plotSelection->InsertNextValue(j);
|
|
}
|
|
}
|
|
}
|
|
plot->SetSelection(plotSelection);
|
|
if (ownPlotSelection)
|
|
{
|
|
plotSelection->Delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vtkDebugMacro("No annotation link set.");
|
|
}
|
|
|
|
this->CalculateBarPlots();
|
|
|
|
if (this->AutoAxes)
|
|
{
|
|
vtkTuple<bool, 4> visibilities(false);
|
|
for (int i = 0; i < static_cast<int>(this->ChartPrivate->PlotCorners.size()); ++i)
|
|
{
|
|
int visible = 0;
|
|
for (unsigned int j = 0;
|
|
j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
|
|
{
|
|
if (vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]
|
|
->GetItem(j))->GetVisible())
|
|
{
|
|
++visible;
|
|
}
|
|
}
|
|
if (visible)
|
|
{
|
|
visibilities[i % 4] = true;
|
|
visibilities[(i+1) % 4] = true;
|
|
}
|
|
}
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
this->ChartPrivate->axes[i]->SetVisible(visibilities[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::Paint(vtkContext2D *painter)
|
|
{
|
|
// This is where everything should be drawn, or dispatched to other methods.
|
|
vtkDebugMacro(<< "Paint event called.");
|
|
if (!this->Visible)
|
|
{
|
|
// The geometry of the chart must be valid before anything can be drawn
|
|
return false;
|
|
}
|
|
|
|
vtkVector2i geometry(0, 0);
|
|
bool recalculateTransform = false;
|
|
if (this->LayoutStrategy == vtkChart::FILL_SCENE)
|
|
{
|
|
geometry = vtkVector2i(this->GetScene()->GetSceneWidth(),
|
|
this->GetScene()->GetSceneHeight());
|
|
if (geometry.GetX() != this->Geometry[0] || geometry.GetY() != this->Geometry[1])
|
|
{
|
|
recalculateTransform = true;
|
|
this->LayoutChanged = true;
|
|
}
|
|
this->SetSize(vtkRectf(0.0, 0.0, geometry.GetX(), geometry.GetY()));
|
|
}
|
|
|
|
int visiblePlots = 0;
|
|
for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
if (this->ChartPrivate->plots[i]->GetVisible())
|
|
{
|
|
++visiblePlots;
|
|
}
|
|
}
|
|
if (visiblePlots == 0 && !this->RenderEmpty)
|
|
{
|
|
// Nothing to plot, so don't draw anything.
|
|
return false;
|
|
}
|
|
|
|
this->Update();
|
|
|
|
if (this->MTime < this->ChartPrivate->axes[0]->GetMTime())
|
|
{
|
|
// Cause the plot transform to be recalculated if necessary
|
|
recalculateTransform = true;
|
|
this->LayoutChanged = true;
|
|
}
|
|
|
|
this->UpdateLayout(painter);
|
|
|
|
// Recalculate the plot transform, min and max values if necessary
|
|
if (!this->PlotTransformValid)
|
|
{
|
|
this->RecalculatePlotBounds();
|
|
recalculateTransform = true;
|
|
}
|
|
if (this->UpdateLayout(painter) || recalculateTransform)
|
|
{
|
|
this->RecalculatePlotTransforms();
|
|
}
|
|
|
|
// Now that plot transforms, including whether to use log scaling and the
|
|
// shift-scale factors, have been updated, we give the vtkPlot instances an
|
|
// opportunity to update caches.
|
|
for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
this->ChartPrivate->plots[i]->UpdateCache();
|
|
}
|
|
|
|
// Update the clipping if necessary
|
|
this->ChartPrivate->Clip->SetClip(this->Point1[0], this->Point1[1],
|
|
this->Point2[0]-this->Point1[0],
|
|
this->Point2[1]-this->Point1[1]);
|
|
|
|
// draw background
|
|
if(this->BackgroundBrush)
|
|
{
|
|
painter->GetPen()->SetLineType(vtkPen::NO_PEN);
|
|
painter->ApplyBrush(this->BackgroundBrush);
|
|
painter->DrawRect(this->Point1[0], this->Point1[1],
|
|
this->Geometry[0], this->Geometry[1]);
|
|
}
|
|
|
|
// Use the scene to render most of the chart.
|
|
this->PaintChildren(painter);
|
|
|
|
// Draw the selection box if necessary
|
|
if (this->DrawBox)
|
|
{
|
|
painter->GetBrush()->SetColor(255, 255, 255, 0);
|
|
painter->GetPen()->SetColor(0, 0, 0, 255);
|
|
painter->GetPen()->SetWidth(1.0);
|
|
painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);
|
|
painter->DrawRect(this->MouseBox.GetX(), this->MouseBox.GetY(),
|
|
this->MouseBox.GetWidth(), this->MouseBox.GetHeight());
|
|
}
|
|
|
|
// Draw the selection polygon if necessary
|
|
if (this->DrawSelectionPolygon)
|
|
{
|
|
painter->GetBrush()->SetColor(255, 0, 0, 0);
|
|
painter->GetPen()->SetColor(0, 255, 0, 255);
|
|
painter->GetPen()->SetWidth(2.0);
|
|
painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);
|
|
|
|
const vtkContextPolygon &polygon = this->SelectionPolygon;
|
|
|
|
// draw each line segment
|
|
for(vtkIdType i = 0; i < polygon.GetNumberOfPoints() - 1; i++)
|
|
{
|
|
const vtkVector2f &a = polygon.GetPoint(i);
|
|
const vtkVector2f &b = polygon.GetPoint(i+1);
|
|
|
|
painter->DrawLine(a.GetX(), a.GetY(), b.GetX(), b.GetY());
|
|
}
|
|
|
|
// draw a line from the end to the start
|
|
if(polygon.GetNumberOfPoints() >= 3)
|
|
{
|
|
const vtkVector2f &start = polygon.GetPoint(0);
|
|
const vtkVector2f &end = polygon.GetPoint(polygon.GetNumberOfPoints() - 1);
|
|
|
|
painter->DrawLine(start.GetX(), start.GetY(), end.GetX(), end.GetY());
|
|
}
|
|
}
|
|
|
|
if (this->Title)
|
|
{
|
|
int offset = 0; // title margin.
|
|
vtkAxis* topAxis = this->ChartPrivate->axes[vtkAxis::TOP];
|
|
if (topAxis->GetVisible())
|
|
{
|
|
vtkRectf bounds = topAxis->GetBoundingRect(painter);
|
|
offset += static_cast<int>(bounds.GetHeight());
|
|
}
|
|
vtkPoints2D *rect = vtkPoints2D::New();
|
|
rect->InsertNextPoint(this->Point1[0], this->Point2[1] + offset);
|
|
rect->InsertNextPoint(this->Point2[0]-this->Point1[0], 10);
|
|
painter->ApplyTextProp(this->TitleProperties);
|
|
painter->DrawStringRect(rect, this->Title);
|
|
rect->Delete();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::CalculateBarPlots()
|
|
{
|
|
// Calculate the width, spacing and offsets for the bar plot - they are grouped
|
|
size_t n = this->ChartPrivate->plots.size();
|
|
std::vector<vtkPlotBar *> bars;
|
|
for (size_t i = 0; i < n; ++i)
|
|
{
|
|
vtkPlotBar* bar = vtkPlotBar::SafeDownCast(this->ChartPrivate->plots[i]);
|
|
if (bar && bar->GetVisible())
|
|
{
|
|
bars.push_back(bar);
|
|
}
|
|
}
|
|
if (bars.size())
|
|
{
|
|
// We have some bar plots - work out offsets etc.
|
|
float barWidth = 0.1;
|
|
vtkPlotBar* bar = bars[0];
|
|
if (!bar->GetUseIndexForXSeries())
|
|
{
|
|
vtkTable *table = bar->GetData()->GetInput();
|
|
if (table)
|
|
{
|
|
vtkDataArray* x = bar->GetData()->GetInputArrayToProcess(0, table);
|
|
if (x && x->GetNumberOfTuples() > 1)
|
|
{
|
|
double x0 = x->GetTuple1(0);
|
|
double x1 = x->GetTuple1(1);
|
|
float width = static_cast<float>(fabs(x1 - x0) * this->BarWidthFraction);
|
|
barWidth = width / bars.size();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
barWidth = 1.0f / bars.size() * this->BarWidthFraction;
|
|
}
|
|
|
|
// Now set the offsets and widths on each bar
|
|
// The offsetIndex deals with the fact that half the bars
|
|
// must shift to the left of the point and half to the right
|
|
int offsetIndex = static_cast<int>(bars.size() - 1);
|
|
for (size_t i = 0; i < bars.size(); ++i)
|
|
{
|
|
bars[i]->SetWidth(barWidth);
|
|
bars[i]->SetOffset(offsetIndex * (barWidth / 2));
|
|
// Increment by two since we need to shift by half widths
|
|
// but make room for entire bars. Increment backwards because
|
|
// offsets are always subtracted and Positive offsets move
|
|
// the bar leftwards. Negative offsets will shift the bar
|
|
// to the right.
|
|
offsetIndex -= 2;
|
|
//bars[i]->SetOffset(float(bars.size()-i-1)*(barWidth/2));
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::RecalculatePlotTransforms()
|
|
{
|
|
for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
|
|
{
|
|
if (this->ChartPrivate->PlotCorners[i]->GetNumberOfItems())
|
|
{
|
|
vtkAxis *xAxis = 0;
|
|
vtkAxis *yAxis = 0;
|
|
// Get the appropriate axes, and recalculate the transform.
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
{
|
|
xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
|
|
break;
|
|
}
|
|
case 1:
|
|
xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
|
|
break;
|
|
case 2:
|
|
xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
|
|
break;
|
|
case 3:
|
|
xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
|
|
break;
|
|
default:
|
|
vtkWarningMacro(
|
|
"Error: default case in recalculate plot transforms.");
|
|
}
|
|
this->CalculatePlotTransform(
|
|
xAxis, yAxis, this->ChartPrivate->PlotCorners[i]->GetTransform());
|
|
// Now we need to set the scale factor on the plots to ensure they rescale
|
|
// their input data when necessary.
|
|
vtkRectd shiftScale(xAxis->GetShift(), yAxis->GetShift(),
|
|
xAxis->GetScalingFactor(), yAxis->GetScalingFactor());
|
|
for (unsigned int j = 0;
|
|
j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
|
|
{
|
|
vtkPlot *plot =
|
|
vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
|
|
if (plot)
|
|
{
|
|
plot->SetShiftScale(shiftScale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this->PlotTransformValid = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int vtkChartXY::GetPlotCorner(vtkPlot *plot)
|
|
{
|
|
vtkAxis *x = plot->GetXAxis();
|
|
vtkAxis *y = plot->GetYAxis();
|
|
if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
|
|
y == this->ChartPrivate->axes[vtkAxis::LEFT])
|
|
{
|
|
return 0;
|
|
}
|
|
else if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
|
|
y == this->ChartPrivate->axes[vtkAxis::RIGHT])
|
|
{
|
|
return 1;
|
|
}
|
|
else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
|
|
y == this->ChartPrivate->axes[vtkAxis::RIGHT])
|
|
{
|
|
return 2;
|
|
}
|
|
else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
|
|
y == this->ChartPrivate->axes[vtkAxis::LEFT])
|
|
{
|
|
return 3;
|
|
}
|
|
else
|
|
{
|
|
// Should never happen.
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetPlotCorner(vtkPlot *plot, int corner)
|
|
{
|
|
if (corner < 0 || corner > 3)
|
|
{
|
|
vtkWarningMacro("Invalid corner specified, should be between 0 and 3: "
|
|
<< corner);
|
|
return;
|
|
}
|
|
if (this->GetPlotCorner(plot) == corner)
|
|
{
|
|
return;
|
|
}
|
|
this->RemovePlotFromCorners(plot);
|
|
// Grow the plot corners if necessary
|
|
while (static_cast<int>(this->ChartPrivate->PlotCorners.size() - 1) < corner)
|
|
{
|
|
vtkNew<vtkContextTransform> transform;
|
|
this->ChartPrivate->PlotCorners.push_back(transform.GetPointer());
|
|
this->ChartPrivate->Clip->AddItem(transform.GetPointer()); // Clip maintains ownership.
|
|
}
|
|
this->ChartPrivate->PlotCorners[corner]->AddItem(plot);
|
|
if (corner == 0)
|
|
{
|
|
plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
|
|
plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
|
|
}
|
|
else if (corner == 1)
|
|
{
|
|
plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
|
|
plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
|
|
}
|
|
else if (corner == 2)
|
|
{
|
|
plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
|
|
plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
|
|
}
|
|
else if (corner == 3)
|
|
{
|
|
plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
|
|
plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
|
|
}
|
|
this->PlotTransformValid = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::RecalculatePlotBounds()
|
|
{
|
|
// Get the bounds of each plot, and each axis - ordering as laid out below
|
|
double y1[] = { 0.0, 0.0 }; // left -> 0
|
|
double x1[] = { 0.0, 0.0 }; // bottom -> 1
|
|
double y2[] = { 0.0, 0.0 }; // right -> 2
|
|
double x2[] = { 0.0, 0.0 }; // top -> 3
|
|
// Store whether the ranges have been initialized - follows same order
|
|
bool initialized[] = { false, false, false, false };
|
|
|
|
std::vector<vtkPlot*>::iterator it;
|
|
double bounds[4] = { 0.0, 0.0, 0.0, 0.0 };
|
|
for (it = this->ChartPrivate->plots.begin();
|
|
it != this->ChartPrivate->plots.end(); ++it)
|
|
{
|
|
if ((*it)->GetVisible() == false)
|
|
{
|
|
continue;
|
|
}
|
|
(*it)->GetBounds(bounds);
|
|
if (bounds[1] - bounds[0] < 0.0)
|
|
{
|
|
// skip uninitialized bounds.
|
|
continue;
|
|
}
|
|
int corner = this->GetPlotCorner(*it);
|
|
|
|
// Initialize the appropriate ranges, or push out the ranges
|
|
if ((corner == 0 || corner == 3)) // left
|
|
{
|
|
if (!initialized[0])
|
|
{
|
|
y1[0] = bounds[2];
|
|
y1[1] = bounds[3];
|
|
initialized[0] = true;
|
|
}
|
|
else
|
|
{
|
|
if (y1[0] > bounds[2]) // min
|
|
{
|
|
y1[0] = bounds[2];
|
|
}
|
|
if (y1[1] < bounds[3]) // max
|
|
{
|
|
y1[1] = bounds[3];
|
|
}
|
|
}
|
|
}
|
|
if ((corner == 0 || corner == 1)) // bottom
|
|
{
|
|
if (!initialized[1])
|
|
{
|
|
x1[0] = bounds[0];
|
|
x1[1] = bounds[1];
|
|
initialized[1] = true;
|
|
}
|
|
else
|
|
{
|
|
if (x1[0] > bounds[0]) // min
|
|
{
|
|
x1[0] = bounds[0];
|
|
}
|
|
if (x1[1] < bounds[1]) // max
|
|
{
|
|
x1[1] = bounds[1];
|
|
}
|
|
}
|
|
}
|
|
if ((corner == 1 || corner == 2)) // right
|
|
{
|
|
if (!initialized[2])
|
|
{
|
|
y2[0] = bounds[2];
|
|
y2[1] = bounds[3];
|
|
initialized[2] = true;
|
|
}
|
|
else
|
|
{
|
|
if (y2[0] > bounds[2]) // min
|
|
{
|
|
y2[0] = bounds[2];
|
|
}
|
|
if (y2[1] < bounds[3]) // max
|
|
{
|
|
y2[1] = bounds[3];
|
|
}
|
|
}
|
|
}
|
|
if ((corner == 2 || corner == 3)) // top
|
|
{
|
|
if (!initialized[3])
|
|
{
|
|
x2[0] = bounds[0];
|
|
x2[1] = bounds[1];
|
|
initialized[3] = true;
|
|
}
|
|
else
|
|
{
|
|
if (x2[0] > bounds[0]) // min
|
|
{
|
|
x2[0] = bounds[0];
|
|
}
|
|
if (x2[1] < bounds[1]) // max
|
|
{
|
|
x2[1] = bounds[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now set the newly calculated bounds on the axes
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
vtkAxis *axis = this->ChartPrivate->axes[i];
|
|
double *range = 0;
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
range = y1;
|
|
break;
|
|
case 1:
|
|
range = x1;
|
|
break;
|
|
case 2:
|
|
range = y2;
|
|
break;
|
|
case 3:
|
|
range = x2;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (this->ForceAxesToBounds)
|
|
{
|
|
axis->SetMinimumLimit(range[0]);
|
|
axis->SetMaximumLimit(range[1]);
|
|
}
|
|
if (axis->GetBehavior() == vtkAxis::AUTO && initialized[i])
|
|
{
|
|
axis->SetRange(range[0], range[1]);
|
|
axis->AutoScale();
|
|
}
|
|
}
|
|
|
|
this->Modified();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
|
|
{
|
|
// The main use of this method is currently to query the visible axes for
|
|
// their bounds, and to update the chart in response to that.
|
|
bool changed = false;
|
|
|
|
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
|
|
vtkVector2i hiddenAxisBorder = tileScale * this->HiddenAxisBorder;
|
|
|
|
// Axes
|
|
if (this->LayoutStrategy == vtkChart::FILL_SCENE ||
|
|
this->LayoutStrategy == vtkChart::FILL_RECT)
|
|
{
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
int border = 0;
|
|
vtkAxis* axis = this->ChartPrivate->axes[i];
|
|
axis->Update();
|
|
if (axis->GetVisible())
|
|
{
|
|
vtkRectf bounds = axis->GetBoundingRect(painter);
|
|
if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
|
|
{// Horizontal axes
|
|
border = int(bounds.GetHeight());
|
|
}
|
|
else
|
|
{// Vertical axes
|
|
border = int(bounds.GetWidth());
|
|
}
|
|
}
|
|
border += this->GetLegendBorder(painter, i);
|
|
if (i == vtkAxis::TOP && this->Title)
|
|
{
|
|
painter->ApplyTextProp(this->TitleProperties);
|
|
float bounds[4];
|
|
painter->ComputeStringBounds(this->Title, bounds);
|
|
if (bounds[3] > 0)
|
|
{
|
|
border += (5 * tileScale.GetY()) /* title margin */
|
|
+ bounds[3]; // add the title text height to the border.
|
|
}
|
|
}
|
|
|
|
if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
|
|
{
|
|
border = std::max(border, hiddenAxisBorder.GetY());
|
|
}
|
|
else
|
|
{
|
|
border = std::max(border, hiddenAxisBorder.GetX());
|
|
}
|
|
|
|
if (this->ChartPrivate->Borders[i] != border)
|
|
{
|
|
this->ChartPrivate->Borders[i] = border;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this->LayoutChanged || changed)
|
|
{
|
|
if (this->DrawAxesAtOrigin)
|
|
{
|
|
this->SetBorders(hiddenAxisBorder.GetX(),
|
|
hiddenAxisBorder.GetY(),
|
|
this->ChartPrivate->Borders[2],
|
|
this->ChartPrivate->Borders[3]);
|
|
// Get the screen coordinates for the origin, and move the axes there.
|
|
vtkVector2f origin(0.0);
|
|
vtkTransform2D* transform =
|
|
this->ChartPrivate->PlotCorners[0]->GetTransform();
|
|
transform->TransformPoints(origin.GetData(), origin.GetData(), 1);
|
|
// Need to clamp the axes in the plot area.
|
|
if (int(origin[0]) < this->Point1[0])
|
|
{
|
|
origin[0] = this->Point1[0];
|
|
}
|
|
if (int(origin[0]) > this->Point2[0])
|
|
{
|
|
origin[0] = this->Point2[0];
|
|
}
|
|
if (int(origin[1]) < this->Point1[1])
|
|
{
|
|
origin[1] = this->Point1[1];
|
|
}
|
|
if (int(origin[1]) > this->Point2[1])
|
|
{
|
|
origin[1] = this->Point2[1];
|
|
}
|
|
|
|
this->ChartPrivate->axes[vtkAxis::BOTTOM]
|
|
->SetPoint1(this->Point1[0], origin[1]);
|
|
this->ChartPrivate->axes[vtkAxis::BOTTOM]
|
|
->SetPoint2(this->Point2[0], origin[1]);
|
|
this->ChartPrivate->axes[vtkAxis::LEFT]
|
|
->SetPoint1(origin[0], this->Point1[1]);
|
|
this->ChartPrivate->axes[vtkAxis::LEFT]
|
|
->SetPoint2(origin[0], this->Point2[1]);
|
|
}
|
|
else
|
|
{
|
|
if (this->LayoutStrategy == vtkChart::AXES_TO_RECT)
|
|
{
|
|
this->SetBorders(0, 0, 0, 0);
|
|
this->ChartPrivate->axes[0]->GetBoundingRect(painter);
|
|
this->ChartPrivate->axes[1]->GetBoundingRect(painter);
|
|
this->ChartPrivate->axes[2]->GetBoundingRect(painter);
|
|
this->ChartPrivate->axes[3]->GetBoundingRect(painter);
|
|
}
|
|
else
|
|
{
|
|
this->SetBorders(this->ChartPrivate->Borders[0],
|
|
this->ChartPrivate->Borders[1],
|
|
this->ChartPrivate->Borders[2],
|
|
this->ChartPrivate->Borders[3]);
|
|
}
|
|
// This is where we set the axes up too
|
|
// Y axis (left)
|
|
this->ChartPrivate->axes[0]->SetPoint1(this->Point1[0], this->Point1[1]);
|
|
this->ChartPrivate->axes[0]->SetPoint2(this->Point1[0], this->Point2[1]);
|
|
// X axis (bottom)
|
|
this->ChartPrivate->axes[1]->SetPoint1(this->Point1[0], this->Point1[1]);
|
|
this->ChartPrivate->axes[1]->SetPoint2(this->Point2[0], this->Point1[1]);
|
|
}
|
|
// Y axis (right)
|
|
this->ChartPrivate->axes[2]->SetPoint1(this->Point2[0], this->Point1[1]);
|
|
this->ChartPrivate->axes[2]->SetPoint2(this->Point2[0], this->Point2[1]);
|
|
// X axis (top)
|
|
this->ChartPrivate->axes[3]->SetPoint1(this->Point1[0], this->Point2[1]);
|
|
this->ChartPrivate->axes[3]->SetPoint2(this->Point2[0], this->Point2[1]);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
this->ChartPrivate->axes[i]->Update();
|
|
}
|
|
}
|
|
this->SetLegendPosition(this->Legend->GetBoundingRect(painter));
|
|
|
|
return changed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
|
|
{
|
|
if (!this->Legend->GetVisible() || this->Legend->GetInline())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
|
|
|
|
int padding = 10;
|
|
vtkVector2i legendSize(0, 0);
|
|
vtkVector2i legendAlignment(this->Legend->GetHorizontalAlignment(),
|
|
this->Legend->GetVerticalAlignment());
|
|
this->Legend->Update();
|
|
vtkRectf rect = this->Legend->GetBoundingRect(painter);
|
|
legendSize.Set(static_cast<int>(rect.GetWidth()),
|
|
static_cast<int>(rect.GetHeight()));
|
|
|
|
// Figure out the correct place and alignment based on the legend layout.
|
|
if (axisPosition == vtkAxis::LEFT &&
|
|
legendAlignment.GetX() == vtkChartLegend::LEFT)
|
|
{
|
|
return legendSize.GetX() + padding * tileScale.GetX();
|
|
}
|
|
else if (axisPosition == vtkAxis::RIGHT &&
|
|
legendAlignment.GetX() == vtkChartLegend::RIGHT)
|
|
{
|
|
return legendSize.GetX() + padding * tileScale.GetX();
|
|
}
|
|
else if ((axisPosition == vtkAxis::TOP || axisPosition == vtkAxis::BOTTOM) &&
|
|
(legendAlignment.GetX() == vtkChartLegend::LEFT ||
|
|
legendAlignment.GetX() == vtkChartLegend::RIGHT))
|
|
{
|
|
return 0;
|
|
}
|
|
else if (axisPosition == vtkAxis::TOP &&
|
|
legendAlignment.GetY() == vtkChartLegend::TOP)
|
|
{
|
|
return legendSize.GetY() + padding * tileScale.GetY();
|
|
}
|
|
else if (axisPosition == vtkAxis::BOTTOM &&
|
|
legendAlignment.GetY() == vtkChartLegend::BOTTOM)
|
|
{
|
|
return legendSize.GetY() + padding * tileScale.GetY();
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetLegendPosition(const vtkRectf& rect)
|
|
{
|
|
// Put the legend in the top corner of the chart
|
|
vtkVector2f pos(0, 0);
|
|
int padding = 5;
|
|
vtkVector2i legendAlignment(this->Legend->GetHorizontalAlignment(),
|
|
this->Legend->GetVerticalAlignment());
|
|
|
|
if (legendAlignment[0] == vtkChartLegend::CUSTOM ||
|
|
legendAlignment[1] == vtkChartLegend::CUSTOM)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this->Legend->GetInline())
|
|
{
|
|
switch (this->Legend->GetHorizontalAlignment())
|
|
{
|
|
case vtkChartLegend::LEFT:
|
|
pos.SetX(this->Point1[0]);
|
|
break;
|
|
case vtkChartLegend::CENTER:
|
|
pos.SetX(((this->Point2[0] - this->Point1[0]) / 2.0)
|
|
- rect.GetWidth() / 2.0 + this->Point1[0]);
|
|
break;
|
|
case vtkChartLegend::RIGHT:
|
|
default:
|
|
pos.SetX(this->Point2[0] - rect.GetWidth());
|
|
}
|
|
switch (this->Legend->GetVerticalAlignment())
|
|
{
|
|
case vtkChartLegend::TOP:
|
|
pos.SetY(this->Point2[1] - rect.GetHeight());
|
|
break;
|
|
case vtkChartLegend::CENTER:
|
|
pos.SetY((this->Point2[1] - this->Point1[1]) / 2.0
|
|
- rect.GetHeight() / 2.0 + this->Point1[1]);
|
|
break;
|
|
case vtkChartLegend::BOTTOM:
|
|
default:
|
|
pos.SetY(this->Point1[1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Non-inline legends.
|
|
if (legendAlignment.GetX() == vtkChartLegend::LEFT)
|
|
{
|
|
pos.SetX(this->Point1[0] - this->ChartPrivate->Borders[vtkAxis::LEFT]
|
|
+ padding);
|
|
}
|
|
else if (legendAlignment.GetX() == vtkChartLegend::RIGHT)
|
|
{
|
|
pos.SetX(this->Point2[0] + this->ChartPrivate->Borders[vtkAxis::RIGHT]
|
|
- rect.GetWidth() - padding);
|
|
}
|
|
else if (legendAlignment.GetX() == vtkChartLegend::CENTER)
|
|
{
|
|
pos.SetX(((this->Point2[0] - this->Point1[0]) / 2.0)
|
|
- (rect.GetWidth() / 2.0) + this->Point1[0]);
|
|
// Check for the special case where the legend is on the top or bottom
|
|
if (legendAlignment.GetY() == vtkChartLegend::TOP)
|
|
{
|
|
pos.SetY(this->Point2[1] + this->ChartPrivate->Borders[vtkAxis::TOP]
|
|
- rect.GetHeight() - padding);
|
|
}
|
|
else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
|
|
{
|
|
pos.SetY(this->Point1[1] - this->ChartPrivate->Borders[vtkAxis::BOTTOM]
|
|
+ padding);
|
|
}
|
|
}
|
|
// Vertical alignment
|
|
if (legendAlignment.GetX() != vtkChartLegend::CENTER)
|
|
{
|
|
if (legendAlignment.GetY() == vtkChartLegend::TOP)
|
|
{
|
|
pos.SetY(this->Point2[1] - rect.GetHeight());
|
|
}
|
|
else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
|
|
{
|
|
pos.SetY(this->Point1[1]);
|
|
}
|
|
}
|
|
if (legendAlignment.GetY() == vtkChartLegend::CENTER)
|
|
{
|
|
pos.SetY(((this->Point2[1] - this->Point1[1]) / 2.0)
|
|
- (rect.GetHeight() / 2.0) + this->Point1[1]);
|
|
}
|
|
}
|
|
|
|
this->Legend->SetPoint(pos);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkPlot * vtkChartXY::AddPlot(int type)
|
|
{
|
|
// Use a variable to return the object created (or NULL), this is necessary
|
|
// as the HP compiler is broken (thinks this function does not return) and
|
|
// the MS compiler generates a warning about unreachable code if a redundant
|
|
// return is added at the end.
|
|
vtkPlot *plot = NULL;
|
|
vtkColor3ub color = this->ChartPrivate->Colors->GetColorRepeating(
|
|
static_cast<int>(this->ChartPrivate->plots.size()));
|
|
switch (type)
|
|
{
|
|
case LINE:
|
|
{
|
|
vtkPlotLine *line = vtkPlotLine::New();
|
|
line->GetPen()->SetColor(color.GetData());
|
|
plot = line;
|
|
break;
|
|
}
|
|
case POINTS:
|
|
{
|
|
vtkPlotPoints *points = vtkPlotPoints::New();
|
|
points->GetPen()->SetColor(color.GetData());
|
|
plot = points;
|
|
break;
|
|
}
|
|
case BAR:
|
|
{
|
|
vtkPlotBar *bar = vtkPlotBar::New();
|
|
bar->GetBrush()->SetColor(color.GetData());
|
|
plot = bar;
|
|
break;
|
|
}
|
|
case FUNCTIONALBAG:
|
|
{
|
|
vtkPlotFunctionalBag *bag = vtkPlotFunctionalBag::New();
|
|
bag->GetBrush()->SetColor(color.GetData());
|
|
plot = bag;
|
|
break;
|
|
}
|
|
case STACKED:
|
|
{
|
|
vtkPlotStacked *stacked = vtkPlotStacked::New();
|
|
stacked->SetParent(this);
|
|
stacked->GetBrush()->SetColor(color.GetData());
|
|
plot = stacked;
|
|
break;
|
|
}
|
|
case BAG:
|
|
{
|
|
vtkPlotBag *bag = vtkPlotBag::New();
|
|
bag->SetParent(this);
|
|
bag->GetBrush()->SetColor(color.GetData());
|
|
plot = bag;
|
|
break;
|
|
}
|
|
case AREA:
|
|
{
|
|
vtkPlotArea* area = vtkPlotArea::New();
|
|
area->SetParent(this);
|
|
area->GetBrush()->SetColor(color.GetData());
|
|
plot = area;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
plot = NULL;
|
|
}
|
|
if (plot)
|
|
{
|
|
this->AddPlot(plot);
|
|
plot->Delete();
|
|
}
|
|
return plot;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::AddPlot(vtkPlot * plot)
|
|
{
|
|
if (plot == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
plot->Register(this);
|
|
this->ChartPrivate->plots.push_back(plot);
|
|
vtkIdType plotIndex = this->ChartPrivate->plots.size() - 1;
|
|
this->SetPlotCorner(plot, 0);
|
|
// Ensure that the bounds are recalculated
|
|
this->PlotTransformValid = false;
|
|
// Mark the scene as dirty
|
|
if (this->Scene)
|
|
{
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
return plotIndex;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::RemovePlot(vtkIdType index)
|
|
{
|
|
if (index < static_cast<vtkIdType>(this->ChartPrivate->plots.size()))
|
|
{
|
|
this->RemovePlotFromCorners(this->ChartPrivate->plots[index]);
|
|
this->ChartPrivate->plots[index]->Delete();
|
|
this->ChartPrivate->plots.erase(this->ChartPrivate->plots.begin()+index);
|
|
|
|
// Ensure that the bounds are recalculated
|
|
this->PlotTransformValid = false;
|
|
if (this->Scene)
|
|
{
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::ClearPlots()
|
|
{
|
|
for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
this->ChartPrivate->plots[i]->Delete();
|
|
}
|
|
this->ChartPrivate->plots.clear();
|
|
// Clear the corners too
|
|
for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
|
|
{
|
|
this->ChartPrivate->PlotCorners[i]->ClearItems();
|
|
if (i > 0)
|
|
{
|
|
this->ChartPrivate->Clip->RemoveItem(this->ChartPrivate->PlotCorners[i]);
|
|
}
|
|
}
|
|
this->ChartPrivate->PlotCorners.resize(1);
|
|
|
|
// Ensure that the bounds are recalculated
|
|
this->PlotTransformValid = false;
|
|
if (this->Scene)
|
|
{
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkPlot* vtkChartXY::GetPlot(vtkIdType index)
|
|
{
|
|
if (static_cast<vtkIdType>(this->ChartPrivate->plots.size()) > index)
|
|
{
|
|
return this->ChartPrivate->plots[index];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::GetPlotIndex(vtkPlot* plot)
|
|
{
|
|
int corner = this->GetPlotCorner(plot);
|
|
return corner >= 0 && corner < 4 ?
|
|
this->ChartPrivate->PlotCorners[corner]->GetItemIndex(plot) :
|
|
static_cast<vtkIdType>(-1);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::RaisePlot(vtkPlot* plot)
|
|
{
|
|
vtkIdType plotIndex = this->GetPlotIndex(plot);
|
|
int corner = this->GetPlotCorner(plot);
|
|
if (corner < 0 || corner >=4)
|
|
{
|
|
return plotIndex;
|
|
}
|
|
return this->ChartPrivate->PlotCorners[corner]->Raise(plotIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::StackPlotAbove(vtkPlot* plot, vtkPlot* under)
|
|
{
|
|
vtkIdType plotIndex = this->GetPlotIndex(plot);
|
|
vtkIdType underIndex = this->GetPlotIndex(under);
|
|
int corner = this->GetPlotCorner(plot);
|
|
if (corner < 0 || corner >=4 ||
|
|
underIndex != this->GetPlotCorner(under))
|
|
{
|
|
return plotIndex;
|
|
}
|
|
return this->ChartPrivate->PlotCorners[corner]->StackAbove(plotIndex,
|
|
underIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::LowerPlot(vtkPlot* plot)
|
|
{
|
|
vtkIdType plotIndex = this->GetPlotIndex(plot);
|
|
int corner = this->GetPlotCorner(plot);
|
|
if (corner < 0 || corner >=4)
|
|
{
|
|
return plotIndex;
|
|
}
|
|
return this->ChartPrivate->PlotCorners[corner]->Lower(plotIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::StackPlotUnder(vtkPlot* plot, vtkPlot* above)
|
|
{
|
|
vtkIdType plotIndex = this->GetPlotIndex(plot);
|
|
vtkIdType aboveIndex = this->GetPlotIndex(above);
|
|
int corner = this->GetPlotCorner(plot);
|
|
if (corner < 0 || corner >=4 ||
|
|
corner != this->GetPlotCorner(above))
|
|
{
|
|
return plotIndex;
|
|
}
|
|
return this->ChartPrivate->PlotCorners[corner]->StackUnder(plotIndex,
|
|
aboveIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetShowLegend(bool visible)
|
|
{
|
|
this->vtkChart::SetShowLegend(visible);
|
|
this->Legend->SetVisible(visible);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkChartLegend* vtkChartXY::GetLegend()
|
|
{
|
|
return this->Legend;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetTooltip(vtkTooltipItem *tooltip)
|
|
{
|
|
if(tooltip == this->Tooltip)
|
|
{
|
|
// nothing to change
|
|
return;
|
|
}
|
|
|
|
if(this->Tooltip)
|
|
{
|
|
// remove current tooltip from scene
|
|
this->RemoveItem(this->Tooltip);
|
|
}
|
|
|
|
this->Tooltip = tooltip;
|
|
|
|
if(this->Tooltip)
|
|
{
|
|
// add new tooltip to scene
|
|
this->AddItem(this->Tooltip);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkTooltipItem* vtkChartXY::GetTooltip()
|
|
{
|
|
return this->Tooltip;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::GetNumberOfPlots()
|
|
{
|
|
return this->ChartPrivate->plots.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkAxis* vtkChartXY::GetAxis(int axisIndex)
|
|
{
|
|
if (axisIndex < 4)
|
|
{
|
|
return this->ChartPrivate->axes[axisIndex];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
vtkIdType vtkChartXY::GetNumberOfAxes()
|
|
{
|
|
return 4;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::RecalculateBounds()
|
|
{
|
|
// Ensure that the bounds are recalculated
|
|
this->PlotTransformValid = false;
|
|
if (this->Scene)
|
|
{
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetSelectionMethod(int method)
|
|
{
|
|
if (method == this->SelectionMethod)
|
|
{
|
|
return;
|
|
}
|
|
if (method == vtkChart::SELECTION_PLOTS)
|
|
{
|
|
// Clear the selection on the plots which may be shared between all of them.
|
|
// Now iterate through the plots to update selection data
|
|
std::vector<vtkPlot*>::iterator it =
|
|
this->ChartPrivate->plots.begin();
|
|
for ( ; it != this->ChartPrivate->plots.end(); ++it)
|
|
{
|
|
(*it)->SetSelection(NULL);
|
|
}
|
|
}
|
|
Superclass::SetSelectionMethod(method);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::Hit(const vtkContextMouseEvent &mouse)
|
|
{
|
|
if (!this->Interactive)
|
|
{
|
|
return false;
|
|
}
|
|
vtkVector2i pos(mouse.GetScreenPos());
|
|
if (pos[0] > this->Point1[0] &&
|
|
pos[0] < this->Point2[0] &&
|
|
pos[1] > this->Point1[1] &&
|
|
pos[1] < this->Point2[1])
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseEnterEvent(const vtkContextMouseEvent &)
|
|
{
|
|
// Find the nearest point on the curves and snap to it
|
|
this->DrawNearestPoint = true;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseMoveEvent(const vtkContextMouseEvent &mouse)
|
|
{
|
|
// Iterate through each corner, and check for a nearby point
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
if (this->ChartPrivate->PlotCorners[i]->MouseMoveEvent(mouse))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (mouse.GetButton() == this->Actions.Pan())
|
|
{
|
|
// Figure out how much the mouse has moved by in plot coordinates - pan
|
|
vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
|
|
vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());
|
|
vtkVector2d pos(0.0, 0.0);
|
|
vtkVector2d last(0.0, 0.0);
|
|
|
|
// Go from screen to scene coordinates to work out the delta
|
|
vtkAxis* xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
|
|
vtkAxis* yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
|
|
vtkTransform2D *transform =
|
|
this->ChartPrivate->PlotCorners[0]->GetTransform();
|
|
transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
|
|
transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
|
|
vtkVector2d delta = last - pos;
|
|
delta[0] /= xAxis->GetScalingFactor();
|
|
delta[1] /= yAxis->GetScalingFactor();
|
|
|
|
// Now move the axes and recalculate the transform
|
|
delta[0] = delta[0] > 0 ?
|
|
std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum()) :
|
|
std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
|
|
delta[1] = delta[1] > 0 ?
|
|
std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
|
|
std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
|
|
xAxis->SetMinimum(xAxis->GetMinimum() + delta[0]);
|
|
xAxis->SetMaximum(xAxis->GetMaximum() + delta[0]);
|
|
yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
|
|
yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);
|
|
|
|
if (this->ChartPrivate->PlotCorners.size() == 2)
|
|
{
|
|
// Figure out the right axis position, if greater than 2 both will be done
|
|
// in the else if block below.
|
|
screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
|
|
lastScreenPos =
|
|
vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
|
|
pos = vtkVector2d(0.0, 0.0);
|
|
last = vtkVector2d(0.0, 0.0);
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
|
|
transform = this->ChartPrivate->PlotCorners[1]->GetTransform();
|
|
transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
|
|
transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
|
|
delta = last - pos;
|
|
delta[0] /= xAxis->GetScalingFactor();
|
|
delta[1] /= yAxis->GetScalingFactor();
|
|
|
|
// Now move the axes and recalculate the transform
|
|
delta[1] = delta[1] > 0 ?
|
|
std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
|
|
std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
|
|
yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
|
|
yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);
|
|
}
|
|
else if (this->ChartPrivate->PlotCorners.size() > 2)
|
|
{
|
|
// Figure out the right and top axis positions.
|
|
// Go from screen to scene coordinates to work out the delta
|
|
screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
|
|
lastScreenPos =
|
|
vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
|
|
pos = vtkVector2d(0.0, 0.0);
|
|
last = vtkVector2d(0.0, 0.0);
|
|
xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
|
|
yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
|
|
transform = this->ChartPrivate->PlotCorners[2]->GetTransform();
|
|
transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
|
|
transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
|
|
delta = last - pos;
|
|
delta[0] /= xAxis->GetScalingFactor();
|
|
delta[1] /= yAxis->GetScalingFactor();
|
|
|
|
// Now move the axes and recalculate the transform
|
|
delta[0] = delta[0] > 0 ?
|
|
std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum()) :
|
|
std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
|
|
delta[1] = delta[1] > 0 ?
|
|
std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
|
|
std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
|
|
xAxis->SetMinimum(xAxis->GetMinimum() + delta[0]);
|
|
xAxis->SetMaximum(xAxis->GetMaximum() + delta[0]);
|
|
yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
|
|
yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);
|
|
}
|
|
|
|
this->RecalculatePlotTransforms();
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
|
|
this->InvokeEvent(vtkCommand::InteractionEvent);
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.Zoom() ||
|
|
mouse.GetButton() == this->Actions.Select())
|
|
{
|
|
this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
|
|
this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.ZoomAxis())
|
|
{
|
|
vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
|
|
vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());
|
|
|
|
vtkAxis *axes[] = {
|
|
this->ChartPrivate->axes[vtkAxis::BOTTOM],
|
|
this->ChartPrivate->axes[vtkAxis::LEFT],
|
|
this->ChartPrivate->axes[vtkAxis::TOP],
|
|
this->ChartPrivate->axes[vtkAxis::RIGHT]
|
|
};
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
vtkAxis *axis = axes[i];
|
|
if(!axis)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// bottom, top -> 0, right, left -> 1
|
|
int side = i % 2;
|
|
|
|
// get mouse delta in the given direction for the axis
|
|
double delta = lastScreenPos[side] - screenPos[side];
|
|
if(std::abs(delta) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// scale and invert delta
|
|
delta /= -100.0;
|
|
|
|
// zoom axis range
|
|
double min = axis->GetMinimum();
|
|
double max = axis->GetMaximum();
|
|
double frac = (max - min) * 0.1;
|
|
if (frac > 0.0)
|
|
{
|
|
min += delta*frac;
|
|
max -= delta*frac;
|
|
}
|
|
else
|
|
{
|
|
min -= delta*frac;
|
|
max += delta*frac;
|
|
}
|
|
axis->SetMinimum(min);
|
|
axis->SetMaximum(max);
|
|
axis->RecalculateTickSpacing();
|
|
}
|
|
|
|
this->RecalculatePlotTransforms();
|
|
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.SelectPolygon())
|
|
{
|
|
if(this->SelectionPolygon.GetNumberOfPoints() > 0)
|
|
{
|
|
vtkVector2f lastPoint =
|
|
this->SelectionPolygon.GetPoint(
|
|
this->SelectionPolygon.GetNumberOfPoints() - 1);
|
|
|
|
if((lastPoint - mouse.GetPos()).SquaredNorm() > 100)
|
|
{
|
|
this->SelectionPolygon.AddPoint(mouse.GetPos());
|
|
}
|
|
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
}
|
|
else if (mouse.GetButton() == vtkContextMouseEvent::NO_BUTTON)
|
|
{
|
|
this->Scene->SetDirty(true);
|
|
|
|
if(this->Tooltip)
|
|
{
|
|
this->Tooltip->SetVisible(this->LocatePointInPlots(mouse));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int vtkChartXY::LocatePointInPlot(const vtkVector2f &position,
|
|
const vtkVector2f &tolerance,
|
|
vtkVector2f &plotPos,
|
|
vtkPlot *plot,
|
|
vtkIdType &segmentIndex)
|
|
{
|
|
if (plot && plot->GetVisible())
|
|
{
|
|
vtkPlotBar* plotBar = vtkPlotBar::SafeDownCast(plot);
|
|
if (plotBar)
|
|
{
|
|
// If the plot is a vtkPlotBar, get the segment index too
|
|
return plotBar->GetNearestPoint(position, tolerance,
|
|
&plotPos, &segmentIndex);
|
|
}
|
|
else
|
|
{
|
|
return plot->GetNearestPoint(position, tolerance, &plotPos);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::LocatePointInPlots(const vtkContextMouseEvent &mouse,
|
|
int invokeEvent)
|
|
{
|
|
size_t n = this->ChartPrivate->plots.size();
|
|
vtkVector2i pos(mouse.GetScreenPos());
|
|
if (pos[0] > this->Point1[0] &&
|
|
pos[0] < this->Point2[0] &&
|
|
pos[1] > this->Point1[1] &&
|
|
pos[1] < this->Point2[1] && n)
|
|
{
|
|
// Iterate through each corner, and check for a nearby point
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
|
|
->GetNumberOfItems());
|
|
if (items)
|
|
{
|
|
vtkVector2f plotPos, position;
|
|
vtkTransform2D* transform =
|
|
this->ChartPrivate->PlotCorners[i]->GetTransform();
|
|
transform->InverseTransformPoints(mouse.GetPos().GetData(),
|
|
position.GetData(), 1);
|
|
// Use a tolerance of +/- 5 pixels
|
|
vtkVector2f tolerance(
|
|
std::fabs(5*(1.0/transform->GetMatrix()->GetElement(0, 0))),
|
|
std::fabs(5*(1.0/transform->GetMatrix()->GetElement(1, 1))));
|
|
// Iterate through the visible plots and return on the first hit
|
|
vtkIdType segmentIndex = -1;
|
|
|
|
for (int j = items-1; j >= 0; --j)
|
|
{
|
|
vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
|
|
PlotCorners[i]->GetItem(j));
|
|
int seriesIndex = LocatePointInPlot(position, tolerance, plotPos,
|
|
plot, segmentIndex);
|
|
if (seriesIndex >= 0)
|
|
{
|
|
// We found a point, set up the tooltip and return
|
|
vtkRectd ss(plot->GetShiftScale());
|
|
vtkVector2d plotPosd(plotPos[0] / ss[2] - ss[0],
|
|
plotPos[1] / ss[3] - ss[1]);
|
|
this->SetTooltipInfo(mouse, plotPosd, seriesIndex, plot,
|
|
segmentIndex);
|
|
if (invokeEvent >= 0)
|
|
{
|
|
vtkChartPlotData plotIndex;
|
|
plotIndex.SeriesName = plot->GetLabel();
|
|
plotIndex.Position = plotPos;
|
|
plotIndex.ScreenPosition = mouse.GetScreenPos();
|
|
plotIndex.Index = seriesIndex;
|
|
// Invoke an event, with the client data supplied
|
|
this->InvokeEvent(invokeEvent, static_cast<void*>(&plotIndex));
|
|
|
|
if (invokeEvent == vtkCommand::SelectionChangedEvent)
|
|
{
|
|
// Construct a new selection with the selected point in it.
|
|
vtkNew<vtkIdTypeArray> selectionIds;
|
|
selectionIds->InsertNextValue(seriesIndex);
|
|
plot->SetSelection(selectionIds.GetPointer());
|
|
|
|
if (this->AnnotationLink)
|
|
{
|
|
vtkChartSelectionHelper::MakeSelection(this->AnnotationLink,
|
|
selectionIds.GetPointer(),
|
|
plot);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::SetTooltipInfo(const vtkContextMouseEvent& mouse,
|
|
const vtkVector2d &plotPos,
|
|
vtkIdType seriesIndex, vtkPlot* plot,
|
|
vtkIdType segmentIndex)
|
|
{
|
|
if (!this->Tooltip)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Have the plot generate its tooltip label
|
|
vtkStdString tooltipLabel = plot->GetTooltipLabel(plotPos, seriesIndex,
|
|
segmentIndex);
|
|
|
|
// Set the tooltip
|
|
this->Tooltip->SetText(tooltipLabel);
|
|
this->Tooltip->SetPosition(mouse.GetScreenPos()[0] + 2,
|
|
mouse.GetScreenPos()[1] + 2);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseLeaveEvent(const vtkContextMouseEvent &)
|
|
{
|
|
this->DrawNearestPoint = false;
|
|
|
|
if(this->Tooltip)
|
|
{
|
|
this->Tooltip->SetVisible(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseButtonPressEvent(const vtkContextMouseEvent &mouse)
|
|
{
|
|
if(this->Tooltip)
|
|
{
|
|
this->Tooltip->SetVisible(false);
|
|
}
|
|
|
|
// Iterate through each corner, and check for a nearby point
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
if (this->ChartPrivate->PlotCorners[i]->MouseButtonPressEvent(mouse))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (mouse.GetButton() == this->Actions.Pan())
|
|
{
|
|
// The mouse panning action.
|
|
this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
|
|
this->DrawBox = false;
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.Zoom() ||
|
|
mouse.GetButton() == this->Actions.Select())
|
|
{
|
|
// Selection, for now at least...
|
|
this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
|
|
this->DrawBox = true;
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.ZoomAxis())
|
|
{
|
|
this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
|
|
this->DrawBox = false;
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.SelectPolygon())
|
|
{
|
|
this->SelectionPolygon.Clear();
|
|
this->SelectionPolygon.AddPoint(mouse.GetPos());
|
|
this->DrawSelectionPolygon = true;
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->ActionsClick.Select() ||
|
|
mouse.GetButton() == this->ActionsClick.Notify())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseButtonReleaseEvent(const vtkContextMouseEvent &mouse)
|
|
{
|
|
// Iterate through each corner, and check for a nearby point
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
if (this->ChartPrivate->PlotCorners[i]->MouseButtonReleaseEvent(mouse))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (mouse.GetButton() > vtkContextMouseEvent::NO_BUTTON &&
|
|
mouse.GetButton() <= vtkContextMouseEvent::RIGHT_BUTTON)
|
|
{
|
|
this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
|
|
this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
|
|
if ((fabs(this->MouseBox.GetWidth()) < 0.5 && fabs(this->MouseBox.GetHeight()) < 0.5)
|
|
&& (mouse.GetButton() == this->Actions.Select() ||
|
|
mouse.GetButton() == this->Actions.Pan()))
|
|
{
|
|
// Invalid box size - treat as a single clicke event
|
|
this->MouseBox.SetWidth(0.0);
|
|
this->MouseBox.SetHeight(0.0);
|
|
this->DrawBox = false;
|
|
if (mouse.GetButton() == this->ActionsClick.Notify())
|
|
{
|
|
this->LocatePointInPlots(mouse, vtkCommand::InteractionEvent);
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->ActionsClick.Select())
|
|
{
|
|
this->LocatePointInPlots(mouse, vtkCommand::SelectionChangedEvent);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (mouse.GetButton() == this->Actions.Select() ||
|
|
mouse.GetButton() == this->Actions.SelectPolygon())
|
|
{
|
|
// Modifiers or selection modes can affect how selection is performed.
|
|
int selectionMode =
|
|
vtkChartSelectionHelper::GetMouseSelectionMode(mouse,
|
|
this->SelectionMode);
|
|
bool polygonMode(mouse.GetButton() == this->Actions.SelectPolygon());
|
|
this->Scene->SetDirty(true);
|
|
|
|
// Update the polygon or box with the last mouse position.
|
|
if (polygonMode)
|
|
{
|
|
this->SelectionPolygon.AddPoint(mouse.GetPos());
|
|
this->DrawSelectionPolygon = false;
|
|
}
|
|
else
|
|
{
|
|
this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
|
|
this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
|
|
this->DrawBox = false;
|
|
}
|
|
|
|
// Check whether we have a valid selection area, exit early if not.
|
|
if (polygonMode && this->SelectionPolygon.GetNumberOfPoints() < 3)
|
|
{
|
|
// There is no polygon to select points in.
|
|
this->SelectionPolygon.Clear();
|
|
return true;
|
|
}
|
|
else if (fabs(this->MouseBox.GetWidth()) < 0.5 ||
|
|
fabs(this->MouseBox.GetHeight()) < 0.5)
|
|
{
|
|
// The box is too small, and no useful selection can be made.
|
|
this->MouseBox.SetWidth(0.0);
|
|
this->MouseBox.SetHeight(0.0);
|
|
return true;
|
|
}
|
|
|
|
// Iterate through the plots and build a selection. Two main behaviors are
|
|
// supported - row-based selections add all rows from all plots and set that
|
|
// as the selection, plot-based selections create a selection node for each
|
|
// plot.
|
|
vtkNew<vtkIdTypeArray> oldSelection;
|
|
vtkNew<vtkIdTypeArray> accumulateSelection;
|
|
if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
|
|
{
|
|
// There is only one global selection, we build up a union of all rows
|
|
// selected in all charts and set that on all plots.
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
|
|
->GetNumberOfItems());
|
|
if (items)
|
|
{
|
|
vtkTransform2D *transform =
|
|
this->ChartPrivate->PlotCorners[i]->GetTransform();
|
|
vtkVector2f min;
|
|
vtkVector2f max;
|
|
vtkContextPolygon polygon;
|
|
this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
|
|
min, max, polygon);
|
|
|
|
// Iterate through the plots and create the selection.
|
|
for (int j = 0; j < items; ++j)
|
|
{
|
|
vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
|
|
PlotCorners[i]->GetItem(j));
|
|
if (plot && plot->GetVisible() && plot->GetSelectable())
|
|
{
|
|
// There is only really one old selection in this mode.
|
|
if (i == 0 && j == 0)
|
|
{
|
|
oldSelection->DeepCopy(plot->GetSelection());
|
|
}
|
|
// Populate the selection using the appropriate shape.
|
|
if (polygonMode)
|
|
{
|
|
plot->SelectPointsInPolygon(polygon);
|
|
}
|
|
else
|
|
{
|
|
plot->SelectPoints(min, max);
|
|
}
|
|
|
|
// Accumulate the selection in each plot.
|
|
vtkChartSelectionHelper::BuildSelection(0,
|
|
vtkContextScene::SELECTION_ADDITION,
|
|
accumulateSelection.GetPointer(),
|
|
plot->GetSelection(),
|
|
0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Now add the accumulated selection to the old selection.
|
|
vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
|
|
selectionMode,
|
|
accumulateSelection.GetPointer(),
|
|
oldSelection.GetPointer(),
|
|
0);
|
|
}
|
|
else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
|
|
{
|
|
// We are performing plot based selections.
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
|
|
->GetNumberOfItems());
|
|
if (items)
|
|
{
|
|
vtkTransform2D *transform =
|
|
this->ChartPrivate->PlotCorners[i]->GetTransform();
|
|
vtkVector2f min;
|
|
vtkVector2f max;
|
|
vtkContextPolygon polygon;
|
|
this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
|
|
min, max, polygon);
|
|
|
|
for (int j = 0; j < items; ++j)
|
|
{
|
|
vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
|
|
PlotCorners[i]->GetItem(j));
|
|
if (plot && plot->GetVisible() && plot->GetSelectable())
|
|
{
|
|
oldSelection->DeepCopy(plot->GetSelection());
|
|
// Populate the selection using the appropriate shape.
|
|
if (polygonMode)
|
|
{
|
|
plot->SelectPointsInPolygon(polygon);
|
|
}
|
|
else
|
|
{
|
|
plot->SelectPoints(min, max);
|
|
}
|
|
|
|
// Combine the selection in this plot with any previous selection.
|
|
vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
|
|
selectionMode,
|
|
plot->GetSelection(),
|
|
oldSelection.GetPointer(),
|
|
plot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
|
|
{
|
|
if (this->AnnotationLink)
|
|
{
|
|
this->AnnotationLink->Update();
|
|
vtkSelection *selection =
|
|
vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
|
|
vtkSelectionNode *node = selection->GetNumberOfNodes() > 0?
|
|
selection->GetNode(0) : NULL;
|
|
if (node)
|
|
{
|
|
oldSelection->DeepCopy(vtkIdTypeArray::SafeDownCast(node->GetSelectionList()));
|
|
}
|
|
}
|
|
vtkNew<vtkIdTypeArray> plotSelection;
|
|
// We are performing plot based selections.
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
|
|
->GetNumberOfItems());
|
|
if (items)
|
|
{
|
|
vtkTransform2D *transform =
|
|
this->ChartPrivate->PlotCorners[i]->GetTransform();
|
|
vtkVector2f min;
|
|
vtkVector2f max;
|
|
vtkContextPolygon polygon;
|
|
this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
|
|
min, max, polygon);
|
|
|
|
for (int j = 0; j < items; ++j)
|
|
{
|
|
vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
|
|
PlotCorners[i]->GetItem(j));
|
|
if (plot && plot->GetVisible() && plot->GetSelectable())
|
|
{
|
|
bool selected = false;
|
|
// Populate the selection using the appropriate shape.
|
|
if (polygonMode)
|
|
{
|
|
selected = plot->SelectPointsInPolygon(polygon);
|
|
}
|
|
else
|
|
{
|
|
selected = plot->SelectPoints(min, max);
|
|
}
|
|
vtkNew<vtkIdTypeArray> plotsSelection;
|
|
if (selected)
|
|
{
|
|
int idx = 1; // y
|
|
vtkAbstractArray* column = plot->GetData()->GetInputAbstractArrayToProcess(
|
|
idx, plot->GetInput());
|
|
int columnID = -1;
|
|
plot->GetInput()->GetRowData()->GetAbstractArray(column->GetName(), columnID);
|
|
if (plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
|
|
{
|
|
plotSelection->SetNumberOfTuples(0);
|
|
for (vtkIdType k = 0; k < column->GetNumberOfTuples(); ++k)
|
|
{
|
|
plotSelection->InsertNextValue(k);
|
|
}
|
|
}
|
|
plot->SetSelection(plotSelection.GetPointer());
|
|
accumulateSelection->InsertNextValue(columnID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vtkIdType* ptrSelection =
|
|
reinterpret_cast<vtkIdType *>(accumulateSelection->GetVoidPointer(0));
|
|
std::sort(ptrSelection, ptrSelection + accumulateSelection->GetNumberOfTuples());
|
|
// Now add the accumulated selection to the old selection
|
|
vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
|
|
selectionMode,
|
|
accumulateSelection.GetPointer(),
|
|
oldSelection.GetPointer(),
|
|
0);
|
|
}
|
|
this->InvokeEvent(vtkCommand::SelectionChangedEvent);
|
|
this->MouseBox.SetWidth(0.0);
|
|
this->MouseBox.SetHeight(0.0);
|
|
this->SelectionPolygon.Clear();
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.Zoom())
|
|
{
|
|
// Check whether a valid zoom box was drawn
|
|
if (fabs(this->MouseBox.GetWidth()) < 0.5 || fabs(this->MouseBox.GetHeight()) < 0.5)
|
|
{
|
|
// Invalid box size - do nothing
|
|
this->MouseBox.SetWidth(0.0);
|
|
this->MouseBox.SetHeight(0.0);
|
|
this->DrawBox = false;
|
|
return true;
|
|
}
|
|
|
|
// Zoom into the chart by the specified amount, and recalculate the bounds
|
|
vtkVector2f point2(mouse.GetPos());
|
|
|
|
this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::BOTTOM],
|
|
this->ChartPrivate->axes[vtkAxis::LEFT],
|
|
this->MouseBox.GetData(), point2.GetData());
|
|
this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::TOP],
|
|
this->ChartPrivate->axes[vtkAxis::RIGHT],
|
|
this->MouseBox.GetData(), point2.GetData());
|
|
|
|
this->RecalculatePlotTransforms();
|
|
this->MouseBox.SetWidth(0.0);
|
|
this->MouseBox.SetHeight(0.0);
|
|
this->DrawBox = false;
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
this->InvokeEvent(vtkCommand::InteractionEvent);
|
|
return true;
|
|
}
|
|
else if (mouse.GetButton() == this->Actions.ZoomAxis())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void vtkChartXY::ZoomInAxes(vtkAxis *x, vtkAxis *y, float *originf, float *maxf)
|
|
{
|
|
vtkNew<vtkTransform2D> transform;
|
|
this->CalculateUnscaledPlotTransform(x, y, transform.GetPointer());
|
|
vtkVector2d origin(originf[0], originf[1]);
|
|
vtkVector2d max(maxf[0], maxf[1]);
|
|
vtkVector2d torigin;
|
|
transform->InverseTransformPoints(origin.GetData(), torigin.GetData(), 1);
|
|
vtkVector2d tmax;
|
|
transform->InverseTransformPoints(max.GetData(), tmax.GetData(), 1);
|
|
|
|
// Ensure we preserve the directionality of the axes
|
|
if (x->GetMaximum() > x->GetMinimum())
|
|
{
|
|
x->SetMaximum(torigin[0] > tmax[0] ? torigin[0] : tmax[0]);
|
|
x->SetMinimum(torigin[0] < tmax[0] ? torigin[0] : tmax[0]);
|
|
}
|
|
else
|
|
{
|
|
x->SetMaximum(torigin[0] < tmax[0] ? torigin[0] : tmax[0]);
|
|
x->SetMinimum(torigin[0] > tmax[0] ? torigin[0] : tmax[0]);
|
|
}
|
|
if (y->GetMaximum() > y->GetMinimum())
|
|
{
|
|
y->SetMaximum(torigin[1] > tmax[1] ? torigin[1] : tmax[1]);
|
|
y->SetMinimum(torigin[1] < tmax[1] ? torigin[1] : tmax[1]);
|
|
}
|
|
else
|
|
{
|
|
y->SetMaximum(torigin[1] < tmax[1] ? torigin[1] : tmax[1]);
|
|
y->SetMinimum(torigin[1] > tmax[1] ? torigin[1] : tmax[1]);
|
|
}
|
|
x->RecalculateTickSpacing();
|
|
y->RecalculateTickSpacing();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::MouseWheelEvent(const vtkContextMouseEvent &, int delta)
|
|
{
|
|
if(this->Tooltip)
|
|
{
|
|
this->Tooltip->SetVisible(false);
|
|
}
|
|
if (!this->ZoomWithMouseWheel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get the bounds of each plot.
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
vtkAxis *axis = this->ChartPrivate->axes[i];
|
|
double min = axis->GetMinimum();
|
|
double max = axis->GetMaximum();
|
|
double frac = (max - min) * 0.1;
|
|
if (frac > 0.0)
|
|
{
|
|
min += delta*frac;
|
|
max -= delta*frac;
|
|
}
|
|
else
|
|
{
|
|
min -= delta*frac;
|
|
max += delta*frac;
|
|
}
|
|
axis->SetMinimum(min);
|
|
axis->SetMaximum(max);
|
|
axis->RecalculateTickSpacing();
|
|
}
|
|
|
|
this->RecalculatePlotTransforms();
|
|
|
|
// Mark the scene as dirty
|
|
this->Scene->SetDirty(true);
|
|
|
|
this->InvokeEvent(vtkCommand::InteractionEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::KeyPressEvent(const vtkContextKeyEvent &key)
|
|
{
|
|
switch (key.GetKeyCode())
|
|
{
|
|
// Reset the chart axes
|
|
case 'r':
|
|
case 'R':
|
|
this->RecalculateBounds();
|
|
this->Scene->SetDirty(true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool vtkChartXY::RemovePlotFromCorners(vtkPlot *plot)
|
|
{
|
|
// We know the plot will only ever be in one of the corners
|
|
for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
|
|
{
|
|
if (this->ChartPrivate->PlotCorners[i]->RemoveItem(plot))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
inline void vtkChartXY::TransformBoxOrPolygon(bool polygonMode,
|
|
vtkTransform2D *transform,
|
|
const vtkVector2f &mousePosition,
|
|
vtkVector2f &min, vtkVector2f &max,
|
|
vtkContextPolygon &polygon)
|
|
{
|
|
if (polygonMode)
|
|
{
|
|
vtkNew<vtkTransform2D> inverseTransform;
|
|
inverseTransform->SetMatrix(transform->GetMatrix());
|
|
inverseTransform->Inverse();
|
|
polygon =
|
|
this->SelectionPolygon.Transformed(inverseTransform.GetPointer());
|
|
}
|
|
else
|
|
{
|
|
transform->InverseTransformPoints(this->MouseBox.GetData(),
|
|
min.GetData(), 1);
|
|
transform->InverseTransformPoints(mousePosition.GetData(),
|
|
max.GetData(), 1);
|
|
// Normalize the rectangle selection area before using it.
|
|
if (min.GetX() > max.GetX())
|
|
{
|
|
float tmp = min.GetX();
|
|
min.SetX(max.GetX());
|
|
max.SetX(tmp);
|
|
}
|
|
if (min.GetY() > max.GetY())
|
|
{
|
|
float tmp = min.GetY();
|
|
min.SetY(max.GetY());
|
|
max.SetY(tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void vtkChartXY::PrintSelf(ostream &os, vtkIndent indent)
|
|
{
|
|
this->Superclass::PrintSelf(os, indent);
|
|
os << indent << "Axes: " << endl;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
this->ChartPrivate->axes[i]->PrintSelf(os, indent.GetNextIndent());
|
|
}
|
|
if (this->ChartPrivate)
|
|
{
|
|
os << indent << "Number of plots: " << this->ChartPrivate->plots.size()
|
|
<< endl;
|
|
for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
|
|
{
|
|
os << indent << "Plot " << i << ":" << endl;
|
|
this->ChartPrivate->plots[i]->PrintSelf(os, indent.GetNextIndent());
|
|
}
|
|
}
|
|
os << indent << "ZoomWithMouseWheel: " << this->ZoomWithMouseWheel << endl;
|
|
|
|
}
|