1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkAxisActor2D.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 #include "vtkAxisActor2D.h"
16 
17 #include "vtkCellArray.h"
18 #include "vtkMath.h"
19 #include "vtkObjectFactory.h"
20 #include "vtkPolyData.h"
21 #include "vtkPolyDataMapper2D.h"
22 #include "vtkTextMapper.h"
23 #include "vtkTextProperty.h"
24 #include "vtkViewport.h"
25 #include "vtkWindow.h"
26 
27 #include <cmath>
28 #include <limits>
29 
30 vtkStandardNewMacro(vtkAxisActor2D);
31 
32 vtkCxxSetObjectMacro(vtkAxisActor2D, LabelTextProperty, vtkTextProperty);
33 vtkCxxSetObjectMacro(vtkAxisActor2D, TitleTextProperty, vtkTextProperty);
34 
35 //------------------------------------------------------------------------------
36 // Instantiate this object.
vtkAxisActor2D()37 vtkAxisActor2D::vtkAxisActor2D()
38 {
39   this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport();
40   this->PositionCoordinate->SetValue(0.0, 0.0);
41 
42   this->Position2Coordinate->SetCoordinateSystemToNormalizedViewport();
43   this->Position2Coordinate->SetValue(0.75, 0.0);
44   this->Position2Coordinate->SetReferenceCoordinate(nullptr);
45 
46   this->NumberOfLabels = 5;
47 
48   this->Title = nullptr;
49 
50   this->TitlePosition = 0.5;
51 
52   this->AdjustLabels = 1;
53 
54   this->TickLength = 5;
55   this->MinorTickLength = 3;
56   this->TickOffset = 2;
57   this->NumberOfMinorTicks = 0;
58 
59   this->Range[0] = 0.0;
60   this->Range[1] = 1.0;
61 
62   this->FontFactor = 1.0;
63   this->LabelFactor = 0.75;
64 
65   this->SizeFontRelativeToAxis = 0;
66   this->UseFontSizeFromProperty = 0;
67 
68   this->RulerMode = 0;
69   this->RulerDistance = 1.0;
70 
71   this->LabelTextProperty = vtkTextProperty::New();
72   this->LabelTextProperty->SetBold(1);
73   this->LabelTextProperty->SetItalic(1);
74   this->LabelTextProperty->SetShadow(1);
75   this->LabelTextProperty->SetFontFamilyToArial();
76 
77   this->TitleTextProperty = vtkTextProperty::New();
78   this->TitleTextProperty->ShallowCopy(this->LabelTextProperty);
79 
80   this->LabelFormat = new char[8];
81   snprintf(this->LabelFormat, 8, "%s", "%-#6.3g");
82 
83   this->TitleMapper = vtkTextMapper::New();
84   this->TitleActor = vtkActor2D::New();
85   this->TitleActor->SetMapper(this->TitleMapper);
86 
87   // To avoid deleting/rebuilding create once up front
88   this->NumberOfLabelsBuilt = 0;
89   this->LabelMappers = new vtkTextMapper*[VTK_MAX_LABELS];
90   this->LabelActors = new vtkActor2D*[VTK_MAX_LABELS];
91   for (int i = 0; i < VTK_MAX_LABELS; i++)
92   {
93     this->LabelMappers[i] = vtkTextMapper::New();
94     this->LabelActors[i] = vtkActor2D::New();
95     this->LabelActors[i]->SetMapper(this->LabelMappers[i]);
96   }
97 
98   this->Axis = vtkPolyData::New();
99   this->AxisMapper = vtkPolyDataMapper2D::New();
100   this->AxisMapper->SetInputData(this->Axis);
101   this->AxisActor = vtkActor2D::New();
102   this->AxisActor->SetMapper(this->AxisMapper);
103 
104   this->AxisVisibility = 1;
105   this->TickVisibility = 1;
106   this->LabelVisibility = 1;
107   this->TitleVisibility = 1;
108 
109   this->LastPosition[0] = this->LastPosition[1] = 0;
110   this->LastPosition2[0] = this->LastPosition2[1] = 0;
111 
112   this->LastSize[0] = this->LastSize[1] = 0;
113   this->LastMaxLabelSize[0] = this->LastMaxLabelSize[1] = 0;
114 }
115 
116 //------------------------------------------------------------------------------
~vtkAxisActor2D()117 vtkAxisActor2D::~vtkAxisActor2D()
118 {
119   delete[] this->LabelFormat;
120   this->LabelFormat = nullptr;
121 
122   this->TitleMapper->Delete();
123   this->TitleActor->Delete();
124 
125   delete[] this->Title;
126   this->Title = nullptr;
127 
128   if (this->LabelMappers != nullptr)
129   {
130     for (int i = 0; i < VTK_MAX_LABELS; i++)
131     {
132       this->LabelMappers[i]->Delete();
133       this->LabelActors[i]->Delete();
134     }
135     delete[] this->LabelMappers;
136     delete[] this->LabelActors;
137   }
138 
139   this->Axis->Delete();
140   this->AxisMapper->Delete();
141   this->AxisActor->Delete();
142 
143   this->SetLabelTextProperty(nullptr);
144   this->SetTitleTextProperty(nullptr);
145 }
146 
147 //------------------------------------------------------------------------------
148 // Build the axis, ticks, title, and labels and render.
149 
RenderOpaqueGeometry(vtkViewport * viewport)150 int vtkAxisActor2D::RenderOpaqueGeometry(vtkViewport* viewport)
151 {
152   int i, renderedSomething = 0;
153 
154   this->BuildAxis(viewport);
155 
156   // Everything is built, just have to render
157   if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility)
158   {
159     renderedSomething += this->TitleActor->RenderOpaqueGeometry(viewport);
160   }
161 
162   if (this->AxisVisibility || this->TickVisibility)
163   {
164     renderedSomething += this->AxisActor->RenderOpaqueGeometry(viewport);
165   }
166 
167   if (this->LabelVisibility)
168   {
169     for (i = 0; i < this->NumberOfLabelsBuilt; i++)
170     {
171       renderedSomething += this->LabelActors[i]->RenderOpaqueGeometry(viewport);
172     }
173   }
174 
175   return renderedSomething;
176 }
177 
178 //------------------------------------------------------------------------------
179 // Render the axis, ticks, title, and labels.
180 
RenderOverlay(vtkViewport * viewport)181 int vtkAxisActor2D::RenderOverlay(vtkViewport* viewport)
182 {
183   int i, renderedSomething = 0;
184 
185   // Everything is built, just have to render
186   if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility)
187   {
188     renderedSomething += this->TitleActor->RenderOverlay(viewport);
189   }
190 
191   if (this->AxisVisibility || this->TickVisibility)
192   {
193     renderedSomething += this->AxisActor->RenderOverlay(viewport);
194   }
195 
196   if (this->LabelVisibility)
197   {
198     for (i = 0; i < this->NumberOfLabelsBuilt; i++)
199     {
200       renderedSomething += this->LabelActors[i]->RenderOverlay(viewport);
201     }
202   }
203 
204   return renderedSomething;
205 }
206 
207 //------------------------------------------------------------------------------
208 // Description:
209 // Does this prop have some translucent polygonal geometry?
HasTranslucentPolygonalGeometry()210 vtkTypeBool vtkAxisActor2D::HasTranslucentPolygonalGeometry()
211 {
212   return 0;
213 }
214 
215 //------------------------------------------------------------------------------
216 // Release any graphics resources that are being consumed by this actor.
217 // The parameter window could be used to determine which graphic
218 // resources to release.
ReleaseGraphicsResources(vtkWindow * win)219 void vtkAxisActor2D::ReleaseGraphicsResources(vtkWindow* win)
220 {
221   this->TitleActor->ReleaseGraphicsResources(win);
222   for (int i = 0; i < VTK_MAX_LABELS; i++)
223   {
224     this->LabelActors[i]->ReleaseGraphicsResources(win);
225   }
226   this->AxisActor->ReleaseGraphicsResources(win);
227 }
228 
229 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)230 void vtkAxisActor2D::PrintSelf(ostream& os, vtkIndent indent)
231 {
232   this->Superclass::PrintSelf(os, indent);
233 
234   if (this->TitleTextProperty)
235   {
236     os << indent << "Title Text Property:\n";
237     this->TitleTextProperty->PrintSelf(os, indent.GetNextIndent());
238   }
239   else
240   {
241     os << indent << "Title Text Property: (none)\n";
242   }
243 
244   if (this->LabelTextProperty)
245   {
246     os << indent << "Label Text Property:\n";
247     this->LabelTextProperty->PrintSelf(os, indent.GetNextIndent());
248   }
249   else
250   {
251     os << indent << "Label Text Property: (none)\n";
252   }
253 
254   os << indent << "Title: " << (this->Title ? this->Title : "(none)") << "\n";
255   os << indent << "Ruler Mode: " << (this->RulerMode ? "On" : "Off") << "\n";
256   os << indent << "Ruler Distance: " << this->GetRulerDistance() << "\n";
257   os << indent << "Number Of Labels: " << this->NumberOfLabels << "\n";
258   os << indent << "Number Of Labels Built: " << this->NumberOfLabelsBuilt << "\n";
259   os << indent << "Range: (" << this->Range[0] << ", " << this->Range[1] << ")\n";
260 
261   os << indent << "Label Format: " << this->LabelFormat << "\n";
262   os << indent << "Font Factor: " << this->FontFactor << "\n";
263   os << indent << "Label Factor: " << this->LabelFactor << "\n";
264   os << indent << "Tick Length: " << this->TickLength << "\n";
265   os << indent << "Tick Offset: " << this->TickOffset << "\n";
266 
267   os << indent << "Adjust Labels: " << (this->AdjustLabels ? "On\n" : "Off\n");
268 
269   os << indent << "Axis Visibility: " << (this->AxisVisibility ? "On\n" : "Off\n");
270 
271   os << indent << "Tick Visibility: " << (this->TickVisibility ? "On\n" : "Off\n");
272 
273   os << indent << "Label Visibility: " << (this->LabelVisibility ? "On\n" : "Off\n");
274 
275   os << indent << "Title Visibility: " << (this->TitleVisibility ? "On\n" : "Off\n");
276 
277   os << indent << "MinorTickLength: " << this->MinorTickLength << endl;
278   os << indent << "NumberOfMinorTicks: " << this->NumberOfMinorTicks << endl;
279   os << indent << "TitlePosition: " << this->TitlePosition << endl;
280 
281   os << indent
282      << "Size Font Relative To Axis: " << (this->SizeFontRelativeToAxis ? "On\n" : "Off\n");
283 }
284 
285 //------------------------------------------------------------------------------
BuildAxis(vtkViewport * viewport)286 void vtkAxisActor2D::BuildAxis(vtkViewport* viewport)
287 {
288   int i, *x, viewportSizeHasChanged, positionsHaveChanged;
289   vtkIdType ptIds[2];
290   double p1[3], p2[3], offset;
291   double interval, deltaX, deltaY;
292   double xTick[3];
293   double theta, val;
294   int *size, stringSize[2];
295   char string[512];
296 
297   if (this->TitleVisibility && !this->TitleTextProperty)
298   {
299     vtkErrorMacro(<< "Need title text property to render axis actor");
300     return;
301   }
302 
303   if (this->LabelVisibility && !this->LabelTextProperty)
304   {
305     vtkErrorMacro(<< "Need label text property to render axis actor");
306     return;
307   }
308 
309   // Check to see whether we have to rebuild everything
310   // Viewport change may not require rebuild
311   positionsHaveChanged = 0;
312   int* lastPosition = this->PositionCoordinate->GetComputedViewportValue(viewport);
313   int* lastPosition2 = this->Position2Coordinate->GetComputedViewportValue(viewport);
314   if (lastPosition[0] != this->LastPosition[0] || lastPosition[1] != this->LastPosition[1] ||
315     lastPosition2[0] != this->LastPosition2[0] || lastPosition2[1] != this->LastPosition2[1])
316   {
317     positionsHaveChanged = 1;
318   }
319 
320   // See whether fonts have to be rebuilt (font size depends on viewport size)
321   viewportSizeHasChanged = 0;
322   size = viewport->GetSize();
323   if (this->LastSize[0] != size[0] || this->LastSize[1] != size[1])
324   {
325     viewportSizeHasChanged = 1;
326     this->LastSize[0] = size[0];
327     this->LastSize[1] = size[1];
328   }
329 
330   if (!viewport->GetVTKWindow() ||
331     (!positionsHaveChanged && !viewportSizeHasChanged && viewport->GetMTime() < this->BuildTime &&
332       viewport->GetVTKWindow()->GetMTime() < this->BuildTime &&
333       this->GetMTime() < this->BuildTime &&
334       (!this->LabelVisibility || this->LabelTextProperty->GetMTime() < this->BuildTime) &&
335       (!this->TitleVisibility || this->TitleTextProperty->GetMTime() < this->BuildTime)))
336   {
337     return;
338   }
339 
340   vtkDebugMacro(<< "Rebuilding axis");
341 
342   // Initialize and get important info
343   this->Axis->Initialize();
344   this->AxisActor->SetProperty(this->GetProperty());
345 
346   // Compute the location of tick marks and labels
347 
348   this->UpdateAdjustedRange();
349 
350   interval = (this->AdjustedRange[1] - this->AdjustedRange[0]) / (this->AdjustedNumberOfLabels - 1);
351 
352   this->NumberOfLabelsBuilt = this->AdjustedNumberOfLabels;
353 
354   // Generate the axis and tick marks.
355   // We'll do our computation in viewport coordinates. First determine the
356   // location of the endpoints.
357   x = this->PositionCoordinate->GetComputedViewportValue(viewport);
358   p1[0] = x[0];
359   p1[1] = x[1];
360   p1[2] = 0.0;
361   this->LastPosition[0] = x[0];
362   this->LastPosition[1] = x[1];
363 
364   x = this->Position2Coordinate->GetComputedViewportValue(viewport);
365   p2[0] = x[0];
366   p2[1] = x[1];
367   p2[2] = 0.0;
368   this->LastPosition2[0] = x[0];
369   this->LastPosition2[1] = x[1];
370 
371   double *xp1, *xp2, len = 0.0;
372   if (this->SizeFontRelativeToAxis)
373   {
374     xp1 = this->PositionCoordinate->GetComputedDoubleViewportValue(viewport);
375     xp2 = this->Position2Coordinate->GetComputedDoubleViewportValue(viewport);
376     len = sqrt((xp2[0] - xp1[0]) * (xp2[0] - xp1[0]) + (xp2[1] - xp1[1]) * (xp2[1] - xp1[1]));
377   }
378 
379   vtkPoints* pts = vtkPoints::New();
380   vtkCellArray* lines = vtkCellArray::New();
381   this->Axis->SetPoints(pts);
382   this->Axis->SetLines(lines);
383   pts->Delete();
384   lines->Delete();
385 
386   // Generate point along axis (as well as tick points)
387   deltaX = p2[0] - p1[0];
388   deltaY = p2[1] - p1[1];
389 
390   if (deltaX == 0. && deltaY == 0.)
391   {
392     theta = 0.;
393   }
394   else
395   {
396     theta = atan2(deltaY, deltaX);
397   }
398 
399   // First axis point, where first tick is located
400   ptIds[0] = pts->InsertNextPoint(p1);
401   xTick[0] = p1[0] + this->TickLength * sin(theta);
402   xTick[1] = p1[1] - this->TickLength * cos(theta);
403   xTick[2] = 0.0;
404   pts->InsertNextPoint(xTick);
405 
406   // Set up creation of ticks
407   double p21[3], length;
408   p21[0] = p2[0] - p1[0];
409   p21[1] = p2[1] - p1[1];
410   p21[2] = p2[2] - p1[2];
411   length = vtkMath::Normalize(p21);
412 
413   // Sum of all the ticks: minor and majors. Contains the start and end ticks.
414   int numTicks;
415   // Distance between each minor tick.
416   double distance;
417   if (this->RulerMode)
418   {
419     double wp1[3], wp2[3], wp21[3];
420     this->PositionCoordinate->GetValue(wp1);
421     this->Position2Coordinate->GetValue(wp2);
422     wp21[0] = wp2[0] - wp1[0];
423     wp21[1] = wp2[1] - wp1[1];
424     wp21[2] = wp2[2] - wp1[2];
425     const double worldLength = vtkMath::Norm(wp21);
426     const double worldDistance = this->RulerDistance / (this->NumberOfMinorTicks + 1);
427     numTicks = static_cast<int>(worldDistance <= 0.0 ? 0.0 : (worldLength / worldDistance));
428     const double precision = std::numeric_limits<double>::epsilon();
429     const bool hasRemainderInDivision =
430       worldDistance <= 0.0 ? false : std::fmod(worldLength, worldDistance) > precision;
431     // numTicks must contain the start and end ticks
432     // Don't add the end tick if it is already in numTicks:
433     //   when wLength / wDistance is an integer.
434     numTicks += hasRemainderInDivision ? 2 : 1;
435     // Tick distance was computed in world coordinates, convert to viewport
436     // coordinates.
437     const double worldToLocalRatio = (worldLength <= 0.0 ? 0.0 : length / worldLength);
438     distance = worldDistance * worldToLocalRatio;
439   }
440   else
441   {
442     numTicks = (this->AdjustedNumberOfLabels - 1) * (this->NumberOfMinorTicks + 1) + 1;
443     distance = length / (numTicks - 1);
444   }
445 
446   // Only draw the inner ticks (not the start/end ticks)
447   for (i = 1; i < numTicks - 1; i++)
448   {
449     int tickLength = 0;
450     if (i % (this->NumberOfMinorTicks + 1) == 0)
451     {
452       tickLength = this->TickLength;
453     }
454     else
455     {
456       tickLength = this->MinorTickLength;
457     }
458     xTick[0] = p1[0] + i * p21[0] * distance;
459     xTick[1] = p1[1] + i * p21[1] * distance;
460     pts->InsertNextPoint(xTick);
461     xTick[0] = xTick[0] + tickLength * sin(theta);
462     xTick[1] = xTick[1] - tickLength * cos(theta);
463     pts->InsertNextPoint(xTick);
464   }
465 
466   // Last axis point
467   ptIds[1] = pts->InsertNextPoint(p2);
468   xTick[0] = p2[0] + this->TickLength * sin(theta);
469   xTick[1] = p2[1] - this->TickLength * cos(theta);
470   pts->InsertNextPoint(xTick);
471 
472   // Add the axis if requested
473   if (this->AxisVisibility)
474   {
475     lines->InsertNextCell(2, ptIds);
476   }
477 
478   // Create lines representing the tick marks
479   if (this->TickVisibility)
480   {
481     for (i = 0; i < numTicks; i++)
482     {
483       ptIds[0] = 2 * i;
484       ptIds[1] = 2 * i + 1;
485       lines->InsertNextCell(2, ptIds);
486     }
487   }
488 
489   // Build the labels
490   if (this->LabelVisibility)
491   {
492     // Update the labels text. Do it only if the range has been adjusted,
493     // i.e. if we think that new labels must be created.
494     // WARNING: if LabelFormat has changed, they should be recreated too
495     // but at this point the check on LabelFormat is "included" in
496     // UpdateAdjustedRange(), which is the function that update
497     // AdjustedRangeBuildTime or not.
498     vtkMTimeType labeltime = this->AdjustedRangeBuildTime;
499     if (this->AdjustedRangeBuildTime > this->BuildTime)
500     {
501       for (i = 0; i < this->AdjustedNumberOfLabels; i++)
502       {
503         val = this->AdjustedRange[0] + i * interval;
504         snprintf(string, sizeof(string), this->LabelFormat, val);
505         this->LabelMappers[i]->SetInput(string);
506 
507         // Check if the label text has changed
508 
509         if (this->LabelMappers[i]->GetMTime() > labeltime)
510         {
511           labeltime = this->LabelMappers[i]->GetMTime();
512         }
513       }
514     }
515 
516     // Copy prop and text prop eventually
517     for (i = 0; i < this->AdjustedNumberOfLabels; i++)
518     {
519       if (this->LabelTextProperty->GetMTime() > this->BuildTime ||
520         this->AdjustedRangeBuildTime > this->BuildTime)
521       {
522         // Shallow copy here so that the size of the label prop is not
523         // affected by the automatic adjustment of its text mapper's
524         // size (i.e. its mapper's text property is identical except
525         // for the font size which will be modified later). This
526         // allows text actors to share the same text property, and in
527         // that case specifically allows the title and label text prop
528         // to be the same.
529         this->LabelMappers[i]->GetTextProperty()->ShallowCopy(this->LabelTextProperty);
530       }
531     }
532 
533     // Resize the mappers if needed (i.e. viewport has changed, than
534     // font size should be changed, or label text property has changed,
535     // or some of the labels have changed (got bigger for example)
536     if (positionsHaveChanged || viewportSizeHasChanged ||
537       this->LabelTextProperty->GetMTime() > this->BuildTime || labeltime > this->BuildTime)
538     {
539       if (!this->SizeFontRelativeToAxis)
540       {
541         vtkTextMapper::SetMultipleRelativeFontSize(viewport, this->LabelMappers,
542           this->AdjustedNumberOfLabels, size, this->LastMaxLabelSize,
543           0.015 * this->FontFactor * this->LabelFactor);
544       }
545       else
546       {
547         int minFontSize = 1000, fontSize, minLabel = 0;
548         for (i = 0; i < this->AdjustedNumberOfLabels; i++)
549         {
550           fontSize = this->LabelMappers[i]->SetConstrainedFontSize(viewport,
551             static_cast<int>((1.0 / this->AdjustedNumberOfLabels) * len),
552             static_cast<int>(0.2 * len));
553           if (fontSize < minFontSize)
554           {
555             minFontSize = fontSize;
556             minLabel = i;
557           }
558         }
559         for (i = 0; i < this->AdjustedNumberOfLabels; i++)
560         {
561           this->LabelMappers[i]->GetTextProperty()->SetFontSize(minFontSize);
562         }
563         this->LabelMappers[minLabel]->GetSize(viewport, this->LastMaxLabelSize);
564       }
565     }
566 
567     // Position the mappers
568     for (i = 0; i < this->AdjustedNumberOfLabels; i++)
569     {
570       pts->GetPoint((this->NumberOfMinorTicks + 1) * 2 * i + 1, xTick);
571       this->LabelMappers[i]->GetSize(viewport, stringSize);
572       vtkAxisActor2D::SetOffsetPosition(xTick, theta, this->LastMaxLabelSize[0],
573         this->LastMaxLabelSize[1], this->TickOffset, this->LabelActors[i]);
574     }
575   } // If labels visible
576 
577   // Now build the title
578   if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility)
579   {
580     this->TitleMapper->SetInput(this->Title);
581 
582     if (this->TitleTextProperty->GetMTime() > this->BuildTime)
583     {
584       // Shallow copy here so that the size of the title prop is not
585       // affected by the automatic adjustment of its text mapper's
586       // size (i.e. its mapper's text property is identical except for
587       // the font size which will be modified later). This allows text
588       // actors to share the same text property, and in that case
589       // specifically allows the title and label text prop to be the same.
590       this->TitleMapper->GetTextProperty()->ShallowCopy(this->TitleTextProperty);
591     }
592 
593     if (positionsHaveChanged || viewportSizeHasChanged ||
594       this->TitleTextProperty->GetMTime() > this->BuildTime)
595     {
596       if (!this->UseFontSizeFromProperty)
597       {
598         if (!this->SizeFontRelativeToAxis)
599         {
600           vtkTextMapper::SetRelativeFontSize(
601             this->TitleMapper, viewport, size, stringSize, 0.015 * this->FontFactor);
602         }
603         else
604         {
605           this->TitleMapper->SetConstrainedFontSize(
606             viewport, static_cast<int>(0.33 * len), static_cast<int>(0.2 * len));
607           this->TitleMapper->GetSize(viewport, stringSize);
608         }
609       }
610       else
611       {
612         this->TitleMapper->GetSize(viewport, stringSize);
613       }
614     }
615     else
616     {
617       this->TitleMapper->GetSize(viewport, stringSize);
618     }
619 
620     xTick[0] = p1[0] + (p2[0] - p1[0]) * this->TitlePosition;
621     xTick[1] = p1[1] + (p2[1] - p1[1]) * this->TitlePosition;
622     xTick[0] = xTick[0] + (this->TickLength + this->TickOffset) * sin(theta);
623     xTick[1] = xTick[1] - (this->TickLength + this->TickOffset) * cos(theta);
624 
625     offset = 0.0;
626     if (this->LabelVisibility)
627     {
628       offset = vtkAxisActor2D::ComputeStringOffset(
629         this->LastMaxLabelSize[0], this->LastMaxLabelSize[1], theta);
630     }
631 
632     vtkAxisActor2D::SetOffsetPosition(
633       xTick, theta, stringSize[0], stringSize[1], static_cast<int>(offset), this->TitleActor);
634   } // If title visible
635 
636   this->BuildTime.Modified();
637 }
638 
639 //------------------------------------------------------------------------------
UpdateAdjustedRange()640 void vtkAxisActor2D::UpdateAdjustedRange()
641 {
642   // Try not to update/adjust the range to often, do not update it
643   // if the object has not been modified.
644   // Nevertheless, try the following optimization: there is no need to
645   // update the range if the position coordinate of this actor have
646   // changed. But since vtkActor2D::GetMTime() includes the check for
647   // both Position and Position2 coordinates, we will have to bypass
648   // it.
649 
650   // NOLINTNEXTLINE(bugprone-parent-virtual-call)
651   if (this->vtkActor2D::Superclass::GetMTime() <= this->AdjustedRangeBuildTime)
652   {
653     return;
654   }
655 
656   if (this->AdjustLabels)
657   {
658     double interval;
659     vtkAxisActor2D::ComputeRange(this->Range, this->AdjustedRange, this->NumberOfLabels,
660       this->AdjustedNumberOfLabels, interval);
661   }
662   else
663   {
664     this->AdjustedNumberOfLabels = this->NumberOfLabels;
665     this->AdjustedRange[0] = this->Range[0];
666     this->AdjustedRange[1] = this->Range[1];
667   }
668   this->AdjustedRangeBuildTime.Modified();
669 }
670 
671 // this is a helper function that computes some useful functions
672 // for an axis. It returns the number of ticks
vtkAxisActor2DComputeTicks(double sRange[2],double & interval,double & root)673 static int vtkAxisActor2DComputeTicks(double sRange[2], double& interval, double& root)
674 {
675   // first we try assuming the first value is reasonable
676   int numTicks;
677   double range = fabs(sRange[1] - sRange[0]);
678   int rootPower = static_cast<int>(floor(log10(range) - 1));
679   root = pow(10.0, rootPower);
680   // val will be between 10 and 100 inclusive of 10 but not 100
681   double val = range / root;
682   // first we check for an exact match
683   for (numTicks = 5; numTicks < 9; ++numTicks)
684   {
685     if (fabs(val / (numTicks - 1.0) - floor(val / (numTicks - 1.0))) < .0001)
686     {
687       interval = val * root / (numTicks - 1.0);
688       return numTicks;
689     }
690   }
691 
692   // if there isn't an exact match find a reasonable value
693   int newIntScale = 10;
694   if (val > 10)
695   {
696     newIntScale = 12;
697   }
698   if (val > 12)
699   {
700     newIntScale = 15;
701   }
702   if (val > 15)
703   {
704     newIntScale = 18;
705   }
706   if (val > 18)
707   {
708     newIntScale = 20;
709   }
710   if (val > 20)
711   {
712     newIntScale = 25;
713   }
714   if (val > 25)
715   {
716     newIntScale = 30;
717   }
718   if (val > 30)
719   {
720     newIntScale = 40;
721   }
722   if (val > 40)
723   {
724     newIntScale = 50;
725   }
726   if (val > 50)
727   {
728     newIntScale = 60;
729   }
730   if (val > 60)
731   {
732     newIntScale = 70;
733   }
734   if (val > 70)
735   {
736     newIntScale = 80;
737   }
738   if (val > 80)
739   {
740     newIntScale = 90;
741   }
742   if (val > 90)
743   {
744     newIntScale = 100;
745   }
746 
747   // how many ticks should we have
748   switch (newIntScale)
749   {
750     case 12:
751     case 20:
752     case 40:
753     case 80:
754       numTicks = 5;
755       break;
756     case 18:
757     case 30:
758     case 60:
759     case 90:
760       numTicks = 7;
761       break;
762     case 10:
763     case 15:
764     case 25:
765     case 50:
766     case 100:
767       numTicks = 6;
768       break;
769     case 70:
770       numTicks = 8;
771       break;
772   }
773 
774   interval = newIntScale * root / (numTicks - 1.0);
775   return numTicks;
776 }
777 
778 //------------------------------------------------------------------------------
779 // this method takes an initial range and an initial number of ticks and then
780 // computes a final range and number of ticks so that two properties are
781 // satisfied. First the final range includes at least the initial range, and
782 // second the final range divided by the number of ticks (minus one) will be a
783 // reasonable interval
ComputeRange(double inRange[2],double outRange[2],int vtkNotUsed (inNumTicks),int & numTicks,double & interval)784 void vtkAxisActor2D::ComputeRange(double inRange[2], double outRange[2], int vtkNotUsed(inNumTicks),
785   int& numTicks, double& interval)
786 {
787   // Handle the range
788   double sRange[2];
789   if (inRange[0] < inRange[1])
790   {
791     sRange[0] = inRange[0];
792     sRange[1] = inRange[1];
793   }
794   else if (inRange[0] > inRange[1])
795   {
796     sRange[1] = inRange[0];
797     sRange[0] = inRange[1];
798   }
799   else // they're equal, so perturb them by 1 percent
800   {
801     double perturb = 100.;
802     if (inRange[0] == 0.0)
803     { // if they are both zero, then just perturb about zero
804       sRange[0] = -1 / perturb;
805       sRange[1] = 1 / perturb;
806     }
807     else
808     {
809       sRange[0] = inRange[0] - inRange[0] / perturb;
810       sRange[1] = inRange[0] + inRange[0] / perturb;
811     }
812   }
813 
814   double root;
815   numTicks = vtkAxisActor2DComputeTicks(sRange, interval, root);
816 
817   // is the starting point reasonable?
818   if (fabs(sRange[0] / root - floor(sRange[0] / root)) < 0.01)
819   {
820     outRange[0] = sRange[0];
821     outRange[1] = outRange[0] + (numTicks - 1.0) * interval;
822   }
823   else
824   {
825     // OK the starting point is not a good number, so we must widen the range
826     // First see if the current range will handle moving the start point
827     outRange[0] = floor(sRange[0] / root) * root;
828     if (outRange[0] + (numTicks - 1.0) * interval <= sRange[1])
829     {
830       outRange[1] = outRange[0] + (numTicks - 1.0) * interval;
831     }
832     else
833     {
834       // Finally in this case we must switch to a larger range to
835       // have reasonable starting and ending values
836       sRange[0] = outRange[0];
837       numTicks = vtkAxisActor2DComputeTicks(sRange, interval, root);
838       outRange[1] = outRange[0] + (numTicks - 1.0) * interval;
839     }
840   }
841 
842   // Adust if necessary
843   if (inRange[0] > inRange[1])
844   {
845     sRange[0] = outRange[1];
846     outRange[1] = outRange[0];
847     outRange[0] = sRange[0];
848     interval = -interval;
849   }
850 }
851 
852 //------------------------------------------------------------------------------
853 // Position text with respect to a point (xTick) where the angle of the line
854 // from the point to the center of the text is given by theta. The offset
855 // is the spacing between ticks and labels.
SetOffsetPosition(double xTick[3],double theta,int stringWidth,int stringHeight,int offset,vtkActor2D * actor)856 void vtkAxisActor2D::SetOffsetPosition(
857   double xTick[3], double theta, int stringWidth, int stringHeight, int offset, vtkActor2D* actor)
858 {
859   double x, y, center[2];
860   int pos[2];
861 
862   x = stringWidth / 2.0 + offset;
863   y = stringHeight / 2.0 + offset;
864 
865   center[0] = xTick[0] + x * sin(theta);
866   center[1] = xTick[1] - y * cos(theta);
867 
868   pos[0] = static_cast<int>(center[0] - stringWidth / 2.0);
869   pos[1] = static_cast<int>(center[1] - stringHeight / 2.0);
870 
871   actor->SetPosition(pos[0], pos[1]);
872 }
873 
874 //------------------------------------------------------------------------------
ComputeStringOffset(double width,double height,double theta)875 double vtkAxisActor2D::ComputeStringOffset(double width, double height, double theta)
876 {
877   double f1 = height * cos(theta);
878   double f2 = width * sin(theta);
879   return (1.2 * sqrt(f1 * f1 + f2 * f2));
880 }
881 
882 //------------------------------------------------------------------------------
ShallowCopy(vtkProp * prop)883 void vtkAxisActor2D::ShallowCopy(vtkProp* prop)
884 {
885   vtkAxisActor2D* a = vtkAxisActor2D::SafeDownCast(prop);
886   if (a != nullptr)
887   {
888     this->SetRange(a->GetRange());
889     this->SetNumberOfLabels(a->GetNumberOfLabels());
890     this->SetLabelFormat(a->GetLabelFormat());
891     this->SetAdjustLabels(a->GetAdjustLabels());
892     this->SetTitle(a->GetTitle());
893     this->SetTickLength(a->GetTickLength());
894     this->SetTickOffset(a->GetTickOffset());
895     this->SetAxisVisibility(a->GetAxisVisibility());
896     this->SetTickVisibility(a->GetTickVisibility());
897     this->SetLabelVisibility(a->GetLabelVisibility());
898     this->SetTitleVisibility(a->GetTitleVisibility());
899     this->SetFontFactor(a->GetFontFactor());
900     this->SetLabelFactor(a->GetLabelFactor());
901     this->SetLabelTextProperty(a->GetLabelTextProperty());
902     this->SetTitleTextProperty(a->GetTitleTextProperty());
903   }
904 
905   // Now do superclass
906   this->vtkActor2D::ShallowCopy(prop);
907 }
908