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

/**
* Helper classes for LayoutEngine.
*
* Strategy pattern for usage of direction methods for hierarchical layouts.
*/
var TimSort = require('timsort');
/**
* 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.
*
* **Note:** chrome has non-stable sorting implementation, which
* has a tendency to change the order of the array items,
* even if the custom sort function returns 0.
*
* For this reason, an external sort implementation is used,
* which has the added benefit of being faster than the standard
* platforms implementation. This has been verified on `node.js`,
* `firefox` and `chrome` (all linux).
*
* @param {Array.<Node>} 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) {
TimSort.sort(nodeArray, function(a, b) {
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) {
TimSort.sort(nodeArray, function(a, b) {
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};