mxRadialTreeLayout.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxRadialTreeLayout
  7. *
  8. * Extends <mxGraphLayout> to implement a radial tree algorithm. This
  9. * layout is suitable for graphs that have no cycles (trees). Vertices that are
  10. * not connected to the tree will be ignored by this layout.
  11. *
  12. * Example:
  13. *
  14. * (code)
  15. * var layout = new mxRadialTreeLayout(graph);
  16. * layout.execute(graph.getDefaultParent());
  17. * (end)
  18. *
  19. * Constructor: mxRadialTreeLayout
  20. *
  21. * Constructs a new radial tree layout for the specified graph
  22. */
  23. function mxRadialTreeLayout(graph)
  24. {
  25. mxCompactTreeLayout.call(this, graph , false);
  26. };
  27. /**
  28. * Extends mxGraphLayout.
  29. */
  30. mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);
  31. /**
  32. * Variable: angleOffset
  33. *
  34. * The initial offset to compute the angle position.
  35. */
  36. mxRadialTreeLayout.prototype.angleOffset = 0.5;
  37. /**
  38. * Variable: rootx
  39. *
  40. * The X co-ordinate of the root cell
  41. */
  42. mxRadialTreeLayout.prototype.rootx = 0;
  43. /**
  44. * Variable: rooty
  45. *
  46. * The Y co-ordinate of the root cell
  47. */
  48. mxRadialTreeLayout.prototype.rooty = 0;
  49. /**
  50. * Variable: levelDistance
  51. *
  52. * Holds the levelDistance. Default is 120.
  53. */
  54. mxRadialTreeLayout.prototype.levelDistance = 120;
  55. /**
  56. * Variable: nodeDistance
  57. *
  58. * Holds the nodeDistance. Default is 10.
  59. */
  60. mxRadialTreeLayout.prototype.nodeDistance = 10;
  61. /**
  62. * Variable: autoRadius
  63. *
  64. * Specifies if the radios should be computed automatically
  65. */
  66. mxRadialTreeLayout.prototype.autoRadius = false;
  67. /**
  68. * Variable: sortEdges
  69. *
  70. * Specifies if edges should be sorted according to the order of their
  71. * opposite terminal cell in the model.
  72. */
  73. mxRadialTreeLayout.prototype.sortEdges = false;
  74. /**
  75. * Variable: rowMinX
  76. *
  77. * Array of leftmost x coordinate of each row
  78. */
  79. mxRadialTreeLayout.prototype.rowMinX = [];
  80. /**
  81. * Variable: rowMaxX
  82. *
  83. * Array of rightmost x coordinate of each row
  84. */
  85. mxRadialTreeLayout.prototype.rowMaxX = [];
  86. /**
  87. * Variable: rowMinCenX
  88. *
  89. * Array of x coordinate of leftmost vertex of each row
  90. */
  91. mxRadialTreeLayout.prototype.rowMinCenX = [];
  92. /**
  93. * Variable: rowMaxCenX
  94. *
  95. * Array of x coordinate of rightmost vertex of each row
  96. */
  97. mxRadialTreeLayout.prototype.rowMaxCenX = [];
  98. /**
  99. * Variable: rowRadi
  100. *
  101. * Array of y deltas of each row behind root vertex, also the radius in the tree
  102. */
  103. mxRadialTreeLayout.prototype.rowRadi = [];
  104. /**
  105. * Variable: row
  106. *
  107. * Array of vertices on each row
  108. */
  109. mxRadialTreeLayout.prototype.row = [];
  110. /**
  111. * Function: isVertexIgnored
  112. *
  113. * Returns a boolean indicating if the given <mxCell> should be ignored as a
  114. * vertex. This returns true if the cell has no connections.
  115. *
  116. * Parameters:
  117. *
  118. * vertex - <mxCell> whose ignored state should be returned.
  119. */
  120. mxRadialTreeLayout.prototype.isVertexIgnored = function(vertex)
  121. {
  122. return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
  123. this.graph.getConnections(vertex).length == 0;
  124. };
  125. /**
  126. * Function: execute
  127. *
  128. * Implements <mxGraphLayout.execute>.
  129. *
  130. * If the parent has any connected edges, then it is used as the root of
  131. * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
  132. * root node within the set of children of the given parent.
  133. *
  134. * Parameters:
  135. *
  136. * parent - <mxCell> whose children should be laid out.
  137. * root - Optional <mxCell> that will be used as the root of the tree.
  138. */
  139. mxRadialTreeLayout.prototype.execute = function(parent, root)
  140. {
  141. this.parent = parent;
  142. this.useBoundingBox = false;
  143. this.edgeRouting = false;
  144. //this.horizontal = false;
  145. mxCompactTreeLayout.prototype.execute.apply(this, arguments);
  146. var bounds = null;
  147. var rootBounds = this.getVertexBounds(this.root);
  148. this.centerX = rootBounds.x + rootBounds.width / 2;
  149. this.centerY = rootBounds.y + rootBounds.height / 2;
  150. // Calculate the bounds of the involved vertices directly from the values set in the compact tree
  151. for (var vertex in this.visited)
  152. {
  153. var vertexBounds = this.getVertexBounds(this.visited[vertex]);
  154. bounds = (bounds != null) ? bounds : vertexBounds.clone();
  155. bounds.add(vertexBounds);
  156. }
  157. this.calcRowDims([this.node], 0);
  158. var maxLeftGrad = 0;
  159. var maxRightGrad = 0;
  160. // Find the steepest left and right gradients
  161. for (var i = 0; i < this.row.length; i++)
  162. {
  163. var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];
  164. var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];
  165. maxLeftGrad = Math.max (maxLeftGrad, leftGrad);
  166. maxRightGrad = Math.max (maxRightGrad, rightGrad);
  167. }
  168. // Extend out row so they meet the maximum gradient and convert to polar co-ords
  169. for (var i = 0; i < this.row.length; i++)
  170. {
  171. var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];
  172. var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];
  173. var fullWidth = xRightLimit - xLeftLimit;
  174. for (var j = 0; j < this.row[i].length; j ++)
  175. {
  176. var row = this.row[i];
  177. var node = row[j];
  178. var vertexBounds = this.getVertexBounds(node.cell);
  179. var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth);
  180. var theta = 2 * Math.PI * xProportion;
  181. node.theta = theta;
  182. }
  183. }
  184. // Post-process from outside inwards to try to align parents with children
  185. for (var i = this.row.length - 2; i >= 0; i--)
  186. {
  187. var row = this.row[i];
  188. for (var j = 0; j < row.length; j++)
  189. {
  190. var node = row[j];
  191. var child = node.child;
  192. var counter = 0;
  193. var totalTheta = 0;
  194. while (child != null)
  195. {
  196. totalTheta += child.theta;
  197. counter++;
  198. child = child.next;
  199. }
  200. if (counter > 0)
  201. {
  202. var averTheta = totalTheta / counter;
  203. if (averTheta > node.theta && j < row.length - 1)
  204. {
  205. var nextTheta = row[j+1].theta;
  206. node.theta = Math.min (averTheta, nextTheta - Math.PI/10);
  207. }
  208. else if (averTheta < node.theta && j > 0 )
  209. {
  210. var lastTheta = row[j-1].theta;
  211. node.theta = Math.max (averTheta, lastTheta + Math.PI/10);
  212. }
  213. }
  214. }
  215. }
  216. // Set locations
  217. for (var i = 0; i < this.row.length; i++)
  218. {
  219. for (var j = 0; j < this.row[i].length; j ++)
  220. {
  221. var row = this.row[i];
  222. var node = row[j];
  223. var vertexBounds = this.getVertexBounds(node.cell);
  224. this.setVertexLocation(node.cell,
  225. this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),
  226. this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta));
  227. }
  228. }
  229. };
  230. /**
  231. * Function: calcRowDims
  232. *
  233. * Recursive function to calculate the dimensions of each row
  234. *
  235. * Parameters:
  236. *
  237. * row - Array of internal nodes, the children of which are to be processed.
  238. * rowNum - Integer indicating which row is being processed.
  239. */
  240. mxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum)
  241. {
  242. if (row == null || row.length == 0)
  243. {
  244. return;
  245. }
  246. // Place root's children proportionally around the first level
  247. this.rowMinX[rowNum] = this.centerX;
  248. this.rowMaxX[rowNum] = this.centerX;
  249. this.rowMinCenX[rowNum] = this.centerX;
  250. this.rowMaxCenX[rowNum] = this.centerX;
  251. this.row[rowNum] = [];
  252. var rowHasChildren = false;
  253. for (var i = 0; i < row.length; i++)
  254. {
  255. var child = row[i] != null ? row[i].child : null;
  256. while (child != null)
  257. {
  258. var cell = child.cell;
  259. var vertexBounds = this.getVertexBounds(cell);
  260. this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);
  261. this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);
  262. this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);
  263. this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);
  264. this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;
  265. if (child.child != null)
  266. {
  267. rowHasChildren = true;
  268. }
  269. this.row[rowNum].push(child);
  270. child = child.next;
  271. }
  272. }
  273. if (rowHasChildren)
  274. {
  275. this.calcRowDims(this.row[rowNum], rowNum + 1);
  276. }
  277. };
  278. __mxOutput.mxRadialTreeLayout = typeof mxRadialTreeLayout !== 'undefined' ? mxRadialTreeLayout : undefined;