vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
6.1 KiB

  1. /**
  2. * Helper classes for LayoutEngine.
  3. *
  4. * Strategy pattern for usage of direction methods for hierarchical layouts.
  5. */
  6. var TimSort = require('timsort');
  7. /**
  8. * Interface definition for direction strategy classes.
  9. *
  10. * This class describes the interface for the Strategy
  11. * pattern classes used to differentiate horizontal and vertical
  12. * direction of hierarchical results.
  13. *
  14. * For a given direction, one coordinate will be 'fixed', meaning that it is
  15. * determined by level.
  16. * The other coordinate is 'unfixed', meaning that the nodes on a given level
  17. * can still move along that coordinate. So:
  18. *
  19. * - `vertical` layout: `x` unfixed, `y` fixed per level
  20. * - `horizontal` layout: `x` fixed per level, `y` unfixed
  21. *
  22. * The local methods are stubs and should be regarded as abstract.
  23. * Derived classes **must** implement all the methods themselves.
  24. *
  25. * @private
  26. */
  27. class DirectionInterface {
  28. /** @ignore **/
  29. abstract() {
  30. throw new Error("Can't instantiate abstract class!");
  31. }
  32. /**
  33. * This is a dummy call which is used to suppress the jsdoc errors of type:
  34. *
  35. * "'param' is assigned a value but never used"
  36. *
  37. * @ignore
  38. **/
  39. fake_use() {
  40. // Do nothing special
  41. }
  42. /**
  43. * Type to use to translate dynamic curves to, in the case of hierarchical layout.
  44. * Dynamic curves do not work for these.
  45. *
  46. * The value should be perpendicular to the actual direction of the layout.
  47. *
  48. * @return {string} Direction, either 'vertical' or 'horizontal'
  49. */
  50. curveType() { return this.abstract(); }
  51. /**
  52. * Return the value of the coordinate that is not fixed for this direction.
  53. *
  54. * @param {Node} node The node to read
  55. * @return {number} Value of the unfixed coordinate
  56. */
  57. getPosition(node) { this.fake_use(node); return this.abstract(); }
  58. /**
  59. * Set the value of the coordinate that is not fixed for this direction.
  60. *
  61. * @param {Node} node The node to adjust
  62. * @param {number} position
  63. * @param {number} [level] if specified, the hierarchy level that this node should be fixed to
  64. */
  65. setPosition(node, position, level = undefined) { this.fake_use(node, position, level); this.abstract(); }
  66. /**
  67. * Get the width of a tree.
  68. *
  69. * A `tree` here is a subset of nodes within the network which are not connected to other nodes,
  70. * only among themselves. In essence, it is a sub-network.
  71. *
  72. * @param {number} index The index number of a tree
  73. * @return {number} the width of a tree in the view coordinates
  74. */
  75. getTreeSize(index) { this.fake_use(index); return this.abstract(); }
  76. /**
  77. * Sort array of nodes on the unfixed coordinates.
  78. *
  79. * **Note:** chrome has non-stable sorting implementation, which
  80. * has a tendency to change the order of the array items,
  81. * even if the custom sort function returns 0.
  82. *
  83. * For this reason, an external sort implementation is used,
  84. * which has the added benefit of being faster than the standard
  85. * platforms implementation. This has been verified on `node.js`,
  86. * `firefox` and `chrome` (all linux).
  87. *
  88. * @param {Array.<Node>} nodeArray array of nodes to sort
  89. */
  90. sort(nodeArray) { this.fake_use(nodeArray); this.abstract(); }
  91. /**
  92. * Assign the fixed coordinate of the node to the given level
  93. *
  94. * @param {Node} node The node to adjust
  95. * @param {number} level The level to fix to
  96. */
  97. fix(node, level) { this.fake_use(node, level); this.abstract(); }
  98. /**
  99. * Add an offset to the unfixed coordinate of the given node.
  100. *
  101. * @param {NodeId} nodeId Id of the node to adjust
  102. * @param {number} diff Offset to add to the unfixed coordinate
  103. */
  104. shift(nodeId, diff) { this.fake_use(nodeId, diff); this.abstract(); }
  105. }
  106. /**
  107. * Vertical Strategy
  108. *
  109. * Coordinate `y` is fixed on levels, coordinate `x` is unfixed.
  110. *
  111. * @extends DirectionInterface
  112. * @private
  113. */
  114. class VerticalStrategy extends DirectionInterface {
  115. /**
  116. * Constructor
  117. *
  118. * @param {Object} layout reference to the parent LayoutEngine instance.
  119. */
  120. constructor(layout) {
  121. super();
  122. this.layout = layout;
  123. }
  124. /** @inheritdoc */
  125. curveType() {
  126. return 'horizontal';
  127. }
  128. /** @inheritdoc */
  129. getPosition(node) {
  130. return node.x;
  131. }
  132. /** @inheritdoc */
  133. setPosition(node, position, level = undefined) {
  134. if (level !== undefined) {
  135. this.layout.hierarchical.addToOrdering(node, level);
  136. }
  137. node.x = position;
  138. }
  139. /** @inheritdoc */
  140. getTreeSize(index) {
  141. let res = this.layout.hierarchical.getTreeSize(this.layout.body.nodes, index);
  142. return {min: res.min_x, max: res.max_x};
  143. }
  144. /** @inheritdoc */
  145. sort(nodeArray) {
  146. TimSort.sort(nodeArray, function(a, b) {
  147. return a.x - b.x;
  148. });
  149. }
  150. /** @inheritdoc */
  151. fix(node, level) {
  152. node.y = this.layout.options.hierarchical.levelSeparation * level;
  153. node.options.fixed.y = true;
  154. }
  155. /** @inheritdoc */
  156. shift(nodeId, diff) {
  157. this.layout.body.nodes[nodeId].x += diff;
  158. }
  159. }
  160. /**
  161. * Horizontal Strategy
  162. *
  163. * Coordinate `x` is fixed on levels, coordinate `y` is unfixed.
  164. *
  165. * @extends DirectionInterface
  166. * @private
  167. */
  168. class HorizontalStrategy extends DirectionInterface {
  169. /**
  170. * Constructor
  171. *
  172. * @param {Object} layout reference to the parent LayoutEngine instance.
  173. */
  174. constructor(layout) {
  175. super();
  176. this.layout = layout;
  177. }
  178. /** @inheritdoc */
  179. curveType() {
  180. return 'vertical';
  181. }
  182. /** @inheritdoc */
  183. getPosition(node) {
  184. return node.y;
  185. }
  186. /** @inheritdoc */
  187. setPosition(node, position, level = undefined) {
  188. if (level !== undefined) {
  189. this.layout.hierarchical.addToOrdering(node, level);
  190. }
  191. node.y = position;
  192. }
  193. /** @inheritdoc */
  194. getTreeSize(index) {
  195. let res = this.layout.hierarchical.getTreeSize(this.layout.body.nodes, index);
  196. return {min: res.min_y, max: res.max_y};
  197. }
  198. /** @inheritdoc */
  199. sort(nodeArray) {
  200. TimSort.sort(nodeArray, function(a, b) {
  201. return a.y - b.y;
  202. });
  203. }
  204. /** @inheritdoc */
  205. fix(node, level) {
  206. node.x = this.layout.options.hierarchical.levelSeparation * level;
  207. node.options.fixed.x = true;
  208. }
  209. /** @inheritdoc */
  210. shift(nodeId, diff) {
  211. this.layout.body.nodes[nodeId].y += diff;
  212. }
  213. }
  214. export {HorizontalStrategy, VerticalStrategy};