/** * Helper classes for LayoutEngine. * * Strategy pattern for usage of direction methods for hierarchical layouts. */ /** * Interface definition for direction strategy classes. * * This class describes the interface for the Strategy * pattern classes used to differentiate horizontal and vertical * direction of hierarchical results. * * For a given direction, one coordinate will be 'fixed', meaning that it is * determined by level. * The other coordinate is 'unfixed', meaning that the nodes on a given level * can still move along that coordinate. So: * * - `vertical` layout: `x` unfixed, `y` fixed per level * - `horizontal` layout: `x` fixed per level, `y` unfixed * * The local methods are stubs and should be regarded as abstract. * Derived classes **must** implement all the methods themselves. * * @private */ class DirectionInterface { /** @ignore **/ abstract() { throw new Error("Can't instantiate abstract class!"); } /** * This is a dummy call which is used to suppress the jsdoc errors of type: * * "'param' is assigned a value but never used" * * @ignore **/ fake_use() { // Do nothing special } /** * Type to use to translate dynamic curves to, in the case of hierarchical layout. * Dynamic curves do not work for these. * * The value should be perpendicular to the actual direction of the layout. * * @return {string} Direction, either 'vertical' or 'horizontal' */ curveType() { return this.abstract(); } /** * Return the value of the coordinate that is not fixed for this direction. * * @param {Node} node The node to read * @return {number} Value of the unfixed coordinate */ getPosition(node) { this.fake_use(node); return this.abstract(); } /** * Set the value of the coordinate that is not fixed for this direction. * * @param {Node} node The node to adjust * @param {number} position * @param {number} [level] if specified, the hierarchy level that this node should be fixed to */ setPosition(node, position, level = undefined) { this.fake_use(node, position, level); this.abstract(); } /** * Get the width of a tree. * * A `tree` here is a subset of nodes within the network which are not connected to other nodes, * only among themselves. In essence, it is a sub-network. * * @param {number} index The index number of a tree * @return {number} the width of a tree in the view coordinates */ getTreeSize(index) { this.fake_use(index); return this.abstract(); } /** * Sort array of nodes on the unfixed coordinates. * * @param {Array.} nodeArray array of nodes to sort */ sort(nodeArray) { this.fake_use(nodeArray); this.abstract(); } /** * Assign the fixed coordinate of the node to the given level * * @param {Node} node The node to adjust * @param {number} level The level to fix to */ fix(node, level) { this.fake_use(node, level); this.abstract(); } /** * Add an offset to the unfixed coordinate of the given node. * * @param {NodeId} nodeId Id of the node to adjust * @param {number} diff Offset to add to the unfixed coordinate */ shift(nodeId, diff) { this.fake_use(nodeId, diff); this.abstract(); } } /** * Vertical Strategy * * Coordinate `y` is fixed on levels, coordinate `x` is unfixed. * * @extends DirectionInterface * @private */ class VerticalStrategy extends DirectionInterface { /** * Constructor * * @param {Object} layout reference to the parent LayoutEngine instance. */ constructor(layout) { super(); this.layout = layout; } /** @inheritdoc */ curveType() { return 'horizontal'; } /** @inheritdoc */ getPosition(node) { return node.x; } /** @inheritdoc */ setPosition(node, position, level = undefined) { if (level !== undefined) { this.layout.hierarchical.addToOrdering(node, level); } node.x = position; } /** @inheritdoc */ getTreeSize(index) { let res = this.layout.hierarchical.getTreeSize(this.layout.body.nodes, index); return {min: res.min_x, max: res.max_x}; } /** @inheritdoc */ sort(nodeArray) { nodeArray.sort(function(a, b) { // Test on 'undefined' takes care of divergent behaviour in chrome if (a.x === undefined || b.x === undefined) return 0; // THIS HAPPENS return a.x - b.x; }); } /** @inheritdoc */ fix(node, level) { node.y = this.layout.options.hierarchical.levelSeparation * level; node.options.fixed.y = true; } /** @inheritdoc */ shift(nodeId, diff) { this.layout.body.nodes[nodeId].x += diff; } } /** * Horizontal Strategy * * Coordinate `x` is fixed on levels, coordinate `y` is unfixed. * * @extends DirectionInterface * @private */ class HorizontalStrategy extends DirectionInterface { /** * Constructor * * @param {Object} layout reference to the parent LayoutEngine instance. */ constructor(layout) { super(); this.layout = layout; } /** @inheritdoc */ curveType() { return 'vertical'; } /** @inheritdoc */ getPosition(node) { return node.y; } /** @inheritdoc */ setPosition(node, position, level = undefined) { if (level !== undefined) { this.layout.hierarchical.addToOrdering(node, level); } node.y = position; } /** @inheritdoc */ getTreeSize(index) { let res = this.layout.hierarchical.getTreeSize(this.layout.body.nodes, index); return {min: res.min_y, max: res.max_y}; } /** @inheritdoc */ sort(nodeArray) { nodeArray.sort(function(a, b) { // Test on 'undefined' takes care of divergent behaviour in chrome if (a.y === undefined || b.y === undefined) return 0; // THIS HAPPENS return a.y - b.y; }); } /** @inheritdoc */ fix(node, level) { node.x = this.layout.options.hierarchical.levelSeparation * level; node.options.fixed.x = true; } /** @inheritdoc */ shift(nodeId, diff) { this.layout.body.nodes[nodeId].y += diff; } } export {HorizontalStrategy, VerticalStrategy};