1 /*=========================================================================
2
3 Program: Visualization Toolkit
4 Module: vtkChartXY.cxx
5
6 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7 All rights reserved.
8 See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9
10 This software is distributed WITHOUT ANY WARRANTY; without even
11 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 PURPOSE. See the above copyright notice for more information.
13
14 =========================================================================*/
15
16 #include "vtkChartXY.h"
17
18 #include "vtkBrush.h"
19 #include "vtkChartSelectionHelper.h"
20 #include "vtkColorSeries.h"
21 #include "vtkContext2D.h"
22 #include "vtkPen.h"
23
24 #include "vtkContextClip.h"
25 #include "vtkContextKeyEvent.h"
26 #include "vtkContextMouseEvent.h"
27 #include "vtkContextScene.h"
28 #include "vtkContextTransform.h"
29 #include "vtkMath.h"
30 #include "vtkPoints2D.h"
31 #include "vtkTransform2D.h"
32 #include "vtkVector.h"
33 #include "vtkVectorOperators.h"
34
35 #include "vtkContextMapper2D.h"
36 #include "vtkPlotArea.h"
37 #include "vtkPlotBag.h"
38 #include "vtkPlotBar.h"
39 #include "vtkPlotFunctionalBag.h"
40 #include "vtkPlotLine.h"
41 #include "vtkPlotPoints.h"
42 #include "vtkPlotStacked.h"
43
44 #include "vtkAxis.h"
45 #include "vtkChartLegend.h"
46 #include "vtkPlotGrid.h"
47 #include "vtkTooltipItem.h"
48
49 #include "vtkDataSetAttributes.h"
50 #include "vtkIdTypeArray.h"
51 #include "vtkTable.h"
52
53 #include "vtkAnnotationLink.h"
54 #include "vtkSelection.h"
55 #include "vtkSelectionNode.h"
56 #include "vtkSmartPointer.h"
57
58 #include "vtkCommand.h"
59 #include "vtkObjectFactory.h"
60
61 #include "vtkStdString.h"
62 #include "vtkTextProperty.h"
63
64 #include "vtkDataArray.h"
65 #include "vtkStringArray.h"
66
67 // My STL containers
68 #include <algorithm>
69 #include <vector>
70
71 //-----------------------------------------------------------------------------
72 class vtkChartXYPrivate
73 {
74 public:
vtkChartXYPrivate()75 vtkChartXYPrivate()
76 {
77 this->Colors = vtkSmartPointer<vtkColorSeries>::New();
78 this->Clip = vtkSmartPointer<vtkContextClip>::New();
79 this->Borders[0] = 60;
80 this->Borders[1] = 50;
81 this->Borders[2] = 20;
82 this->Borders[3] = 20;
83 }
GetPlotByColumn(vtkIdType columnId)84 vtkPlot* GetPlotByColumn(vtkIdType columnId)
85 {
86 std::vector<vtkPlot*>::iterator it = this->plots.begin();
87 for (; it != this->plots.end(); ++it)
88 {
89 vtkPlot* plot = *it;
90 vtkTable* table = plot->GetInput();
91 const int idx = 1; // column
92 if (table &&
93 table->GetColumn(columnId) == plot->GetData()->GetInputAbstractArrayToProcess(idx, table))
94 {
95 return plot;
96 }
97 }
98 return nullptr;
99 }
100
101 std::vector<vtkPlot*> plots; // Charts can contain multiple plots of data
102 std::vector<vtkContextTransform*> PlotCorners; // Stored by corner...
103 std::vector<vtkAxis*> axes; // Charts can contain multiple axes
104 vtkSmartPointer<vtkColorSeries> Colors; // Colors in the chart
105 vtkSmartPointer<vtkContextClip> Clip; // Colors in the chart
106 int Borders[4];
107 vtkTimeStamp TransformCalculatedTime;
108 };
109
110 //-----------------------------------------------------------------------------
111 vtkStandardNewMacro(vtkChartXY);
112
113 //-----------------------------------------------------------------------------
vtkChartXY()114 vtkChartXY::vtkChartXY()
115 {
116 this->ChartPrivate = new vtkChartXYPrivate;
117
118 this->AutoAxes = true;
119 this->HiddenAxisBorder = 20;
120
121 // The plots are drawn in a clipped, transformed area.
122 this->AddItem(this->ChartPrivate->Clip);
123
124 // The grid is drawn first in this clipped, transformed area.
125 vtkPlotGrid* grid1 = vtkPlotGrid::New();
126 this->ChartPrivate->Clip->AddItem(grid1);
127 grid1->Delete();
128
129 // The second grid for the far side/top axis
130 vtkPlotGrid* grid2 = vtkPlotGrid::New();
131 this->ChartPrivate->Clip->AddItem(grid2);
132 grid2->Delete();
133
134 // Set up the bottom-left transform, the rest are often not required (set up
135 // on demand if used later). Add it as a child item, rendered automatically.
136 vtkSmartPointer<vtkContextTransform> corner = vtkSmartPointer<vtkContextTransform>::New();
137 this->ChartPrivate->PlotCorners.push_back(corner);
138 this->ChartPrivate->Clip->AddItem(corner); // Child list maintains ownership.
139
140 // Next is the axes
141 for (int i = 0; i < 4; ++i)
142 {
143 this->ChartPrivate->axes.push_back(vtkAxis::New());
144 // By default just show the left and bottom axes
145 this->ChartPrivate->axes.back()->SetVisible(i < 2 ? true : false);
146 this->AttachAxisRangeListener(this->ChartPrivate->axes.back());
147 this->AddItem(this->ChartPrivate->axes.back());
148 }
149 this->ChartPrivate->axes[vtkAxis::LEFT]->SetPosition(vtkAxis::LEFT);
150 this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetPosition(vtkAxis::BOTTOM);
151 this->ChartPrivate->axes[vtkAxis::RIGHT]->SetPosition(vtkAxis::RIGHT);
152 this->ChartPrivate->axes[vtkAxis::TOP]->SetPosition(vtkAxis::TOP);
153
154 // Set up the x and y axes - should be configured based on data
155 this->ChartPrivate->axes[vtkAxis::LEFT]->SetTitle("Y Axis");
156 this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetTitle("X Axis");
157
158 grid1->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
159 grid1->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
160 grid2->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
161 grid2->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
162
163 // Then the legend is drawn
164 this->Legend = vtkSmartPointer<vtkChartLegend>::New();
165 this->Legend->SetChart(this);
166 this->Legend->SetVisible(false);
167 this->AddItem(this->Legend);
168
169 this->PlotTransformValid = false;
170
171 this->DrawBox = false;
172 this->DrawSelectionPolygon = false;
173 this->DrawNearestPoint = false;
174 this->DrawAxesAtOrigin = false;
175 this->BarWidthFraction = 0.8f;
176
177 this->Tooltip = vtkSmartPointer<vtkTooltipItem>::New();
178 this->Tooltip->SetVisible(false);
179 this->AddItem(this->Tooltip);
180
181 this->ForceAxesToBounds = false;
182 this->ZoomWithMouseWheel = true;
183 this->AdjustLowerBoundForLogPlot = false;
184
185 this->DragPoint = false;
186 this->DragPointAlongX = true;
187 this->DragPointAlongY = true;
188 }
189
190 //-----------------------------------------------------------------------------
~vtkChartXY()191 vtkChartXY::~vtkChartXY()
192 {
193 for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
194 {
195 this->ChartPrivate->plots[i]->Delete();
196 }
197 for (size_t i = 0; i < 4; ++i)
198 {
199 this->ChartPrivate->axes[i]->Delete();
200 }
201 delete this->ChartPrivate;
202 this->ChartPrivate = nullptr;
203 }
204
205 //-----------------------------------------------------------------------------
Update()206 void vtkChartXY::Update()
207 {
208 // Perform any necessary updates that are not graphical
209 // Update the plots if necessary
210 for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
211 {
212 this->ChartPrivate->plots[i]->Update();
213 }
214 this->Legend->Update();
215
216 // Update the selections if necessary.
217 if (this->AnnotationLink)
218 {
219 this->AnnotationLink->Update();
220 vtkSelection* selection =
221 vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
222 // Two major selection methods - row based or plot based.
223 if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
224 {
225 vtkSelectionNode* node = selection->GetNumberOfNodes() > 0 ? selection->GetNode(0) : nullptr;
226 vtkIdTypeArray* idArray =
227 node ? vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList()) : nullptr;
228 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
229 for (; it != this->ChartPrivate->plots.end(); ++it)
230 {
231 // Use the first selection node for all plots to select the rows.
232 (*it)->SetSelection(idArray);
233 }
234 }
235 else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
236 {
237 for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
238 {
239 vtkSelectionNode* node = selection->GetNode(i);
240 vtkIdTypeArray* idArray = vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList());
241 vtkPlot* selectionPlot =
242 vtkPlot::SafeDownCast(node->GetProperties()->Get(vtkSelectionNode::PROP()));
243 // Now iterate through the plots to update selection data
244 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
245 for (; it != this->ChartPrivate->plots.end(); ++it)
246 {
247 if (selectionPlot == *it)
248 {
249 (*it)->SetSelection(idArray);
250 }
251 }
252 }
253 }
254 else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
255 {
256 // Retrieve all the selected plots
257 std::vector<vtkPlot*> selectedPlots;
258 for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
259 {
260 vtkSelectionNode* node = selection->GetNode(i);
261 vtkIdTypeArray* selectedColumns =
262 vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList());
263 vtkIdType* ptr = reinterpret_cast<vtkIdType*>(selectedColumns->GetVoidPointer(0));
264 for (vtkIdType j = 0; j < selectedColumns->GetNumberOfTuples(); ++j)
265 {
266 vtkPlot* selectedPlot = this->ChartPrivate->GetPlotByColumn(ptr[j]);
267 if (selectedPlot)
268 {
269 selectedPlots.push_back(selectedPlot);
270 }
271 }
272 }
273 // Now iterate through the plots to update selection data
274 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
275 for (; it != this->ChartPrivate->plots.end(); ++it)
276 {
277 vtkPlot* plot = *it;
278 vtkIdTypeArray* plotSelection = nullptr;
279 bool ownPlotSelection = false;
280 bool isSelected =
281 std::find(selectedPlots.begin(), selectedPlots.end(), plot) != selectedPlots.end();
282 if (isSelected)
283 {
284 static int idx = 1; // y
285 vtkAbstractArray* column =
286 plot->GetData()->GetInputAbstractArrayToProcess(idx, plot->GetInput());
287 plotSelection = plot->GetSelection();
288 if (!plotSelection || plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
289 {
290 plotSelection = vtkIdTypeArray::New();
291 ownPlotSelection = true;
292 for (vtkIdType j = 0; j < column->GetNumberOfTuples(); ++j)
293 {
294 plotSelection->InsertNextValue(j);
295 }
296 }
297 }
298 plot->SetSelection(plotSelection);
299 if (ownPlotSelection)
300 {
301 plotSelection->Delete();
302 }
303 }
304 }
305 }
306 else
307 {
308 vtkDebugMacro("No annotation link set.");
309 }
310
311 this->CalculateBarPlots();
312
313 if (this->AutoAxes)
314 {
315 vtkTuple<bool, 4> visibilities(false);
316 for (int i = 0; i < static_cast<int>(this->ChartPrivate->PlotCorners.size()); ++i)
317 {
318 int visible = 0;
319 for (vtkIdType j = 0; j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
320 {
321 if (vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j))->GetVisible())
322 {
323 ++visible;
324 }
325 }
326 if (visible)
327 {
328 visibilities[i % 4] = true;
329 visibilities[(i + 1) % 4] = true;
330 }
331 }
332 for (int i = 0; i < 4; ++i)
333 {
334 this->ChartPrivate->axes[i]->SetVisible(visibilities[i]);
335 }
336 }
337 }
338
339 //-----------------------------------------------------------------------------
Paint(vtkContext2D * painter)340 bool vtkChartXY::Paint(vtkContext2D* painter)
341 {
342 // This is where everything should be drawn, or dispatched to other methods.
343 vtkDebugMacro(<< "Paint event called.");
344 if (!this->Visible)
345 {
346 // The geometry of the chart must be valid before anything can be drawn
347 return false;
348 }
349
350 vtkVector2i geometry(0, 0);
351 bool recalculateTransform = false;
352 if (this->LayoutStrategy == vtkChart::FILL_SCENE)
353 {
354 geometry = vtkVector2i(this->GetScene()->GetSceneWidth(), this->GetScene()->GetSceneHeight());
355 if (geometry.GetX() != this->Geometry[0] || geometry.GetY() != this->Geometry[1])
356 {
357 recalculateTransform = true;
358 }
359 this->SetSize(vtkRectf(0.0, 0.0, geometry.GetX(), geometry.GetY()));
360 }
361
362 int visiblePlots = 0;
363 for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
364 {
365 if (this->ChartPrivate->plots[i]->GetVisible())
366 {
367 ++visiblePlots;
368 }
369 }
370 if (visiblePlots == 0 && !this->RenderEmpty)
371 {
372 // Nothing to plot, so don't draw anything.
373 return false;
374 }
375
376 this->Update();
377 this->UpdateLayout(painter);
378
379 // Axes may have changed during updateLayout
380 if (this->ChartPrivate->TransformCalculatedTime < this->ChartPrivate->axes[0]->GetMTime() ||
381 this->ChartPrivate->TransformCalculatedTime < this->ChartPrivate->axes[1]->GetMTime() ||
382 this->ChartPrivate->TransformCalculatedTime < this->ChartPrivate->axes[2]->GetMTime() ||
383 this->ChartPrivate->TransformCalculatedTime < this->ChartPrivate->axes[3]->GetMTime())
384 {
385 // Cause the plot transform to be recalculated if necessary
386 recalculateTransform = true;
387 }
388
389 // Recalculate the plot transform, min and max values if necessary
390 if (!this->PlotTransformValid)
391 {
392 this->RecalculatePlotBounds();
393 recalculateTransform = true;
394 }
395 if (this->UpdateLayout(painter) || recalculateTransform)
396 {
397 this->RecalculatePlotTransforms();
398 }
399
400 // Now that plot transforms, including whether to use log scaling and the
401 // shift-scale factors, have been updated, we give the vtkPlot instances an
402 // opportunity to update caches.
403 for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
404 {
405 this->ChartPrivate->plots[i]->UpdateCache();
406 }
407
408 // Update the clipping if necessary
409 this->ChartPrivate->Clip->SetClip(this->Point1[0], this->Point1[1],
410 this->Point2[0] - this->Point1[0], this->Point2[1] - this->Point1[1]);
411
412 // draw background
413 if (this->BackgroundBrush)
414 {
415 painter->GetPen()->SetLineType(vtkPen::NO_PEN);
416 painter->ApplyBrush(this->BackgroundBrush);
417 painter->DrawRect(this->Point1[0], this->Point1[1], this->Geometry[0], this->Geometry[1]);
418 }
419
420 // Use the scene to render most of the chart.
421 this->PaintChildren(painter);
422
423 // Draw the selection box if necessary
424 if (this->DrawBox)
425 {
426 painter->GetBrush()->SetColor(255, 255, 255, 0);
427 painter->GetPen()->SetColor(0, 0, 0, 255);
428 painter->GetPen()->SetWidth(1.0);
429 painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);
430 painter->DrawRect(this->MouseBox.GetX(), this->MouseBox.GetY(), this->MouseBox.GetWidth(),
431 this->MouseBox.GetHeight());
432 }
433
434 // Draw the selection polygon if necessary
435 if (this->DrawSelectionPolygon)
436 {
437 painter->GetBrush()->SetColor(255, 0, 0, 0);
438 painter->GetPen()->SetColor(0, 255, 0, 255);
439 painter->GetPen()->SetWidth(2.0);
440 painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);
441
442 const vtkContextPolygon& polygon = this->SelectionPolygon;
443
444 // draw each line segment
445 for (vtkIdType i = 0; i < polygon.GetNumberOfPoints() - 1; i++)
446 {
447 const vtkVector2f& a = polygon.GetPoint(i);
448 const vtkVector2f& b = polygon.GetPoint(i + 1);
449
450 painter->DrawLine(a.GetX(), a.GetY(), b.GetX(), b.GetY());
451 }
452
453 // draw a line from the end to the start
454 if (polygon.GetNumberOfPoints() >= 3)
455 {
456 const vtkVector2f& start = polygon.GetPoint(0);
457 const vtkVector2f& end = polygon.GetPoint(polygon.GetNumberOfPoints() - 1);
458
459 painter->DrawLine(start.GetX(), start.GetY(), end.GetX(), end.GetY());
460 }
461 }
462
463 if (this->Title)
464 {
465 int offset = 0; // title margin.
466 vtkAxis* topAxis = this->ChartPrivate->axes[vtkAxis::TOP];
467 if (topAxis->GetVisible())
468 {
469 vtkRectf bounds = topAxis->GetBoundingRect(painter);
470 offset += static_cast<int>(bounds.GetHeight());
471 }
472 vtkPoints2D* rect = vtkPoints2D::New();
473 rect->InsertNextPoint(this->Point1[0], this->Point2[1] + offset);
474 rect->InsertNextPoint(this->Point2[0] - this->Point1[0], 10);
475 painter->ApplyTextProp(this->TitleProperties);
476 painter->DrawStringRect(rect, this->Title);
477 rect->Delete();
478 }
479
480 return true;
481 }
482
483 //-----------------------------------------------------------------------------
CalculateBarPlots()484 void vtkChartXY::CalculateBarPlots()
485 {
486 // Calculate the width, spacing and offsets for the bar plot - they are grouped
487 size_t n = this->ChartPrivate->plots.size();
488 std::vector<vtkPlotBar*> bars;
489 for (size_t i = 0; i < n; ++i)
490 {
491 vtkPlotBar* bar = vtkPlotBar::SafeDownCast(this->ChartPrivate->plots[i]);
492 if (bar && bar->GetVisible())
493 {
494 bars.push_back(bar);
495 }
496 }
497 if (!bars.empty())
498 {
499 // We have some bar plots - work out offsets etc.
500 float barWidth = 0.1;
501 vtkPlotBar* bar = bars[0];
502 if (!bar->GetUseIndexForXSeries())
503 {
504 vtkTable* table = bar->GetData()->GetInput();
505 if (table)
506 {
507 vtkDataArray* x = bar->GetData()->GetInputArrayToProcess(0, table);
508 if (x && x->GetNumberOfTuples() > 1)
509 {
510 double x0 = x->GetTuple1(0);
511 double x1 = x->GetTuple1(1);
512 float width = static_cast<float>(fabs(x1 - x0) * this->BarWidthFraction);
513 barWidth = width / bars.size();
514 }
515 }
516 }
517 else
518 {
519 barWidth = 1.0f / bars.size() * this->BarWidthFraction;
520 }
521
522 // Now set the offsets and widths on each bar
523 // The offsetIndex deals with the fact that half the bars
524 // must shift to the left of the point and half to the right
525 int offsetIndex = static_cast<int>(bars.size() - 1);
526 for (size_t i = 0; i < bars.size(); ++i)
527 {
528 bars[i]->SetWidth(barWidth);
529 bars[i]->SetOffset(offsetIndex * (barWidth / 2));
530 // Increment by two since we need to shift by half widths
531 // but make room for entire bars. Increment backwards because
532 // offsets are always subtracted and Positive offsets move
533 // the bar leftwards. Negative offsets will shift the bar
534 // to the right.
535 offsetIndex -= 2;
536 // bars[i]->SetOffset(float(bars.size()-i-1)*(barWidth/2));
537 }
538 }
539 }
540
541 //-----------------------------------------------------------------------------
RecalculatePlotTransforms()542 void vtkChartXY::RecalculatePlotTransforms()
543 {
544 for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
545 {
546 if (this->ChartPrivate->PlotCorners[i]->GetNumberOfItems())
547 {
548 vtkAxis* xAxis = nullptr;
549 vtkAxis* yAxis = nullptr;
550 // Get the appropriate axes, and recalculate the transform.
551 switch (i)
552 {
553 case 0:
554 {
555 xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
556 yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
557 break;
558 }
559 case 1:
560 xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
561 yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
562 break;
563 case 2:
564 xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
565 yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
566 break;
567 case 3:
568 xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
569 yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
570 break;
571 default:
572 vtkWarningMacro("Error: default case in recalculate plot transforms.");
573 }
574 this->CalculatePlotTransform(
575 xAxis, yAxis, this->ChartPrivate->PlotCorners[i]->GetTransform());
576 // Now we need to set the scale factor on the plots to ensure they rescale
577 // their input data when necessary.
578 vtkRectd shiftScale(
579 xAxis->GetShift(), yAxis->GetShift(), xAxis->GetScalingFactor(), yAxis->GetScalingFactor());
580 for (vtkIdType j = 0; j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
581 {
582 vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
583 if (plot)
584 {
585 plot->SetShiftScale(shiftScale);
586 }
587 }
588 }
589 }
590 this->PlotTransformValid = true;
591 this->ChartPrivate->TransformCalculatedTime.Modified();
592 }
593
594 //-----------------------------------------------------------------------------
GetPlotCorner(vtkPlot * plot)595 int vtkChartXY::GetPlotCorner(vtkPlot* plot)
596 {
597 vtkAxis* x = plot->GetXAxis();
598 vtkAxis* y = plot->GetYAxis();
599 if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
600 y == this->ChartPrivate->axes[vtkAxis::LEFT])
601 {
602 return 0;
603 }
604 else if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
605 y == this->ChartPrivate->axes[vtkAxis::RIGHT])
606 {
607 return 1;
608 }
609 else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
610 y == this->ChartPrivate->axes[vtkAxis::RIGHT])
611 {
612 return 2;
613 }
614 else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
615 y == this->ChartPrivate->axes[vtkAxis::LEFT])
616 {
617 return 3;
618 }
619 else
620 {
621 // Should never happen.
622 return 4;
623 }
624 }
625
626 //-----------------------------------------------------------------------------
SetPlotCorner(vtkPlot * plot,int corner)627 void vtkChartXY::SetPlotCorner(vtkPlot* plot, int corner)
628 {
629 if (corner < 0 || corner > 3)
630 {
631 vtkWarningMacro("Invalid corner specified, should be between 0 and 3: " << corner);
632 return;
633 }
634 if (this->GetPlotCorner(plot) == corner)
635 {
636 return;
637 }
638 this->RemovePlotFromCorners(plot);
639 // Grow the plot corners if necessary
640 while (static_cast<int>(this->ChartPrivate->PlotCorners.size() - 1) < corner)
641 {
642 vtkNew<vtkContextTransform> transform;
643 this->ChartPrivate->PlotCorners.push_back(transform);
644 this->ChartPrivate->Clip->AddItem(transform); // Clip maintains ownership.
645 }
646 this->ChartPrivate->PlotCorners[corner]->AddItem(plot);
647 if (corner == 0)
648 {
649 plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
650 plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
651 }
652 else if (corner == 1)
653 {
654 plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
655 plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
656 }
657 else if (corner == 2)
658 {
659 plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
660 plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
661 }
662 else if (corner == 3)
663 {
664 plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
665 plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
666 }
667 this->PlotTransformValid = false;
668 }
669
670 //-----------------------------------------------------------------------------
RecalculatePlotBounds()671 void vtkChartXY::RecalculatePlotBounds()
672 {
673 // Get the bounds of each plot, and each axis - ordering as laid out below
674 double y1[] = { 0.0, 0.0 }; // left -> 0
675 double x1[] = { 0.0, 0.0 }; // bottom -> 1
676 double y2[] = { 0.0, 0.0 }; // right -> 2
677 double x2[] = { 0.0, 0.0 }; // top -> 3
678 // Store whether the ranges have been initialized - follows same order
679 bool initialized[] = { false, false, false, false };
680
681 std::vector<vtkPlot*>::iterator it;
682 double bounds[4] = { 0.0, 0.0, 0.0, 0.0 };
683 for (it = this->ChartPrivate->plots.begin(); it != this->ChartPrivate->plots.end(); ++it)
684 {
685 if ((*it)->GetVisible() == false)
686 {
687 continue;
688 }
689 (*it)->GetBounds(bounds);
690 if (bounds[1] - bounds[0] < 0.0)
691 {
692 // skip uninitialized bounds.
693 continue;
694 }
695 int corner = this->GetPlotCorner(*it);
696
697 // Initialize the appropriate ranges, or push out the ranges
698 if ((corner == 0 || corner == 3)) // left
699 {
700 if (!initialized[0])
701 {
702 y1[0] = bounds[2];
703 y1[1] = bounds[3];
704 initialized[0] = true;
705 }
706 else
707 {
708 if (y1[0] > bounds[2]) // min
709 {
710 y1[0] = bounds[2];
711 }
712 if (y1[1] < bounds[3]) // max
713 {
714 y1[1] = bounds[3];
715 }
716 }
717 }
718 if ((corner == 0 || corner == 1)) // bottom
719 {
720 if (!initialized[1])
721 {
722 x1[0] = bounds[0];
723 x1[1] = bounds[1];
724 initialized[1] = true;
725 }
726 else
727 {
728 if (x1[0] > bounds[0]) // min
729 {
730 x1[0] = bounds[0];
731 }
732 if (x1[1] < bounds[1]) // max
733 {
734 x1[1] = bounds[1];
735 }
736 }
737 }
738 if ((corner == 1 || corner == 2)) // right
739 {
740 if (!initialized[2])
741 {
742 y2[0] = bounds[2];
743 y2[1] = bounds[3];
744 initialized[2] = true;
745 }
746 else
747 {
748 if (y2[0] > bounds[2]) // min
749 {
750 y2[0] = bounds[2];
751 }
752 if (y2[1] < bounds[3]) // max
753 {
754 y2[1] = bounds[3];
755 }
756 }
757 }
758 if ((corner == 2 || corner == 3)) // top
759 {
760 if (!initialized[3])
761 {
762 x2[0] = bounds[0];
763 x2[1] = bounds[1];
764 initialized[3] = true;
765 }
766 else
767 {
768 if (x2[0] > bounds[0]) // min
769 {
770 x2[0] = bounds[0];
771 }
772 if (x2[1] < bounds[1]) // max
773 {
774 x2[1] = bounds[1];
775 }
776 }
777 }
778 }
779
780 // Now set the newly calculated bounds on the axes
781 for (int i = 0; i < 4; ++i)
782 {
783 vtkAxis* axis = this->ChartPrivate->axes[i];
784 double* range = nullptr;
785 switch (i)
786 {
787 case 0:
788 range = y1;
789 break;
790 case 1:
791 range = x1;
792 break;
793 case 2:
794 range = y2;
795 break;
796 case 3:
797 range = x2;
798 break;
799 default:
800 return;
801 }
802
803 if (this->AdjustLowerBoundForLogPlot && axis->GetLogScale() &&
804 (range[0] <= 0.0 || vtkMath::IsNan(range[0])))
805 {
806 if (range[1] <= 0.0 || vtkMath::IsNan(range[1]))
807 {
808 // All of the data is negative, so we arbitrarily set the axis range to
809 // be positive and show no data
810 range[1] = 1.;
811 }
812
813 // The minimum value is set to either 4 decades below the max or to 1,
814 // regardless of the true minimum value (which is less than 0).
815 if (axis->GetLogScaleActive())
816 {
817 // Need to adjust in log (scaled) space
818 double candidateMin = range[1] - 4.0;
819 range[0] = (candidateMin < 0.0 ? candidateMin : 0.0);
820 }
821 else
822 {
823 // Need to adjust in unscaled space
824 double candidateMin = range[1] * 1.0e-4;
825 range[0] = (candidateMin < 1.0 ? candidateMin : 1.0);
826 }
827 }
828 if (this->ForceAxesToBounds)
829 {
830 axis->SetMinimumLimit(range[0]);
831 axis->SetMaximumLimit(range[1]);
832 }
833 if (axis->GetBehavior() == vtkAxis::AUTO && initialized[i])
834 {
835 axis->SetRange(range[0], range[1]);
836 axis->AutoScale();
837 }
838 }
839
840 this->Modified();
841 }
842
843 //-----------------------------------------------------------------------------
ReleasePlotSelections()844 void vtkChartXY::ReleasePlotSelections()
845 {
846 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
847 for (; it != this->ChartPrivate->plots.end(); ++it)
848 {
849 vtkPlot* plot = *it;
850 if (!plot)
851 {
852 continue;
853 }
854 vtkNew<vtkIdTypeArray> emptySelectionArray;
855 emptySelectionArray->Initialize();
856 plot->SetSelection(emptySelectionArray);
857 }
858 }
859
860 //-----------------------------------------------------------------------------
UpdateLayout(vtkContext2D * painter)861 bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
862 {
863 // The main use of this method is currently to query the visible axes for
864 // their bounds, and to update the chart in response to that.
865 bool changed = false;
866
867 vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
868 vtkVector2i hiddenAxisBorder = tileScale * this->HiddenAxisBorder;
869
870 // Axes
871 if (this->LayoutStrategy == vtkChart::FILL_SCENE || this->LayoutStrategy == vtkChart::FILL_RECT)
872 {
873 for (int i = 0; i < 4; ++i)
874 {
875 int border = 0;
876 vtkAxis* axis = this->ChartPrivate->axes[i];
877 axis->Update();
878 if (axis->GetVisible())
879 {
880 vtkRectf bounds = axis->GetBoundingRect(painter);
881 if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
882 { // Horizontal axes
883 border = int(bounds.GetHeight());
884 }
885 else
886 { // Vertical axes
887 border = int(bounds.GetWidth());
888 }
889 }
890 border += this->GetLegendBorder(painter, i);
891 if (i == vtkAxis::TOP && this->Title)
892 {
893 painter->ApplyTextProp(this->TitleProperties);
894 float bounds[4];
895 painter->ComputeStringBounds(this->Title, bounds);
896 if (bounds[3] > 0)
897 {
898 border += (5 * tileScale.GetY()) /* title margin */
899 + bounds[3]; // add the title text height to the border.
900 }
901 }
902
903 if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
904 {
905 border = std::max(border, hiddenAxisBorder.GetY());
906 }
907 else
908 {
909 border = std::max(border, hiddenAxisBorder.GetX());
910 }
911
912 if (this->ChartPrivate->Borders[i] != border)
913 {
914 this->ChartPrivate->Borders[i] = border;
915 changed = true;
916 }
917 }
918 }
919
920 if (this->DrawAxesAtOrigin)
921 {
922 this->SetBorders(hiddenAxisBorder.GetX(), hiddenAxisBorder.GetY(),
923 this->ChartPrivate->Borders[2], this->ChartPrivate->Borders[3]);
924 // Get the screen coordinates for the origin, and move the axes there.
925 vtkVector2f origin(0.0);
926 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[0]->GetTransform();
927 transform->TransformPoints(origin.GetData(), origin.GetData(), 1);
928 // Need to clamp the axes in the plot area.
929 if (int(origin[0]) < this->Point1[0])
930 {
931 origin[0] = this->Point1[0];
932 }
933 if (int(origin[0]) > this->Point2[0])
934 {
935 origin[0] = this->Point2[0];
936 }
937 if (int(origin[1]) < this->Point1[1])
938 {
939 origin[1] = this->Point1[1];
940 }
941 if (int(origin[1]) > this->Point2[1])
942 {
943 origin[1] = this->Point2[1];
944 }
945
946 this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetPoint1(this->Point1[0], origin[1]);
947 this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetPoint2(this->Point2[0], origin[1]);
948 this->ChartPrivate->axes[vtkAxis::LEFT]->SetPoint1(origin[0], this->Point1[1]);
949 this->ChartPrivate->axes[vtkAxis::LEFT]->SetPoint2(origin[0], this->Point2[1]);
950 }
951 else
952 {
953 if (this->LayoutStrategy == vtkChart::AXES_TO_RECT)
954 {
955 this->SetBorders(0, 0, 0, 0);
956 this->ChartPrivate->axes[0]->GetBoundingRect(painter);
957 this->ChartPrivate->axes[1]->GetBoundingRect(painter);
958 this->ChartPrivate->axes[2]->GetBoundingRect(painter);
959 this->ChartPrivate->axes[3]->GetBoundingRect(painter);
960 }
961 else
962 {
963 this->SetBorders(this->ChartPrivate->Borders[0], this->ChartPrivate->Borders[1],
964 this->ChartPrivate->Borders[2], this->ChartPrivate->Borders[3]);
965 }
966 // This is where we set the axes up too
967 // Y axis (left)
968 this->ChartPrivate->axes[0]->SetPoint1(this->Point1[0], this->Point1[1]);
969 this->ChartPrivate->axes[0]->SetPoint2(this->Point1[0], this->Point2[1]);
970 // X axis (bottom)
971 this->ChartPrivate->axes[1]->SetPoint1(this->Point1[0], this->Point1[1]);
972 this->ChartPrivate->axes[1]->SetPoint2(this->Point2[0], this->Point1[1]);
973 // Y axis (right)
974 this->ChartPrivate->axes[2]->SetPoint1(this->Point2[0], this->Point1[1]);
975 this->ChartPrivate->axes[2]->SetPoint2(this->Point2[0], this->Point2[1]);
976 // X axis (top)
977 this->ChartPrivate->axes[3]->SetPoint1(this->Point1[0], this->Point2[1]);
978 this->ChartPrivate->axes[3]->SetPoint2(this->Point2[0], this->Point2[1]);
979
980 for (int i = 0; i < 4; ++i)
981 {
982 this->ChartPrivate->axes[i]->Update();
983 }
984 }
985 this->SetLegendPosition(this->Legend->GetBoundingRect(painter));
986
987 return changed;
988 }
989
990 //-----------------------------------------------------------------------------
GetLegendBorder(vtkContext2D * painter,int axisPosition)991 int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
992 {
993 if (!this->Legend->GetVisible() || this->Legend->GetInline())
994 {
995 return 0;
996 }
997
998 vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
999
1000 int padding = 10;
1001 vtkVector2i legendSize(0, 0);
1002 vtkVector2i legendAlignment(
1003 this->Legend->GetHorizontalAlignment(), this->Legend->GetVerticalAlignment());
1004 this->Legend->Update();
1005 vtkRectf rect = this->Legend->GetBoundingRect(painter);
1006 legendSize.Set(static_cast<int>(rect.GetWidth()), static_cast<int>(rect.GetHeight()));
1007
1008 // Figure out the correct place and alignment based on the legend layout.
1009 if (axisPosition == vtkAxis::LEFT && legendAlignment.GetX() == vtkChartLegend::LEFT)
1010 {
1011 return legendSize.GetX() + padding * tileScale.GetX();
1012 }
1013 else if (axisPosition == vtkAxis::RIGHT && legendAlignment.GetX() == vtkChartLegend::RIGHT)
1014 {
1015 return legendSize.GetX() + padding * tileScale.GetX();
1016 }
1017 else if ((axisPosition == vtkAxis::TOP || axisPosition == vtkAxis::BOTTOM) &&
1018 (legendAlignment.GetX() == vtkChartLegend::LEFT ||
1019 legendAlignment.GetX() == vtkChartLegend::RIGHT))
1020 {
1021 return 0;
1022 }
1023 else if (axisPosition == vtkAxis::TOP && legendAlignment.GetY() == vtkChartLegend::TOP)
1024 {
1025 return legendSize.GetY() + padding * tileScale.GetY();
1026 }
1027 else if (axisPosition == vtkAxis::BOTTOM && legendAlignment.GetY() == vtkChartLegend::BOTTOM)
1028 {
1029 return legendSize.GetY() + padding * tileScale.GetY();
1030 }
1031 else
1032 {
1033 return 0;
1034 }
1035 }
1036
1037 //-----------------------------------------------------------------------------
SetLegendPosition(const vtkRectf & rect)1038 void vtkChartXY::SetLegendPosition(const vtkRectf& rect)
1039 {
1040 // Put the legend in the top corner of the chart
1041 vtkVector2f pos(0, 0);
1042 int padding = 5;
1043 vtkVector2i legendAlignment(
1044 this->Legend->GetHorizontalAlignment(), this->Legend->GetVerticalAlignment());
1045
1046 if (legendAlignment[0] == vtkChartLegend::CUSTOM || legendAlignment[1] == vtkChartLegend::CUSTOM)
1047 {
1048 return;
1049 }
1050
1051 if (this->Legend->GetInline())
1052 {
1053 switch (this->Legend->GetHorizontalAlignment())
1054 {
1055 case vtkChartLegend::LEFT:
1056 pos.SetX(this->Point1[0]);
1057 break;
1058 case vtkChartLegend::CENTER:
1059 pos.SetX(
1060 ((this->Point2[0] - this->Point1[0]) / 2.0) - rect.GetWidth() / 2.0 + this->Point1[0]);
1061 break;
1062 case vtkChartLegend::RIGHT:
1063 default:
1064 pos.SetX(this->Point2[0] - rect.GetWidth());
1065 }
1066 switch (this->Legend->GetVerticalAlignment())
1067 {
1068 case vtkChartLegend::TOP:
1069 pos.SetY(this->Point2[1] - rect.GetHeight());
1070 break;
1071 case vtkChartLegend::CENTER:
1072 pos.SetY(
1073 (this->Point2[1] - this->Point1[1]) / 2.0 - rect.GetHeight() / 2.0 + this->Point1[1]);
1074 break;
1075 case vtkChartLegend::BOTTOM:
1076 default:
1077 pos.SetY(this->Point1[1]);
1078 }
1079 }
1080 else
1081 {
1082 // Non-inline legends.
1083 if (legendAlignment.GetX() == vtkChartLegend::LEFT)
1084 {
1085 pos.SetX(this->Point1[0] - this->ChartPrivate->Borders[vtkAxis::LEFT] + padding);
1086 }
1087 else if (legendAlignment.GetX() == vtkChartLegend::RIGHT)
1088 {
1089 pos.SetX(
1090 this->Point2[0] + this->ChartPrivate->Borders[vtkAxis::RIGHT] - rect.GetWidth() - padding);
1091 }
1092 else if (legendAlignment.GetX() == vtkChartLegend::CENTER)
1093 {
1094 pos.SetX(
1095 ((this->Point2[0] - this->Point1[0]) / 2.0) - (rect.GetWidth() / 2.0) + this->Point1[0]);
1096 // Check for the special case where the legend is on the top or bottom
1097 if (legendAlignment.GetY() == vtkChartLegend::TOP)
1098 {
1099 pos.SetY(
1100 this->Point2[1] + this->ChartPrivate->Borders[vtkAxis::TOP] - rect.GetHeight() - padding);
1101 }
1102 else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
1103 {
1104 pos.SetY(this->Point1[1] - this->ChartPrivate->Borders[vtkAxis::BOTTOM] + padding);
1105 }
1106 }
1107 // Vertical alignment
1108 if (legendAlignment.GetX() != vtkChartLegend::CENTER)
1109 {
1110 if (legendAlignment.GetY() == vtkChartLegend::TOP)
1111 {
1112 pos.SetY(this->Point2[1] - rect.GetHeight());
1113 }
1114 else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
1115 {
1116 pos.SetY(this->Point1[1]);
1117 }
1118 }
1119 if (legendAlignment.GetY() == vtkChartLegend::CENTER)
1120 {
1121 pos.SetY(
1122 ((this->Point2[1] - this->Point1[1]) / 2.0) - (rect.GetHeight() / 2.0) + this->Point1[1]);
1123 }
1124 }
1125
1126 this->Legend->SetPoint(pos);
1127 }
1128
1129 //-----------------------------------------------------------------------------
AddPlot(int type)1130 vtkPlot* vtkChartXY::AddPlot(int type)
1131 {
1132 // Use a variable to return the object created (or nullptr), this is necessary
1133 // as the HP compiler is broken (thinks this function does not return) and
1134 // the MS compiler generates a warning about unreachable code if a redundant
1135 // return is added at the end.
1136 vtkPlot* plot = nullptr;
1137 vtkColor3ub color = this->ChartPrivate->Colors->GetColorRepeating(
1138 static_cast<int>(this->ChartPrivate->plots.size()));
1139 switch (type)
1140 {
1141 case LINE:
1142 {
1143 vtkPlotLine* line = vtkPlotLine::New();
1144 line->GetPen()->SetColor(color.GetData());
1145 plot = line;
1146 break;
1147 }
1148 case POINTS:
1149 {
1150 vtkPlotPoints* points = vtkPlotPoints::New();
1151 points->GetPen()->SetColor(color.GetData());
1152 plot = points;
1153 break;
1154 }
1155 case BAR:
1156 {
1157 vtkPlotBar* bar = vtkPlotBar::New();
1158 bar->GetBrush()->SetColor(color.GetData());
1159 plot = bar;
1160 break;
1161 }
1162 case FUNCTIONALBAG:
1163 {
1164 vtkPlotFunctionalBag* bag = vtkPlotFunctionalBag::New();
1165 bag->GetPen()->SetColor(color.GetData());
1166 bag->GetBrush()->SetColor(color.GetData());
1167 plot = bag;
1168 break;
1169 }
1170 case STACKED:
1171 {
1172 vtkPlotStacked* stacked = vtkPlotStacked::New();
1173 stacked->SetParent(this);
1174 stacked->GetBrush()->SetColor(color.GetData());
1175 plot = stacked;
1176 break;
1177 }
1178 case BAG:
1179 {
1180 vtkPlotBag* bag = vtkPlotBag::New();
1181 bag->SetParent(this);
1182 bag->GetBrush()->SetColor(color.GetData());
1183 plot = bag;
1184 break;
1185 }
1186 case AREA:
1187 {
1188 vtkPlotArea* area = vtkPlotArea::New();
1189 area->SetParent(this);
1190 area->GetBrush()->SetColor(color.GetData());
1191 plot = area;
1192 break;
1193 }
1194
1195 default:
1196 plot = nullptr;
1197 }
1198 if (plot)
1199 {
1200 this->AddPlot(plot);
1201 plot->Delete();
1202 }
1203 return plot;
1204 }
1205
1206 //-----------------------------------------------------------------------------
AddPlot(vtkPlot * plot)1207 vtkIdType vtkChartXY::AddPlot(vtkPlot* plot)
1208 {
1209 if (plot == nullptr)
1210 {
1211 return -1;
1212 }
1213 plot->Register(this);
1214 this->ChartPrivate->plots.push_back(plot);
1215 vtkIdType plotIndex = static_cast<vtkIdType>(this->ChartPrivate->plots.size() - 1);
1216 this->SetPlotCorner(plot, 0);
1217 // Ensure that the bounds are recalculated
1218 this->PlotTransformValid = false;
1219 // Mark the scene as dirty
1220 if (this->Scene)
1221 {
1222 this->Scene->SetDirty(true);
1223 }
1224 return plotIndex;
1225 }
1226
1227 //-----------------------------------------------------------------------------
RemovePlot(vtkIdType index)1228 bool vtkChartXY::RemovePlot(vtkIdType index)
1229 {
1230 if (index < static_cast<vtkIdType>(this->ChartPrivate->plots.size()))
1231 {
1232 this->RemovePlotFromCorners(this->ChartPrivate->plots[index]);
1233 this->ChartPrivate->plots[index]->Delete();
1234 this->ChartPrivate->plots.erase(this->ChartPrivate->plots.begin() + index);
1235
1236 // Ensure that the bounds are recalculated
1237 this->PlotTransformValid = false;
1238 if (this->Scene)
1239 {
1240 // Mark the scene as dirty
1241 this->Scene->SetDirty(true);
1242 }
1243 return true;
1244 }
1245 else
1246 {
1247 return false;
1248 }
1249 }
1250
1251 //-----------------------------------------------------------------------------
ClearPlots()1252 void vtkChartXY::ClearPlots()
1253 {
1254 for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
1255 {
1256 this->ChartPrivate->plots[i]->Delete();
1257 }
1258 this->ChartPrivate->plots.clear();
1259 // Clear the corners too
1260 for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
1261 {
1262 this->ChartPrivate->PlotCorners[i]->ClearItems();
1263 if (i > 0)
1264 {
1265 this->ChartPrivate->Clip->RemoveItem(this->ChartPrivate->PlotCorners[i]);
1266 }
1267 }
1268 this->ChartPrivate->PlotCorners.resize(1);
1269
1270 // Ensure that the bounds are recalculated
1271 this->PlotTransformValid = false;
1272 if (this->Scene)
1273 {
1274 // Mark the scene as dirty
1275 this->Scene->SetDirty(true);
1276 }
1277 }
1278
1279 //-----------------------------------------------------------------------------
GetPlot(vtkIdType index)1280 vtkPlot* vtkChartXY::GetPlot(vtkIdType index)
1281 {
1282 if (static_cast<vtkIdType>(this->ChartPrivate->plots.size()) > index)
1283 {
1284 return this->ChartPrivate->plots[index];
1285 }
1286 else
1287 {
1288 return nullptr;
1289 }
1290 }
1291
1292 //-----------------------------------------------------------------------------
GetPlotIndex(vtkPlot * plot)1293 vtkIdType vtkChartXY::GetPlotIndex(vtkPlot* plot)
1294 {
1295 int corner = this->GetPlotCorner(plot);
1296 return corner >= 0 && corner < 4 ? this->ChartPrivate->PlotCorners[corner]->GetItemIndex(plot)
1297 : static_cast<vtkIdType>(-1);
1298 }
1299
1300 //-----------------------------------------------------------------------------
RaisePlot(vtkPlot * plot)1301 vtkIdType vtkChartXY::RaisePlot(vtkPlot* plot)
1302 {
1303 vtkIdType plotIndex = this->GetPlotIndex(plot);
1304 int corner = this->GetPlotCorner(plot);
1305 if (corner < 0 || corner >= 4)
1306 {
1307 return plotIndex;
1308 }
1309 return this->ChartPrivate->PlotCorners[corner]->Raise(plotIndex);
1310 }
1311
1312 //-----------------------------------------------------------------------------
StackPlotAbove(vtkPlot * plot,vtkPlot * under)1313 vtkIdType vtkChartXY::StackPlotAbove(vtkPlot* plot, vtkPlot* under)
1314 {
1315 vtkIdType plotIndex = this->GetPlotIndex(plot);
1316 vtkIdType underIndex = this->GetPlotIndex(under);
1317 int corner = this->GetPlotCorner(plot);
1318 if (corner < 0 || corner >= 4 || underIndex != this->GetPlotCorner(under))
1319 {
1320 return plotIndex;
1321 }
1322 return this->ChartPrivate->PlotCorners[corner]->StackAbove(plotIndex, underIndex);
1323 }
1324
1325 //-----------------------------------------------------------------------------
LowerPlot(vtkPlot * plot)1326 vtkIdType vtkChartXY::LowerPlot(vtkPlot* plot)
1327 {
1328 vtkIdType plotIndex = this->GetPlotIndex(plot);
1329 int corner = this->GetPlotCorner(plot);
1330 if (corner < 0 || corner >= 4)
1331 {
1332 return plotIndex;
1333 }
1334 return this->ChartPrivate->PlotCorners[corner]->Lower(plotIndex);
1335 }
1336
1337 //-----------------------------------------------------------------------------
StackPlotUnder(vtkPlot * plot,vtkPlot * above)1338 vtkIdType vtkChartXY::StackPlotUnder(vtkPlot* plot, vtkPlot* above)
1339 {
1340 vtkIdType plotIndex = this->GetPlotIndex(plot);
1341 vtkIdType aboveIndex = this->GetPlotIndex(above);
1342 int corner = this->GetPlotCorner(plot);
1343 if (corner < 0 || corner >= 4 || corner != this->GetPlotCorner(above))
1344 {
1345 return plotIndex;
1346 }
1347 return this->ChartPrivate->PlotCorners[corner]->StackUnder(plotIndex, aboveIndex);
1348 }
1349
1350 //-----------------------------------------------------------------------------
SetShowLegend(bool visible)1351 void vtkChartXY::SetShowLegend(bool visible)
1352 {
1353 this->vtkChart::SetShowLegend(visible);
1354 this->Legend->SetVisible(visible);
1355 }
1356
1357 //-----------------------------------------------------------------------------
GetLegend()1358 vtkChartLegend* vtkChartXY::GetLegend()
1359 {
1360 return this->Legend;
1361 }
1362
1363 //-----------------------------------------------------------------------------
SetTooltip(vtkTooltipItem * tooltip)1364 void vtkChartXY::SetTooltip(vtkTooltipItem* tooltip)
1365 {
1366 if (tooltip == this->Tooltip)
1367 {
1368 // nothing to change
1369 return;
1370 }
1371
1372 if (this->Tooltip)
1373 {
1374 // remove current tooltip from scene
1375 this->RemoveItem(this->Tooltip);
1376 }
1377
1378 this->Tooltip = tooltip;
1379
1380 if (this->Tooltip)
1381 {
1382 // add new tooltip to scene
1383 this->AddItem(this->Tooltip);
1384 }
1385 }
1386
1387 //-----------------------------------------------------------------------------
GetTooltip()1388 vtkTooltipItem* vtkChartXY::GetTooltip()
1389 {
1390 return this->Tooltip;
1391 }
1392
1393 //-----------------------------------------------------------------------------
GetNumberOfPlots()1394 vtkIdType vtkChartXY::GetNumberOfPlots()
1395 {
1396 return static_cast<vtkIdType>(this->ChartPrivate->plots.size());
1397 }
1398
1399 //-----------------------------------------------------------------------------
GetAxis(int axisIndex)1400 vtkAxis* vtkChartXY::GetAxis(int axisIndex)
1401 {
1402 if (axisIndex < 4)
1403 {
1404 return this->ChartPrivate->axes[axisIndex];
1405 }
1406 else
1407 {
1408 return nullptr;
1409 }
1410 }
1411
1412 //-----------------------------------------------------------------------------
SetAxis(int axisIndex,vtkAxis * axis)1413 void vtkChartXY::SetAxis(int axisIndex, vtkAxis * axis)
1414 {
1415 if ((axisIndex < 4) && (axisIndex >= 0))
1416 {
1417 vtkAxis * old_axis = this->ChartPrivate->axes[axisIndex];
1418 this->ChartPrivate->axes[axisIndex] = axis;
1419 this->ChartPrivate->axes[axisIndex]->SetVisible(old_axis->GetVisible());
1420
1421 // remove the old axis
1422 this->RemoveItem(old_axis);
1423
1424 this->AttachAxisRangeListener(this->ChartPrivate->axes[axisIndex]);
1425 this->AddItem(this->ChartPrivate->axes[axisIndex]);
1426
1427 this->ChartPrivate->axes[axisIndex]->SetPosition(axisIndex);
1428
1429 vtkPlotGrid* grid1 = static_cast<vtkPlotGrid *>(this->ChartPrivate->Clip->GetItem(0));
1430 vtkPlotGrid* grid2 = static_cast<vtkPlotGrid *>(this->ChartPrivate->Clip->GetItem(1));
1431 switch (axisIndex)
1432 {
1433 case vtkAxis::BOTTOM:
1434 grid1->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
1435 break;
1436 case vtkAxis::LEFT:
1437 grid1->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
1438 break;
1439 case vtkAxis::TOP:
1440 grid2->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
1441 break;
1442 case vtkAxis::RIGHT:
1443 grid2->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
1444 break;
1445 }
1446 }
1447 }
1448
1449 //-----------------------------------------------------------------------------
GetNumberOfAxes()1450 vtkIdType vtkChartXY::GetNumberOfAxes()
1451 {
1452 return 4;
1453 }
1454
1455 //-----------------------------------------------------------------------------
RecalculateBounds()1456 void vtkChartXY::RecalculateBounds()
1457 {
1458 // Ensure that the bounds are recalculated
1459 this->PlotTransformValid = false;
1460 if (this->Scene)
1461 {
1462 // Mark the scene as dirty
1463 this->Scene->SetDirty(true);
1464 }
1465 }
1466
1467 //-----------------------------------------------------------------------------
SetSelectionMethod(int method)1468 void vtkChartXY::SetSelectionMethod(int method)
1469 {
1470 if (method == this->SelectionMethod)
1471 {
1472 return;
1473 }
1474 if (method == vtkChart::SELECTION_PLOTS)
1475 {
1476 // Clear the selection on the plots which may be shared between all of them.
1477 // Now iterate through the plots to update selection data
1478 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
1479 for (; it != this->ChartPrivate->plots.end(); ++it)
1480 {
1481 (*it)->SetSelection(nullptr);
1482 }
1483 }
1484 Superclass::SetSelectionMethod(method);
1485 }
1486
1487 //-----------------------------------------------------------------------------
RemovePlotSelections()1488 void vtkChartXY::RemovePlotSelections()
1489 {
1490 std::vector<vtkPlot*>::iterator it = this->ChartPrivate->plots.begin();
1491 for (; it != this->ChartPrivate->plots.end(); ++it)
1492 {
1493 vtkPlot* plot = *it;
1494 if (!plot)
1495 {
1496 continue;
1497 }
1498 vtkNew<vtkIdTypeArray> emptySelectionArray;
1499 emptySelectionArray->Initialize();
1500 plot->SetSelection(emptySelectionArray);
1501 }
1502 this->InvokeEvent(vtkCommand::SelectionChangedEvent);
1503 }
1504
1505 //-----------------------------------------------------------------------------
Hit(const vtkContextMouseEvent & mouse)1506 bool vtkChartXY::Hit(const vtkContextMouseEvent& mouse)
1507 {
1508 if (!this->Interactive)
1509 {
1510 return false;
1511 }
1512 vtkVector2i pos(mouse.GetScreenPos());
1513 if (pos[0] > this->Point1[0] && pos[0] < this->Point2[0] && pos[1] > this->Point1[1] &&
1514 pos[1] < this->Point2[1])
1515 {
1516 return true;
1517 }
1518 else
1519 {
1520 return false;
1521 }
1522 }
1523
1524 //-----------------------------------------------------------------------------
MouseEnterEvent(const vtkContextMouseEvent &)1525 bool vtkChartXY::MouseEnterEvent(const vtkContextMouseEvent&)
1526 {
1527 // Find the nearest point on the curves and snap to it
1528 this->DrawNearestPoint = true;
1529 return true;
1530 }
1531
1532 //-----------------------------------------------------------------------------
MouseMoveEvent(const vtkContextMouseEvent & mouse)1533 bool vtkChartXY::MouseMoveEvent(const vtkContextMouseEvent& mouse)
1534 {
1535 // Iterate through each corner, and check for a nearby point
1536 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
1537 {
1538 if (this->ChartPrivate->PlotCorners[i]->MouseMoveEvent(mouse))
1539 {
1540 return true;
1541 }
1542 }
1543
1544 if (mouse.GetButton() == this->Actions.Pan())
1545 {
1546 // Figure out how much the mouse has moved by in plot coordinates - pan
1547 vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
1548 vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());
1549 vtkVector2d pos(0.0, 0.0);
1550 vtkVector2d last(0.0, 0.0);
1551
1552 // Go from screen to scene coordinates to work out the delta
1553 vtkAxis* xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
1554 vtkAxis* yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
1555 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[0]->GetTransform();
1556 transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
1557 transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
1558 vtkVector2d delta = last - pos;
1559 delta[0] /= xAxis->GetScalingFactor();
1560 delta[1] /= yAxis->GetScalingFactor();
1561
1562 // Now move the axes and recalculate the transform
1563 delta[0] = delta[0] > 0 ? std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum())
1564 : std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
1565 delta[1] = delta[1] > 0 ? std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum())
1566 : std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
1567 xAxis->SetRange(xAxis->GetMinimum() + delta[0], xAxis->GetMaximum() + delta[0]);
1568 yAxis->SetRange(yAxis->GetMinimum() + delta[1], yAxis->GetMaximum() + delta[1]);
1569
1570 if (this->ChartPrivate->PlotCorners.size() == 2)
1571 {
1572 // Figure out the right axis position, if greater than 2 both will be done
1573 // in the else if block below.
1574 screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
1575 lastScreenPos = vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
1576 pos = vtkVector2d(0.0, 0.0);
1577 last = vtkVector2d(0.0, 0.0);
1578 yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
1579 transform = this->ChartPrivate->PlotCorners[1]->GetTransform();
1580 transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
1581 transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
1582 delta = last - pos;
1583 delta[0] /= xAxis->GetScalingFactor();
1584 delta[1] /= yAxis->GetScalingFactor();
1585
1586 // Now move the axes and recalculate the transform
1587 delta[1] = delta[1] > 0 ? std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum())
1588 : std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
1589 yAxis->SetRange(yAxis->GetMinimum() + delta[1], yAxis->GetMaximum() + delta[1]);
1590 }
1591 else if (this->ChartPrivate->PlotCorners.size() > 2)
1592 {
1593 // Figure out the right and top axis positions.
1594 // Go from screen to scene coordinates to work out the delta
1595 screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
1596 lastScreenPos = vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
1597 pos = vtkVector2d(0.0, 0.0);
1598 last = vtkVector2d(0.0, 0.0);
1599 xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
1600 yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
1601 transform = this->ChartPrivate->PlotCorners[2]->GetTransform();
1602 transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
1603 transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
1604 delta = last - pos;
1605 delta[0] /= xAxis->GetScalingFactor();
1606 delta[1] /= yAxis->GetScalingFactor();
1607
1608 // Now move the axes and recalculate the transform
1609 delta[0] = delta[0] > 0 ? std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum())
1610 : std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
1611 delta[1] = delta[1] > 0 ? std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum())
1612 : std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
1613 xAxis->SetRange(xAxis->GetMinimum() + delta[0], xAxis->GetMaximum() + delta[0]);
1614 yAxis->SetRange(yAxis->GetMinimum() + delta[1], yAxis->GetMaximum() + delta[1]);
1615 }
1616
1617 this->RecalculatePlotTransforms();
1618 // Mark the scene as dirty
1619 this->Scene->SetDirty(true);
1620
1621 this->InvokeEvent(vtkCommand::InteractionEvent);
1622 }
1623 else if (mouse.GetButton() == this->Actions.Zoom() || mouse.GetButton() == this->Actions.Select())
1624 {
1625 this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
1626 this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
1627 // Mark the scene as dirty
1628 this->Scene->SetDirty(true);
1629 }
1630 else if (mouse.GetButton() == this->Actions.ZoomAxis())
1631 {
1632 vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
1633 vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());
1634
1635 vtkAxis* axes[] = { this->ChartPrivate->axes[vtkAxis::BOTTOM],
1636 this->ChartPrivate->axes[vtkAxis::LEFT], this->ChartPrivate->axes[vtkAxis::TOP],
1637 this->ChartPrivate->axes[vtkAxis::RIGHT] };
1638
1639 for (int i = 0; i < 4; i++)
1640 {
1641 vtkAxis* axis = axes[i];
1642 if (!axis)
1643 {
1644 continue;
1645 }
1646
1647 // bottom, top -> 0, right, left -> 1
1648 int side = i % 2;
1649
1650 // get mouse delta in the given direction for the axis
1651 double delta = lastScreenPos[side] - screenPos[side];
1652 if (std::abs(delta) == 0)
1653 {
1654 continue;
1655 }
1656
1657 // scale and invert delta
1658 delta /= -100.0;
1659
1660 // zoom axis range
1661 double min = axis->GetMinimum();
1662 double max = axis->GetMaximum();
1663 double frac = (max - min) * 0.1;
1664 if (frac > 0.0)
1665 {
1666 min += delta * frac;
1667 max -= delta * frac;
1668 }
1669 else
1670 {
1671 min -= delta * frac;
1672 max += delta * frac;
1673 }
1674 axis->SetRange(min, max);
1675 axis->RecalculateTickSpacing();
1676 }
1677
1678 this->RecalculatePlotTransforms();
1679
1680 // Mark the scene as dirty
1681 this->Scene->SetDirty(true);
1682
1683 this->InvokeEvent(vtkCommand::InteractionEvent);
1684 }
1685 else if (mouse.GetButton() == this->Actions.SelectPolygon())
1686 {
1687 if (this->SelectionPolygon.GetNumberOfPoints() > 0)
1688 {
1689 vtkVector2f lastPoint =
1690 this->SelectionPolygon.GetPoint(this->SelectionPolygon.GetNumberOfPoints() - 1);
1691
1692 if ((lastPoint - mouse.GetPos()).SquaredNorm() > 100)
1693 {
1694 this->SelectionPolygon.AddPoint(mouse.GetPos());
1695 }
1696
1697 // Mark the scene as dirty
1698 this->Scene->SetDirty(true);
1699 }
1700 }
1701 else if (mouse.GetButton() == this->Actions.ClickAndDrag() &&
1702 this->DragPoint && (this->DragPointAlongX || this->DragPointAlongY))
1703 {
1704 // Iterate through each corner, and check for a nearby point
1705 std::vector<vtkContextTransform*>::iterator it = this->ChartPrivate->PlotCorners.begin();
1706 for (; it != this->ChartPrivate->PlotCorners.end(); ++it)
1707 {
1708 vtkContextTransform* plotCorner = *it;
1709 if (!plotCorner)
1710 {
1711 continue;
1712 }
1713
1714 int items = static_cast<int>(plotCorner->GetNumberOfItems());
1715 if (items == 0)
1716 {
1717 continue;
1718 }
1719
1720 vtkVector2f position;
1721 vtkTransform2D* transform = plotCorner->GetTransform();
1722 transform->InverseTransformPoints(mouse.GetPos().GetData(),
1723 position.GetData(), 1);
1724 for (int j = 0; j < items; ++j)
1725 {
1726 vtkPlot* plot = vtkPlot::SafeDownCast(plotCorner->GetItem(j));
1727 if (!plot || plot->IsA("vtkPlotBar"))
1728 {
1729 continue;
1730 }
1731 vtkIdTypeArray* selectionArray = plot->GetSelection();
1732 if (!selectionArray || selectionArray->GetNumberOfValues() < 1)
1733 {
1734 continue;
1735 }
1736 if (selectionArray->GetNumberOfValues() > 1)
1737 {
1738 vtkDebugMacro("Move event (Click and Drag) found more than one point to update.");
1739 }
1740 vtkIdType index = selectionArray->GetValue(0);
1741 if (this->DragPointAlongX)
1742 {
1743 vtkDataArray* xArray = plot->GetData()->GetInputArrayToProcess(0, plot->GetInput());
1744 xArray->SetVariantValue(index, position.GetX());
1745 }
1746 if (this->DragPointAlongY)
1747 {
1748 vtkDataArray* yArray = plot->GetData()->GetInputArrayToProcess(1, plot->GetInput());
1749 yArray->SetVariantValue(index, position.GetY());
1750 }
1751 plot->GetSelection()->Modified();
1752 plot->GetInput()->Modified();
1753 this->Scene->SetDirty(true);
1754 }
1755 }
1756 }
1757 else if (mouse.GetButton() == vtkContextMouseEvent::NO_BUTTON)
1758 {
1759 this->Scene->SetDirty(true);
1760
1761 if (this->Tooltip)
1762 {
1763 this->Tooltip->SetVisible(this->LocatePointInPlots(mouse));
1764 }
1765 }
1766
1767 return true;
1768 }
1769
1770 //-----------------------------------------------------------------------------
LocatePointInPlot(const vtkVector2f & position,const vtkVector2f & tolerance,vtkVector2f & plotPos,vtkPlot * plot,vtkIdType & segmentIndex)1771 int vtkChartXY::LocatePointInPlot(const vtkVector2f& position, const vtkVector2f& tolerance,
1772 vtkVector2f& plotPos, vtkPlot* plot, vtkIdType& segmentIndex)
1773 {
1774 if (plot && plot->GetVisible())
1775 {
1776 vtkPlotBar* plotBar = vtkPlotBar::SafeDownCast(plot);
1777 if (plotBar)
1778 {
1779 // If the plot is a vtkPlotBar, get the segment index too
1780 return plotBar->GetNearestPoint(position, tolerance, &plotPos, &segmentIndex);
1781 }
1782 else
1783 {
1784 return plot->GetNearestPoint(position, tolerance, &plotPos);
1785 }
1786 }
1787 return -1;
1788 }
1789
1790 //-----------------------------------------------------------------------------
LocatePointInPlots(const vtkContextMouseEvent & mouse,int invokeEvent)1791 bool vtkChartXY::LocatePointInPlots(const vtkContextMouseEvent& mouse, int invokeEvent)
1792 {
1793 size_t n = this->ChartPrivate->plots.size();
1794 vtkVector2i pos(mouse.GetScreenPos());
1795 if (pos[0] > this->Point1[0] && pos[0] < this->Point2[0] && pos[1] > this->Point1[1] &&
1796 pos[1] < this->Point2[1] && n)
1797 {
1798 // Iterate through each corner, and check for a nearby point
1799 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
1800 {
1801 int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]->GetNumberOfItems());
1802 if (items)
1803 {
1804 vtkVector2f plotPos, position;
1805 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[i]->GetTransform();
1806 transform->InverseTransformPoints(mouse.GetPos().GetData(), position.GetData(), 1);
1807 // Use a tolerance of +/- 5 pixels
1808 vtkVector2f tolerance(std::fabs(5 * (1.0 / transform->GetMatrix()->GetElement(0, 0))),
1809 std::fabs(5 * (1.0 / transform->GetMatrix()->GetElement(1, 1))));
1810 // Iterate through the visible plots and return on the first hit
1811 vtkIdType segmentIndex = -1;
1812
1813 for (int j = items - 1; j >= 0; --j)
1814 {
1815 vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
1816 int seriesIndex = LocatePointInPlot(position, tolerance, plotPos, plot, segmentIndex);
1817 if (seriesIndex >= 0)
1818 {
1819 // We found a point, set up the tooltip and return
1820 vtkRectd ss(plot->GetShiftScale());
1821 vtkVector2d plotPosd(plotPos[0] / ss[2] - ss[0], plotPos[1] / ss[3] - ss[1]);
1822 this->SetTooltipInfo(mouse, plotPosd, seriesIndex, plot, segmentIndex);
1823 if (invokeEvent >= 0)
1824 {
1825 vtkChartPlotData plotIndex;
1826 plotIndex.SeriesName = plot->GetLabel();
1827 plotIndex.Position = plotPos;
1828 plotIndex.ScreenPosition = mouse.GetScreenPos();
1829 plotIndex.Index = seriesIndex;
1830 // Invoke an event, with the client data supplied
1831 this->InvokeEvent(invokeEvent, static_cast<void*>(&plotIndex));
1832
1833 if (invokeEvent == vtkCommand::SelectionChangedEvent)
1834 {
1835 // Construct a new selection with the selected point in it.
1836 vtkNew<vtkIdTypeArray> selectionIds;
1837 selectionIds->InsertNextValue(seriesIndex);
1838 plot->SetSelection(selectionIds);
1839
1840 if (this->AnnotationLink)
1841 {
1842 vtkChartSelectionHelper::MakeSelection(
1843 this->AnnotationLink, selectionIds, plot);
1844 }
1845 }
1846 }
1847 return true;
1848 }
1849 }
1850 }
1851 }
1852 }
1853 return false;
1854 }
1855
1856 //-----------------------------------------------------------------------------
SetTooltipInfo(const vtkContextMouseEvent & mouse,const vtkVector2d & plotPos,vtkIdType seriesIndex,vtkPlot * plot,vtkIdType segmentIndex)1857 void vtkChartXY::SetTooltipInfo(const vtkContextMouseEvent& mouse, const vtkVector2d& plotPos,
1858 vtkIdType seriesIndex, vtkPlot* plot, vtkIdType segmentIndex)
1859 {
1860 if (!this->Tooltip)
1861 {
1862 return;
1863 }
1864
1865 // Have the plot generate its tooltip label
1866 vtkStdString tooltipLabel = plot->GetTooltipLabel(plotPos, seriesIndex, segmentIndex);
1867
1868 // Set the tooltip
1869 this->Tooltip->SetText(tooltipLabel);
1870 this->Tooltip->SetPosition(mouse.GetScreenPos()[0] + 2, mouse.GetScreenPos()[1] + 2);
1871 }
1872
1873 //-----------------------------------------------------------------------------
MouseLeaveEvent(const vtkContextMouseEvent &)1874 bool vtkChartXY::MouseLeaveEvent(const vtkContextMouseEvent&)
1875 {
1876 this->DrawNearestPoint = false;
1877
1878 if (this->Tooltip)
1879 {
1880 this->Tooltip->SetVisible(false);
1881 }
1882
1883 return true;
1884 }
1885
1886 //-----------------------------------------------------------------------------
MouseButtonPressEvent(const vtkContextMouseEvent & mouse)1887 bool vtkChartXY::MouseButtonPressEvent(const vtkContextMouseEvent& mouse)
1888 {
1889 if (this->Tooltip)
1890 {
1891 this->Tooltip->SetVisible(false);
1892 }
1893
1894 // Iterate through each corner, and check for a nearby point
1895 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
1896 {
1897 if (this->ChartPrivate->PlotCorners[i]->MouseButtonPressEvent(mouse))
1898 {
1899 return true;
1900 }
1901 }
1902 if (mouse.GetButton() == this->Actions.Pan())
1903 {
1904 // The mouse panning action.
1905 this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
1906 this->DrawBox = false;
1907 return true;
1908 }
1909 else if (mouse.GetButton() == this->Actions.Zoom() || mouse.GetButton() == this->Actions.Select())
1910 {
1911 // Selection, for now at least...
1912 this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
1913 this->DrawBox = true;
1914 return true;
1915 }
1916 else if (mouse.GetButton() == this->Actions.ZoomAxis())
1917 {
1918 this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
1919 this->DrawBox = false;
1920 return true;
1921 }
1922 else if (mouse.GetButton() == this->Actions.SelectPolygon())
1923 {
1924 this->SelectionPolygon.Clear();
1925 this->SelectionPolygon.AddPoint(mouse.GetPos());
1926 this->DrawSelectionPolygon = true;
1927 return true;
1928 }
1929 else if (mouse.GetButton() == this->Actions.ClickAndDrag())
1930 {
1931 this->ReleasePlotSelections();
1932 this->DragPoint = this->LocatePointInPlots(mouse, vtkCommand::SelectionChangedEvent);
1933 this->InvokeEvent(vtkCommand::SelectionChangedEvent);
1934 return true;
1935 }
1936 else if (mouse.GetButton() == this->ActionsClick.Select() ||
1937 mouse.GetButton() == this->ActionsClick.Notify())
1938 {
1939 return true;
1940 }
1941 else
1942 {
1943 return false;
1944 }
1945 }
1946
1947 //-----------------------------------------------------------------------------
MouseButtonReleaseEvent(const vtkContextMouseEvent & mouse)1948 bool vtkChartXY::MouseButtonReleaseEvent(const vtkContextMouseEvent& mouse)
1949 {
1950 // Iterate through each corner, and check for a nearby point
1951 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
1952 {
1953 if (this->ChartPrivate->PlotCorners[i]->MouseButtonReleaseEvent(mouse))
1954 {
1955 return true;
1956 }
1957 }
1958
1959 // Check single action click interaction/selection
1960 // First check that the selection actions are invalid or it is a pan selection
1961 this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
1962 this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
1963 bool isActionSelectInvalid = fabs(this->MouseBox.GetWidth()) < 0.5 &&
1964 fabs(this->MouseBox.GetHeight()) < 0.5 &&
1965 mouse.GetButton() == this->Actions.Select();
1966 bool isActionSelectPolygonInvalid = this->SelectionPolygon.GetNumberOfPoints() < 2 &&
1967 mouse.GetButton() == this->Actions.SelectPolygon();
1968 bool isActionPan = mouse.GetButton() == this->Actions.Pan();
1969
1970 if (isActionSelectInvalid || isActionSelectPolygonInvalid || isActionPan)
1971 {
1972 this->MouseBox.SetWidth(0.0);
1973 this->MouseBox.SetHeight(0.0);
1974 this->SelectionPolygon.Clear();
1975 this->DrawBox = false;
1976 this->DrawSelectionPolygon = false;
1977 // Find the relative interaction/selection point
1978 if (mouse.GetButton() == this->ActionsClick.Notify())
1979 {
1980 this->LocatePointInPlots(mouse, vtkCommand::InteractionEvent);
1981 }
1982 if (mouse.GetButton() == this->ActionsClick.Select())
1983 {
1984 this->LocatePointInPlots(mouse, vtkCommand::SelectionChangedEvent);
1985 this->InvokeEvent(vtkCommand::SelectionChangedEvent);
1986 }
1987 if (mouse.GetButton() != this->ActionsClick.Notify() &&
1988 mouse.GetButton() != this->ActionsClick.Select())
1989 {
1990 return false;
1991 }
1992 else
1993 {
1994 return true;
1995 }
1996 }
1997
1998 if (mouse.GetButton() == this->Actions.Select() ||
1999 mouse.GetButton() == this->Actions.SelectPolygon())
2000 {
2001 // Modifiers or selection modes can affect how selection is performed.
2002 int selectionMode = vtkChartSelectionHelper::GetMouseSelectionMode(mouse, this->SelectionMode);
2003 bool polygonMode(mouse.GetButton() == this->Actions.SelectPolygon());
2004 this->Scene->SetDirty(true);
2005
2006 // Update the polygon or box with the last mouse position.
2007 if (polygonMode)
2008 {
2009 this->SelectionPolygon.AddPoint(mouse.GetPos());
2010 this->DrawSelectionPolygon = false;
2011 }
2012 else
2013 {
2014 this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
2015 this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
2016 this->DrawBox = false;
2017 }
2018
2019 // Check whether we have a valid selection area, exit early if not.
2020 if (polygonMode && this->SelectionPolygon.GetNumberOfPoints() < 3)
2021 {
2022 // There is no polygon to select points in.
2023 this->SelectionPolygon.Clear();
2024 return true;
2025 }
2026 else if (fabs(this->MouseBox.GetWidth()) < 0.5 || fabs(this->MouseBox.GetHeight()) < 0.5)
2027 {
2028 // The box is too small, and no useful selection can be made.
2029 this->MouseBox.SetWidth(0.0);
2030 this->MouseBox.SetHeight(0.0);
2031 return true;
2032 }
2033
2034 // Iterate through the plots and build a selection. Two main behaviors are
2035 // supported - row-based selections add all rows from all plots and set that
2036 // as the selection, plot-based selections create a selection node for each
2037 // plot.
2038 vtkNew<vtkIdTypeArray> oldSelection;
2039 vtkNew<vtkIdTypeArray> accumulateSelection;
2040 if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
2041 {
2042 // There is only one global selection, we build up a union of all rows
2043 // selected in all charts and set that on all plots.
2044 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
2045 {
2046 int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]->GetNumberOfItems());
2047 if (items)
2048 {
2049 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[i]->GetTransform();
2050 vtkVector2f min;
2051 vtkVector2f max;
2052 vtkContextPolygon polygon;
2053 this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(), min, max, polygon);
2054
2055 // Iterate through the plots and create the selection.
2056 for (int j = 0; j < items; ++j)
2057 {
2058 vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
2059 if (plot && plot->GetVisible() && plot->GetSelectable())
2060 {
2061 // There is only really one old selection in this mode.
2062 if (i == 0 && j == 0)
2063 {
2064 oldSelection->DeepCopy(plot->GetSelection());
2065 }
2066 // Populate the selection using the appropriate shape.
2067 if (polygonMode)
2068 {
2069 plot->SelectPointsInPolygon(polygon);
2070 }
2071 else
2072 {
2073 plot->SelectPoints(min, max);
2074 }
2075
2076 // Accumulate the selection in each plot.
2077 vtkChartSelectionHelper::BuildSelection(nullptr, vtkContextScene::SELECTION_ADDITION,
2078 accumulateSelection, plot->GetSelection(), nullptr);
2079 }
2080 }
2081 }
2082 }
2083 // Now add the accumulated selection to the old selection.
2084 vtkChartSelectionHelper::BuildSelection(this->AnnotationLink, selectionMode,
2085 accumulateSelection, oldSelection, nullptr);
2086 }
2087 else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
2088 {
2089 // We are performing plot based selections.
2090 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
2091 {
2092 int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]->GetNumberOfItems());
2093 if (items)
2094 {
2095 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[i]->GetTransform();
2096 vtkVector2f min;
2097 vtkVector2f max;
2098 vtkContextPolygon polygon;
2099 this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(), min, max, polygon);
2100
2101 for (int j = 0; j < items; ++j)
2102 {
2103 vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
2104 if (plot && plot->GetVisible() && plot->GetSelectable())
2105 {
2106 oldSelection->DeepCopy(plot->GetSelection());
2107 // Populate the selection using the appropriate shape.
2108 if (polygonMode)
2109 {
2110 plot->SelectPointsInPolygon(polygon);
2111 }
2112 else
2113 {
2114 plot->SelectPoints(min, max);
2115 }
2116
2117 // Combine the selection in this plot with any previous selection.
2118 vtkChartSelectionHelper::BuildSelection(this->AnnotationLink, selectionMode,
2119 plot->GetSelection(), oldSelection, plot);
2120 }
2121 }
2122 }
2123 }
2124 }
2125 else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
2126 {
2127 if (this->AnnotationLink)
2128 {
2129 this->AnnotationLink->Update();
2130 vtkSelection* selection =
2131 vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
2132 vtkSelectionNode* node = selection->GetNumberOfNodes() > 0 ? selection->GetNode(0) : nullptr;
2133 if (node)
2134 {
2135 oldSelection->DeepCopy(vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList()));
2136 }
2137 }
2138 vtkNew<vtkIdTypeArray> plotSelection;
2139 // We are performing plot based selections.
2140 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
2141 {
2142 int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]->GetNumberOfItems());
2143 if (items)
2144 {
2145 vtkTransform2D* transform = this->ChartPrivate->PlotCorners[i]->GetTransform();
2146 vtkVector2f min;
2147 vtkVector2f max;
2148 vtkContextPolygon polygon;
2149 this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(), min, max, polygon);
2150
2151 for (int j = 0; j < items; ++j)
2152 {
2153 vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
2154 if (plot && plot->GetVisible() && plot->GetSelectable())
2155 {
2156 bool selected = false;
2157 // Populate the selection using the appropriate shape.
2158 if (polygonMode)
2159 {
2160 selected = plot->SelectPointsInPolygon(polygon);
2161 }
2162 else
2163 {
2164 selected = plot->SelectPoints(min, max);
2165 }
2166 if (selected)
2167 {
2168 int idx = 1; // y
2169 vtkAbstractArray* column =
2170 plot->GetData()->GetInputAbstractArrayToProcess(idx, plot->GetInput());
2171 int columnID = -1;
2172 plot->GetInput()->GetRowData()->GetAbstractArray(column->GetName(), columnID);
2173 if (plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
2174 {
2175 plotSelection->SetNumberOfTuples(0);
2176 for (vtkIdType k = 0; k < column->GetNumberOfTuples(); ++k)
2177 {
2178 plotSelection->InsertNextValue(k);
2179 }
2180 }
2181 plot->SetSelection(plotSelection);
2182 accumulateSelection->InsertNextValue(columnID);
2183 }
2184 }
2185 }
2186 }
2187 }
2188 vtkIdType* ptrSelection =
2189 reinterpret_cast<vtkIdType*>(accumulateSelection->GetVoidPointer(0));
2190 std::sort(ptrSelection, ptrSelection + accumulateSelection->GetNumberOfTuples());
2191 // Now add the accumulated selection to the old selection
2192 vtkChartSelectionHelper::BuildSelection(this->AnnotationLink, selectionMode,
2193 accumulateSelection, oldSelection, nullptr);
2194 }
2195 this->InvokeEvent(vtkCommand::SelectionChangedEvent);
2196 this->MouseBox.SetWidth(0.0);
2197 this->MouseBox.SetHeight(0.0);
2198 this->SelectionPolygon.Clear();
2199 return true;
2200 }
2201 else if (mouse.GetButton() == this->Actions.Zoom())
2202 {
2203 // Check whether a valid zoom box was drawn
2204 if (fabs(this->MouseBox.GetWidth()) < 0.5 || fabs(this->MouseBox.GetHeight()) < 0.5)
2205 {
2206 // Invalid box size - do nothing
2207 this->MouseBox.SetWidth(0.0);
2208 this->MouseBox.SetHeight(0.0);
2209 this->DrawBox = false;
2210 return true;
2211 }
2212
2213 // Zoom into the chart by the specified amount, and recalculate the bounds
2214 vtkVector2f point2(mouse.GetPos());
2215
2216 this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::BOTTOM],
2217 this->ChartPrivate->axes[vtkAxis::LEFT], this->MouseBox.GetData(), point2.GetData());
2218 this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::TOP],
2219 this->ChartPrivate->axes[vtkAxis::RIGHT], this->MouseBox.GetData(), point2.GetData());
2220
2221 this->RecalculatePlotTransforms();
2222 this->MouseBox.SetWidth(0.0);
2223 this->MouseBox.SetHeight(0.0);
2224 this->DrawBox = false;
2225 // Mark the scene as dirty
2226 this->Scene->SetDirty(true);
2227 this->InvokeEvent(vtkCommand::InteractionEvent);
2228 return true;
2229 }
2230 else if (mouse.GetButton() == this->Actions.ZoomAxis())
2231 {
2232 return true;
2233 }
2234 else if (mouse.GetButton() == this->Actions.ClickAndDrag())
2235 {
2236 this->ReleasePlotSelections();
2237 this->InvokeEvent(vtkCommand::SelectionChangedEvent);
2238 this->DragPoint = false;
2239 return true;
2240 }
2241 return false;
2242 }
2243
ZoomInAxes(vtkAxis * x,vtkAxis * y,float * originf,float * maxf)2244 void vtkChartXY::ZoomInAxes(vtkAxis* x, vtkAxis* y, float* originf, float* maxf)
2245 {
2246 vtkNew<vtkTransform2D> transform;
2247 this->CalculateUnscaledPlotTransform(x, y, transform);
2248 vtkVector2d origin(originf[0], originf[1]);
2249 vtkVector2d max(maxf[0], maxf[1]);
2250 vtkVector2d torigin;
2251 transform->InverseTransformPoints(origin.GetData(), torigin.GetData(), 1);
2252 vtkVector2d tmax;
2253 transform->InverseTransformPoints(max.GetData(), tmax.GetData(), 1);
2254
2255 // Ensure we preserve the directionality of the axes
2256 if (x->GetMaximum() > x->GetMinimum())
2257 {
2258 x->SetRange(torigin[0] < tmax[0] ? torigin[0] : tmax[0],
2259 torigin[0] > tmax[0] ? torigin[0] : tmax[0]);
2260 }
2261 else
2262 {
2263 x->SetRange(torigin[0] > tmax[0] ? torigin[0] : tmax[0],
2264 torigin[0] < tmax[0] ? torigin[0] : tmax[0]);
2265 }
2266 if (y->GetMaximum() > y->GetMinimum())
2267 {
2268 y->SetRange(torigin[1] < tmax[1] ? torigin[1] : tmax[1],
2269 torigin[1] > tmax[1] ? torigin[1] : tmax[1]);
2270 }
2271 else
2272 {
2273 y->SetRange(torigin[1] > tmax[1] ? torigin[1] : tmax[1],
2274 torigin[1] < tmax[1] ? torigin[1] : tmax[1]);
2275 }
2276 x->RecalculateTickSpacing();
2277 y->RecalculateTickSpacing();
2278 }
2279
2280 //-----------------------------------------------------------------------------
MouseWheelEvent(const vtkContextMouseEvent &,int delta)2281 bool vtkChartXY::MouseWheelEvent(const vtkContextMouseEvent&, int delta)
2282 {
2283 if (this->Tooltip)
2284 {
2285 this->Tooltip->SetVisible(false);
2286 }
2287 if (!this->ZoomWithMouseWheel)
2288 {
2289 return false;
2290 }
2291
2292 // Get the bounds of each plot.
2293 for (int i = 0; i < 4; ++i)
2294 {
2295 vtkAxis* axis = this->ChartPrivate->axes[i];
2296 double min = axis->GetMinimum();
2297 double max = axis->GetMaximum();
2298 double frac = (max - min) * 0.1;
2299 if (frac > 0.0)
2300 {
2301 min += delta * frac;
2302 max -= delta * frac;
2303 }
2304 else
2305 {
2306 min -= delta * frac;
2307 max += delta * frac;
2308 }
2309 axis->SetRange(min, max);
2310 axis->RecalculateTickSpacing();
2311 }
2312
2313 this->RecalculatePlotTransforms();
2314
2315 // Mark the scene as dirty
2316 this->Scene->SetDirty(true);
2317
2318 this->InvokeEvent(vtkCommand::InteractionEvent);
2319
2320 return true;
2321 }
2322
2323 //-----------------------------------------------------------------------------
KeyPressEvent(const vtkContextKeyEvent & key)2324 bool vtkChartXY::KeyPressEvent(const vtkContextKeyEvent& key)
2325 {
2326 switch (key.GetKeyCode())
2327 {
2328 // Reset the chart axes
2329 case 'r':
2330 case 'R':
2331 this->RecalculateBounds();
2332 this->Scene->SetDirty(true);
2333 }
2334
2335 return true;
2336 }
2337
2338 //-----------------------------------------------------------------------------
RemovePlotFromCorners(vtkPlot * plot)2339 bool vtkChartXY::RemovePlotFromCorners(vtkPlot* plot)
2340 {
2341 // We know the plot will only ever be in one of the corners
2342 for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
2343 {
2344 if (this->ChartPrivate->PlotCorners[i]->RemoveItem(plot))
2345 {
2346 return true;
2347 }
2348 }
2349 return false;
2350 }
2351
2352 //-----------------------------------------------------------------------------
TransformBoxOrPolygon(bool polygonMode,vtkTransform2D * transform,const vtkVector2f & mousePosition,vtkVector2f & min,vtkVector2f & max,vtkContextPolygon & polygon)2353 inline void vtkChartXY::TransformBoxOrPolygon(bool polygonMode, vtkTransform2D* transform,
2354 const vtkVector2f& mousePosition, vtkVector2f& min, vtkVector2f& max, vtkContextPolygon& polygon)
2355 {
2356 if (polygonMode)
2357 {
2358 vtkNew<vtkTransform2D> inverseTransform;
2359 inverseTransform->SetMatrix(transform->GetMatrix());
2360 inverseTransform->Inverse();
2361 polygon = this->SelectionPolygon.Transformed(inverseTransform);
2362 }
2363 else
2364 {
2365 transform->InverseTransformPoints(this->MouseBox.GetData(), min.GetData(), 1);
2366 transform->InverseTransformPoints(mousePosition.GetData(), max.GetData(), 1);
2367 // Normalize the rectangle selection area before using it.
2368 if (min.GetX() > max.GetX())
2369 {
2370 float tmp = min.GetX();
2371 min.SetX(max.GetX());
2372 max.SetX(tmp);
2373 }
2374 if (min.GetY() > max.GetY())
2375 {
2376 float tmp = min.GetY();
2377 min.SetY(max.GetY());
2378 max.SetY(tmp);
2379 }
2380 }
2381 }
2382
2383 //-----------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)2384 void vtkChartXY::PrintSelf(ostream& os, vtkIndent indent)
2385 {
2386 this->Superclass::PrintSelf(os, indent);
2387 os << indent << "Axes: " << endl;
2388 for (int i = 0; i < 4; ++i)
2389 {
2390 this->ChartPrivate->axes[i]->PrintSelf(os, indent.GetNextIndent());
2391 }
2392 if (this->ChartPrivate)
2393 {
2394 os << indent << "Number of plots: " << this->ChartPrivate->plots.size() << endl;
2395 for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
2396 {
2397 os << indent << "Plot " << i << ":" << endl;
2398 this->ChartPrivate->plots[i]->PrintSelf(os, indent.GetNextIndent());
2399 }
2400 }
2401 os << indent << "ZoomWithMouseWheel: " << this->ZoomWithMouseWheel << endl;
2402 }
2403