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