/*========================================================================= 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 "vtkBrush.h" #include "vtkChartSelectionHelper.h" #include "vtkColorSeries.h" #include "vtkContext2D.h" #include "vtkPen.h" #include "vtkContextClip.h" #include "vtkContextKeyEvent.h" #include "vtkContextMouseEvent.h" #include "vtkContextScene.h" #include "vtkContextTransform.h" #include "vtkMath.h" #include "vtkPoints2D.h" #include "vtkTransform2D.h" #include "vtkVector.h" #include "vtkVectorOperators.h" #include "vtkContextMapper2D.h" #include "vtkPlotArea.h" #include "vtkPlotBag.h" #include "vtkPlotBar.h" #include "vtkPlotFunctionalBag.h" #include "vtkPlotLine.h" #include "vtkPlotPoints.h" #include "vtkPlotStacked.h" #include "vtkAxis.h" #include "vtkChartLegend.h" #include "vtkPlotGrid.h" #include "vtkTooltipItem.h" #include "vtkDataSetAttributes.h" #include "vtkIdTypeArray.h" #include "vtkTable.h" #include "vtkAnnotationLink.h" #include "vtkSelection.h" #include "vtkSelectionNode.h" #include "vtkSmartPointer.h" #include "vtkCommand.h" #include "vtkObjectFactory.h" #include "vtkStdString.h" #include "vtkTextProperty.h" #include "vtkDataArray.h" #include "vtkStringArray.h" // My STL containers #include #include //----------------------------------------------------------------------------- class vtkChartXYPrivate { public: vtkChartXYPrivate() { this->Colors = vtkSmartPointer::New(); this->Clip = vtkSmartPointer::New(); this->Borders[0] = 60; this->Borders[1] = 50; this->Borders[2] = 20; this->Borders[3] = 20; } vtkPlot* GetPlotByColumn(vtkIdType columnId) { std::vector::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 nullptr; } std::vector plots; // Charts can contain multiple plots of data std::vector PlotCorners; // Stored by corner... std::vector axes; // Charts can contain multiple axes vtkSmartPointer Colors; // Colors in the chart vtkSmartPointer 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 corner = vtkSmartPointer::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::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::New(); this->Tooltip->SetVisible(false); this->AddItem(this->Tooltip); this->ForceAxesToBounds = false; this->ZoomWithMouseWheel = true; this->AdjustLowerBoundForLogPlot = false; this->DragPoint = false; this->DragPointAlongX = true; this->DragPointAlongY = 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 = nullptr; } //----------------------------------------------------------------------------- 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) : nullptr; vtkIdTypeArray* idArray = node ? vtkArrayDownCast(node->GetSelectionList()) : nullptr; std::vector::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 = vtkArrayDownCast(node->GetSelectionList()); vtkPlot* selectionPlot = vtkPlot::SafeDownCast(node->GetProperties()->Get(vtkSelectionNode::PROP())); // Now iterate through the plots to update selection data std::vector::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 selectedPlots; for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i) { vtkSelectionNode* node = selection->GetNode(i); vtkIdTypeArray* selectedColumns = vtkArrayDownCast(node->GetSelectionList()); vtkIdType* ptr = reinterpret_cast(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::iterator it = this->ChartPrivate->plots.begin(); for (; it != this->ChartPrivate->plots.end(); ++it) { vtkPlot* plot = *it; vtkIdTypeArray* plotSelection = nullptr; 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 visibilities(false); for (int i = 0; i < static_cast(this->ChartPrivate->PlotCorners.size()); ++i) { int visible = 0; for (vtkIdType 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->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(); this->UpdateLayout(painter); // Axes may have changed during updateLayout if (this->MTime < this->ChartPrivate->axes[0]->GetMTime()) { // Cause the plot transform to be recalculated if necessary recalculateTransform = true; } // 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(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 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.empty()) { // 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(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(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 = nullptr; vtkAxis* yAxis = nullptr; // 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 (vtkIdType 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(this->ChartPrivate->PlotCorners.size() - 1) < corner) { vtkNew transform; this->ChartPrivate->PlotCorners.push_back(transform); this->ChartPrivate->Clip->AddItem(transform); // 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::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 = nullptr; 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->AdjustLowerBoundForLogPlot && axis->GetLogScale() && range[0] <= 0.) { if (range[1] <= 0.) { // All of the data is negative, so we arbitrarily set the axis range to // be positive and show no data range[1] = 1.; } // The minimum value is set to either 4 decades below the max or to 1, // regardless of the true minimum value (which is less than 0) range[0] = (range[1] < 1.e4 ? range[1] / 1.e4 : 1.); } 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(); } //----------------------------------------------------------------------------- void vtkChartXY::ReleasePlotSelections() { std::vector::iterator it = this->ChartPrivate->plots.begin(); for (; it != this->ChartPrivate->plots.end(); ++it) { vtkPlot* plot = *it; if (!plot) { continue; } vtkNew emptySelectionArray; emptySelectionArray->Initialize(); plot->SetSelection(emptySelectionArray); } } //----------------------------------------------------------------------------- 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->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(rect.GetWidth()), static_cast(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 nullptr), 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 = nullptr; vtkColor3ub color = this->ChartPrivate->Colors->GetColorRepeating( static_cast(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->GetPen()->SetColor(color.GetData()); 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 = nullptr; } if (plot) { this->AddPlot(plot); plot->Delete(); } return plot; } //----------------------------------------------------------------------------- vtkIdType vtkChartXY::AddPlot(vtkPlot* plot) { if (plot == nullptr) { return -1; } plot->Register(this); this->ChartPrivate->plots.push_back(plot); vtkIdType plotIndex = static_cast(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(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(this->ChartPrivate->plots.size()) > index) { return this->ChartPrivate->plots[index]; } else { return nullptr; } } //----------------------------------------------------------------------------- vtkIdType vtkChartXY::GetPlotIndex(vtkPlot* plot) { int corner = this->GetPlotCorner(plot); return corner >= 0 && corner < 4 ? this->ChartPrivate->PlotCorners[corner]->GetItemIndex(plot) : static_cast(-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 static_cast(this->ChartPrivate->plots.size()); } //----------------------------------------------------------------------------- vtkAxis* vtkChartXY::GetAxis(int axisIndex) { if (axisIndex < 4) { return this->ChartPrivate->axes[axisIndex]; } else { return nullptr; } } //----------------------------------------------------------------------------- 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::iterator it = this->ChartPrivate->plots.begin(); for (; it != this->ChartPrivate->plots.end(); ++it) { (*it)->SetSelection(nullptr); } } 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().GetData()); vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast().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().GetData()); lastScreenPos = vtkVector2d(mouse.GetLastScreenPos().Cast().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().GetData()); lastScreenPos = vtkVector2d(mouse.GetLastScreenPos().Cast().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().GetData()); vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast().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() == this->Actions.ClickAndDrag() && this->DragPoint && (this->DragPointAlongX || this->DragPointAlongY)) { // Iterate through each corner, and check for a nearby point std::vector::iterator it = this->ChartPrivate->PlotCorners.begin(); for (; it != this->ChartPrivate->PlotCorners.end(); ++it) { vtkContextTransform* plotCorner = *it; if (!plotCorner) { continue; } int items = static_cast(plotCorner->GetNumberOfItems()); if (items == 0) { continue; } vtkVector2f position; vtkTransform2D* transform = plotCorner->GetTransform(); transform->InverseTransformPoints(mouse.GetPos().GetData(), position.GetData(), 1); for (int j = 0; j < items; ++j) { vtkPlot* plot = vtkPlot::SafeDownCast(plotCorner->GetItem(j)); if (!plot || plot->IsA("vtkPlotBar")) { continue; } vtkIdTypeArray* selectionArray = plot->GetSelection(); if (!selectionArray || selectionArray->GetNumberOfValues() < 1) { continue; } if (selectionArray->GetNumberOfValues() > 1) { vtkDebugMacro("Move event (Click and Drag) found more than one point to update."); } vtkIdType index = selectionArray->GetValue(0); if (this->DragPointAlongX) { vtkDataArray* xArray = plot->GetData()->GetInputArrayToProcess(0, plot->GetInput()); xArray->SetVariantValue(index, position.GetX()); } if (this->DragPointAlongY) { vtkDataArray* yArray = plot->GetData()->GetInputArrayToProcess(1, plot->GetInput()); yArray->SetVariantValue(index, position.GetY()); } plot->GetSelection()->Modified(); plot->GetInput()->Modified(); 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(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(&plotIndex)); if (invokeEvent == vtkCommand::SelectionChangedEvent) { // Construct a new selection with the selected point in it. vtkNew selectionIds; selectionIds->InsertNextValue(seriesIndex); plot->SetSelection(selectionIds); if (this->AnnotationLink) { vtkChartSelectionHelper::MakeSelection( this->AnnotationLink, selectionIds, 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->Actions.ClickAndDrag()) { this->ReleasePlotSelections(); this->DragPoint = this->LocatePointInPlots(mouse, vtkCommand::SelectionChangedEvent); this->InvokeEvent(vtkCommand::SelectionChangedEvent); 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 oldSelection; vtkNew 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(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(nullptr, vtkContextScene::SELECTION_ADDITION, accumulateSelection, plot->GetSelection(), nullptr); } } } } // Now add the accumulated selection to the old selection. vtkChartSelectionHelper::BuildSelection(this->AnnotationLink, selectionMode, accumulateSelection, oldSelection, nullptr); } 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(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, 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) : nullptr; if (node) { oldSelection->DeepCopy(vtkArrayDownCast(node->GetSelectionList())); } } vtkNew plotSelection; // We are performing plot based selections. for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i) { int items = static_cast(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 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); accumulateSelection->InsertNextValue(columnID); } } } } } vtkIdType* ptrSelection = reinterpret_cast(accumulateSelection->GetVoidPointer(0)); std::sort(ptrSelection, ptrSelection + accumulateSelection->GetNumberOfTuples()); // Now add the accumulated selection to the old selection vtkChartSelectionHelper::BuildSelection(this->AnnotationLink, selectionMode, accumulateSelection, oldSelection, nullptr); } 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; } else if (mouse.GetButton() == this->Actions.ClickAndDrag()) { this->ReleasePlotSelections(); this->InvokeEvent(vtkCommand::SelectionChangedEvent); this->DragPoint = false; return true; } return false; } void vtkChartXY::ZoomInAxes(vtkAxis* x, vtkAxis* y, float* originf, float* maxf) { vtkNew transform; this->CalculateUnscaledPlotTransform(x, y, transform); 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 inverseTransform; inverseTransform->SetMatrix(transform->GetMatrix()); inverseTransform->Inverse(); polygon = this->SelectionPolygon.Transformed(inverseTransform); } 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; }