1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkStackedTreeLayoutStrategy.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   Copyright 2008 Sandia Corporation.
17   Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
18   the U.S. Government retains certain rights in this software.
19 -------------------------------------------------------------------------*/
20 
21 #include "vtkStackedTreeLayoutStrategy.h"
22 
23 #include "vtkCellArray.h"
24 #include "vtkCellData.h"
25 #include "vtkDataArray.h"
26 #include "vtkDoubleArray.h"
27 #include "vtkFloatArray.h"
28 #include "vtkInformation.h"
29 #include "vtkInformationVector.h"
30 #include "vtkIntArray.h"
31 #include "vtkMath.h"
32 #include "vtkObjectFactory.h"
33 #include "vtkPointData.h"
34 #include "vtkPoints.h"
35 #include "vtkTree.h"
36 #include "vtkTreeDFSIterator.h"
37 #include "vtkTreeLevelsFilter.h"
38 
39 #include "vtkSmartPointer.h"
40 #define VTK_CREATE(type, name) vtkSmartPointer<type> name = vtkSmartPointer<type>::New()
41 
42 vtkStandardNewMacro(vtkStackedTreeLayoutStrategy);
43 
vtkStackedTreeLayoutStrategy()44 vtkStackedTreeLayoutStrategy::vtkStackedTreeLayoutStrategy()
45 {
46   this->InteriorRadius = 6.0;
47   this->RingThickness = 1.0;
48   this->RootStartAngle = 0.;
49   this->RootEndAngle = 360.;
50   this->UseRectangularCoordinates = false;
51   this->Reverse = false;
52   this->InteriorLogSpacingValue = 1.0;
53 }
54 
55 vtkStackedTreeLayoutStrategy::~vtkStackedTreeLayoutStrategy() = default;
56 
PrintSelf(ostream & os,vtkIndent indent)57 void vtkStackedTreeLayoutStrategy::PrintSelf(ostream& os, vtkIndent indent)
58 {
59   this->Superclass::PrintSelf(os, indent);
60   os << indent << "InteriorRadius: " << this->InteriorRadius << endl;
61   os << indent << "RingThickness: " << this->RingThickness << endl;
62   os << indent << "RootStartAngle: " << this->RootStartAngle << endl;
63   os << indent << "RootEndAngle: " << this->RootEndAngle << endl;
64   os << indent << "UseRectangularCoordinates: " << this->UseRectangularCoordinates << endl;
65   os << indent << "Reverse: " << this->Reverse << endl;
66   os << indent << "InteriorLogSpacingValue: " << this->InteriorLogSpacingValue << endl;
67 }
68 
Layout(vtkTree * inputTree,vtkDataArray * coordsArray,vtkDataArray * sizeArray)69 void vtkStackedTreeLayoutStrategy::Layout(
70   vtkTree* inputTree, vtkDataArray* coordsArray, vtkDataArray* sizeArray)
71 {
72   if (!inputTree || inputTree->GetNumberOfVertices() == 0)
73   {
74     return;
75   }
76   if (!coordsArray)
77   {
78     vtkErrorMacro("Area array not defined.");
79     return;
80   }
81 
82   vtkDataSetAttributes* data = inputTree->GetVertexData();
83 
84   VTK_CREATE(vtkDoubleArray, textRotationArray);
85   textRotationArray->SetName("TextRotation");
86   textRotationArray->SetNumberOfComponents(1);
87   textRotationArray->SetNumberOfTuples(inputTree->GetNumberOfVertices());
88   data->AddArray(textRotationArray);
89 
90   VTK_CREATE(vtkDoubleArray, textBoundedSizeArray);
91   textBoundedSizeArray->SetName("TextBoundedSize");
92   textBoundedSizeArray->SetNumberOfComponents(2);
93   textBoundedSizeArray->SetNumberOfTuples(inputTree->GetNumberOfVertices());
94   data->AddArray(textBoundedSizeArray);
95 
96   double outer_radius = 0.0;
97   if (this->Reverse)
98   {
99     VTK_CREATE(vtkTreeLevelsFilter, levelFilter);
100     VTK_CREATE(vtkTree, newTree);
101     newTree->ShallowCopy(inputTree);
102     levelFilter->SetInputData(newTree);
103     levelFilter->Update();
104     vtkTree* levelTree = levelFilter->GetOutput();
105 
106     vtkIntArray* levelArray =
107       vtkArrayDownCast<vtkIntArray>(levelTree->GetVertexData()->GetAbstractArray("level"));
108     int max_level = 0;
109     for (int i = 0; i < levelTree->GetNumberOfVertices(); i++)
110     {
111       int level = levelArray->GetValue(i);
112       if (level > max_level)
113       {
114         max_level = level;
115       }
116     }
117     outer_radius = max_level * this->RingThickness + this->InteriorRadius;
118   }
119 
120   // Get the root vertex and set it
121   vtkIdType rootId = inputTree->GetRoot();
122   float coords[4] = { this->RootStartAngle, this->RootEndAngle, 0.0, 0.0 };
123   if (this->Reverse)
124   {
125     coords[2] = outer_radius - this->RingThickness;
126     coords[3] = outer_radius;
127   }
128   else
129   {
130     coords[3] = this->InteriorRadius;
131   }
132   coordsArray->SetTuple(rootId, coords);
133 
134   // Now layout the children vertices
135   this->LayoutChildren(inputTree, coordsArray, sizeArray, inputTree->GetNumberOfChildren(rootId),
136     rootId, 0, coords[2], coords[3], coords[0], coords[1]);
137 
138   vtkPoints* points = vtkPoints::New();
139   vtkIdType numVerts = inputTree->GetNumberOfVertices();
140   points->SetNumberOfPoints(numVerts);
141   for (vtkIdType i = 0; i < numVerts; i++)
142   {
143     double sector_coords[4];
144     coordsArray->GetTuple(i, sector_coords);
145     double x, y, z;
146     if (this->UseRectangularCoordinates)
147     {
148       x = 0.5 * (sector_coords[0] + sector_coords[1]);
149       y = 0.5 * (sector_coords[2] + sector_coords[3]);
150       z = 0.;
151 
152       textRotationArray->SetValue(i, 0);
153       textBoundedSizeArray->SetValue(2 * i, sector_coords[1] - sector_coords[0]);
154       textBoundedSizeArray->SetValue(2 * i + 1, sector_coords[3] - sector_coords[2]);
155     }
156     else
157     {
158       if (i == rootId)
159       {
160         x = y = z = 0.;
161 
162         textRotationArray->SetValue(i, 0);
163         textBoundedSizeArray->SetValue(2 * i, 0);
164         textBoundedSizeArray->SetValue(2 * i + 1, 0);
165       }
166       else
167       {
168         double r = (0.5 * (sector_coords[3] - sector_coords[2])) + sector_coords[2];
169         double theta = sector_coords[0] + (0.5 * (sector_coords[1] - sector_coords[0]));
170         x = r * cos(vtkMath::RadiansFromDegrees(theta));
171         y = r * sin(vtkMath::RadiansFromDegrees(theta));
172         z = 0.;
173 
174         double sector_arc_length =
175           r * vtkMath::RadiansFromDegrees(sector_coords[1] - sector_coords[0]);
176         double radial_arc_length = sector_coords[3] - sector_coords[2];
177         double aspect_ratio = sector_arc_length / radial_arc_length;
178         if (aspect_ratio > 1)
179         {
180           // sector length is greater than radial length;
181           // align text with the sector
182           if (theta > 0. && theta < 180.)
183           {
184             textRotationArray->SetValue(i, theta - 90.);
185           }
186           else
187           {
188             textRotationArray->SetValue(i, theta + 90.);
189           }
190           textBoundedSizeArray->SetValue(2 * i, sector_arc_length);
191           textBoundedSizeArray->SetValue(2 * i + 1, radial_arc_length);
192         }
193         else
194         {
195           // radial length is greater than sector length;
196           // align text radially...
197           if (theta > 90. && theta < 270.)
198           {
199             textRotationArray->SetValue(i, theta - 180.);
200           }
201           else
202           {
203             textRotationArray->SetValue(i, theta);
204           }
205           textBoundedSizeArray->SetValue(2 * i, radial_arc_length);
206           textBoundedSizeArray->SetValue(2 * i + 1, sector_arc_length);
207         }
208       }
209     }
210     points->SetPoint(i, x, y, z);
211   }
212   inputTree->SetPoints(points);
213   points->Delete();
214 }
215 
LayoutEdgePoints(vtkTree * inputTree,vtkDataArray * sectorsArray,vtkDataArray * vtkNotUsed (sizeArray),vtkTree * outputTree)216 void vtkStackedTreeLayoutStrategy::LayoutEdgePoints(vtkTree* inputTree, vtkDataArray* sectorsArray,
217   vtkDataArray* vtkNotUsed(sizeArray), vtkTree* outputTree)
218 {
219   VTK_CREATE(vtkTreeLevelsFilter, levelFilter);
220   VTK_CREATE(vtkTree, newTree);
221   newTree->ShallowCopy(inputTree);
222   levelFilter->SetInputData(newTree);
223   levelFilter->Update();
224   vtkTree* levelTree = levelFilter->GetOutput();
225   outputTree->ShallowCopy(levelTree);
226 
227   vtkIntArray* levelArray =
228     vtkArrayDownCast<vtkIntArray>(levelTree->GetVertexData()->GetAbstractArray("level"));
229 
230   double exteriorRadius = VTK_DOUBLE_MAX;
231   double sector_coords[4];
232   int max_level = 0;
233   for (int i = 0; i < outputTree->GetNumberOfVertices(); i++)
234   {
235     int level = levelArray->GetValue(i);
236     if (level > max_level)
237     {
238       max_level = level;
239     }
240     if (inputTree->IsLeaf(i))
241     {
242       sectorsArray->GetTuple(i, sector_coords);
243       if (sector_coords[2] < exteriorRadius)
244       {
245         exteriorRadius = sector_coords[2];
246       }
247     }
248   }
249 
250   double spacing = this->InteriorLogSpacingValue;
251 
252   // The distance between level L-1 and L is s^L.
253   // Thus, if s < 1 then the distance between levels gets smaller in higher levels,
254   //       if s = 1 the distance remains the same, and
255   //       if s > 1 the distance get larger.
256   // The height (distance from the root) of level L, then, is
257   // s + s^2 + s^3 + ... + s^L, where s is the log spacing value.
258   // The max height (used for normalization) is
259   // s + s^2 + s^3 + ... + s^maxLevel.
260   // The quick formula for computing this is
261   // sum_{i=1}^{n} s^i = (s^(n+1) - 1)/(s - 1) - 1        if s != 1
262   //                   = n                                if s == 1
263   double maxHeight = max_level;
264   double eps = 1e-8;
265   double diff = spacing - 1.0 > 0 ? spacing - 1.0 : 1.0 - spacing;
266   if (diff > eps)
267   {
268     maxHeight = (pow(spacing, max_level + 1.0) - 1.0) / (spacing - 1.0) - 1.0;
269   }
270 
271   vtkPoints* points = vtkPoints::New();
272   vtkIdType rootId = outputTree->GetRoot();
273   vtkIdType numVerts = outputTree->GetNumberOfVertices();
274   points->SetNumberOfPoints(numVerts);
275   for (vtkIdType i = 0; i < numVerts; i++)
276   {
277     if (!this->UseRectangularCoordinates && i == rootId)
278     {
279       points->SetPoint(i, 0, 0, 0);
280       continue;
281     }
282 
283     sectorsArray->GetTuple(i, sector_coords);
284 
285     double x = 0.0;
286     double y = 0.0;
287     double z = 0.0;
288     if (this->UseRectangularCoordinates)
289     {
290       if (inputTree->IsLeaf(i))
291       {
292         if (this->Reverse)
293         {
294           y = sector_coords[2];
295         }
296         else
297         {
298           y = sector_coords[3];
299         }
300       }
301       else
302       {
303         if (this->Reverse)
304         {
305           y = this->InteriorRadius -
306             this->RingThickness * (maxHeight + maxHeight - inputTree->GetLevel(i));
307         }
308         else
309         {
310           y = this->InteriorRadius +
311             this->RingThickness * (maxHeight + maxHeight - inputTree->GetLevel(i));
312         }
313       }
314       x = 0.5 * (sector_coords[0] + sector_coords[1]);
315       z = 0.;
316     }
317     else
318     {
319       double r;
320       if (inputTree->IsLeaf(i))
321       {
322         r = sector_coords[2];
323       }
324       else
325       {
326         if (diff <= eps)
327         {
328           r = outputTree->GetLevel(i) / maxHeight;
329         }
330         else
331         {
332           r = ((pow(spacing, outputTree->GetLevel(i) + 1.0) - 1.0) / (spacing - 1.0) - 1.0) /
333             maxHeight;
334         }
335         // scale the spacing value based on the radius of the
336         // circle we have to work with...
337         r *= exteriorRadius;
338       }
339 
340       double theta = sector_coords[0] + (0.5 * (sector_coords[1] - sector_coords[0]));
341       x = r * cos(vtkMath::RadiansFromDegrees(theta));
342       y = r * sin(vtkMath::RadiansFromDegrees(theta));
343       z = 0.;
344     }
345     points->SetPoint(i, x, y, z);
346   }
347   outputTree->SetPoints(points);
348   points->Delete();
349 }
350 
LayoutChildren(vtkTree * tree,vtkDataArray * coordsArray,vtkDataArray * sizeArray,vtkIdType nchildren,vtkIdType parent,vtkIdType begin,float parentInnerRad,float parentOuterRad,float parentStartAng,float parentEndAng)351 void vtkStackedTreeLayoutStrategy::LayoutChildren(vtkTree* tree, vtkDataArray* coordsArray,
352   vtkDataArray* sizeArray, vtkIdType nchildren, vtkIdType parent, vtkIdType begin,
353   float parentInnerRad, float parentOuterRad, float parentStartAng, float parentEndAng)
354 {
355   double new_interior_rad = 0.0;
356   double new_outer_rad = 0.0;
357   if (this->Reverse)
358   {
359     new_interior_rad = parentInnerRad - this->RingThickness;
360     new_outer_rad = parentInnerRad;
361   }
362   else
363   {
364     new_interior_rad = parentOuterRad;
365     new_outer_rad = new_interior_rad + this->RingThickness;
366   }
367   // FIXME - we may want to do this instead...
368   // double new_outer_rad = new_interior_rad +this->RingThickness[level];
369 
370   double radial_spacing = this->ShrinkPercentage * this->RingThickness;
371   new_outer_rad -= radial_spacing;
372   // new_interior_rad += 0.5*radial_spacing;
373 
374   // now calculate the width of each of the sectors for each vertex
375   // first calculate the total summed weight for each of the children vertices
376   double total_weighted_sum = 0;
377   vtkIdType i;
378   for (i = begin; i < nchildren; i++)
379   {
380     if (sizeArray)
381     {
382       total_weighted_sum += static_cast<float>(sizeArray->GetTuple1(tree->GetChild(parent, i)));
383     }
384     else
385     {
386       total_weighted_sum += 1.0;
387     }
388   }
389 
390   // If we are doing radial layout, put extra space on the full rings
391   // so the first and last children don't butt up against each other.
392   vtkIdType num_spaces = nchildren - 1;
393   double parent_angle = parentEndAng - parentStartAng;
394   if (!this->UseRectangularCoordinates && parent_angle == 360.0)
395   {
396     num_spaces = nchildren;
397   }
398   double available_angle = parent_angle;
399   double conversion = vtkMath::Pi() / 180.0;
400   double spacing = 0.0;
401   if (nchildren > 1)
402   {
403     double parent_length;
404     if (this->UseRectangularCoordinates)
405     {
406       parent_length = parent_angle;
407     }
408     else
409     {
410       parent_length = conversion * parent_angle * new_outer_rad;
411     }
412     double spacing_length;
413     if (radial_spacing * num_spaces > 0.25 * parent_length)
414     {
415       spacing_length = 0.25 * parent_length;
416     }
417     else
418     {
419       spacing_length = radial_spacing * num_spaces;
420     }
421     double total_space;
422     if (this->UseRectangularCoordinates)
423     {
424       total_space = spacing_length;
425     }
426     else
427     {
428       total_space = spacing_length / new_outer_rad / conversion;
429     }
430     spacing = total_space / num_spaces;
431     available_angle -= total_space;
432   }
433 
434   float coords[4];
435   double current_angle = parentStartAng;
436   for (i = begin; i < nchildren; i++)
437   {
438     int id = tree->GetChild(parent, i);
439     float cur_size = 1.0;
440     if (sizeArray)
441     {
442       cur_size = static_cast<float>(sizeArray->GetTuple1(id));
443     }
444     double this_arc = available_angle * (cur_size / total_weighted_sum);
445 
446     coords[2] = new_interior_rad;
447     coords[3] = new_outer_rad;
448     coords[0] = current_angle;
449     coords[1] = current_angle + this_arc;
450 
451     coordsArray->SetTuple(id, coords);
452 
453     current_angle += this_arc + spacing;
454 
455     vtkIdType numNewChildren = tree->GetNumberOfChildren(id);
456     if (numNewChildren > 0)
457     {
458       this->LayoutChildren(tree, coordsArray, sizeArray, numNewChildren, id, 0, coords[2],
459         coords[3], coords[0], coords[1]);
460     }
461   }
462 }
463 
FindVertex(vtkTree * otree,vtkDataArray * array,float pnt[2])464 vtkIdType vtkStackedTreeLayoutStrategy::FindVertex(
465   vtkTree* otree, vtkDataArray* array, float pnt[2])
466 {
467   if (this->UseRectangularCoordinates)
468   {
469     float blimits[4];
470     vtkIdType vertex = otree->GetRoot();
471     if (vertex < 0)
472     {
473       return vertex;
474     }
475     vtkFloatArray* boundsInfo = vtkArrayDownCast<vtkFloatArray>(array);
476 
477     // Now try to find the vertex that contains the point
478     boundsInfo->GetTypedTuple(vertex, blimits); // Get the extents of the root
479     if (((pnt[1] > blimits[2]) && (pnt[1] < blimits[3])) &&
480       ((pnt[0] > blimits[0]) && (pnt[0] < blimits[1])))
481     {
482       // Point is at the root vertex.
483       return vertex;
484     }
485 
486     // Now traverse the children to try and find
487     // the vertex that contains the point
488     vtkIdType child;
489     VTK_CREATE(vtkTreeDFSIterator, it);
490     it->SetTree(otree);
491     it->SetStartVertex(vertex);
492 
493     while (it->HasNext())
494     {
495       child = it->Next();
496       boundsInfo->GetTypedTuple(child, blimits); // Get the extents of the child
497       bool beyond_radial_bounds = false;
498       bool beyond_angle_bounds = false;
499       if ((pnt[1] < blimits[2]) || (pnt[1] > blimits[3]))
500         beyond_radial_bounds = true;
501       if ((pnt[0] < blimits[0]) || (pnt[0] > blimits[1]))
502         beyond_angle_bounds = true;
503 
504       if (beyond_radial_bounds || beyond_angle_bounds)
505       {
506         continue;
507       }
508       // If we are here then the point is contained by the child
509       return child;
510     }
511   }
512   else
513   {
514     // Radial layout
515     float polar_location[2];
516     polar_location[0] = sqrt((pnt[0] * pnt[0]) + (pnt[1] * pnt[1]));
517     polar_location[1] = vtkMath::DegreesFromRadians(atan2(pnt[1], pnt[0]));
518     if (polar_location[1] < 0)
519       polar_location[1] += 360.;
520 
521     float blimits[4];
522     vtkIdType vertex = otree->GetRoot();
523     if (vertex < 0)
524     {
525       return vertex;
526     }
527     vtkFloatArray* boundsInfo = vtkArrayDownCast<vtkFloatArray>(array);
528 
529     // Now try to find the vertex that contains the point
530     boundsInfo->GetTypedTuple(vertex, blimits); // Get the extents of the root
531     if (((polar_location[0] > blimits[2]) && (polar_location[0] < blimits[3])) &&
532       ((polar_location[1] > blimits[0]) && (polar_location[1] < blimits[1])))
533     {
534       // Point is at the root vertex.
535       // but we don't want the root to be pickable, so return -1.
536       // This won't work for blimits spanning the 0/360 rollover, but the test below
537       // catches that case.
538       return -1;
539     }
540 
541     // Now traverse the children to try and find
542     // the vertex that contains the point
543     vtkIdType child;
544     VTK_CREATE(vtkTreeDFSIterator, it);
545     it->SetTree(otree);
546     it->SetStartVertex(vertex);
547 
548     while (it->HasNext())
549     {
550       child = it->Next();
551       // if the root boundary starts anywhere but zero, the root node will have passed
552       // the earlier test.  This will skip the root and prevent it from being picked.
553       if (child == vertex)
554       {
555         continue;
556       }
557       boundsInfo->GetTypedTuple(child, blimits); // Get the extents of the child
558 
559       // the range checking below doesn't work if either or both of blimits > 360
560       if ((blimits[0] > 360.0) && (blimits[1] > 360.0))
561       {
562         blimits[0] -= 360.0;
563         blimits[1] -= 360.0;
564       }
565       else if ((blimits[0] < 360.0) && (blimits[1] > 360.0) && (polar_location[1] < 360.0))
566       { // if the range spans the rollover at 0/360 on the circle
567         if (polar_location[1] < 90.0)
568         {
569           blimits[0] = 0.0;
570           blimits[1] -= 360.0;
571         }
572         else if (polar_location[1] > 270.)
573         {
574           blimits[1] = 360.0;
575         }
576       }
577       bool beyond_radial_bounds = false;
578       bool beyond_angle_bounds = false;
579       if ((polar_location[0] < blimits[2]) || (polar_location[0] > blimits[3]))
580         beyond_radial_bounds = true;
581       if ((polar_location[1] < blimits[0]) || (polar_location[1] > blimits[1]))
582         beyond_angle_bounds = true;
583 
584       if (beyond_radial_bounds || beyond_angle_bounds)
585       {
586         continue;
587       }
588       // If we are here then the point is contained by the child
589       return child;
590     }
591   }
592   return -1;
593 }
594