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.

244 lines
6.0 KiB

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