mxFastOrganicLayout.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxFastOrganicLayout
  7. *
  8. * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
  9. * The vertices need to be connected for this layout to work, vertices
  10. * with no connections are ignored.
  11. *
  12. * Example:
  13. *
  14. * (code)
  15. * var layout = new mxFastOrganicLayout(graph);
  16. * layout.execute(graph.getDefaultParent());
  17. * (end)
  18. *
  19. * Constructor: mxCompactTreeLayout
  20. *
  21. * Constructs a new fast organic layout for the specified graph.
  22. */
  23. function mxFastOrganicLayout(graph)
  24. {
  25. mxGraphLayout.call(this, graph);
  26. };
  27. /**
  28. * Extends mxGraphLayout.
  29. */
  30. mxFastOrganicLayout.prototype = new mxGraphLayout();
  31. mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
  32. /**
  33. * Variable: useInputOrigin
  34. *
  35. * Specifies if the top left corner of the input cells should be the origin
  36. * of the layout result. Default is true.
  37. */
  38. mxFastOrganicLayout.prototype.useInputOrigin = true;
  39. /**
  40. * Variable: resetEdges
  41. *
  42. * Specifies if all edge points of traversed edges should be removed.
  43. * Default is true.
  44. */
  45. mxFastOrganicLayout.prototype.resetEdges = true;
  46. /**
  47. * Variable: disableEdgeStyle
  48. *
  49. * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
  50. * modified by the result. Default is true.
  51. */
  52. mxFastOrganicLayout.prototype.disableEdgeStyle = true;
  53. /**
  54. * Variable: forceConstant
  55. *
  56. * The force constant by which the attractive forces are divided and the
  57. * replusive forces are multiple by the square of. The value equates to the
  58. * average radius there is of free space around each node. Default is 50.
  59. */
  60. mxFastOrganicLayout.prototype.forceConstant = 50;
  61. /**
  62. * Variable: forceConstantSquared
  63. *
  64. * Cache of <forceConstant>^2 for performance.
  65. */
  66. mxFastOrganicLayout.prototype.forceConstantSquared = 0;
  67. /**
  68. * Variable: minDistanceLimit
  69. *
  70. * Minimal distance limit. Default is 2. Prevents of
  71. * dividing by zero.
  72. */
  73. mxFastOrganicLayout.prototype.minDistanceLimit = 2;
  74. /**
  75. * Variable: maxDistanceLimit
  76. *
  77. * Maximal distance limit. Default is 500. Prevents of
  78. * dividing by zero.
  79. */
  80. mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
  81. /**
  82. * Variable: minDistanceLimitSquared
  83. *
  84. * Cached version of <minDistanceLimit> squared.
  85. */
  86. mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
  87. /**
  88. * Variable: initialTemp
  89. *
  90. * Start value of temperature. Default is 200.
  91. */
  92. mxFastOrganicLayout.prototype.initialTemp = 200;
  93. /**
  94. * Variable: temperature
  95. *
  96. * Temperature to limit displacement at later stages of layout.
  97. */
  98. mxFastOrganicLayout.prototype.temperature = 0;
  99. /**
  100. * Variable: maxIterations
  101. *
  102. * Total number of iterations to run the layout though.
  103. */
  104. mxFastOrganicLayout.prototype.maxIterations = 0;
  105. /**
  106. * Variable: iteration
  107. *
  108. * Current iteration count.
  109. */
  110. mxFastOrganicLayout.prototype.iteration = 0;
  111. /**
  112. * Variable: vertexArray
  113. *
  114. * An array of all vertices to be laid out.
  115. */
  116. mxFastOrganicLayout.prototype.vertexArray;
  117. /**
  118. * Variable: dispX
  119. *
  120. * An array of locally stored X co-ordinate displacements for the vertices.
  121. */
  122. mxFastOrganicLayout.prototype.dispX;
  123. /**
  124. * Variable: dispY
  125. *
  126. * An array of locally stored Y co-ordinate displacements for the vertices.
  127. */
  128. mxFastOrganicLayout.prototype.dispY;
  129. /**
  130. * Variable: cellLocation
  131. *
  132. * An array of locally stored co-ordinate positions for the vertices.
  133. */
  134. mxFastOrganicLayout.prototype.cellLocation;
  135. /**
  136. * Variable: radius
  137. *
  138. * The approximate radius of each cell, nodes only.
  139. */
  140. mxFastOrganicLayout.prototype.radius;
  141. /**
  142. * Variable: radiusSquared
  143. *
  144. * The approximate radius squared of each cell, nodes only.
  145. */
  146. mxFastOrganicLayout.prototype.radiusSquared;
  147. /**
  148. * Variable: isMoveable
  149. *
  150. * Array of booleans representing the movable states of the vertices.
  151. */
  152. mxFastOrganicLayout.prototype.isMoveable;
  153. /**
  154. * Variable: neighbours
  155. *
  156. * Local copy of cell neighbours.
  157. */
  158. mxFastOrganicLayout.prototype.neighbours;
  159. /**
  160. * Variable: indices
  161. *
  162. * Hashtable from cells to local indices.
  163. */
  164. mxFastOrganicLayout.prototype.indices;
  165. /**
  166. * Variable: allowedToRun
  167. *
  168. * Boolean flag that specifies if the layout is allowed to run. If this is
  169. * set to false, then the layout exits in the following iteration.
  170. */
  171. mxFastOrganicLayout.prototype.allowedToRun = true;
  172. /**
  173. * Function: isVertexIgnored
  174. *
  175. * Returns a boolean indicating if the given <mxCell> should be ignored as a
  176. * vertex. This returns true if the cell has no connections.
  177. *
  178. * Parameters:
  179. *
  180. * vertex - <mxCell> whose ignored state should be returned.
  181. */
  182. mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
  183. {
  184. return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
  185. this.graph.getConnections(vertex).length == 0;
  186. };
  187. /**
  188. * Function: execute
  189. *
  190. * Implements <mxGraphLayout.execute>. This operates on all children of the
  191. * given parent where <isVertexIgnored> returns false.
  192. */
  193. mxFastOrganicLayout.prototype.execute = function(parent)
  194. {
  195. var model = this.graph.getModel();
  196. this.vertexArray = [];
  197. var cells = this.graph.getChildVertices(parent);
  198. for (var i = 0; i < cells.length; i++)
  199. {
  200. if (!this.isVertexIgnored(cells[i]))
  201. {
  202. this.vertexArray.push(cells[i]);
  203. }
  204. }
  205. var initialBounds = (this.useInputOrigin) ?
  206. this.graph.getBoundingBoxFromGeometry(this.vertexArray) :
  207. null;
  208. var n = this.vertexArray.length;
  209. this.indices = [];
  210. this.dispX = [];
  211. this.dispY = [];
  212. this.cellLocation = [];
  213. this.isMoveable = [];
  214. this.neighbours = [];
  215. this.radius = [];
  216. this.radiusSquared = [];
  217. if (this.forceConstant < 0.001)
  218. {
  219. this.forceConstant = 0.001;
  220. }
  221. this.forceConstantSquared = this.forceConstant * this.forceConstant;
  222. // Create a map of vertices first. This is required for the array of
  223. // arrays called neighbours which holds, for each vertex, a list of
  224. // ints which represents the neighbours cells to that vertex as
  225. // the indices into vertexArray
  226. for (var i = 0; i < this.vertexArray.length; i++)
  227. {
  228. var vertex = this.vertexArray[i];
  229. this.cellLocation[i] = [];
  230. // Set up the mapping from array indices to cells
  231. var id = mxObjectIdentity.get(vertex);
  232. this.indices[id] = i;
  233. var bounds = this.getVertexBounds(vertex);
  234. // Set the X,Y value of the internal version of the cell to
  235. // the center point of the vertex for better positioning
  236. var width = bounds.width;
  237. var height = bounds.height;
  238. // Randomize (0, 0) locations
  239. var x = bounds.x;
  240. var y = bounds.y;
  241. this.cellLocation[i][0] = x + width / 2.0;
  242. this.cellLocation[i][1] = y + height / 2.0;
  243. this.radius[i] = Math.min(width, height);
  244. this.radiusSquared[i] = this.radius[i] * this.radius[i];
  245. }
  246. // Moves cell location back to top-left from center locations used in
  247. // algorithm, resetting the edge points is part of the transaction
  248. model.beginUpdate();
  249. try
  250. {
  251. for (var i = 0; i < n; i++)
  252. {
  253. this.dispX[i] = 0;
  254. this.dispY[i] = 0;
  255. this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
  256. // Get lists of neighbours to all vertices, translate the cells
  257. // obtained in indices into vertexArray and store as an array
  258. // against the orginial cell index
  259. var edges = this.graph.getConnections(this.vertexArray[i], parent);
  260. var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
  261. this.neighbours[i] = [];
  262. for (var j = 0; j < cells.length; j++)
  263. {
  264. // Resets the points on the traversed edge
  265. if (this.resetEdges)
  266. {
  267. this.graph.resetEdge(edges[j]);
  268. }
  269. if (this.disableEdgeStyle)
  270. {
  271. this.setEdgeStyleEnabled(edges[j], false);
  272. }
  273. // Looks the cell up in the indices dictionary
  274. var id = mxObjectIdentity.get(cells[j]);
  275. var index = this.indices[id];
  276. // Check the connected cell in part of the vertex list to be
  277. // acted on by this layout
  278. if (index != null)
  279. {
  280. this.neighbours[i][j] = index;
  281. }
  282. // Else if index of the other cell doesn't correspond to
  283. // any cell listed to be acted upon in this layout. Set
  284. // the index to the value of this vertex (a dummy self-loop)
  285. // so the attraction force of the edge is not calculated
  286. else
  287. {
  288. this.neighbours[i][j] = i;
  289. }
  290. }
  291. }
  292. this.temperature = this.initialTemp;
  293. // If max number of iterations has not been set, guess it
  294. if (this.maxIterations == 0)
  295. {
  296. this.maxIterations = 20 * Math.sqrt(n);
  297. }
  298. // Main iteration loop
  299. for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
  300. {
  301. if (!this.allowedToRun)
  302. {
  303. return;
  304. }
  305. // Calculate repulsive forces on all vertices
  306. this.calcRepulsion();
  307. // Calculate attractive forces through edges
  308. this.calcAttraction();
  309. this.calcPositions();
  310. this.reduceTemperature();
  311. }
  312. var minx = null;
  313. var miny = null;
  314. for (var i = 0; i < this.vertexArray.length; i++)
  315. {
  316. var vertex = this.vertexArray[i];
  317. if (this.isVertexMovable(vertex))
  318. {
  319. var bounds = this.getVertexBounds(vertex);
  320. if (bounds != null)
  321. {
  322. this.cellLocation[i][0] -= bounds.width / 2.0;
  323. this.cellLocation[i][1] -= bounds.height / 2.0;
  324. var x = this.graph.snap(Math.round(this.cellLocation[i][0]));
  325. var y = this.graph.snap(Math.round(this.cellLocation[i][1]));
  326. this.setVertexLocation(vertex, x, y);
  327. if (minx == null)
  328. {
  329. minx = x;
  330. }
  331. else
  332. {
  333. minx = Math.min(minx, x);
  334. }
  335. if (miny == null)
  336. {
  337. miny = y;
  338. }
  339. else
  340. {
  341. miny = Math.min(miny, y);
  342. }
  343. }
  344. }
  345. }
  346. // Modifies the cloned geometries in-place. Not needed
  347. // to clone the geometries again as we're in the same
  348. // undoable change.
  349. var dx = -(minx || 0) + 1;
  350. var dy = -(miny || 0) + 1;
  351. if (initialBounds != null)
  352. {
  353. dx += initialBounds.x;
  354. dy += initialBounds.y;
  355. }
  356. this.graph.moveCells(this.vertexArray, dx, dy);
  357. }
  358. finally
  359. {
  360. model.endUpdate();
  361. }
  362. };
  363. /**
  364. * Function: calcPositions
  365. *
  366. * Takes the displacements calculated for each cell and applies them to the
  367. * local cache of cell positions. Limits the displacement to the current
  368. * temperature.
  369. */
  370. mxFastOrganicLayout.prototype.calcPositions = function()
  371. {
  372. for (var index = 0; index < this.vertexArray.length; index++)
  373. {
  374. if (this.isMoveable[index])
  375. {
  376. // Get the distance of displacement for this node for this
  377. // iteration
  378. var deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] +
  379. this.dispY[index] * this.dispY[index]);
  380. if (deltaLength < 0.001)
  381. {
  382. deltaLength = 0.001;
  383. }
  384. // Scale down by the current temperature if less than the
  385. // displacement distance
  386. var newXDisp = this.dispX[index] / deltaLength
  387. * Math.min(deltaLength, this.temperature);
  388. var newYDisp = this.dispY[index] / deltaLength
  389. * Math.min(deltaLength, this.temperature);
  390. // reset displacements
  391. this.dispX[index] = 0;
  392. this.dispY[index] = 0;
  393. // Update the cached cell locations
  394. this.cellLocation[index][0] += newXDisp;
  395. this.cellLocation[index][1] += newYDisp;
  396. }
  397. }
  398. };
  399. /**
  400. * Function: calcAttraction
  401. *
  402. * Calculates the attractive forces between all laid out nodes linked by
  403. * edges
  404. */
  405. mxFastOrganicLayout.prototype.calcAttraction = function()
  406. {
  407. // Check the neighbours of each vertex and calculate the attractive
  408. // force of the edge connecting them
  409. for (var i = 0; i < this.vertexArray.length; i++)
  410. {
  411. for (var k = 0; k < this.neighbours[i].length; k++)
  412. {
  413. // Get the index of the othe cell in the vertex array
  414. var j = this.neighbours[i][k];
  415. // Do not proceed self-loops
  416. if (i != j &&
  417. this.isMoveable[i] &&
  418. this.isMoveable[j])
  419. {
  420. var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
  421. var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
  422. // The distance between the nodes
  423. var deltaLengthSquared = xDelta * xDelta + yDelta
  424. * yDelta - this.radiusSquared[i] - this.radiusSquared[j];
  425. if (deltaLengthSquared < this.minDistanceLimitSquared)
  426. {
  427. deltaLengthSquared = this.minDistanceLimitSquared;
  428. }
  429. var deltaLength = Math.sqrt(deltaLengthSquared);
  430. var force = (deltaLengthSquared) / this.forceConstant;
  431. var displacementX = (xDelta / deltaLength) * force;
  432. var displacementY = (yDelta / deltaLength) * force;
  433. this.dispX[i] -= displacementX;
  434. this.dispY[i] -= displacementY;
  435. this.dispX[j] += displacementX;
  436. this.dispY[j] += displacementY;
  437. }
  438. }
  439. }
  440. };
  441. /**
  442. * Function: calcRepulsion
  443. *
  444. * Calculates the repulsive forces between all laid out nodes
  445. */
  446. mxFastOrganicLayout.prototype.calcRepulsion = function()
  447. {
  448. var vertexCount = this.vertexArray.length;
  449. for (var i = 0; i < vertexCount; i++)
  450. {
  451. for (var j = i; j < vertexCount; j++)
  452. {
  453. // Exits if the layout is no longer allowed to run
  454. if (!this.allowedToRun)
  455. {
  456. return;
  457. }
  458. if (j != i &&
  459. this.isMoveable[i] &&
  460. this.isMoveable[j])
  461. {
  462. var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
  463. var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
  464. if (xDelta == 0)
  465. {
  466. xDelta = 0.01 + Math.random();
  467. }
  468. if (yDelta == 0)
  469. {
  470. yDelta = 0.01 + Math.random();
  471. }
  472. // Distance between nodes
  473. var deltaLength = Math.sqrt((xDelta * xDelta)
  474. + (yDelta * yDelta));
  475. var deltaLengthWithRadius = deltaLength - this.radius[i]
  476. - this.radius[j];
  477. if (deltaLengthWithRadius > this.maxDistanceLimit)
  478. {
  479. // Ignore vertices too far apart
  480. continue;
  481. }
  482. if (deltaLengthWithRadius < this.minDistanceLimit)
  483. {
  484. deltaLengthWithRadius = this.minDistanceLimit;
  485. }
  486. var force = this.forceConstantSquared / deltaLengthWithRadius;
  487. var displacementX = (xDelta / deltaLength) * force;
  488. var displacementY = (yDelta / deltaLength) * force;
  489. this.dispX[i] += displacementX;
  490. this.dispY[i] += displacementY;
  491. this.dispX[j] -= displacementX;
  492. this.dispY[j] -= displacementY;
  493. }
  494. }
  495. }
  496. };
  497. /**
  498. * Function: reduceTemperature
  499. *
  500. * Reduces the temperature of the layout from an initial setting in a linear
  501. * fashion to zero.
  502. */
  503. mxFastOrganicLayout.prototype.reduceTemperature = function()
  504. {
  505. this.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);
  506. };
  507. __mxOutput.mxFastOrganicLayout = typeof mxFastOrganicLayout !== 'undefined' ? mxFastOrganicLayout : undefined;