initial commit
This commit is contained in:
244
node_modules/vis/lib/network/modules/components/DirectionStrategy.js
generated
vendored
Normal file
244
node_modules/vis/lib/network/modules/components/DirectionStrategy.js
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* 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.<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) {
|
||||
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};
|
||||
781
node_modules/vis/lib/network/modules/components/Edge.js
generated
vendored
Normal file
781
node_modules/vis/lib/network/modules/components/Edge.js
generated
vendored
Normal file
@@ -0,0 +1,781 @@
|
||||
var util = require('../../../util');
|
||||
var Label = require('./shared/Label').default;
|
||||
var ComponentUtil = require('./shared/ComponentUtil').default;
|
||||
var CubicBezierEdge = require('./edges/CubicBezierEdge').default;
|
||||
var BezierEdgeDynamic = require('./edges/BezierEdgeDynamic').default;
|
||||
var BezierEdgeStatic = require('./edges/BezierEdgeStatic').default;
|
||||
var StraightEdge = require('./edges/StraightEdge').default;
|
||||
|
||||
|
||||
/**
|
||||
* An edge connects two nodes and has a specific direction.
|
||||
*/
|
||||
class Edge {
|
||||
/**
|
||||
* @param {Object} options values specific to this edge, must contain at least 'from' and 'to'
|
||||
* @param {Object} body shared state from Network instance
|
||||
* @param {Object} globalOptions options from the EdgesHandler instance
|
||||
* @param {Object} defaultOptions default options from the EdgeHandler instance. Value and reference are constant
|
||||
*/
|
||||
constructor(options, body, globalOptions, defaultOptions) {
|
||||
if (body === undefined) {
|
||||
throw new Error("No body provided");
|
||||
}
|
||||
|
||||
// Since globalOptions is constant in values as well as reference,
|
||||
// Following needs to be done only once.
|
||||
|
||||
this.options = util.bridgeObject(globalOptions);
|
||||
this.globalOptions = globalOptions;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.body = body;
|
||||
|
||||
// initialize variables
|
||||
this.id = undefined;
|
||||
this.fromId = undefined;
|
||||
this.toId = undefined;
|
||||
this.selected = false;
|
||||
this.hover = false;
|
||||
this.labelDirty = true;
|
||||
|
||||
this.baseWidth = this.options.width;
|
||||
this.baseFontSize = this.options.font.size;
|
||||
|
||||
this.from = undefined; // a node
|
||||
this.to = undefined; // a node
|
||||
|
||||
this.edgeType = undefined;
|
||||
|
||||
this.connected = false;
|
||||
|
||||
this.labelModule = new Label(this.body, this.options, true /* It's an edge label */);
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set or overwrite options for the edge
|
||||
* @param {Object} options an object with options
|
||||
* @returns {null|boolean} null if no options, boolean if date changed
|
||||
*/
|
||||
setOptions(options) {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
Edge.parseOptions(this.options, options, true, this.globalOptions);
|
||||
|
||||
if (options.id !== undefined) {
|
||||
this.id = options.id;
|
||||
}
|
||||
if (options.from !== undefined) {
|
||||
this.fromId = options.from;
|
||||
}
|
||||
if (options.to !== undefined) {
|
||||
this.toId = options.to;
|
||||
}
|
||||
if (options.title !== undefined) {
|
||||
this.title = options.title;
|
||||
}
|
||||
if (options.value !== undefined) {
|
||||
options.value = parseFloat(options.value);
|
||||
}
|
||||
|
||||
let pile = [options, this.options, this.defaultOptions];
|
||||
this.chooser = ComponentUtil.choosify('edge', pile);
|
||||
|
||||
// update label Module
|
||||
this.updateLabelModule(options);
|
||||
|
||||
let dataChanged = this.updateEdgeType();
|
||||
|
||||
// if anything has been updates, reset the selection width and the hover width
|
||||
this._setInteractionWidths();
|
||||
|
||||
// A node is connected when it has a from and to node that both exist in the network.body.nodes.
|
||||
this.connect();
|
||||
|
||||
if (options.hidden !== undefined || options.physics !== undefined) {
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} parentOptions
|
||||
* @param {Object} newOptions
|
||||
* @param {boolean} [allowDeletion=false]
|
||||
* @param {Object} [globalOptions={}]
|
||||
* @param {boolean} [copyFromGlobals=false]
|
||||
*/
|
||||
static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, copyFromGlobals = false) {
|
||||
var fields = [
|
||||
'arrowStrikethrough',
|
||||
'id',
|
||||
'from',
|
||||
'hidden',
|
||||
'hoverWidth',
|
||||
'labelHighlightBold',
|
||||
'length',
|
||||
'line',
|
||||
'opacity',
|
||||
'physics',
|
||||
'scaling',
|
||||
'selectionWidth',
|
||||
'selfReferenceSize',
|
||||
'to',
|
||||
'title',
|
||||
'value',
|
||||
'width',
|
||||
'font',
|
||||
'chosen',
|
||||
'widthConstraint'
|
||||
];
|
||||
|
||||
// only deep extend the items in the field array. These do not have shorthand.
|
||||
util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion);
|
||||
|
||||
// Only copy label if it's a legal value.
|
||||
if (ComponentUtil.isValidLabel(newOptions.label)) {
|
||||
parentOptions.label = newOptions.label;
|
||||
} else {
|
||||
parentOptions.label = undefined;
|
||||
}
|
||||
|
||||
util.mergeOptions(parentOptions, newOptions, 'smooth', globalOptions);
|
||||
util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
|
||||
|
||||
if (newOptions.dashes !== undefined && newOptions.dashes !== null) {
|
||||
parentOptions.dashes = newOptions.dashes;
|
||||
}
|
||||
else if (allowDeletion === true && newOptions.dashes === null) {
|
||||
parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option.
|
||||
}
|
||||
|
||||
// set the scaling newOptions
|
||||
if (newOptions.scaling !== undefined && newOptions.scaling !== null) {
|
||||
if (newOptions.scaling.min !== undefined) {parentOptions.scaling.min = newOptions.scaling.min;}
|
||||
if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;}
|
||||
util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
|
||||
}
|
||||
else if (allowDeletion === true && newOptions.scaling === null) {
|
||||
parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option.
|
||||
}
|
||||
|
||||
// handle multiple input cases for arrows
|
||||
if (newOptions.arrows !== undefined && newOptions.arrows !== null) {
|
||||
if (typeof newOptions.arrows === 'string') {
|
||||
let arrows = newOptions.arrows.toLowerCase();
|
||||
parentOptions.arrows.to.enabled = arrows.indexOf("to") != -1;
|
||||
parentOptions.arrows.middle.enabled = arrows.indexOf("middle") != -1;
|
||||
parentOptions.arrows.from.enabled = arrows.indexOf("from") != -1;
|
||||
}
|
||||
else if (typeof newOptions.arrows === 'object') {
|
||||
util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', globalOptions.arrows);
|
||||
util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', globalOptions.arrows);
|
||||
util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', globalOptions.arrows);
|
||||
}
|
||||
else {
|
||||
throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(newOptions.arrows));
|
||||
}
|
||||
}
|
||||
else if (allowDeletion === true && newOptions.arrows === null) {
|
||||
parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option.
|
||||
}
|
||||
|
||||
// handle multiple input cases for color
|
||||
if (newOptions.color !== undefined && newOptions.color !== null) {
|
||||
let fromColor = newOptions.color;
|
||||
let toColor = parentOptions.color;
|
||||
|
||||
// If passed, fill in values from default options - required in the case of no prototype bridging
|
||||
if (copyFromGlobals) {
|
||||
util.deepExtend(toColor, globalOptions.color, false, allowDeletion);
|
||||
} else {
|
||||
// Clear local properties - need to do it like this in order to retain prototype bridges
|
||||
for (var i in toColor) {
|
||||
if (toColor.hasOwnProperty(i)) {
|
||||
delete toColor[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (util.isString(toColor)) {
|
||||
toColor.color = toColor;
|
||||
toColor.highlight = toColor;
|
||||
toColor.hover = toColor;
|
||||
toColor.inherit = false;
|
||||
if (fromColor.opacity === undefined) {
|
||||
toColor.opacity = 1.0; // set default
|
||||
}
|
||||
}
|
||||
else {
|
||||
let colorsDefined = false;
|
||||
if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;}
|
||||
if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;}
|
||||
if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;}
|
||||
if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;}
|
||||
if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));}
|
||||
|
||||
if (colorsDefined === true) {
|
||||
toColor.inherit = false;
|
||||
} else {
|
||||
if (toColor.inherit === undefined) {
|
||||
toColor.inherit = 'from'; // Set default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (allowDeletion === true && newOptions.color === null) {
|
||||
parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
|
||||
}
|
||||
|
||||
if (allowDeletion === true && newOptions.font === null) {
|
||||
parentOptions.font = util.bridgeObject(globalOptions.font); // set the object back to the global options
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}}
|
||||
*/
|
||||
getFormattingValues() {
|
||||
let toArrow = (this.options.arrows.to === true) || (this.options.arrows.to.enabled === true)
|
||||
let fromArrow = (this.options.arrows.from === true) || (this.options.arrows.from.enabled === true)
|
||||
let middleArrow = (this.options.arrows.middle === true) || (this.options.arrows.middle.enabled === true)
|
||||
let inheritsColor = this.options.color.inherit;
|
||||
let values = {
|
||||
toArrow: toArrow,
|
||||
toArrowScale: this.options.arrows.to.scaleFactor,
|
||||
toArrowType: this.options.arrows.to.type,
|
||||
middleArrow: middleArrow,
|
||||
middleArrowScale: this.options.arrows.middle.scaleFactor,
|
||||
middleArrowType: this.options.arrows.middle.type,
|
||||
fromArrow: fromArrow,
|
||||
fromArrowScale: this.options.arrows.from.scaleFactor,
|
||||
fromArrowType: this.options.arrows.from.type,
|
||||
arrowStrikethrough: this.options.arrowStrikethrough,
|
||||
color: (inheritsColor? undefined : this.options.color.color),
|
||||
inheritsColor: inheritsColor,
|
||||
opacity: this.options.color.opacity,
|
||||
hidden: this.options.hidden,
|
||||
length: this.options.length,
|
||||
shadow: this.options.shadow.enabled,
|
||||
shadowColor: this.options.shadow.color,
|
||||
shadowSize: this.options.shadow.size,
|
||||
shadowX: this.options.shadow.x,
|
||||
shadowY: this.options.shadow.y,
|
||||
dashes: this.options.dashes,
|
||||
width: this.options.width
|
||||
};
|
||||
if (this.selected || this.hover) {
|
||||
if (this.chooser === true) {
|
||||
if (this.selected) {
|
||||
let selectedWidth = this.options.selectionWidth;
|
||||
if (typeof selectedWidth === 'function') {
|
||||
values.width = selectedWidth(values.width);
|
||||
} else if (typeof selectedWidth === 'number') {
|
||||
values.width += selectedWidth;
|
||||
}
|
||||
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
|
||||
values.color = this.options.color.highlight;
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
} else if (this.hover) {
|
||||
let hoverWidth = this.options.hoverWidth;
|
||||
if (typeof hoverWidth === 'function') {
|
||||
values.width = hoverWidth(values.width);
|
||||
} else if (typeof hoverWidth === 'number') {
|
||||
values.width += hoverWidth;
|
||||
}
|
||||
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
|
||||
values.color = this.options.color.hover;
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
}
|
||||
} else if (typeof this.chooser === 'function') {
|
||||
this.chooser(values, this.options.id, this.selected, this.hover);
|
||||
if (values.color !== undefined) {
|
||||
values.inheritsColor = false;
|
||||
}
|
||||
if (values.shadow === false) {
|
||||
if ((values.shadowColor !== this.options.shadow.color) ||
|
||||
(values.shadowSize !== this.options.shadow.size) ||
|
||||
(values.shadowX !== this.options.shadow.x) ||
|
||||
(values.shadowY !== this.options.shadow.y)) {
|
||||
values.shadow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the options in the label module
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
updateLabelModule(options) {
|
||||
let pile = [
|
||||
options,
|
||||
this.options,
|
||||
this.globalOptions, // Currently set global edge options
|
||||
this.defaultOptions
|
||||
];
|
||||
|
||||
this.labelModule.update(this.options, pile);
|
||||
|
||||
if (this.labelModule.baseSize !== undefined) {
|
||||
this.baseFontSize = this.labelModule.baseSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update the edge type, set the options
|
||||
* @returns {boolean}
|
||||
*/
|
||||
updateEdgeType() {
|
||||
let smooth = this.options.smooth;
|
||||
let dataChanged = false;
|
||||
let changeInType = true;
|
||||
if (this.edgeType !== undefined) {
|
||||
if ((((this.edgeType instanceof BezierEdgeDynamic) &&
|
||||
(smooth.enabled === true) &&
|
||||
(smooth.type === 'dynamic'))) ||
|
||||
(((this.edgeType instanceof CubicBezierEdge) &&
|
||||
(smooth.enabled === true) &&
|
||||
(smooth.type === 'cubicBezier'))) ||
|
||||
(((this.edgeType instanceof BezierEdgeStatic) &&
|
||||
(smooth.enabled === true) &&
|
||||
(smooth.type !== 'dynamic') &&
|
||||
(smooth.type !== 'cubicBezier'))) ||
|
||||
(((this.edgeType instanceof StraightEdge) &&
|
||||
(smooth.type.enabled === false)))) {
|
||||
changeInType = false;
|
||||
}
|
||||
if (changeInType === true) {
|
||||
dataChanged = this.cleanup();
|
||||
}
|
||||
}
|
||||
if (changeInType === true) {
|
||||
if (smooth.enabled === true) {
|
||||
if (smooth.type === 'dynamic') {
|
||||
dataChanged = true;
|
||||
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
|
||||
} else if (smooth.type === 'cubicBezier') {
|
||||
this.edgeType = new CubicBezierEdge(this.options, this.body, this.labelModule);
|
||||
} else {
|
||||
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
|
||||
}
|
||||
} else {
|
||||
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
|
||||
}
|
||||
} else { // if nothing changes, we just set the options.
|
||||
this.edgeType.setOptions(this.options);
|
||||
}
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect an edge to its nodes
|
||||
*/
|
||||
connect() {
|
||||
this.disconnect();
|
||||
|
||||
this.from = this.body.nodes[this.fromId] || undefined;
|
||||
this.to = this.body.nodes[this.toId] || undefined;
|
||||
this.connected = (this.from !== undefined && this.to !== undefined);
|
||||
|
||||
if (this.connected === true) {
|
||||
this.from.attachEdge(this);
|
||||
this.to.attachEdge(this);
|
||||
}
|
||||
else {
|
||||
if (this.from) {
|
||||
this.from.detachEdge(this);
|
||||
}
|
||||
if (this.to) {
|
||||
this.to.detachEdge(this);
|
||||
}
|
||||
}
|
||||
|
||||
this.edgeType.connect();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect an edge from its nodes
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.from) {
|
||||
this.from.detachEdge(this);
|
||||
this.from = undefined;
|
||||
}
|
||||
if (this.to) {
|
||||
this.to.detachEdge(this);
|
||||
this.to = undefined;
|
||||
}
|
||||
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the title of this edge.
|
||||
* @return {string} title The title of the edge, or undefined when no title
|
||||
* has been set.
|
||||
*/
|
||||
getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if this node is selecte
|
||||
* @return {boolean} selected True if node is selected, else false
|
||||
*/
|
||||
isSelected() {
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the value of the edge. Can be undefined
|
||||
* @return {number} value
|
||||
*/
|
||||
getValue() {
|
||||
return this.options.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjust the value range of the edge. The edge will adjust it's width
|
||||
* based on its value.
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @param {number} total
|
||||
*/
|
||||
setValueRange(min, max, total) {
|
||||
if (this.options.value !== undefined) {
|
||||
var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value);
|
||||
var widthDiff = this.options.scaling.max - this.options.scaling.min;
|
||||
if (this.options.scaling.label.enabled === true) {
|
||||
var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min;
|
||||
this.options.font.size = this.options.scaling.label.min + scale * fontDiff;
|
||||
}
|
||||
this.options.width = this.options.scaling.min + scale * widthDiff;
|
||||
}
|
||||
else {
|
||||
this.options.width = this.baseWidth;
|
||||
this.options.font.size = this.baseFontSize;
|
||||
}
|
||||
|
||||
this._setInteractionWidths();
|
||||
this.updateLabelModule();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setInteractionWidths() {
|
||||
if (typeof this.options.hoverWidth === 'function') {
|
||||
this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width);
|
||||
} else {
|
||||
this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width;
|
||||
}
|
||||
if (typeof this.options.selectionWidth === 'function') {
|
||||
this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width);
|
||||
} else {
|
||||
this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redraw a edge
|
||||
* Draw this edge in the given canvas
|
||||
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
draw(ctx) {
|
||||
let values = this.getFormattingValues();
|
||||
if (values.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the via node from the edge type
|
||||
let viaNode = this.edgeType.getViaNode();
|
||||
let arrowData = {};
|
||||
|
||||
// restore edge targets to defaults
|
||||
this.edgeType.fromPoint = this.edgeType.from;
|
||||
this.edgeType.toPoint = this.edgeType.to;
|
||||
|
||||
// from and to arrows give a different end point for edges. we set them here
|
||||
if (values.fromArrow) {
|
||||
arrowData.from = this.edgeType.getArrowData(ctx, 'from', viaNode, this.selected, this.hover, values);
|
||||
if (values.arrowStrikethrough === false)
|
||||
this.edgeType.fromPoint = arrowData.from.core;
|
||||
}
|
||||
if (values.toArrow) {
|
||||
arrowData.to = this.edgeType.getArrowData(ctx, 'to', viaNode, this.selected, this.hover, values);
|
||||
if (values.arrowStrikethrough === false)
|
||||
this.edgeType.toPoint = arrowData.to.core;
|
||||
}
|
||||
|
||||
// the middle arrow depends on the line, which can depend on the to and from arrows so we do this one lastly.
|
||||
if (values.middleArrow) {
|
||||
arrowData.middle = this.edgeType.getArrowData(ctx,'middle', viaNode, this.selected, this.hover, values);
|
||||
}
|
||||
|
||||
// draw everything
|
||||
this.edgeType.drawLine(ctx, values, this.selected, this.hover, viaNode);
|
||||
this.drawArrows(ctx, arrowData, values);
|
||||
this.drawLabel(ctx, viaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Object} arrowData
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
drawArrows(ctx, arrowData, values) {
|
||||
if (values.fromArrow) {
|
||||
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.from);
|
||||
}
|
||||
if (values.middleArrow) {
|
||||
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.middle);
|
||||
}
|
||||
if (values.toArrow) {
|
||||
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.to);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Node} viaNode
|
||||
*/
|
||||
drawLabel(ctx, viaNode) {
|
||||
if (this.options.label !== undefined) {
|
||||
// set style
|
||||
var node1 = this.from;
|
||||
var node2 = this.to;
|
||||
|
||||
if (this.labelModule.differentState(this.selected, this.hover)) {
|
||||
this.labelModule.getTextSize(ctx, this.selected, this.hover);
|
||||
}
|
||||
|
||||
if (node1.id != node2.id) {
|
||||
this.labelModule.pointToSelf = false;
|
||||
var point = this.edgeType.getPoint(0.5, viaNode);
|
||||
ctx.save();
|
||||
|
||||
let rotationPoint = this._getRotation(ctx);
|
||||
if (rotationPoint.angle != 0) {
|
||||
ctx.translate(rotationPoint.x, rotationPoint.y);
|
||||
ctx.rotate(rotationPoint.angle);
|
||||
}
|
||||
|
||||
// draw the label
|
||||
this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
|
||||
|
||||
/*
|
||||
// Useful debug code: draw a border around the label
|
||||
// This should **not** be enabled in production!
|
||||
var size = this.labelModule.getSize();; // ;; intentional so lint catches it
|
||||
ctx.strokeStyle = "#ff0000";
|
||||
ctx.strokeRect(size.left, size.top, size.width, size.height);
|
||||
// End debug code
|
||||
*/
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
else {
|
||||
// Ignore the orientations.
|
||||
this.labelModule.pointToSelf = true;
|
||||
var x, y;
|
||||
var radius = this.options.selfReferenceSize;
|
||||
if (node1.shape.width > node1.shape.height) {
|
||||
x = node1.x + node1.shape.width * 0.5;
|
||||
y = node1.y - radius;
|
||||
}
|
||||
else {
|
||||
x = node1.x + radius;
|
||||
y = node1.y - node1.shape.height * 0.5;
|
||||
}
|
||||
point = this._pointOnCircle(x, y, radius, 0.125);
|
||||
this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine all visual elements of this edge instance, in which the given
|
||||
* point falls within the bounding shape.
|
||||
*
|
||||
* @param {point} point
|
||||
* @returns {Array.<edgeClickItem|edgeLabelClickItem>} list with the items which are on the point
|
||||
*/
|
||||
getItemsOnPoint(point) {
|
||||
var ret = [];
|
||||
|
||||
if (this.labelModule.visible()) {
|
||||
let rotationPoint = this._getRotation();
|
||||
if (ComponentUtil.pointInRect(this.labelModule.getSize(), point, rotationPoint)) {
|
||||
ret.push({edgeId:this.id, labelId:0});
|
||||
}
|
||||
}
|
||||
|
||||
let obj = {
|
||||
left: point.x,
|
||||
top: point.y
|
||||
};
|
||||
|
||||
if (this.isOverlappingWith(obj)) {
|
||||
ret.push({edgeId:this.id});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this object is overlapping with the provided object
|
||||
* @param {Object} obj an object with parameters left, top
|
||||
* @return {boolean} True if location is located on the edge
|
||||
*/
|
||||
isOverlappingWith(obj) {
|
||||
if (this.connected) {
|
||||
var distMax = 10;
|
||||
var xFrom = this.from.x;
|
||||
var yFrom = this.from.y;
|
||||
var xTo = this.to.x;
|
||||
var yTo = this.to.y;
|
||||
var xObj = obj.left;
|
||||
var yObj = obj.top;
|
||||
|
||||
var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
|
||||
|
||||
return (dist < distMax);
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the rotation point, if any.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} [ctx] if passed, do a recalculation of the label size
|
||||
* @returns {rotationPoint} the point to rotate around and the angle in radians to rotate
|
||||
* @private
|
||||
*/
|
||||
_getRotation(ctx) {
|
||||
let viaNode = this.edgeType.getViaNode();
|
||||
let point = this.edgeType.getPoint(0.5, viaNode);
|
||||
|
||||
if (ctx !== undefined) {
|
||||
this.labelModule.calculateLabelSize(ctx, this.selected, this.hover, point.x, point.y);
|
||||
}
|
||||
|
||||
let ret = {
|
||||
x: point.x,
|
||||
y: this.labelModule.size.yLine,
|
||||
angle: 0
|
||||
};
|
||||
|
||||
if (!this.labelModule.visible()) {
|
||||
return ret; // Don't even bother doing the atan2, there's nothing to draw
|
||||
}
|
||||
|
||||
if (this.options.font.align === "horizontal") {
|
||||
return ret; // No need to calculate angle
|
||||
}
|
||||
|
||||
var dy = this.from.y - this.to.y;
|
||||
var dx = this.from.x - this.to.x;
|
||||
var angle = Math.atan2(dy, dx); // radians
|
||||
|
||||
// rotate so that label is readable
|
||||
if ((angle < -1 && dx < 0) || (angle > 0 && dx < 0)) {
|
||||
angle += Math.PI;
|
||||
}
|
||||
ret.angle = angle;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a point on a circle
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} radius
|
||||
* @param {number} percentage Value between 0 (line start) and 1 (line end)
|
||||
* @return {Object} point
|
||||
* @private
|
||||
*/
|
||||
_pointOnCircle(x, y, radius, percentage) {
|
||||
var angle = percentage * 2 * Math.PI;
|
||||
return {
|
||||
x: x + radius * Math.cos(angle),
|
||||
y: y - radius * Math.sin(angle)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selected state to true
|
||||
*/
|
||||
select() {
|
||||
this.selected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selected state to false
|
||||
*/
|
||||
unselect() {
|
||||
this.selected = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* cleans all required things on delete
|
||||
* @returns {*}
|
||||
*/
|
||||
cleanup() {
|
||||
return this.edgeType.cleanup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove edge from the list and perform necessary cleanup.
|
||||
*/
|
||||
remove() {
|
||||
this.cleanup();
|
||||
this.disconnect();
|
||||
delete this.body.edges[this.id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if both connecting nodes exist
|
||||
* @returns {boolean}
|
||||
*/
|
||||
endPointsValid() {
|
||||
return this.body.nodes[this.fromId] !== undefined
|
||||
&& this.body.nodes[this.toId] !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default Edge;
|
||||
278
node_modules/vis/lib/network/modules/components/NavigationHandler.js
generated
vendored
Normal file
278
node_modules/vis/lib/network/modules/components/NavigationHandler.js
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
var Hammer = require('../../../module/hammer');
|
||||
var hammerUtil = require('../../../hammerUtil');
|
||||
var keycharm = require('keycharm');
|
||||
|
||||
/**
|
||||
* Navigation Handler
|
||||
*/
|
||||
class NavigationHandler {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {Canvas} canvas
|
||||
*/
|
||||
constructor(body, canvas) {
|
||||
this.body = body;
|
||||
this.canvas = canvas;
|
||||
|
||||
this.iconsCreated = false;
|
||||
this.navigationHammers = [];
|
||||
this.boundFunctions = {};
|
||||
this.touchTime = 0;
|
||||
this.activated = false;
|
||||
|
||||
|
||||
this.body.emitter.on("activate", () => {this.activated = true; this.configureKeyboardBindings();});
|
||||
this.body.emitter.on("deactivate", () => {this.activated = false; this.configureKeyboardBindings();});
|
||||
this.body.emitter.on("destroy", () => {if (this.keycharm !== undefined) {this.keycharm.destroy();}});
|
||||
|
||||
this.options = {}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
if (options !== undefined) {
|
||||
this.options = options;
|
||||
this.create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or refreshes navigation and sets key bindings
|
||||
*/
|
||||
create() {
|
||||
if (this.options.navigationButtons === true) {
|
||||
if (this.iconsCreated === false) {
|
||||
this.loadNavigationElements();
|
||||
}
|
||||
}
|
||||
else if (this.iconsCreated === true) {
|
||||
this.cleanNavigation();
|
||||
}
|
||||
|
||||
this.configureKeyboardBindings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up previous navigation items
|
||||
*/
|
||||
cleanNavigation() {
|
||||
// clean hammer bindings
|
||||
if (this.navigationHammers.length != 0) {
|
||||
for (var i = 0; i < this.navigationHammers.length; i++) {
|
||||
this.navigationHammers[i].destroy();
|
||||
}
|
||||
this.navigationHammers = [];
|
||||
}
|
||||
|
||||
// clean up previous navigation items
|
||||
if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) {
|
||||
this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']);
|
||||
}
|
||||
|
||||
this.iconsCreated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
|
||||
* they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
|
||||
* on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
|
||||
* This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
loadNavigationElements() {
|
||||
this.cleanNavigation();
|
||||
|
||||
this.navigationDOM = {};
|
||||
var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
|
||||
var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_fit'];
|
||||
|
||||
this.navigationDOM['wrapper'] = document.createElement('div');
|
||||
this.navigationDOM['wrapper'].className = 'vis-navigation';
|
||||
this.canvas.frame.appendChild(this.navigationDOM['wrapper']);
|
||||
|
||||
for (var i = 0; i < navigationDivs.length; i++) {
|
||||
this.navigationDOM[navigationDivs[i]] = document.createElement('div');
|
||||
this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i];
|
||||
this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]);
|
||||
|
||||
var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]);
|
||||
if (navigationDivActions[i] === "_fit") {
|
||||
hammerUtil.onTouch(hammer, this._fit.bind(this));
|
||||
}
|
||||
else {
|
||||
hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this,navigationDivActions[i]));
|
||||
}
|
||||
|
||||
this.navigationHammers.push(hammer);
|
||||
}
|
||||
|
||||
// use a hammer for the release so we do not require the one used in the rest of the network
|
||||
// the one the rest uses can be overloaded by the manipulation system.
|
||||
var hammerFrame = new Hammer(this.canvas.frame);
|
||||
hammerUtil.onRelease(hammerFrame, () => {this._stopMovement();});
|
||||
this.navigationHammers.push(hammerFrame);
|
||||
|
||||
this.iconsCreated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} action
|
||||
*/
|
||||
bindToRedraw(action) {
|
||||
if (this.boundFunctions[action] === undefined) {
|
||||
this.boundFunctions[action] = this[action].bind(this);
|
||||
this.body.emitter.on("initRedraw", this.boundFunctions[action]);
|
||||
this.body.emitter.emit("_startRendering");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} action
|
||||
*/
|
||||
unbindFromRedraw(action) {
|
||||
if (this.boundFunctions[action] !== undefined) {
|
||||
this.body.emitter.off("initRedraw", this.boundFunctions[action]);
|
||||
this.body.emitter.emit("_stopRendering");
|
||||
delete this.boundFunctions[action];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this stops all movement induced by the navigation buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_fit() {
|
||||
if (new Date().valueOf() - this.touchTime > 700) { // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?)
|
||||
this.body.emitter.emit("fit", {duration: 700});
|
||||
this.touchTime = new Date().valueOf();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this stops all movement induced by the navigation buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_stopMovement() {
|
||||
for (let boundAction in this.boundFunctions) {
|
||||
if (this.boundFunctions.hasOwnProperty(boundAction)) {
|
||||
this.body.emitter.off("initRedraw", this.boundFunctions[boundAction]);
|
||||
this.body.emitter.emit("_stopRendering");
|
||||
}
|
||||
}
|
||||
this.boundFunctions = {};
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_moveUp() {this.body.view.translation.y += this.options.keyboard.speed.y;}
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_moveDown() {this.body.view.translation.y -= this.options.keyboard.speed.y;}
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_moveLeft() {this.body.view.translation.x += this.options.keyboard.speed.x;}
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_moveRight(){this.body.view.translation.x -= this.options.keyboard.speed.x;}
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_zoomIn() {
|
||||
var scaleOld = this.body.view.scale;
|
||||
var scale = this.body.view.scale * (1 + this.options.keyboard.speed.zoom);
|
||||
var translation = this.body.view.translation;
|
||||
var scaleFrac = scale / scaleOld;
|
||||
var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac;
|
||||
var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac;
|
||||
|
||||
this.body.view.scale = scale;
|
||||
this.body.view.translation = { x: tx, y: ty };
|
||||
this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale, pointer: null });
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_zoomOut() {
|
||||
var scaleOld = this.body.view.scale;
|
||||
var scale = this.body.view.scale / (1 + this.options.keyboard.speed.zoom);
|
||||
var translation = this.body.view.translation;
|
||||
var scaleFrac = scale / scaleOld;
|
||||
var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac;
|
||||
var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac;
|
||||
|
||||
this.body.view.scale = scale;
|
||||
this.body.view.translation = { x: tx, y: ty };
|
||||
this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale, pointer: null });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* bind all keys using keycharm.
|
||||
*/
|
||||
configureKeyboardBindings() {
|
||||
if (this.keycharm !== undefined) {
|
||||
this.keycharm.destroy();
|
||||
}
|
||||
|
||||
if (this.options.keyboard.enabled === true) {
|
||||
if (this.options.keyboard.bindToWindow === true) {
|
||||
this.keycharm = keycharm({container: window, preventDefault: true});
|
||||
}
|
||||
else {
|
||||
this.keycharm = keycharm({container: this.canvas.frame, preventDefault: true});
|
||||
}
|
||||
|
||||
this.keycharm.reset();
|
||||
|
||||
if (this.activated === true) {
|
||||
this.keycharm.bind("up", () => {this.bindToRedraw("_moveUp") ;}, "keydown");
|
||||
this.keycharm.bind("down", () => {this.bindToRedraw("_moveDown") ;}, "keydown");
|
||||
this.keycharm.bind("left", () => {this.bindToRedraw("_moveLeft") ;}, "keydown");
|
||||
this.keycharm.bind("right", () => {this.bindToRedraw("_moveRight");}, "keydown");
|
||||
this.keycharm.bind("=", () => {this.bindToRedraw("_zoomIn") ;}, "keydown");
|
||||
this.keycharm.bind("num+", () => {this.bindToRedraw("_zoomIn") ;}, "keydown");
|
||||
this.keycharm.bind("num-", () => {this.bindToRedraw("_zoomOut") ;}, "keydown");
|
||||
this.keycharm.bind("-", () => {this.bindToRedraw("_zoomOut") ;}, "keydown");
|
||||
this.keycharm.bind("[", () => {this.bindToRedraw("_zoomOut") ;}, "keydown");
|
||||
this.keycharm.bind("]", () => {this.bindToRedraw("_zoomIn") ;}, "keydown");
|
||||
this.keycharm.bind("pageup", () => {this.bindToRedraw("_zoomIn") ;}, "keydown");
|
||||
this.keycharm.bind("pagedown", () => {this.bindToRedraw("_zoomOut") ;}, "keydown");
|
||||
|
||||
this.keycharm.bind("up", () => {this.unbindFromRedraw("_moveUp") ;}, "keyup");
|
||||
this.keycharm.bind("down", () => {this.unbindFromRedraw("_moveDown") ;}, "keyup");
|
||||
this.keycharm.bind("left", () => {this.unbindFromRedraw("_moveLeft") ;}, "keyup");
|
||||
this.keycharm.bind("right", () => {this.unbindFromRedraw("_moveRight");}, "keyup");
|
||||
this.keycharm.bind("=", () => {this.unbindFromRedraw("_zoomIn") ;}, "keyup");
|
||||
this.keycharm.bind("num+", () => {this.unbindFromRedraw("_zoomIn") ;}, "keyup");
|
||||
this.keycharm.bind("num-", () => {this.unbindFromRedraw("_zoomOut") ;}, "keyup");
|
||||
this.keycharm.bind("-", () => {this.unbindFromRedraw("_zoomOut") ;}, "keyup");
|
||||
this.keycharm.bind("[", () => {this.unbindFromRedraw("_zoomOut") ;}, "keyup");
|
||||
this.keycharm.bind("]", () => {this.unbindFromRedraw("_zoomIn") ;}, "keyup");
|
||||
this.keycharm.bind("pageup", () => {this.unbindFromRedraw("_zoomIn") ;}, "keyup");
|
||||
this.keycharm.bind("pagedown", () => {this.unbindFromRedraw("_zoomOut") ;}, "keyup");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default NavigationHandler;
|
||||
654
node_modules/vis/lib/network/modules/components/Node.js
generated
vendored
Normal file
654
node_modules/vis/lib/network/modules/components/Node.js
generated
vendored
Normal file
@@ -0,0 +1,654 @@
|
||||
var util = require('../../../util');
|
||||
|
||||
var Label = require('./shared/Label').default;
|
||||
var ComponentUtil = require('./shared/ComponentUtil').default;
|
||||
var Box = require('./nodes/shapes/Box').default;
|
||||
var Circle = require('./nodes/shapes/Circle').default;
|
||||
var CircularImage = require('./nodes/shapes/CircularImage').default;
|
||||
var Database = require('./nodes/shapes/Database').default;
|
||||
var Diamond = require('./nodes/shapes/Diamond').default;
|
||||
var Dot = require('./nodes/shapes/Dot').default;
|
||||
var Ellipse = require('./nodes/shapes/Ellipse').default;
|
||||
var Icon = require('./nodes/shapes/Icon').default;
|
||||
var Image = require('./nodes/shapes/Image').default;
|
||||
var Square = require('./nodes/shapes/Square').default;
|
||||
var Hexagon = require('./nodes/shapes/Hexagon').default;
|
||||
var Star = require('./nodes/shapes/Star').default;
|
||||
var Text = require('./nodes/shapes/Text').default;
|
||||
var Triangle = require('./nodes/shapes/Triangle').default;
|
||||
var TriangleDown = require('./nodes/shapes/TriangleDown').default;
|
||||
var { printStyle } = require("../../../shared/Validator");
|
||||
|
||||
|
||||
/**
|
||||
* A node. A node can be connected to other nodes via one or multiple edges.
|
||||
*/
|
||||
class Node {
|
||||
/**
|
||||
*
|
||||
* @param {object} options An object containing options for the node. All
|
||||
* options are optional, except for the id.
|
||||
* {number} id Id of the node. Required
|
||||
* {string} label Text label for the node
|
||||
* {number} x Horizontal position of the node
|
||||
* {number} y Vertical position of the node
|
||||
* {string} shape Node shape
|
||||
* {string} image An image url
|
||||
* {string} title A title text, can be HTML
|
||||
* {anytype} group A group name or number
|
||||
*
|
||||
* @param {Object} body Shared state of current network instance
|
||||
* @param {Network.Images} imagelist A list with images. Only needed when the node has an image
|
||||
* @param {Groups} grouplist A list with groups. Needed for retrieving group options
|
||||
* @param {Object} globalOptions Current global node options; these serve as defaults for the node instance
|
||||
* @param {Object} defaultOptions Global default options for nodes; note that this is also the prototype
|
||||
* for parameter `globalOptions`.
|
||||
*/
|
||||
constructor(options, body, imagelist, grouplist, globalOptions, defaultOptions) {
|
||||
this.options = util.bridgeObject(globalOptions);
|
||||
this.globalOptions = globalOptions;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.body = body;
|
||||
|
||||
this.edges = []; // all edges connected to this node
|
||||
|
||||
// set defaults for the options
|
||||
this.id = undefined;
|
||||
this.imagelist = imagelist;
|
||||
this.grouplist = grouplist;
|
||||
|
||||
// state options
|
||||
this.x = undefined;
|
||||
this.y = undefined;
|
||||
this.baseSize = this.options.size;
|
||||
this.baseFontSize = this.options.font.size;
|
||||
this.predefinedPosition = false; // used to check if initial fit should just take the range or approximate
|
||||
this.selected = false;
|
||||
this.hover = false;
|
||||
|
||||
this.labelModule = new Label(this.body, this.options, false /* Not edge label */);
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a edge to the node
|
||||
* @param {Edge} edge
|
||||
*/
|
||||
attachEdge(edge) {
|
||||
if (this.edges.indexOf(edge) === -1) {
|
||||
this.edges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detach a edge from the node
|
||||
*
|
||||
* @param {Edge} edge
|
||||
*/
|
||||
detachEdge(edge) {
|
||||
var index = this.edges.indexOf(edge);
|
||||
if (index != -1) {
|
||||
this.edges.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set or overwrite options for the node
|
||||
*
|
||||
* @param {Object} options an object with options
|
||||
* @returns {null|boolean}
|
||||
*/
|
||||
setOptions(options) {
|
||||
let currentShape = this.options.shape;
|
||||
if (!options) {
|
||||
return; // Note that the return value will be 'undefined'! This is OK.
|
||||
}
|
||||
|
||||
// basic options
|
||||
if (options.id !== undefined) {this.id = options.id;}
|
||||
|
||||
if (this.id === undefined) {
|
||||
throw new Error("Node must have an id");
|
||||
}
|
||||
|
||||
Node.checkMass(options, this.id);
|
||||
|
||||
// set these options locally
|
||||
// clear x and y positions
|
||||
if (options.x !== undefined) {
|
||||
if (options.x === null) {this.x = undefined; this.predefinedPosition = false;}
|
||||
else {this.x = parseInt(options.x); this.predefinedPosition = true;}
|
||||
}
|
||||
if (options.y !== undefined) {
|
||||
if (options.y === null) {this.y = undefined; this.predefinedPosition = false;}
|
||||
else {this.y = parseInt(options.y); this.predefinedPosition = true;}
|
||||
}
|
||||
if (options.size !== undefined) {this.baseSize = options.size;}
|
||||
if (options.value !== undefined) {options.value = parseFloat(options.value);}
|
||||
|
||||
// this transforms all shorthands into fully defined options
|
||||
Node.parseOptions(this.options, options, true, this.globalOptions, this.grouplist);
|
||||
|
||||
let pile = [options, this.options, this.defaultOptions];
|
||||
this.chooser = ComponentUtil.choosify('node', pile);
|
||||
|
||||
this._load_images();
|
||||
this.updateLabelModule(options);
|
||||
this.updateShape(currentShape);
|
||||
|
||||
return (options.hidden !== undefined || options.physics !== undefined);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the images from the options, for the nodes that need them.
|
||||
*
|
||||
* TODO: The imageObj members should be moved to CircularImageBase.
|
||||
* It's the only place where they are required.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_load_images() {
|
||||
// Don't bother loading for nodes without images
|
||||
if (this.options.shape !== 'circularImage' && this.options.shape !== 'image') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.image === undefined) {
|
||||
throw new Error("Option image must be defined for node type '" + this.options.shape + "'");
|
||||
}
|
||||
|
||||
if (this.imagelist === undefined) {
|
||||
throw new Error("Internal Error: No images provided");
|
||||
}
|
||||
|
||||
if (typeof this.options.image === 'string') {
|
||||
this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage, this.id);
|
||||
} else {
|
||||
if (this.options.image.unselected === undefined) {
|
||||
throw new Error("No unselected image provided");
|
||||
}
|
||||
|
||||
this.imageObj = this.imagelist.load(this.options.image.unselected, this.options.brokenImage, this.id);
|
||||
|
||||
if (this.options.image.selected !== undefined) {
|
||||
this.imageObjAlt = this.imagelist.load(this.options.image.selected, this.options.brokenImage, this.id);
|
||||
} else {
|
||||
this.imageObjAlt = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy group option values into the node options.
|
||||
*
|
||||
* The group options override the global node options, so the copy of group options
|
||||
* must happen *after* the global node options have been set.
|
||||
*
|
||||
* This method must also be called also if the global node options have changed and the group options did not.
|
||||
*
|
||||
* @param {Object} parentOptions
|
||||
* @param {Object} newOptions new values for the options, currently only passed in for check
|
||||
* @param {Object} groupList
|
||||
*/
|
||||
static updateGroupOptions(parentOptions, newOptions, groupList) {
|
||||
if (groupList === undefined) return; // No groups, nothing to do
|
||||
|
||||
var group = parentOptions.group;
|
||||
|
||||
// paranoia: the selected group is already merged into node options, check.
|
||||
if (newOptions !== undefined && newOptions.group !== undefined && group !== newOptions.group) {
|
||||
throw new Error("updateGroupOptions: group values in options don't match.");
|
||||
}
|
||||
|
||||
var hasGroup = (typeof group === 'number' || (typeof group === 'string' && group != ''));
|
||||
if (!hasGroup) return; // current node has no group, no need to merge
|
||||
|
||||
var groupObj = groupList.get(group);
|
||||
|
||||
// Skip merging of group font options into parent; these are required to be distinct for labels
|
||||
// TODO: It might not be a good idea either to merge the rest of the options, investigate this.
|
||||
util.selectiveNotDeepExtend(['font'], parentOptions, groupObj);
|
||||
|
||||
// the color object needs to be completely defined.
|
||||
// Since groups can partially overwrite the colors, we parse it again, just in case.
|
||||
parentOptions.color = util.parseColor(parentOptions.color);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This process all possible shorthands in the new options and makes sure that the parentOptions are fully defined.
|
||||
* Static so it can also be used by the handler.
|
||||
*
|
||||
* @param {Object} parentOptions
|
||||
* @param {Object} newOptions
|
||||
* @param {boolean} [allowDeletion=false]
|
||||
* @param {Object} [globalOptions={}]
|
||||
* @param {Object} [groupList]
|
||||
* @static
|
||||
*/
|
||||
static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, groupList) {
|
||||
|
||||
var fields = [
|
||||
'color',
|
||||
'fixed',
|
||||
'shadow'
|
||||
];
|
||||
util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion);
|
||||
|
||||
Node.checkMass(newOptions);
|
||||
|
||||
// merge the shadow options into the parent.
|
||||
util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
|
||||
|
||||
// individual shape newOptions
|
||||
if (newOptions.color !== undefined && newOptions.color !== null) {
|
||||
let parsedColor = util.parseColor(newOptions.color);
|
||||
util.fillIfDefined(parentOptions.color, parsedColor);
|
||||
}
|
||||
else if (allowDeletion === true && newOptions.color === null) {
|
||||
parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
|
||||
}
|
||||
|
||||
// handle the fixed options
|
||||
if (newOptions.fixed !== undefined && newOptions.fixed !== null) {
|
||||
if (typeof newOptions.fixed === 'boolean') {
|
||||
parentOptions.fixed.x = newOptions.fixed;
|
||||
parentOptions.fixed.y = newOptions.fixed;
|
||||
}
|
||||
else {
|
||||
if (newOptions.fixed.x !== undefined && typeof newOptions.fixed.x === 'boolean') {
|
||||
parentOptions.fixed.x = newOptions.fixed.x;
|
||||
}
|
||||
if (newOptions.fixed.y !== undefined && typeof newOptions.fixed.y === 'boolean') {
|
||||
parentOptions.fixed.y = newOptions.fixed.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowDeletion === true && newOptions.font === null) {
|
||||
parentOptions.font = util.bridgeObject(globalOptions.font); // set the object back to the global options
|
||||
}
|
||||
|
||||
Node.updateGroupOptions(parentOptions, newOptions, groupList);
|
||||
|
||||
// handle the scaling options, specifically the label part
|
||||
if (newOptions.scaling !== undefined) {
|
||||
util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {{color: *, borderWidth: *, borderColor: *, size: *, borderDashes: (boolean|Array|allOptions.nodes.shapeProperties.borderDashes|{boolean, array}), borderRadius: (number|allOptions.nodes.shapeProperties.borderRadius|{number}|Array), shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *}}
|
||||
*/
|
||||
getFormattingValues() {
|
||||
let values = {
|
||||
color: this.options.color.background,
|
||||
borderWidth: this.options.borderWidth,
|
||||
borderColor: this.options.color.border,
|
||||
size: this.options.size,
|
||||
borderDashes: this.options.shapeProperties.borderDashes,
|
||||
borderRadius: this.options.shapeProperties.borderRadius,
|
||||
shadow: this.options.shadow.enabled,
|
||||
shadowColor: this.options.shadow.color,
|
||||
shadowSize: this.options.shadow.size,
|
||||
shadowX: this.options.shadow.x,
|
||||
shadowY: this.options.shadow.y
|
||||
};
|
||||
if (this.selected || this.hover) {
|
||||
if (this.chooser === true) {
|
||||
if (this.selected) {
|
||||
values.borderWidth *= 2;
|
||||
values.color = this.options.color.highlight.background;
|
||||
values.borderColor = this.options.color.highlight.border;
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
} else if (this.hover) {
|
||||
values.color = this.options.color.hover.background;
|
||||
values.borderColor = this.options.color.hover.border;
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
}
|
||||
} else if (typeof this.chooser === 'function') {
|
||||
this.chooser(values, this.options.id, this.selected, this.hover);
|
||||
if (values.shadow === false) {
|
||||
if ((values.shadowColor !== this.options.shadow.color) ||
|
||||
(values.shadowSize !== this.options.shadow.size) ||
|
||||
(values.shadowX !== this.options.shadow.x) ||
|
||||
(values.shadowY !== this.options.shadow.y)) {
|
||||
values.shadow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values.shadow = this.options.shadow.enabled;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
updateLabelModule(options) {
|
||||
if (this.options.label === undefined || this.options.label === null) {
|
||||
this.options.label = '';
|
||||
}
|
||||
|
||||
Node.updateGroupOptions(this.options, options, this.grouplist);
|
||||
|
||||
//
|
||||
// Note:The prototype chain for this.options is:
|
||||
//
|
||||
// this.options -> NodesHandler.options -> NodesHandler.defaultOptions
|
||||
// (also: this.globalOptions)
|
||||
//
|
||||
// Note that the prototypes are mentioned explicitly in the pile list below;
|
||||
// WE DON'T WANT THE ORDER OF THE PROTOTYPES!!!! At least, not for font handling of labels.
|
||||
// This is a good indication that the prototype usage of options is deficient.
|
||||
//
|
||||
var currentGroup = this.grouplist.get(this.options.group, false);
|
||||
let pile = [
|
||||
options, // new options
|
||||
this.options, // current node options, see comment above for prototype
|
||||
currentGroup, // group options, if any
|
||||
this.globalOptions, // Currently set global node options
|
||||
this.defaultOptions // Default global node options
|
||||
];
|
||||
this.labelModule.update(this.options, pile);
|
||||
|
||||
if (this.labelModule.baseSize !== undefined) {
|
||||
this.baseFontSize = this.labelModule.baseSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} currentShape
|
||||
*/
|
||||
updateShape(currentShape) {
|
||||
if (currentShape === this.options.shape && this.shape) {
|
||||
this.shape.setOptions(this.options, this.imageObj, this.imageObjAlt);
|
||||
}
|
||||
else {
|
||||
// choose draw method depending on the shape
|
||||
switch (this.options.shape) {
|
||||
case 'box':
|
||||
this.shape = new Box(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'circle':
|
||||
this.shape = new Circle(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'circularImage':
|
||||
this.shape = new CircularImage(this.options, this.body, this.labelModule, this.imageObj, this.imageObjAlt);
|
||||
break;
|
||||
case 'database':
|
||||
this.shape = new Database(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'diamond':
|
||||
this.shape = new Diamond(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'dot':
|
||||
this.shape = new Dot(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'ellipse':
|
||||
this.shape = new Ellipse(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'icon':
|
||||
this.shape = new Icon(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'image':
|
||||
this.shape = new Image(this.options, this.body, this.labelModule, this.imageObj, this.imageObjAlt);
|
||||
break;
|
||||
case 'square':
|
||||
this.shape = new Square(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'hexagon':
|
||||
this.shape = new Hexagon(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'star':
|
||||
this.shape = new Star(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'text':
|
||||
this.shape = new Text(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'triangle':
|
||||
this.shape = new Triangle(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
case 'triangleDown':
|
||||
this.shape = new TriangleDown(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
default:
|
||||
this.shape = new Ellipse(this.options, this.body, this.labelModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.needsRefresh();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* select this node
|
||||
*/
|
||||
select() {
|
||||
this.selected = true;
|
||||
this.needsRefresh();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* unselect this node
|
||||
*/
|
||||
unselect() {
|
||||
this.selected = false;
|
||||
this.needsRefresh();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reset the calculated size of the node, forces it to recalculate its size
|
||||
*/
|
||||
needsRefresh() {
|
||||
this.shape.refreshNeeded = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the title of this node.
|
||||
* @return {string} title The title of the node, or undefined when no title
|
||||
* has been set.
|
||||
*/
|
||||
getTitle() {
|
||||
return this.options.title;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the distance to the border of the Node
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle Angle in radians
|
||||
* @returns {number} distance Distance to the border in pixels
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this.shape.distanceToBorder(ctx,angle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this node has a fixed x and y position
|
||||
* @return {boolean} true if fixed, false if not
|
||||
*/
|
||||
isFixed() {
|
||||
return (this.options.fixed.x && this.options.fixed.y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if this node is selecte
|
||||
* @return {boolean} selected True if node is selected, else false
|
||||
*/
|
||||
isSelected() {
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the value of the node. Can be undefined
|
||||
* @return {number} value
|
||||
*/
|
||||
getValue() {
|
||||
return this.options.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current dimensions of the label
|
||||
*
|
||||
* @return {rect}
|
||||
*/
|
||||
getLabelSize() {
|
||||
return this.labelModule.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjust the value range of the node. The node will adjust it's size
|
||||
* based on its value.
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @param {number} total
|
||||
*/
|
||||
setValueRange(min, max, total) {
|
||||
if (this.options.value !== undefined) {
|
||||
var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value);
|
||||
var sizeDiff = this.options.scaling.max - this.options.scaling.min;
|
||||
if (this.options.scaling.label.enabled === true) {
|
||||
var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min;
|
||||
this.options.font.size = this.options.scaling.label.min + scale * fontDiff;
|
||||
}
|
||||
this.options.size = this.options.scaling.min + scale * sizeDiff;
|
||||
}
|
||||
else {
|
||||
this.options.size = this.baseSize;
|
||||
this.options.font.size = this.baseFontSize;
|
||||
}
|
||||
|
||||
this.updateLabelModule();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draw this node in the given canvas
|
||||
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
draw(ctx) {
|
||||
let values = this.getFormattingValues();
|
||||
this.shape.draw(ctx, this.x, this.y, this.selected, this.hover, values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the bounding box of the shape
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
updateBoundingBox(ctx) {
|
||||
this.shape.updateBoundingBox(this.x,this.y,ctx);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recalculate the size of this node in the given canvas
|
||||
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
resize(ctx) {
|
||||
let values = this.getFormattingValues();
|
||||
this.shape.resize(ctx, this.selected, this.hover, values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine all visual elements of this node instance, in which the given
|
||||
* point falls within the bounding shape.
|
||||
*
|
||||
* @param {point} point
|
||||
* @returns {Array.<nodeClickItem|nodeLabelClickItem>} list with the items which are on the point
|
||||
*/
|
||||
getItemsOnPoint(point) {
|
||||
var ret = [];
|
||||
|
||||
if (this.labelModule.visible()) {
|
||||
if (ComponentUtil.pointInRect(this.labelModule.getSize(), point)) {
|
||||
ret.push({nodeId:this.id, labelId:0});
|
||||
}
|
||||
}
|
||||
|
||||
if (ComponentUtil.pointInRect(this.shape.boundingBox, point)) {
|
||||
ret.push({nodeId:this.id});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this object is overlapping with the provided object
|
||||
* @param {Object} obj an object with parameters left, top, right, bottom
|
||||
* @return {boolean} True if location is located on node
|
||||
*/
|
||||
isOverlappingWith(obj) {
|
||||
return (
|
||||
this.shape.left < obj.right &&
|
||||
this.shape.left + this.shape.width > obj.left &&
|
||||
this.shape.top < obj.bottom &&
|
||||
this.shape.top + this.shape.height > obj.top
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this object is overlapping with the provided object
|
||||
* @param {Object} obj an object with parameters left, top, right, bottom
|
||||
* @return {boolean} True if location is located on node
|
||||
*/
|
||||
isBoundingBoxOverlappingWith(obj) {
|
||||
return (
|
||||
this.shape.boundingBox.left < obj.right &&
|
||||
this.shape.boundingBox.right > obj.left &&
|
||||
this.shape.boundingBox.top < obj.bottom &&
|
||||
this.shape.boundingBox.bottom > obj.top
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check valid values for mass
|
||||
*
|
||||
* The mass may not be negative or zero. If it is, reset to 1
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {Node.id} id
|
||||
* @static
|
||||
*/
|
||||
static checkMass(options, id) {
|
||||
if (options.mass !== undefined && options.mass <= 0) {
|
||||
let strId = '';
|
||||
if (id !== undefined) {
|
||||
strId = ' in node id: ' + id;
|
||||
}
|
||||
console.log('%cNegative or zero mass disallowed' + strId +
|
||||
', setting mass to 1.' , printStyle);
|
||||
options.mass = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Node;
|
||||
68
node_modules/vis/lib/network/modules/components/algorithms/FloydWarshall.js
generated
vendored
Normal file
68
node_modules/vis/lib/network/modules/components/algorithms/FloydWarshall.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* The Floyd–Warshall algorithm is an algorithm for finding shortest paths in
|
||||
* a weighted graph with positive or negative edge weights (but with no negative
|
||||
* cycles). - https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm
|
||||
*/
|
||||
class FloydWarshall {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} body
|
||||
* @param {Array.<Node>} nodesArray
|
||||
* @param {Array.<Edge>} edgesArray
|
||||
* @returns {{}}
|
||||
*/
|
||||
getDistances(body, nodesArray, edgesArray) {
|
||||
let D_matrix = {};
|
||||
let edges = body.edges;
|
||||
|
||||
// prepare matrix with large numbers
|
||||
for (let i = 0; i < nodesArray.length; i++) {
|
||||
let node = nodesArray[i];
|
||||
let cell = {};
|
||||
D_matrix[node] = cell;
|
||||
for (let j = 0; j < nodesArray.length; j++) {
|
||||
cell[nodesArray[j]] = (i == j ? 0 : 1e9);
|
||||
}
|
||||
}
|
||||
|
||||
// put the weights for the edges in. This assumes unidirectionality.
|
||||
for (let i = 0; i < edgesArray.length; i++) {
|
||||
let edge = edges[edgesArray[i]];
|
||||
// edge has to be connected if it counts to the distances. If it is connected to inner clusters it will crash so we also check if it is in the D_matrix
|
||||
if (edge.connected === true && D_matrix[edge.fromId] !== undefined && D_matrix[edge.toId] !== undefined) {
|
||||
D_matrix[edge.fromId][edge.toId] = 1;
|
||||
D_matrix[edge.toId][edge.fromId] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
let nodeCount = nodesArray.length;
|
||||
|
||||
// Adapted FloydWarshall based on unidirectionality to greatly reduce complexity.
|
||||
for (let k = 0; k < nodeCount; k++) {
|
||||
let knode = nodesArray[k];
|
||||
let kcolm = D_matrix[knode];
|
||||
for (let i = 0; i < nodeCount - 1; i++) {
|
||||
let inode = nodesArray[i];
|
||||
let icolm = D_matrix[inode];
|
||||
for (let j = i + 1; j < nodeCount; j++) {
|
||||
let jnode = nodesArray[j];
|
||||
let jcolm = D_matrix[jnode];
|
||||
|
||||
let val = Math.min(icolm[jnode], icolm[knode] + kcolm[jnode]);
|
||||
icolm[jnode] = val;
|
||||
jcolm[inode] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return D_matrix;
|
||||
}
|
||||
}
|
||||
|
||||
export default FloydWarshall;
|
||||
194
node_modules/vis/lib/network/modules/components/edges/BezierEdgeDynamic.js
generated
vendored
Normal file
194
node_modules/vis/lib/network/modules/components/edges/BezierEdgeDynamic.js
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
import BezierEdgeBase from './util/BezierEdgeBase'
|
||||
|
||||
/**
|
||||
* A Dynamic Bezier Edge. Bezier curves are used to model smooth gradual
|
||||
* curves in paths between nodes. The Dynamic piece refers to how the curve
|
||||
* reacts to physics changes.
|
||||
*
|
||||
* @extends BezierEdgeBase
|
||||
*/
|
||||
class BezierEdgeDynamic extends BezierEdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
//this.via = undefined; // Here for completeness but not allowed to defined before super() is invoked.
|
||||
super(options, body, labelModule); // --> this calls the setOptions below
|
||||
this._boundFunction = () => {this.positionBezierNode();};
|
||||
this.body.emitter.on("_repositionBezierNodes", this._boundFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
// check if the physics has changed.
|
||||
let physicsChange = false;
|
||||
if (this.options.physics !== options.physics) {
|
||||
physicsChange = true;
|
||||
}
|
||||
|
||||
// set the options and the to and from nodes
|
||||
this.options = options;
|
||||
this.id = this.options.id;
|
||||
this.from = this.body.nodes[this.options.from];
|
||||
this.to = this.body.nodes[this.options.to];
|
||||
|
||||
// setup the support node and connect
|
||||
this.setupSupportNode();
|
||||
this.connect();
|
||||
|
||||
// when we change the physics state of the edge, we reposition the support node.
|
||||
if (physicsChange === true) {
|
||||
this.via.setOptions({physics: this.options.physics});
|
||||
this.positionBezierNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects an edge to node(s)
|
||||
*/
|
||||
connect() {
|
||||
this.from = this.body.nodes[this.options.from];
|
||||
this.to = this.body.nodes[this.options.to];
|
||||
if (this.from === undefined || this.to === undefined || this.options.physics === false) {
|
||||
this.via.setOptions({physics:false})
|
||||
}
|
||||
else {
|
||||
// fix weird behaviour where a self referencing node has physics enabled
|
||||
if (this.from.id === this.to.id) {
|
||||
this.via.setOptions({physics: false})
|
||||
}
|
||||
else {
|
||||
this.via.setOptions({physics: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the support nodes
|
||||
* @returns {boolean}
|
||||
*/
|
||||
cleanup() {
|
||||
this.body.emitter.off("_repositionBezierNodes", this._boundFunction);
|
||||
if (this.via !== undefined) {
|
||||
delete this.body.nodes[this.via.id];
|
||||
this.via = undefined;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
|
||||
* are used for the force calculation.
|
||||
*
|
||||
* The changed data is not called, if needed, it is returned by the main edge constructor.
|
||||
* @private
|
||||
*/
|
||||
setupSupportNode() {
|
||||
if (this.via === undefined) {
|
||||
var nodeId = "edgeId:" + this.id;
|
||||
var node = this.body.functions.createNode({
|
||||
id: nodeId,
|
||||
shape: 'circle',
|
||||
physics:true,
|
||||
hidden:true
|
||||
});
|
||||
this.body.nodes[nodeId] = node;
|
||||
this.via = node;
|
||||
this.via.parentEdgeId = this.id;
|
||||
this.positionBezierNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions bezier node
|
||||
*/
|
||||
positionBezierNode() {
|
||||
if (this.via !== undefined && this.from !== undefined && this.to !== undefined) {
|
||||
this.via.x = 0.5 * (this.from.x + this.to.x);
|
||||
this.via.y = 0.5 * (this.from.y + this.to.y);
|
||||
}
|
||||
else if (this.via !== undefined) {
|
||||
this.via.x = 0;
|
||||
this.via.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line between two nodes
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @param {Node} viaNode
|
||||
* @private
|
||||
*/
|
||||
_line(ctx, values, viaNode) {
|
||||
this._bezierCurve(ctx, values, viaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Node|undefined|*|{index, line, column}}
|
||||
*/
|
||||
getViaNode() {
|
||||
return this.via;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
|
||||
*
|
||||
* @param {number} percentage
|
||||
* @param {Node} viaNode
|
||||
* @returns {{x: number, y: number}}
|
||||
* @private
|
||||
*/
|
||||
getPoint(percentage, viaNode = this.via) {
|
||||
let t = percentage;
|
||||
let x, y;
|
||||
if (this.from === this.to){
|
||||
let [cx,cy,cr] = this._getCircleData(this.from);
|
||||
let a = 2 * Math.PI * (1 - t);
|
||||
x = cx + cr * Math.sin(a);
|
||||
y = cy + cr - cr * (1 - Math.cos(a));
|
||||
} else {
|
||||
x = Math.pow(1 - t, 2) * this.fromPoint.x + 2 * t * (1 - t) * viaNode.x + Math.pow(t, 2) * this.toPoint.x;
|
||||
y = Math.pow(1 - t, 2) * this.fromPoint.y + 2 * t * (1 - t) * viaNode.y + Math.pow(t, 2) * this.toPoint.y;
|
||||
}
|
||||
|
||||
return {x: x, y: y};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPosition(nearNode, ctx) {
|
||||
return this._findBorderPositionBezier(nearNode, ctx, this.via);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToEdge(x1, y1, x2, y2, x3, y3) { // x3,y3 is the point
|
||||
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default BezierEdgeDynamic;
|
||||
207
node_modules/vis/lib/network/modules/components/edges/BezierEdgeStatic.js
generated
vendored
Normal file
207
node_modules/vis/lib/network/modules/components/edges/BezierEdgeStatic.js
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
import BezierEdgeBase from './util/BezierEdgeBase'
|
||||
|
||||
/**
|
||||
* A Static Bezier Edge. Bezier curves are used to model smooth gradual
|
||||
* curves in paths between nodes.
|
||||
*
|
||||
* @extends BezierEdgeBase
|
||||
*/
|
||||
class BezierEdgeStatic extends BezierEdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line between two nodes
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @param {Node} viaNode
|
||||
* @private
|
||||
*/
|
||||
_line(ctx, values, viaNode) {
|
||||
this._bezierCurve(ctx, values, viaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Array.<{x: number, y: number}>}
|
||||
*/
|
||||
getViaNode() {
|
||||
return this._getViaCoordinates();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We do not use the to and fromPoints here to make the via nodes the same as edges without arrows.
|
||||
* @returns {{x: undefined, y: undefined}}
|
||||
* @private
|
||||
*/
|
||||
_getViaCoordinates() {
|
||||
// Assumption: x/y coordinates in from/to always defined
|
||||
let xVia = undefined;
|
||||
let yVia = undefined;
|
||||
let factor = this.options.smooth.roundness;
|
||||
let type = this.options.smooth.type;
|
||||
let dx = Math.abs(this.from.x - this.to.x);
|
||||
let dy = Math.abs(this.from.y - this.to.y);
|
||||
if (type === 'discrete' || type === 'diagonalCross') {
|
||||
let stepX;
|
||||
let stepY;
|
||||
|
||||
if (dx <= dy) {
|
||||
stepX = stepY = factor * dy;
|
||||
} else {
|
||||
stepX = stepY = factor * dx;
|
||||
}
|
||||
|
||||
if (this.from.x > this.to.x) stepX = -stepX;
|
||||
if (this.from.y >= this.to.y) stepY = -stepY;
|
||||
|
||||
xVia = this.from.x + stepX;
|
||||
yVia = this.from.y + stepY;
|
||||
|
||||
if (type === "discrete") {
|
||||
if (dx <= dy) {
|
||||
xVia = dx < factor * dy ? this.from.x : xVia;
|
||||
} else {
|
||||
yVia = dy < factor * dx ? this.from.y : yVia;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type === "straightCross") {
|
||||
let stepX = (1 - factor) * dx;
|
||||
let stepY = (1 - factor) * dy;
|
||||
|
||||
if (dx <= dy) { // up - down
|
||||
stepX = 0;
|
||||
if (this.from.y < this.to.y) stepY = -stepY;
|
||||
}
|
||||
else { // left - right
|
||||
if (this.from.x < this.to.x) stepX = -stepX;
|
||||
stepY = 0;
|
||||
}
|
||||
xVia = this.to.x + stepX;
|
||||
yVia = this.to.y + stepY;
|
||||
}
|
||||
else if (type === 'horizontal') {
|
||||
let stepX = (1 - factor) * dx;
|
||||
if (this.from.x < this.to.x) stepX = -stepX;
|
||||
xVia = this.to.x + stepX;
|
||||
yVia = this.from.y;
|
||||
}
|
||||
else if (type === 'vertical') {
|
||||
let stepY = (1 - factor) * dy;
|
||||
if (this.from.y < this.to.y) stepY = -stepY;
|
||||
xVia = this.from.x;
|
||||
yVia = this.to.y + stepY;
|
||||
}
|
||||
else if (type === 'curvedCW') {
|
||||
dx = this.to.x - this.from.x;
|
||||
dy = this.from.y - this.to.y;
|
||||
let radius = Math.sqrt(dx * dx + dy * dy);
|
||||
let pi = Math.PI;
|
||||
|
||||
let originalAngle = Math.atan2(dy, dx);
|
||||
let myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
|
||||
|
||||
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
|
||||
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
|
||||
}
|
||||
else if (type === 'curvedCCW') {
|
||||
dx = this.to.x - this.from.x;
|
||||
dy = this.from.y - this.to.y;
|
||||
let radius = Math.sqrt(dx * dx + dy * dy);
|
||||
let pi = Math.PI;
|
||||
|
||||
let originalAngle = Math.atan2(dy, dx);
|
||||
let myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
|
||||
|
||||
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
|
||||
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
|
||||
}
|
||||
else { // continuous
|
||||
let stepX;
|
||||
let stepY;
|
||||
|
||||
if (dx <= dy) {
|
||||
stepX = stepY = factor * dy;
|
||||
} else {
|
||||
stepX = stepY = factor * dx;
|
||||
}
|
||||
|
||||
if (this.from.x > this.to.x) stepX = -stepX;
|
||||
if (this.from.y >= this.to.y) stepY = -stepY;
|
||||
|
||||
xVia = this.from.x + stepX;
|
||||
yVia = this.from.y + stepY;
|
||||
|
||||
if (dx <= dy) {
|
||||
if (this.from.x <= this.to.x) {
|
||||
xVia = this.to.x < xVia ? this.to.x : xVia;
|
||||
}
|
||||
else {
|
||||
xVia = this.to.x > xVia ? this.to.x : xVia;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.from.y >= this.to.y) {
|
||||
yVia = this.to.y > yVia ? this.to.y : yVia;
|
||||
} else {
|
||||
yVia = this.to.y < yVia ? this.to.y : yVia;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {x: xVia, y: yVia};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Object} options
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPosition(nearNode, ctx, options = {}) {
|
||||
return this._findBorderPositionBezier(nearNode, ctx, options.via);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @param {Node} viaNode
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToEdge(x1, y1, x2, y2, x3, y3, viaNode = this._getViaCoordinates()) { // x3,y3 is the point
|
||||
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, viaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
|
||||
* @param {number} percentage
|
||||
* @param {Node} viaNode
|
||||
* @returns {{x: number, y: number}}
|
||||
* @private
|
||||
*/
|
||||
getPoint(percentage, viaNode = this._getViaCoordinates()) {
|
||||
var t = percentage;
|
||||
var x = Math.pow(1 - t, 2) * this.fromPoint.x + (2 * t * (1 - t)) * viaNode.x + Math.pow(t, 2) * this.toPoint.x;
|
||||
var y = Math.pow(1 - t, 2) * this.fromPoint.y + (2 * t * (1 - t)) * viaNode.y + Math.pow(t, 2) * this.toPoint.y;
|
||||
|
||||
return {x: x, y: y};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default BezierEdgeStatic;
|
||||
121
node_modules/vis/lib/network/modules/components/edges/CubicBezierEdge.js
generated
vendored
Normal file
121
node_modules/vis/lib/network/modules/components/edges/CubicBezierEdge.js
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import CubicBezierEdgeBase from './util/CubicBezierEdgeBase'
|
||||
|
||||
/**
|
||||
* A Cubic Bezier Edge. Bezier curves are used to model smooth gradual
|
||||
* curves in paths between nodes.
|
||||
*
|
||||
* @extends CubicBezierEdgeBase
|
||||
*/
|
||||
class CubicBezierEdge extends CubicBezierEdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line between two nodes
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @param {Array.<Node>} viaNodes
|
||||
* @private
|
||||
*/
|
||||
_line(ctx, values, viaNodes) {
|
||||
// get the coordinates of the support points.
|
||||
let via1 = viaNodes[0];
|
||||
let via2 = viaNodes[1];
|
||||
this._bezierCurve(ctx, values, via1, via2);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Array.<{x: number, y: number}>}
|
||||
* @private
|
||||
*/
|
||||
_getViaCoordinates() {
|
||||
let dx = this.from.x - this.to.x;
|
||||
let dy = this.from.y - this.to.y;
|
||||
|
||||
let x1, y1, x2, y2;
|
||||
let roundness = this.options.smooth.roundness;
|
||||
|
||||
// horizontal if x > y or if direction is forced or if direction is horizontal
|
||||
if ((Math.abs(dx) > Math.abs(dy) || this.options.smooth.forceDirection === true || this.options.smooth.forceDirection === 'horizontal') && this.options.smooth.forceDirection !== 'vertical') {
|
||||
y1 = this.from.y;
|
||||
y2 = this.to.y;
|
||||
x1 = this.from.x - roundness * dx;
|
||||
x2 = this.to.x + roundness * dx;
|
||||
}
|
||||
else {
|
||||
y1 = this.from.y - roundness * dy;
|
||||
y2 = this.to.y + roundness * dy;
|
||||
x1 = this.from.x;
|
||||
x2 = this.to.x;
|
||||
}
|
||||
|
||||
return [{x: x1, y: y1},{x: x2, y: y2}];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Array.<{x: number, y: number}>}
|
||||
*/
|
||||
getViaNode() {
|
||||
return this._getViaCoordinates();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @returns {{x: number, y: number, t: number}}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPosition(nearNode, ctx) {
|
||||
return this._findBorderPositionBezier(nearNode, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @param {Node} via1
|
||||
* @param {Node} via2
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToEdge(x1, y1, x2, y2, x3, y3, [via1, via2] = this._getViaCoordinates()) { // x3,y3 is the point
|
||||
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
|
||||
* @param {number} percentage
|
||||
* @param {{x: number, y: number}} [via1=this._getViaCoordinates()[0]]
|
||||
* @param {{x: number, y: number}} [via2=this._getViaCoordinates()[1]]
|
||||
* @returns {{x: number, y: number}}
|
||||
* @private
|
||||
*/
|
||||
getPoint(percentage, [via1, via2] = this._getViaCoordinates()) {
|
||||
let t = percentage;
|
||||
let vec = [];
|
||||
vec[0] = Math.pow(1 - t, 3);
|
||||
vec[1] = 3 * t * Math.pow(1 - t, 2);
|
||||
vec[2] = 3 * Math.pow(t,2) * (1 - t);
|
||||
vec[3] = Math.pow(t, 3);
|
||||
let x = vec[0] * this.fromPoint.x + vec[1] * via1.x + vec[2] * via2.x + vec[3] * this.toPoint.x;
|
||||
let y = vec[0] * this.fromPoint.y + vec[1] * via1.y + vec[2] * via2.y + vec[3] * this.toPoint.y;
|
||||
|
||||
return {x: x, y: y};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default CubicBezierEdge;
|
||||
103
node_modules/vis/lib/network/modules/components/edges/StraightEdge.js
generated
vendored
Normal file
103
node_modules/vis/lib/network/modules/components/edges/StraightEdge.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import EdgeBase from './util/EdgeBase'
|
||||
|
||||
/**
|
||||
* A Straight Edge.
|
||||
*
|
||||
* @extends EdgeBase
|
||||
*/
|
||||
class StraightEdge extends EdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line between two nodes
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @private
|
||||
*/
|
||||
_line(ctx, values) {
|
||||
// draw a straight line
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.fromPoint.x, this.fromPoint.y);
|
||||
ctx.lineTo(this.toPoint.x, this.toPoint.y);
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
ctx.stroke();
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
getViaNode() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
|
||||
*
|
||||
* @param {number} percentage
|
||||
* @returns {{x: number, y: number}}
|
||||
* @private
|
||||
*/
|
||||
getPoint(percentage) {
|
||||
return {
|
||||
x: (1 - percentage) * this.fromPoint.x + percentage * this.toPoint.x,
|
||||
y: (1 - percentage) * this.fromPoint.y + percentage * this.toPoint.y
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @returns {{x: number, y: number}}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPosition(nearNode, ctx) {
|
||||
let node1 = this.to;
|
||||
let node2 = this.from;
|
||||
if (nearNode.id === this.from.id) {
|
||||
node1 = this.from;
|
||||
node2 = this.to;
|
||||
}
|
||||
|
||||
let angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
|
||||
let dx = (node1.x - node2.x);
|
||||
let dy = (node1.y - node2.y);
|
||||
let edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
|
||||
let toBorderDist = nearNode.distanceToBorder(ctx, angle);
|
||||
let toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
|
||||
|
||||
let borderPos = {};
|
||||
borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x;
|
||||
borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y;
|
||||
|
||||
return borderPos;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToEdge(x1, y1, x2, y2, x3, y3) { // x3,y3 is the point
|
||||
return this._getDistanceToLine(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StraightEdge;
|
||||
159
node_modules/vis/lib/network/modules/components/edges/util/BezierEdgeBase.js
generated
vendored
Normal file
159
node_modules/vis/lib/network/modules/components/edges/util/BezierEdgeBase.js
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
import EdgeBase from './EdgeBase'
|
||||
|
||||
/**
|
||||
* The Base Class for all Bezier edges. Bezier curves are used to model smooth
|
||||
* gradual curves in paths between nodes.
|
||||
*
|
||||
* @extends EdgeBase
|
||||
*/
|
||||
class BezierEdgeBase extends EdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Node} viaNode
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPositionBezier(nearNode, ctx, viaNode = this._getViaCoordinates()) {
|
||||
var maxIterations = 10;
|
||||
var iteration = 0;
|
||||
var low = 0;
|
||||
var high = 1;
|
||||
var pos, angle, distanceToBorder, distanceToPoint, difference;
|
||||
var threshold = 0.2;
|
||||
var node = this.to;
|
||||
var from = false;
|
||||
if (nearNode.id === this.from.id) {
|
||||
node = this.from;
|
||||
from = true;
|
||||
}
|
||||
|
||||
while (low <= high && iteration < maxIterations) {
|
||||
var middle = (low + high) * 0.5;
|
||||
|
||||
pos = this.getPoint(middle, viaNode);
|
||||
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
|
||||
distanceToBorder = node.distanceToBorder(ctx, angle);
|
||||
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
|
||||
difference = distanceToBorder - distanceToPoint;
|
||||
if (Math.abs(difference) < threshold) {
|
||||
break; // found
|
||||
}
|
||||
else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
|
||||
if (from === false) {
|
||||
low = middle;
|
||||
}
|
||||
else {
|
||||
high = middle;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (from === false) {
|
||||
high = middle;
|
||||
}
|
||||
else {
|
||||
low = middle;
|
||||
}
|
||||
}
|
||||
|
||||
iteration++;
|
||||
}
|
||||
pos.t = middle;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the distance between a point (x3,y3) and a line segment from
|
||||
* (x1,y1) to (x2,y2).
|
||||
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
|
||||
* @param {number} x1 from x
|
||||
* @param {number} y1 from y
|
||||
* @param {number} x2 to x
|
||||
* @param {number} y2 to y
|
||||
* @param {number} x3 point to check x
|
||||
* @param {number} y3 point to check y
|
||||
* @param {Node} via
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { // x3,y3 is the point
|
||||
let minDistance = 1e9;
|
||||
let distance;
|
||||
let i, t, x, y;
|
||||
let lastX = x1;
|
||||
let lastY = y1;
|
||||
for (i = 1; i < 10; i++) {
|
||||
t = 0.1 * i;
|
||||
x = Math.pow(1 - t, 2) * x1 + (2 * t * (1 - t)) * via.x + Math.pow(t, 2) * x2;
|
||||
y = Math.pow(1 - t, 2) * y1 + (2 * t * (1 - t)) * via.y + Math.pow(t, 2) * y2;
|
||||
if (i > 0) {
|
||||
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
|
||||
minDistance = distance < minDistance ? distance : minDistance;
|
||||
}
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draw a bezier curve between two nodes
|
||||
*
|
||||
* The method accepts zero, one or two control points.
|
||||
* Passing zero control points just draws a straight line
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Object} values | options for shadow drawing
|
||||
* @param {Object|undefined} viaNode1 | first control point for curve drawing
|
||||
* @param {Object|undefined} viaNode2 | second control point for curve drawing
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_bezierCurve(ctx, values, viaNode1, viaNode2) {
|
||||
var hasNode1 = (viaNode1 !== undefined && viaNode1.x !== undefined);
|
||||
var hasNode2 = (viaNode2 !== undefined && viaNode2.x !== undefined);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.fromPoint.x, this.fromPoint.y);
|
||||
|
||||
if (hasNode1 && hasNode2) {
|
||||
ctx.bezierCurveTo(viaNode1.x, viaNode1.y, viaNode2.x, viaNode2.y, this.toPoint.x, this.toPoint.y);
|
||||
} else if (hasNode1) {
|
||||
ctx.quadraticCurveTo(viaNode1.x, viaNode1.y, this.toPoint.x, this.toPoint.y);
|
||||
} else {
|
||||
// fallback to normal straight edge
|
||||
ctx.lineTo(this.toPoint.x, this.toPoint.y);
|
||||
}
|
||||
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
ctx.stroke();
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {*|{x, y}|{x: undefined, y: undefined}}
|
||||
*/
|
||||
getViaNode() {
|
||||
return this._getViaCoordinates();
|
||||
}
|
||||
}
|
||||
|
||||
export default BezierEdgeBase;
|
||||
62
node_modules/vis/lib/network/modules/components/edges/util/CubicBezierEdgeBase.js
generated
vendored
Normal file
62
node_modules/vis/lib/network/modules/components/edges/util/CubicBezierEdgeBase.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import BezierEdgeBase from './BezierEdgeBase'
|
||||
|
||||
/**
|
||||
* A Base Class for all Cubic Bezier Edges. Bezier curves are used to model
|
||||
* smooth gradual curves in paths between nodes.
|
||||
*
|
||||
* @extends BezierEdgeBase
|
||||
*/
|
||||
class CubicBezierEdgeBase extends BezierEdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance between a point (x3,y3) and a line segment from
|
||||
* (x1,y1) to (x2,y2).
|
||||
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
|
||||
* https://en.wikipedia.org/wiki/B%C3%A9zier_curve
|
||||
* @param {number} x1 from x
|
||||
* @param {number} y1 from y
|
||||
* @param {number} x2 to x
|
||||
* @param {number} y2 to y
|
||||
* @param {number} x3 point to check x
|
||||
* @param {number} y3 point to check y
|
||||
* @param {Node} via1
|
||||
* @param {Node} via2
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2) { // x3,y3 is the point
|
||||
let minDistance = 1e9;
|
||||
let distance;
|
||||
let i, t, x, y;
|
||||
let lastX = x1;
|
||||
let lastY = y1;
|
||||
let vec = [0,0,0,0]
|
||||
for (i = 1; i < 10; i++) {
|
||||
t = 0.1 * i;
|
||||
vec[0] = Math.pow(1 - t, 3);
|
||||
vec[1] = 3 * t * Math.pow(1 - t, 2);
|
||||
vec[2] = 3 * Math.pow(t,2) * (1 - t);
|
||||
vec[3] = Math.pow(t, 3);
|
||||
x = vec[0] * x1 + vec[1] * via1.x + vec[2] * via2.x + vec[3] * x2;
|
||||
y = vec[0] * y1 + vec[1] * via1.y + vec[2] * via2.y + vec[3] * y2;
|
||||
if (i > 0) {
|
||||
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
|
||||
minDistance = distance < minDistance ? distance : minDistance;
|
||||
}
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
return minDistance;
|
||||
}
|
||||
}
|
||||
|
||||
export default CubicBezierEdgeBase;
|
||||
597
node_modules/vis/lib/network/modules/components/edges/util/EdgeBase.js
generated
vendored
Normal file
597
node_modules/vis/lib/network/modules/components/edges/util/EdgeBase.js
generated
vendored
Normal file
@@ -0,0 +1,597 @@
|
||||
let util = require("../../../../../util");
|
||||
let EndPoints = require("./EndPoints").default;
|
||||
|
||||
|
||||
/**
|
||||
* The Base Class for all edges.
|
||||
*
|
||||
*/
|
||||
class EdgeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
this.body = body;
|
||||
this.labelModule = labelModule;
|
||||
this.options = {};
|
||||
this.setOptions(options);
|
||||
this.colorDirty = true;
|
||||
this.color = {};
|
||||
this.selectionWidth = 2;
|
||||
this.hoverWidth = 1.5;
|
||||
this.fromPoint = this.from;
|
||||
this.toPoint = this.to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a node to itself
|
||||
*/
|
||||
connect() {
|
||||
this.from = this.body.nodes[this.options.from];
|
||||
this.to = this.body.nodes[this.options.to];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean} always false
|
||||
*/
|
||||
cleanup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
this.from = this.body.nodes[this.options.from];
|
||||
this.to = this.body.nodes[this.options.to];
|
||||
this.id = this.options.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw a edge as a line
|
||||
* Draw this edge in the given canvas
|
||||
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Array} values
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {Node} viaNode
|
||||
* @private
|
||||
*/
|
||||
drawLine(ctx, values, selected, hover, viaNode) {
|
||||
// set style
|
||||
ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
|
||||
ctx.lineWidth = values.width;
|
||||
|
||||
if (values.dashes !== false) {
|
||||
this._drawDashedLine(ctx, values, viaNode);
|
||||
}
|
||||
else {
|
||||
this._drawLine(ctx, values, viaNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Array} values
|
||||
* @param {Node} viaNode
|
||||
* @param {{x: number, y: number}} [fromPoint]
|
||||
* @param {{x: number, y: number}} [toPoint]
|
||||
* @private
|
||||
*/
|
||||
_drawLine(ctx, values, viaNode, fromPoint, toPoint) {
|
||||
if (this.from != this.to) {
|
||||
// draw line
|
||||
this._line(ctx, values, viaNode, fromPoint, toPoint);
|
||||
}
|
||||
else {
|
||||
let [x,y,radius] = this._getCircleData(ctx);
|
||||
this._circle(ctx, values, x, y, radius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Array} values
|
||||
* @param {Node} viaNode
|
||||
* @param {{x: number, y: number}} [fromPoint] TODO: Remove in next major release
|
||||
* @param {{x: number, y: number}} [toPoint] TODO: Remove in next major release
|
||||
* @private
|
||||
*/
|
||||
_drawDashedLine(ctx, values, viaNode, fromPoint, toPoint) { // eslint-disable-line no-unused-vars
|
||||
ctx.lineCap = 'round';
|
||||
let pattern = [5,5];
|
||||
if (Array.isArray(values.dashes) === true) {
|
||||
pattern = values.dashes;
|
||||
}
|
||||
|
||||
// only firefox and chrome support this method, else we use the legacy one.
|
||||
if (ctx.setLineDash !== undefined) {
|
||||
ctx.save();
|
||||
|
||||
// set dash settings for chrome or firefox
|
||||
ctx.setLineDash(pattern);
|
||||
ctx.lineDashOffset = 0;
|
||||
|
||||
// draw the line
|
||||
if (this.from != this.to) {
|
||||
// draw line
|
||||
this._line(ctx, values, viaNode);
|
||||
}
|
||||
else {
|
||||
let [x,y,radius] = this._getCircleData(ctx);
|
||||
this._circle(ctx, values, x, y, radius);
|
||||
}
|
||||
|
||||
// restore the dash settings.
|
||||
ctx.setLineDash([0]);
|
||||
ctx.lineDashOffset = 0;
|
||||
ctx.restore();
|
||||
}
|
||||
else { // unsupporting smooth lines
|
||||
if (this.from != this.to) {
|
||||
// draw line
|
||||
ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, pattern);
|
||||
}
|
||||
else {
|
||||
let [x,y,radius] = this._getCircleData(ctx);
|
||||
this._circle(ctx, values, x, y, radius);
|
||||
}
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Node} nearNode
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Object} options
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
findBorderPosition(nearNode, ctx, options) {
|
||||
if (this.from != this.to) {
|
||||
return this._findBorderPosition(nearNode, ctx, options);
|
||||
}
|
||||
else {
|
||||
return this._findBorderPositionCircle(nearNode, ctx, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @returns {{from: ({x: number, y: number, t: number}|*), to: ({x: number, y: number, t: number}|*)}}
|
||||
*/
|
||||
findBorderPositions(ctx) {
|
||||
let from = {};
|
||||
let to = {};
|
||||
if (this.from != this.to) {
|
||||
from = this._findBorderPosition(this.from, ctx);
|
||||
to = this._findBorderPosition(this.to, ctx);
|
||||
}
|
||||
else {
|
||||
let [x,y] = this._getCircleData(ctx).slice(0, 2);
|
||||
|
||||
from = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.25, high:0.6, direction:-1});
|
||||
to = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.6, high:0.8, direction:1});
|
||||
}
|
||||
return {from, to};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @returns {Array.<number>} x, y, radius
|
||||
* @private
|
||||
*/
|
||||
_getCircleData(ctx) {
|
||||
let x, y;
|
||||
let node = this.from;
|
||||
let radius = this.options.selfReferenceSize;
|
||||
|
||||
if (ctx !== undefined) {
|
||||
if (node.shape.width === undefined) {
|
||||
node.shape.resize(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// get circle coordinates
|
||||
if (node.shape.width > node.shape.height) {
|
||||
x = node.x + node.shape.width * 0.5;
|
||||
y = node.y - radius;
|
||||
}
|
||||
else {
|
||||
x = node.x + radius;
|
||||
y = node.y - node.shape.height * 0.5;
|
||||
}
|
||||
return [x,y,radius];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a point on a circle
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} radius
|
||||
* @param {number} percentage - Value between 0 (line start) and 1 (line end)
|
||||
* @return {Object} point
|
||||
* @private
|
||||
*/
|
||||
_pointOnCircle(x, y, radius, percentage) {
|
||||
let angle = percentage * 2 * Math.PI;
|
||||
return {
|
||||
x: x + radius * Math.cos(angle),
|
||||
y: y - radius * Math.sin(angle)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function uses binary search to look for the point where the circle crosses the border of the node.
|
||||
* @param {Node} node
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Object} options
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_findBorderPositionCircle(node, ctx, options) {
|
||||
let x = options.x;
|
||||
let y = options.y;
|
||||
let low = options.low;
|
||||
let high = options.high;
|
||||
let direction = options.direction;
|
||||
|
||||
let maxIterations = 10;
|
||||
let iteration = 0;
|
||||
let radius = this.options.selfReferenceSize;
|
||||
let pos, angle, distanceToBorder, distanceToPoint, difference;
|
||||
let threshold = 0.05;
|
||||
let middle = (low + high) * 0.5;
|
||||
|
||||
while (low <= high && iteration < maxIterations) {
|
||||
middle = (low + high) * 0.5;
|
||||
|
||||
pos = this._pointOnCircle(x, y, radius, middle);
|
||||
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
|
||||
distanceToBorder = node.distanceToBorder(ctx, angle);
|
||||
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
|
||||
difference = distanceToBorder - distanceToPoint;
|
||||
if (Math.abs(difference) < threshold) {
|
||||
break; // found
|
||||
}
|
||||
else if (difference > 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
|
||||
if (direction > 0) {
|
||||
low = middle;
|
||||
}
|
||||
else {
|
||||
high = middle;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (direction > 0) {
|
||||
high = middle;
|
||||
}
|
||||
else {
|
||||
low = middle;
|
||||
}
|
||||
}
|
||||
iteration++;
|
||||
|
||||
}
|
||||
pos.t = middle;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line width of the edge. Depends on width and whether one of the
|
||||
* connected nodes is selected.
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @returns {number} width
|
||||
* @private
|
||||
*/
|
||||
getLineWidth(selected, hover) {
|
||||
if (selected === true) {
|
||||
return Math.max(this.selectionWidth, 0.3 / this.body.view.scale);
|
||||
}
|
||||
else {
|
||||
if (hover === true) {
|
||||
return Math.max(this.hoverWidth, 0.3 / this.body.view.scale);
|
||||
}
|
||||
else {
|
||||
return Math.max(this.options.width, 0.3 / this.body.view.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @param {boolean} selected - Unused
|
||||
* @param {boolean} hover - Unused
|
||||
* @returns {string}
|
||||
*/
|
||||
getColor(ctx, values, selected, hover) { // eslint-disable-line no-unused-vars
|
||||
if (values.inheritsColor !== false) {
|
||||
// when this is a loop edge, just use the 'from' method
|
||||
if ((values.inheritsColor === 'both') && (this.from.id !== this.to.id)) {
|
||||
let grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
|
||||
let fromColor, toColor;
|
||||
fromColor = this.from.options.color.highlight.border;
|
||||
toColor = this.to.options.color.highlight.border;
|
||||
|
||||
if ((this.from.selected === false) && (this.to.selected === false)) {
|
||||
fromColor = util.overrideOpacity(this.from.options.color.border, values.opacity);
|
||||
toColor = util.overrideOpacity(this.to.options.color.border, values.opacity);
|
||||
}
|
||||
else if ((this.from.selected === true) && (this.to.selected === false)) {
|
||||
toColor = this.to.options.color.border;
|
||||
}
|
||||
else if ((this.from.selected === false) && (this.to.selected === true)) {
|
||||
fromColor = this.from.options.color.border;
|
||||
}
|
||||
grd.addColorStop(0, fromColor);
|
||||
grd.addColorStop(1, toColor);
|
||||
|
||||
// -------------------- this returns -------------------- //
|
||||
return grd;
|
||||
}
|
||||
|
||||
if (values.inheritsColor === "to") {
|
||||
return util.overrideOpacity(this.to.options.color.border, values.opacity);
|
||||
} else { // "from"
|
||||
return util.overrideOpacity(this.from.options.color.border, values.opacity);
|
||||
}
|
||||
} else {
|
||||
return util.overrideOpacity(values.color, values.opacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line from a node to itself, a circle
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Array} values
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} radius
|
||||
* @private
|
||||
*/
|
||||
_circle(ctx, values, x, y, radius) {
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
|
||||
// draw a circle
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
|
||||
ctx.stroke();
|
||||
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the distance between a point (x3,y3) and a line segment from (x1,y1) to (x2,y2).
|
||||
* (x3,y3) is the point.
|
||||
*
|
||||
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @param {Node} via
|
||||
* @param {Array} values
|
||||
* @returns {number}
|
||||
*/
|
||||
getDistanceToEdge(x1, y1, x2, y2, x3, y3, via, values) { // eslint-disable-line no-unused-vars
|
||||
let returnValue = 0;
|
||||
if (this.from != this.to) {
|
||||
returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via)
|
||||
}
|
||||
else {
|
||||
let [x,y,radius] = this._getCircleData(undefined);
|
||||
let dx = x - x3;
|
||||
let dy = y - y3;
|
||||
returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_getDistanceToLine(x1, y1, x2, y2, x3, y3) {
|
||||
let px = x2 - x1;
|
||||
let py = y2 - y1;
|
||||
let something = px * px + py * py;
|
||||
let u = ((x3 - x1) * px + (y3 - y1) * py) / something;
|
||||
|
||||
if (u > 1) {
|
||||
u = 1;
|
||||
}
|
||||
else if (u < 0) {
|
||||
u = 0;
|
||||
}
|
||||
|
||||
let x = x1 + u * px;
|
||||
let y = y1 + u * py;
|
||||
let dx = x - x3;
|
||||
let dy = y - y3;
|
||||
|
||||
//# Note: If the actual distance does not matter,
|
||||
//# if you only want to compare what this function
|
||||
//# returns to other results of this function, you
|
||||
//# can just return the squared distance instead
|
||||
//# (i.e. remove the sqrt) to gain a little performance
|
||||
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {string} position
|
||||
* @param {Node} viaNode
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {Array} values
|
||||
* @returns {{point: *, core: {x: number, y: number}, angle: *, length: number, type: *}}
|
||||
*/
|
||||
getArrowData(ctx, position, viaNode, selected, hover, values) {
|
||||
// set lets
|
||||
let angle;
|
||||
let arrowPoint;
|
||||
let node1;
|
||||
let node2;
|
||||
let guideOffset;
|
||||
let scaleFactor;
|
||||
let type;
|
||||
let lineWidth = values.width;
|
||||
|
||||
if (position === 'from') {
|
||||
node1 = this.from;
|
||||
node2 = this.to;
|
||||
guideOffset = 0.1;
|
||||
scaleFactor = values.fromArrowScale;
|
||||
type = values.fromArrowType;
|
||||
}
|
||||
else if (position === 'to') {
|
||||
node1 = this.to;
|
||||
node2 = this.from;
|
||||
guideOffset = -0.1;
|
||||
scaleFactor = values.toArrowScale;
|
||||
type = values.toArrowType;
|
||||
}
|
||||
else {
|
||||
node1 = this.to;
|
||||
node2 = this.from;
|
||||
scaleFactor = values.middleArrowScale;
|
||||
type = values.middleArrowType;
|
||||
}
|
||||
|
||||
// if not connected to itself
|
||||
if (node1 != node2) {
|
||||
if (position !== 'middle') {
|
||||
// draw arrow head
|
||||
if (this.options.smooth.enabled === true) {
|
||||
arrowPoint = this.findBorderPosition(node1, ctx, { via: viaNode });
|
||||
let guidePos = this.getPoint(Math.max(0.0, Math.min(1.0, arrowPoint.t + guideOffset)), viaNode);
|
||||
angle = Math.atan2((arrowPoint.y - guidePos.y), (arrowPoint.x - guidePos.x));
|
||||
} else {
|
||||
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
|
||||
arrowPoint = this.findBorderPosition(node1, ctx);
|
||||
}
|
||||
} else {
|
||||
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
|
||||
arrowPoint = this.getPoint(0.5, viaNode); // this is 0.6 to account for the size of the arrow.
|
||||
}
|
||||
} else {
|
||||
// draw circle
|
||||
let [x,y,radius] = this._getCircleData(ctx);
|
||||
|
||||
if (position === 'from') {
|
||||
arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.25, high: 0.6, direction: -1 });
|
||||
angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
|
||||
} else if (position === 'to') {
|
||||
arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.6, high: 1.0, direction: 1 });
|
||||
angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
|
||||
} else {
|
||||
arrowPoint = this._pointOnCircle(x, y, radius, 0.175);
|
||||
angle = 3.9269908169872414; // === 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'middle' && scaleFactor < 0) lineWidth *= -1; // reversed middle arrow
|
||||
let length = 15 * scaleFactor + 3 * lineWidth; // 3* lineWidth is the width of the edge.
|
||||
|
||||
var xi = arrowPoint.x - length * 0.9 * Math.cos(angle);
|
||||
var yi = arrowPoint.y - length * 0.9 * Math.sin(angle);
|
||||
let arrowCore = { x: xi, y: yi };
|
||||
|
||||
return { point: arrowPoint, core: arrowCore, angle: angle, length: length, type: type };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {Object} arrowData
|
||||
*/
|
||||
drawArrowHead(ctx, values, selected, hover, arrowData) {
|
||||
// set style
|
||||
ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
|
||||
ctx.fillStyle = ctx.strokeStyle;
|
||||
ctx.lineWidth = values.width;
|
||||
|
||||
EndPoints.draw(ctx, arrowData);
|
||||
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
ctx.fill();
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
enableShadow(ctx, values) {
|
||||
if (values.shadow === true) {
|
||||
ctx.shadowColor = values.shadowColor;
|
||||
ctx.shadowBlur = values.shadowSize;
|
||||
ctx.shadowOffsetX = values.shadowX;
|
||||
ctx.shadowOffsetY = values.shadowY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
disableShadow(ctx, values) {
|
||||
if (values.shadow === true) {
|
||||
ctx.shadowColor = 'rgba(0,0,0,0)';
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowOffsetX = 0;
|
||||
ctx.shadowOffsetY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EdgeBase;
|
||||
231
node_modules/vis/lib/network/modules/components/edges/util/EndPoints.js
generated
vendored
Normal file
231
node_modules/vis/lib/network/modules/components/edges/util/EndPoints.js
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
/** ============================================================================
|
||||
* Location of all the endpoint drawing routines.
|
||||
*
|
||||
* Every endpoint has its own drawing routine, which contains an endpoint definition.
|
||||
*
|
||||
* The endpoint definitions must have the following properies:
|
||||
*
|
||||
* - (0,0) is the connection point to the node it attaches to
|
||||
* - The endpoints are orientated to the positive x-direction
|
||||
* - The length of the endpoint is at most 1
|
||||
*
|
||||
* As long as the endpoint classes remain simple and not too numerous, they will
|
||||
* be contained within this module.
|
||||
* All classes here except `EndPoints` should be considered as private to this module.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
* ### Further Actions
|
||||
*
|
||||
* After adding a new endpoint here, you also need to do the following things:
|
||||
*
|
||||
* - Add the new endpoint name to `network/options.js` in array `endPoints`.
|
||||
* - Add the new endpoint name to the documentation.
|
||||
* Scan for 'arrows.to.type` and add it to the description.
|
||||
* - Add the endpoint to the examples. At the very least, add it to example
|
||||
* `edgeStyles/arrowTypes`.
|
||||
* ============================================================================= */
|
||||
|
||||
// NOTE: When a typedef is isolated in a separate comment block, an actual description is generated for it,
|
||||
// using the rest of the commenting in the code block. Usage of typedef in other comments then
|
||||
// link to there. TIL.
|
||||
//
|
||||
// Also noteworthy, all typedef's set up in this manner are collected in a single, global page 'global.html'.
|
||||
// In other words, it doesn't matter *where* the typedef's are defined in the code.
|
||||
//
|
||||
//
|
||||
// TODO: add descriptive commenting to given typedef's
|
||||
|
||||
/**
|
||||
* @typedef {{type:string, point:Point, angle:number, length:number}} ArrowData
|
||||
*
|
||||
* Object containing instantiation data for a given endpoint.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{x:number, y:number}} Point
|
||||
*
|
||||
* A point in view-coordinates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common methods for endpoints
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
class EndPoint {
|
||||
|
||||
/**
|
||||
* Apply transformation on points for display.
|
||||
*
|
||||
* The following is done:
|
||||
* - rotate by the specified angle
|
||||
* - multiply the (normalized) coordinates by the passed length
|
||||
* - offset by the target coordinates
|
||||
*
|
||||
* @param {Array<Point>} points
|
||||
* @param {ArrowData} arrowData
|
||||
* @static
|
||||
*/
|
||||
static transform(points, arrowData) {
|
||||
if (!(points instanceof Array)) {
|
||||
points = [points];
|
||||
}
|
||||
|
||||
var x = arrowData.point.x;
|
||||
var y = arrowData.point.y;
|
||||
var angle = arrowData.angle
|
||||
var length = arrowData.length;
|
||||
|
||||
for(var i = 0; i < points.length; ++i) {
|
||||
var p = points[i];
|
||||
var xt = p.x * Math.cos(angle) - p.y * Math.sin(angle);
|
||||
var yt = p.x * Math.sin(angle) + p.y * Math.cos(angle);
|
||||
|
||||
p.x = x + length*xt;
|
||||
p.y = y + length*yt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draw a closed path using the given real coordinates.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Array.<Point>} points
|
||||
* @static
|
||||
*/
|
||||
static drawPath(ctx, points) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for(var i = 1; i < points.length; ++i) {
|
||||
ctx.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Drawing methods for the arrow endpoint.
|
||||
* @extends EndPoint
|
||||
*/
|
||||
class Arrow extends EndPoint {
|
||||
|
||||
/**
|
||||
* Draw this shape at the end of a line.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {ArrowData} arrowData
|
||||
* @static
|
||||
*/
|
||||
static draw(ctx, arrowData) {
|
||||
// Normalized points of closed path, in the order that they should be drawn.
|
||||
// (0, 0) is the attachment point, and the point around which should be rotated
|
||||
var points = [
|
||||
{ x: 0 , y: 0 },
|
||||
{ x:-1 , y: 0.3},
|
||||
{ x:-0.9, y: 0 },
|
||||
{ x:-1 , y:-0.3},
|
||||
];
|
||||
|
||||
EndPoint.transform(points, arrowData);
|
||||
EndPoint.drawPath(ctx, points);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drawing methods for the circle endpoint.
|
||||
*/
|
||||
class Circle {
|
||||
|
||||
/**
|
||||
* Draw this shape at the end of a line.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {ArrowData} arrowData
|
||||
* @static
|
||||
*/
|
||||
static draw(ctx, arrowData) {
|
||||
var point = {x:-0.4, y:0};
|
||||
|
||||
EndPoint.transform(point, arrowData);
|
||||
ctx.circle(point.x, point.y, arrowData.length*0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drawing methods for the bar endpoint.
|
||||
*/
|
||||
class Bar {
|
||||
|
||||
/**
|
||||
* Draw this shape at the end of a line.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {ArrowData} arrowData
|
||||
* @static
|
||||
*/
|
||||
static draw(ctx, arrowData) {
|
||||
/*
|
||||
var points = [
|
||||
{x:0, y:0.5},
|
||||
{x:0, y:-0.5}
|
||||
];
|
||||
|
||||
EndPoint.transform(points, arrowData);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
ctx.lineTo(points[1].x, points[1].y);
|
||||
ctx.stroke();
|
||||
*/
|
||||
|
||||
var points = [
|
||||
{x:0, y:0.5},
|
||||
{x:0, y:-0.5},
|
||||
{x:-0.15, y:-0.5},
|
||||
{x:-0.15, y:0.5},
|
||||
];
|
||||
|
||||
EndPoint.transform(points, arrowData);
|
||||
EndPoint.drawPath(ctx, points);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drawing methods for the endpoints.
|
||||
*/
|
||||
class EndPoints {
|
||||
|
||||
/**
|
||||
* Draw an endpoint
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {ArrowData} arrowData
|
||||
* @static
|
||||
*/
|
||||
static draw(ctx, arrowData) {
|
||||
var type;
|
||||
if (arrowData.type) {
|
||||
type = arrowData.type.toLowerCase();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'circle':
|
||||
Circle.draw(ctx, arrowData);
|
||||
break;
|
||||
case 'bar':
|
||||
Bar.draw(ctx, arrowData);
|
||||
break;
|
||||
case 'arrow': // fall-through
|
||||
default:
|
||||
Arrow.draw(ctx, arrowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EndPoints;
|
||||
85
node_modules/vis/lib/network/modules/components/nodes/Cluster.js
generated
vendored
Normal file
85
node_modules/vis/lib/network/modules/components/nodes/Cluster.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
let util = require("../../../../util");
|
||||
let Node = require("../Node").default;
|
||||
|
||||
/**
|
||||
* A Cluster is a special Node that allows a group of Nodes positioned closely together
|
||||
* to be represented by a single Cluster Node.
|
||||
*
|
||||
* @extends Node
|
||||
*/
|
||||
class Cluster extends Node {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Array.<HTMLImageElement>}imagelist
|
||||
* @param {Array} grouplist
|
||||
* @param {Object} globalOptions
|
||||
* @param {Object} defaultOptions Global default options for nodes
|
||||
*/
|
||||
constructor(options, body, imagelist, grouplist, globalOptions, defaultOptions) {
|
||||
super(options, body, imagelist, grouplist, globalOptions, defaultOptions);
|
||||
|
||||
this.isCluster = true;
|
||||
this.containedNodes = {};
|
||||
this.containedEdges = {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transfer child cluster data to current and disconnect the child cluster.
|
||||
*
|
||||
* Please consult the header comment in 'Clustering.js' for the fields set here.
|
||||
*
|
||||
* @param {string|number} childClusterId id of child cluster to open
|
||||
*/
|
||||
_openChildCluster(childClusterId) {
|
||||
let childCluster = this.body.nodes[childClusterId];
|
||||
if (this.containedNodes[childClusterId] === undefined) {
|
||||
throw new Error('node with id: ' + childClusterId + ' not in current cluster');
|
||||
}
|
||||
if (!childCluster.isCluster) {
|
||||
throw new Error('node with id: ' + childClusterId + ' is not a cluster');
|
||||
}
|
||||
|
||||
// Disconnect child cluster from current cluster
|
||||
delete this.containedNodes[childClusterId];
|
||||
util.forEach(childCluster.edges, (edge) => {
|
||||
delete this.containedEdges[edge.id];
|
||||
});
|
||||
|
||||
// Transfer nodes and edges
|
||||
util.forEach(childCluster.containedNodes, (node, nodeId) => {
|
||||
this.containedNodes[nodeId] = node;
|
||||
});
|
||||
childCluster.containedNodes = {};
|
||||
|
||||
util.forEach(childCluster.containedEdges, (edge, edgeId) => {
|
||||
this.containedEdges[edgeId] = edge;
|
||||
});
|
||||
childCluster.containedEdges = {};
|
||||
|
||||
// Transfer edges within cluster edges which are clustered
|
||||
util.forEach(childCluster.edges, (clusterEdge) => {
|
||||
util.forEach(this.edges, (parentClusterEdge) => {
|
||||
// Assumption: a clustered edge can only be present in a single clustering edge
|
||||
// Not tested here
|
||||
let index = parentClusterEdge.clusteringEdgeReplacingIds.indexOf(clusterEdge.id);
|
||||
if (index === -1) return;
|
||||
|
||||
util.forEach(clusterEdge.clusteringEdgeReplacingIds, (srcId) => {
|
||||
parentClusterEdge.clusteringEdgeReplacingIds.push(srcId);
|
||||
|
||||
// Maintain correct bookkeeping for transferred edge
|
||||
this.body.edges[srcId].edgeReplacedById = parentClusterEdge.id;
|
||||
});
|
||||
|
||||
// Remove cluster edge from parent cluster edge
|
||||
parentClusterEdge.clusteringEdgeReplacingIds.splice(index, 1);
|
||||
});
|
||||
});
|
||||
childCluster.edges = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Cluster;
|
||||
91
node_modules/vis/lib/network/modules/components/nodes/shapes/Box.js
generated
vendored
Normal file
91
node_modules/vis/lib/network/modules/components/nodes/shapes/Box.js
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* A Box Node/Cluster shape.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class Box extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor (options, body, labelModule) {
|
||||
super(options,body,labelModule);
|
||||
this._setMargins(labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
var dimensions = this.getDimensionsFromLabel(ctx, selected, hover);
|
||||
|
||||
this.width = dimensions.width + this.margin.right + this.margin.left;
|
||||
this.height = dimensions.height + this.margin.top + this.margin.bottom;
|
||||
this.radius = this.width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
this.initContextForDraw(ctx, values);
|
||||
ctx.roundRect(this.left, this.top, this.width, this.height, values.borderRadius);
|
||||
this.performFill(ctx, values);
|
||||
|
||||
this.updateBoundingBox(x, y, ctx, selected, hover);
|
||||
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
|
||||
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
*/
|
||||
updateBoundingBox(x, y, ctx, selected, hover) {
|
||||
this._updateBoundingBox(x, y, ctx, selected, hover);
|
||||
|
||||
let borderRadius = this.options.shapeProperties.borderRadius; // only effective for box
|
||||
this._addBoundingBoxMargin(borderRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
this.resize(ctx);
|
||||
let borderWidth = this.options.borderWidth;
|
||||
|
||||
return Math.min(
|
||||
Math.abs((this.width) / 2 / Math.cos(angle)),
|
||||
Math.abs((this.height) / 2 / Math.sin(angle))) + borderWidth;
|
||||
}
|
||||
}
|
||||
|
||||
export default Box;
|
||||
86
node_modules/vis/lib/network/modules/components/nodes/shapes/Circle.js
generated
vendored
Normal file
86
node_modules/vis/lib/network/modules/components/nodes/shapes/Circle.js
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
import CircleImageBase from '../util/CircleImageBase'
|
||||
|
||||
/**
|
||||
* A Circle Node/Cluster shape.
|
||||
*
|
||||
* @extends CircleImageBase
|
||||
*/
|
||||
class Circle extends CircleImageBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
this._setMargins(labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
var dimensions = this.getDimensionsFromLabel(ctx, selected, hover);
|
||||
|
||||
var diameter = Math.max(dimensions.width + this.margin.right + this.margin.left,
|
||||
dimensions.height + this.margin.top + this.margin.bottom);
|
||||
|
||||
this.options.size = diameter / 2; // NOTE: this size field only set here, not in Ellipse, Database, Box
|
||||
this.width = diameter;
|
||||
this.height = diameter;
|
||||
this.radius = this.width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
this._drawRawCircle(ctx, x, y, values);
|
||||
|
||||
this.updateBoundingBox(x,y);
|
||||
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
|
||||
y, selected, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
*/
|
||||
updateBoundingBox(x, y) {
|
||||
this.boundingBox.top = y - this.options.size;
|
||||
this.boundingBox.left = x - this.options.size;
|
||||
this.boundingBox.right = x + this.options.size;
|
||||
this.boundingBox.bottom = y + this.options.size;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle - Unused
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) { // eslint-disable-line no-unused-vars
|
||||
this.resize(ctx);
|
||||
return this.width * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
export default Circle;
|
||||
112
node_modules/vis/lib/network/modules/components/nodes/shapes/CircularImage.js
generated
vendored
Normal file
112
node_modules/vis/lib/network/modules/components/nodes/shapes/CircularImage.js
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
'use strict';
|
||||
|
||||
import CircleImageBase from '../util/CircleImageBase'
|
||||
|
||||
/**
|
||||
* A CircularImage Node/Cluster shape.
|
||||
*
|
||||
* @extends CircleImageBase
|
||||
*/
|
||||
class CircularImage extends CircleImageBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
* @param {Image} imageObj
|
||||
* @param {Image} imageObjAlt
|
||||
*/
|
||||
constructor (options, body, labelModule, imageObj, imageObjAlt) {
|
||||
super(options, body, labelModule);
|
||||
|
||||
this.setImages(imageObj, imageObjAlt);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover) {
|
||||
var imageAbsent = (this.imageObj.src === undefined) ||
|
||||
(this.imageObj.width === undefined) ||
|
||||
(this.imageObj.height === undefined);
|
||||
|
||||
if (imageAbsent) {
|
||||
var diameter = this.options.size * 2;
|
||||
this.width = diameter;
|
||||
this.height = diameter;
|
||||
this.radius = 0.5*this.width;
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, an image is present, i.e. this.imageObj is valid.
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
this._resizeImage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.switchImages(selected);
|
||||
this.resize();
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
// draw the background circle. IMPORTANT: the stroke in this method is used by the clip method below.
|
||||
this._drawRawCircle(ctx, x, y, values);
|
||||
|
||||
// now we draw in the circle, we save so we can revert the clip operation after drawing.
|
||||
ctx.save();
|
||||
// clip is used to use the stroke in drawRawCircle as an area that we can draw in.
|
||||
ctx.clip();
|
||||
// draw the image
|
||||
this._drawImageAtPosition(ctx, values);
|
||||
// restore so we can again draw on the full canvas
|
||||
ctx.restore();
|
||||
|
||||
this._drawImageLabel(ctx, x, y, selected, hover);
|
||||
|
||||
this.updateBoundingBox(x,y);
|
||||
}
|
||||
|
||||
// TODO: compare with Circle.updateBoundingBox(), consolidate? More stuff is happening here
|
||||
/**
|
||||
*
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
*/
|
||||
updateBoundingBox(x,y) {
|
||||
this.boundingBox.top = y - this.options.size;
|
||||
this.boundingBox.left = x - this.options.size;
|
||||
this.boundingBox.right = x + this.options.size;
|
||||
this.boundingBox.bottom = y + this.options.size;
|
||||
|
||||
// TODO: compare with Image.updateBoundingBox(), consolidate?
|
||||
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
|
||||
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
|
||||
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelOffset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle - Unused
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) { // eslint-disable-line no-unused-vars
|
||||
this.resize(ctx);
|
||||
return this.width * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
export default CircularImage;
|
||||
71
node_modules/vis/lib/network/modules/components/nodes/shapes/Database.js
generated
vendored
Normal file
71
node_modules/vis/lib/network/modules/components/nodes/shapes/Database.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* A Database Node/Cluster shape.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class Database extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor (options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
this._setMargins(labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
*/
|
||||
resize(ctx, selected, hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
var dimensions = this.getDimensionsFromLabel(ctx, selected, hover);
|
||||
var size = dimensions.width + this.margin.right + this.margin.left;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.radius = this.width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
this.initContextForDraw(ctx, values);
|
||||
ctx.database(x - this.width / 2, y - this.height / 2, this.width, this.height);
|
||||
this.performFill(ctx, values);
|
||||
|
||||
this.updateBoundingBox(x, y, ctx, selected, hover);
|
||||
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
|
||||
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx, angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Database;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Diamond.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Diamond.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Diamond Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Diamond extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Diamond;
|
||||
45
node_modules/vis/lib/network/modules/components/nodes/shapes/Dot.js
generated
vendored
Normal file
45
node_modules/vis/lib/network/modules/components/nodes/shapes/Dot.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Dot Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Dot extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) { // eslint-disable-line no-unused-vars
|
||||
this.resize(ctx);
|
||||
return this.options.size;
|
||||
}
|
||||
}
|
||||
|
||||
export default Dot;
|
||||
74
node_modules/vis/lib/network/modules/components/nodes/shapes/Ellipse.js
generated
vendored
Normal file
74
node_modules/vis/lib/network/modules/components/nodes/shapes/Ellipse.js
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* Am Ellipse Node/Cluster shape.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class Ellipse extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
var dimensions = this.getDimensionsFromLabel(ctx, selected, hover);
|
||||
|
||||
this.height = dimensions.height * 2;
|
||||
this.width = dimensions.width + dimensions.height;
|
||||
this.radius = 0.5*this.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.left = x - this.width * 0.5;
|
||||
this.top = y - this.height * 0.5;
|
||||
|
||||
this.initContextForDraw(ctx, values);
|
||||
ctx.ellipse_vis(this.left, this.top, this.width, this.height);
|
||||
this.performFill(ctx, values);
|
||||
|
||||
this.updateBoundingBox(x, y, ctx, selected, hover);
|
||||
this.labelModule.draw(ctx, x, y, selected, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
this.resize(ctx);
|
||||
var a = this.width * 0.5;
|
||||
var b = this.height * 0.5;
|
||||
var w = (Math.sin(angle) * a);
|
||||
var h = (Math.cos(angle) * b);
|
||||
return a * b / Math.sqrt(w * w + h * h);
|
||||
}
|
||||
}
|
||||
|
||||
export default Ellipse;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Hexagon.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Hexagon.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Hexagon Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Hexagon extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'hexagon', 4, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Hexagon;
|
||||
127
node_modules/vis/lib/network/modules/components/nodes/shapes/Icon.js
generated
vendored
Normal file
127
node_modules/vis/lib/network/modules/components/nodes/shapes/Icon.js
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* An icon replacement for the default Node shape.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class Icon extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
this._setMargins(labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx - Unused.
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected, hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
this.iconSize = {
|
||||
width: Number(this.options.icon.size),
|
||||
height: Number(this.options.icon.size)
|
||||
};
|
||||
this.width = this.iconSize.width + this.margin.right + this.margin.left;
|
||||
this.height = this.iconSize.height + this.margin.top + this.margin.bottom;
|
||||
this.radius = 0.5*this.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.options.icon.size = this.options.icon.size || 50;
|
||||
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
this._icon(ctx, x, y, selected, hover, values);
|
||||
|
||||
if (this.options.label !== undefined) {
|
||||
var iconTextSpacing = 5;
|
||||
this.labelModule.draw(ctx, this.left + this.iconSize.width / 2 + this.margin.left,
|
||||
y + this.height / 2 + iconTextSpacing, selected);
|
||||
}
|
||||
|
||||
this.updateBoundingBox(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
updateBoundingBox(x, y) {
|
||||
this.boundingBox.top = y - this.options.icon.size * 0.5;
|
||||
this.boundingBox.left = x - this.options.icon.size * 0.5;
|
||||
this.boundingBox.right = x + this.options.icon.size * 0.5;
|
||||
this.boundingBox.bottom = y + this.options.icon.size * 0.5;
|
||||
|
||||
if (this.options.label !== undefined && this.labelModule.size.width > 0) {
|
||||
var iconTextSpacing = 5;
|
||||
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
|
||||
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
|
||||
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height + iconTextSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover - Unused
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
_icon(ctx, x, y, selected, hover, values) {
|
||||
let iconSize = Number(this.options.icon.size);
|
||||
|
||||
if (this.options.icon.code !== undefined) {
|
||||
ctx.font = (selected ? "bold " : "") + iconSize + "px " + this.options.icon.face;
|
||||
|
||||
// draw icon
|
||||
ctx.fillStyle = this.options.icon.color || "black";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
ctx.fillText(this.options.icon.code, x, y);
|
||||
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
} else {
|
||||
console.error('When using the icon shape, you need to define the code in the icon options object. This can be done per node or globally.')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
123
node_modules/vis/lib/network/modules/components/nodes/shapes/Image.js
generated
vendored
Normal file
123
node_modules/vis/lib/network/modules/components/nodes/shapes/Image.js
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
'use strict';
|
||||
|
||||
import CircleImageBase from '../util/CircleImageBase'
|
||||
|
||||
|
||||
/**
|
||||
* An image-based replacement for the default Node shape.
|
||||
*
|
||||
* @extends CircleImageBase
|
||||
*/
|
||||
class Image extends CircleImageBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
* @param {Image} imageObj
|
||||
* @param {Image} imageObjAlt
|
||||
*/
|
||||
constructor (options, body, labelModule, imageObj, imageObjAlt) {
|
||||
super(options, body, labelModule);
|
||||
|
||||
this.setImages(imageObj, imageObjAlt);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx - Unused.
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover) {
|
||||
var imageAbsent = (this.imageObj.src === undefined) ||
|
||||
(this.imageObj.width === undefined) ||
|
||||
(this.imageObj.height === undefined);
|
||||
|
||||
if (imageAbsent) {
|
||||
var side = this.options.size * 2;
|
||||
this.width = side;
|
||||
this.height = side;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
this._resizeImage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.switchImages(selected);
|
||||
this.resize();
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
if (this.options.shapeProperties.useBorderWithImage === true) {
|
||||
var neutralborderWidth = this.options.borderWidth;
|
||||
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
|
||||
var borderWidth = (selected ? selectionLineWidth : neutralborderWidth) / this.body.view.scale;
|
||||
ctx.lineWidth = Math.min(this.width, borderWidth);
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
// setup the line properties.
|
||||
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
|
||||
|
||||
// set a fillstyle
|
||||
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
|
||||
|
||||
// draw a rectangle to form the border around. This rectangle is filled so the opacity of a picture (in future vis releases?) can be used to tint the image
|
||||
ctx.rect(this.left - 0.5 * ctx.lineWidth,
|
||||
this.top - 0.5 * ctx.lineWidth,
|
||||
this.width + ctx.lineWidth,
|
||||
this.height + ctx.lineWidth);
|
||||
ctx.fill();
|
||||
|
||||
this.performStroke(ctx, values);
|
||||
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
this._drawImageAtPosition(ctx, values);
|
||||
|
||||
this._drawImageLabel(ctx, x, y, selected, hover);
|
||||
|
||||
this.updateBoundingBox(x,y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
updateBoundingBox(x, y) {
|
||||
this.resize();
|
||||
this._updateBoundingBox(x, y);
|
||||
|
||||
if (this.options.label !== undefined && this.labelModule.size.width > 0) {
|
||||
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
|
||||
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
|
||||
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Image;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Square.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Square.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Square Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Square extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'square', 2, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Square;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Star.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Star.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Star Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Star extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'star', 4, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Star;
|
||||
72
node_modules/vis/lib/network/modules/components/nodes/shapes/Text.js
generated
vendored
Normal file
72
node_modules/vis/lib/network/modules/components/nodes/shapes/Text.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* A text-based replacement for the default Node shape.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class Text extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
this._setMargins(labelModule);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
*/
|
||||
resize(ctx, selected, hover) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
|
||||
this.width = this.textSize.width + this.margin.right + this.margin.left;
|
||||
this.height = this.textSize.height + this.margin.top + this.margin.bottom;
|
||||
this.radius = 0.5*this.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover);
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
|
||||
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
|
||||
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
|
||||
this.updateBoundingBox(x, y, ctx, selected, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Text;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Triangle.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/Triangle.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A Triangle Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class Triangle extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'triangle', 3, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default Triangle;
|
||||
44
node_modules/vis/lib/network/modules/components/nodes/shapes/TriangleDown.js
generated
vendored
Normal file
44
node_modules/vis/lib/network/modules/components/nodes/shapes/TriangleDown.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import ShapeBase from '../util/ShapeBase'
|
||||
|
||||
/**
|
||||
* A downward facing Triangle Node/Cluster shape.
|
||||
*
|
||||
* @extends ShapeBase
|
||||
*/
|
||||
class TriangleDown extends ShapeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, values) {
|
||||
this._drawShape(ctx, 'triangleDown', 3, x, y, selected, hover, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
*/
|
||||
distanceToBorder(ctx, angle) {
|
||||
return this._distanceToBorder(ctx,angle);
|
||||
}
|
||||
}
|
||||
|
||||
export default TriangleDown;
|
||||
192
node_modules/vis/lib/network/modules/components/nodes/util/CircleImageBase.js
generated
vendored
Normal file
192
node_modules/vis/lib/network/modules/components/nodes/util/CircleImageBase.js
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
import NodeBase from './NodeBase';
|
||||
|
||||
/**
|
||||
* NOTE: This is a bad base class
|
||||
*
|
||||
* Child classes are:
|
||||
*
|
||||
* Image - uses *only* image methods
|
||||
* Circle - uses *only* _drawRawCircle
|
||||
* CircleImage - uses all
|
||||
*
|
||||
* TODO: Refactor, move _drawRawCircle to different module, derive Circle from NodeBase
|
||||
* Rename this to ImageBase
|
||||
* Consolidate common code in Image and CircleImage to base class
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class CircleImageBase extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule);
|
||||
this.labelOffset = 0;
|
||||
this.selected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} [imageObj]
|
||||
* @param {Object} [imageObjAlt]
|
||||
*/
|
||||
setOptions(options, imageObj, imageObjAlt) {
|
||||
this.options = options;
|
||||
|
||||
if (!(imageObj === undefined && imageObjAlt === undefined)) {
|
||||
this.setImages(imageObj, imageObjAlt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the images for this node.
|
||||
*
|
||||
* The images can be updated after the initial setting of options;
|
||||
* therefore, this method needs to be reentrant.
|
||||
*
|
||||
* For correct working in error cases, it is necessary to properly set
|
||||
* field 'nodes.brokenImage' in the options.
|
||||
*
|
||||
* @param {Image} imageObj required; main image to show for this node
|
||||
* @param {Image|undefined} imageObjAlt optional; image to show when node is selected
|
||||
*/
|
||||
setImages(imageObj, imageObjAlt) {
|
||||
if (imageObjAlt && this.selected) {
|
||||
this.imageObj = imageObjAlt;
|
||||
this.imageObjAlt = imageObj;
|
||||
} else {
|
||||
this.imageObj = imageObj;
|
||||
this.imageObjAlt = imageObjAlt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selection and switch between the base and the selected image.
|
||||
*
|
||||
* Do the switch only if imageObjAlt exists.
|
||||
*
|
||||
* @param {boolean} selected value of new selected state for current node
|
||||
*/
|
||||
switchImages(selected) {
|
||||
var selection_changed = ((selected && !this.selected) || (!selected && this.selected));
|
||||
this.selected = selected; // Remember new selection
|
||||
|
||||
if (this.imageObjAlt !== undefined && selection_changed) {
|
||||
let imageTmp = this.imageObj;
|
||||
this.imageObj = this.imageObjAlt;
|
||||
this.imageObjAlt = imageTmp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the node dimensions for a loaded image.
|
||||
*
|
||||
* Pre: this.imageObj is valid
|
||||
*/
|
||||
_resizeImage() {
|
||||
var width, height;
|
||||
|
||||
if (this.options.shapeProperties.useImageSize === false) {
|
||||
// Use the size property
|
||||
var ratio_width = 1;
|
||||
var ratio_height = 1;
|
||||
|
||||
// Only calculate the proper ratio if both width and height not zero
|
||||
if (this.imageObj.width && this.imageObj.height) {
|
||||
if (this.imageObj.width > this.imageObj.height) {
|
||||
ratio_width = this.imageObj.width / this.imageObj.height;
|
||||
}
|
||||
else {
|
||||
ratio_height = this.imageObj.height / this.imageObj.width;
|
||||
}
|
||||
}
|
||||
|
||||
width = this.options.size * 2 * ratio_width;
|
||||
height = this.options.size * 2 * ratio_height;
|
||||
}
|
||||
else {
|
||||
// Use the image size
|
||||
width = this.imageObj.width;
|
||||
height = this.imageObj.height;
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.radius = 0.5 * this.width;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @private
|
||||
*/
|
||||
_drawRawCircle(ctx, x, y, values) {
|
||||
this.initContextForDraw(ctx, values);
|
||||
ctx.circle(x, y, values.size);
|
||||
this.performFill(ctx, values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @private
|
||||
*/
|
||||
_drawImageAtPosition(ctx, values) {
|
||||
if (this.imageObj.width != 0) {
|
||||
// draw the image
|
||||
ctx.globalAlpha = 1.0;
|
||||
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
|
||||
let factor = 1;
|
||||
if (this.options.shapeProperties.interpolation === true) {
|
||||
factor = (this.imageObj.width / this.width) / this.body.view.scale;
|
||||
}
|
||||
|
||||
this.imageObj.drawImageAtPosition(ctx, factor, this.left, this.top, this.width, this.height);
|
||||
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @private
|
||||
*/
|
||||
_drawImageLabel(ctx, x, y, selected, hover) {
|
||||
var yLabel;
|
||||
var offset = 0;
|
||||
|
||||
if (this.height !== undefined) {
|
||||
offset = this.height * 0.5;
|
||||
var labelDimensions = this.labelModule.getTextSize(ctx, selected, hover);
|
||||
if (labelDimensions.lineCount >= 1) {
|
||||
offset += labelDimensions.height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
yLabel = y + offset;
|
||||
|
||||
if (this.options.label) {
|
||||
this.labelOffset = offset;
|
||||
}
|
||||
this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging');
|
||||
}
|
||||
}
|
||||
|
||||
export default CircleImageBase;
|
||||
295
node_modules/vis/lib/network/modules/components/nodes/util/NodeBase.js
generated
vendored
Normal file
295
node_modules/vis/lib/network/modules/components/nodes/util/NodeBase.js
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* The Base class for all Nodes.
|
||||
*/
|
||||
class NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
this.body = body;
|
||||
this.labelModule = labelModule;
|
||||
this.setOptions(options);
|
||||
this.top = undefined;
|
||||
this.left = undefined;
|
||||
this.height = undefined;
|
||||
this.width = undefined;
|
||||
this.radius = undefined;
|
||||
this.margin = undefined;
|
||||
this.refreshNeeded = true;
|
||||
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Label} labelModule
|
||||
* @private
|
||||
*/
|
||||
_setMargins(labelModule) {
|
||||
this.margin = {};
|
||||
if (this.options.margin) {
|
||||
if (typeof this.options.margin == 'object') {
|
||||
this.margin.top = this.options.margin.top;
|
||||
this.margin.right = this.options.margin.right;
|
||||
this.margin.bottom = this.options.margin.bottom;
|
||||
this.margin.left = this.options.margin.left;
|
||||
} else {
|
||||
this.margin.top = this.options.margin;
|
||||
this.margin.right = this.options.margin;
|
||||
this.margin.bottom = this.options.margin;
|
||||
this.margin.left = this.options.margin;
|
||||
}
|
||||
}
|
||||
labelModule.adjustSizes(this.margin)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} angle
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
_distanceToBorder(ctx,angle) {
|
||||
var borderWidth = this.options.borderWidth;
|
||||
this.resize(ctx);
|
||||
return Math.min(
|
||||
Math.abs(this.width / 2 / Math.cos(angle)),
|
||||
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
enableShadow(ctx, values) {
|
||||
if (values.shadow) {
|
||||
ctx.shadowColor = values.shadowColor;
|
||||
ctx.shadowBlur = values.shadowSize;
|
||||
ctx.shadowOffsetX = values.shadowX;
|
||||
ctx.shadowOffsetY = values.shadowY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
disableShadow(ctx, values) {
|
||||
if (values.shadow) {
|
||||
ctx.shadowColor = 'rgba(0,0,0,0)';
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowOffsetX = 0;
|
||||
ctx.shadowOffsetY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
enableBorderDashes(ctx, values) {
|
||||
if (values.borderDashes !== false) {
|
||||
if (ctx.setLineDash !== undefined) {
|
||||
let dashes = values.borderDashes;
|
||||
if (dashes === true) {
|
||||
dashes = [5,15]
|
||||
}
|
||||
ctx.setLineDash(dashes);
|
||||
}
|
||||
else {
|
||||
console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
|
||||
this.options.shapeProperties.borderDashes = false;
|
||||
values.borderDashes = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
disableBorderDashes(ctx, values) {
|
||||
if (values.borderDashes !== false) {
|
||||
if (ctx.setLineDash !== undefined) {
|
||||
ctx.setLineDash([0]);
|
||||
}
|
||||
else {
|
||||
console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
|
||||
this.options.shapeProperties.borderDashes = false;
|
||||
values.borderDashes = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the shape of a node needs to be recalculated.
|
||||
*
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @returns {boolean}
|
||||
* @protected
|
||||
*/
|
||||
needsRefresh(selected, hover) {
|
||||
if (this.refreshNeeded === true) {
|
||||
// This is probably not the best location to reset this member.
|
||||
// However, in the current logic, it is the most convenient one.
|
||||
this.refreshNeeded = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (this.width === undefined) || (this.labelModule.differentState(selected, hover));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
initContextForDraw(ctx, values) {
|
||||
var borderWidth = values.borderWidth / this.body.view.scale;
|
||||
|
||||
ctx.lineWidth = Math.min(this.width, borderWidth);
|
||||
ctx.strokeStyle = values.borderColor;
|
||||
ctx.fillStyle = values.color;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
performStroke(ctx, values) {
|
||||
var borderWidth = values.borderWidth / this.body.view.scale;
|
||||
|
||||
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
|
||||
ctx.save();
|
||||
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
|
||||
if (borderWidth > 0) {
|
||||
this.enableBorderDashes(ctx, values);
|
||||
//draw the border
|
||||
ctx.stroke();
|
||||
//disable dashed border for other elements
|
||||
this.disableBorderDashes(ctx, values);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
*/
|
||||
performFill(ctx, values) {
|
||||
// draw shadow if enabled
|
||||
this.enableShadow(ctx, values);
|
||||
// draw the background
|
||||
ctx.fill();
|
||||
// disable shadows for other elements.
|
||||
this.disableShadow(ctx, values);
|
||||
|
||||
this.performStroke(ctx, values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} margin
|
||||
* @private
|
||||
*/
|
||||
_addBoundingBoxMargin(margin) {
|
||||
this.boundingBox.left -= margin;
|
||||
this.boundingBox.top -= margin;
|
||||
this.boundingBox.bottom += margin;
|
||||
this.boundingBox.right += margin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Actual implementation of this method call.
|
||||
*
|
||||
* Doing it like this makes it easier to override
|
||||
* in the child classes.
|
||||
*
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @private
|
||||
*/
|
||||
_updateBoundingBox(x, y, ctx, selected, hover) {
|
||||
if (ctx !== undefined) {
|
||||
this.resize(ctx, selected, hover);
|
||||
}
|
||||
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height/ 2;
|
||||
|
||||
this.boundingBox.left = this.left;
|
||||
this.boundingBox.top = this.top;
|
||||
this.boundingBox.bottom = this.top + this.height;
|
||||
this.boundingBox.right = this.left + this.width;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of this method call.
|
||||
* This acts as a stub which can be overridden.
|
||||
*
|
||||
* @param {number} x width
|
||||
* @param {number} y height
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
*/
|
||||
updateBoundingBox(x, y, ctx, selected, hover) {
|
||||
this._updateBoundingBox(x, y, ctx, selected, hover);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the dimensions to use for nodes with an internal label
|
||||
*
|
||||
* Currently, these are: Circle, Ellipse, Database, Box
|
||||
* The other nodes have external labels, and will not call this method
|
||||
*
|
||||
* If there is no label, decent default values are supplied.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
* @returns {{width:number, height:number}}
|
||||
*/
|
||||
getDimensionsFromLabel(ctx, selected, hover) {
|
||||
// NOTE: previously 'textSize' was not put in 'this' for Ellipse
|
||||
// TODO: examine the consequences.
|
||||
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
|
||||
var width = this.textSize.width;
|
||||
var height = this.textSize.height;
|
||||
|
||||
const DEFAULT_SIZE = 14;
|
||||
if (width === 0) {
|
||||
// This happens when there is no label text set
|
||||
width = DEFAULT_SIZE; // use a decent default
|
||||
height = DEFAULT_SIZE; // if width zero, then height also always zero
|
||||
}
|
||||
|
||||
return {width:width, height:height};
|
||||
}
|
||||
}
|
||||
|
||||
export default NodeBase;
|
||||
85
node_modules/vis/lib/network/modules/components/nodes/util/ShapeBase.js
generated
vendored
Normal file
85
node_modules/vis/lib/network/modules/components/nodes/util/ShapeBase.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import NodeBase from '../util/NodeBase'
|
||||
|
||||
/**
|
||||
* Base class for constructing Node/Cluster Shapes.
|
||||
*
|
||||
* @extends NodeBase
|
||||
*/
|
||||
class ShapeBase extends NodeBase {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} body
|
||||
* @param {Label} labelModule
|
||||
*/
|
||||
constructor(options, body, labelModule) {
|
||||
super(options, body, labelModule)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} [selected]
|
||||
* @param {boolean} [hover]
|
||||
* @param {Object} [values={size: this.options.size}]
|
||||
*/
|
||||
resize(ctx, selected = this.selected, hover = this.hover, values = { size: this.options.size }) {
|
||||
if (this.needsRefresh(selected, hover)) {
|
||||
this.labelModule.getTextSize(ctx, selected, hover);
|
||||
var size = 2 * values.size;
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.radius = 0.5*this.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {string} shape
|
||||
* @param {number} sizeMultiplier - Unused! TODO: Remove next major release
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
|
||||
* @private
|
||||
*/
|
||||
_drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover, values) {
|
||||
this.resize(ctx, selected, hover, values);
|
||||
this.left = x - this.width / 2;
|
||||
this.top = y - this.height / 2;
|
||||
|
||||
this.initContextForDraw(ctx, values);
|
||||
ctx[shape](x, y, values.size);
|
||||
this.performFill(ctx, values);
|
||||
|
||||
if (this.options.label !== undefined) {
|
||||
// Need to call following here in order to ensure value for `this.labelModule.size.height`
|
||||
this.labelModule.calculateLabelSize(ctx, selected, hover, x, y, 'hanging')
|
||||
let yLabel = y + 0.5 * this.height + 0.5 * this.labelModule.size.height;
|
||||
this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging');
|
||||
}
|
||||
|
||||
this.updateBoundingBox(x,y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
updateBoundingBox(x, y) {
|
||||
this.boundingBox.top = y - this.options.size;
|
||||
this.boundingBox.left = x - this.options.size;
|
||||
this.boundingBox.right = x + this.options.size;
|
||||
this.boundingBox.bottom = y + this.options.size;
|
||||
|
||||
if (this.options.label !== undefined && this.labelModule.size.width > 0) {
|
||||
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
|
||||
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
|
||||
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ShapeBase;
|
||||
494
node_modules/vis/lib/network/modules/components/physics/BarnesHutSolver.js
generated
vendored
Normal file
494
node_modules/vis/lib/network/modules/components/physics/BarnesHutSolver.js
generated
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
* Barnes Hut Solver
|
||||
*/
|
||||
class BarnesHutSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.barnesHutTree;
|
||||
this.setOptions(options);
|
||||
this.randomSeed = 5;
|
||||
|
||||
// debug: show grid
|
||||
// this.body.emitter.on("afterDrawing", (ctx) => {this._debug(ctx,'#ff0000')})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
this.thetaInversed = 1 / this.options.theta;
|
||||
|
||||
// if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius
|
||||
this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} random integer
|
||||
*/
|
||||
seededRandom() {
|
||||
var x = Math.sin(this.randomSeed++) * 10000;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function calculates the forces the nodes apply on each other based on a gravitational model.
|
||||
* The Barnes Hut method is used to speed up this N-body simulation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
solve() {
|
||||
if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) {
|
||||
let node;
|
||||
let nodes = this.body.nodes;
|
||||
let nodeIndices = this.physicsBody.physicsNodeIndices;
|
||||
let nodeCount = nodeIndices.length;
|
||||
|
||||
// create the tree
|
||||
let barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices);
|
||||
|
||||
// for debugging
|
||||
this.barnesHutTree = barnesHutTree;
|
||||
|
||||
// place the nodes one by one recursively
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
node = nodes[nodeIndices[i]];
|
||||
if (node.options.mass > 0) {
|
||||
// starting with root is irrelevant, it never passes the BarnesHutSolver condition
|
||||
this._getForceContributions(barnesHutTree.root, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object} parentBranch
|
||||
* @param {Node} node
|
||||
* @private
|
||||
*/
|
||||
_getForceContributions(parentBranch, node) {
|
||||
this._getForceContribution(parentBranch.children.NW, node);
|
||||
this._getForceContribution(parentBranch.children.NE, node);
|
||||
this._getForceContribution(parentBranch.children.SW, node);
|
||||
this._getForceContribution(parentBranch.children.SE, node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
|
||||
* If a region contains a single node, we check if it is not itself, then we apply the force.
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @param {Node} node
|
||||
* @private
|
||||
*/
|
||||
_getForceContribution(parentBranch, node) {
|
||||
// we get no force contribution from an empty region
|
||||
if (parentBranch.childrenCount > 0) {
|
||||
let dx, dy, distance;
|
||||
|
||||
// get the distance from the center of mass to the node.
|
||||
dx = parentBranch.centerOfMass.x - node.x;
|
||||
dy = parentBranch.centerOfMass.y - node.y;
|
||||
distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// BarnesHutSolver condition
|
||||
// original condition : s/d < theta = passed === d/s > 1/theta = passed
|
||||
// calcSize = 1/s --> d * 1/s > 1/theta = passed
|
||||
if (distance * parentBranch.calcSize > this.thetaInversed) {
|
||||
this._calculateForces(distance, dx, dy, node, parentBranch);
|
||||
}
|
||||
else {
|
||||
// Did not pass the condition, go into children if available
|
||||
if (parentBranch.childrenCount === 4) {
|
||||
this._getForceContributions(parentBranch, node);
|
||||
}
|
||||
else { // parentBranch must have only one node, if it was empty we wouldnt be here
|
||||
if (parentBranch.children.data.id != node.id) { // if it is not self
|
||||
this._calculateForces(distance, dx, dy, node, parentBranch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the forces based on the distance.
|
||||
*
|
||||
* @param {number} distance
|
||||
* @param {number} dx
|
||||
* @param {number} dy
|
||||
* @param {Node} node
|
||||
* @param {Object} parentBranch
|
||||
* @private
|
||||
*/
|
||||
_calculateForces(distance, dx, dy, node, parentBranch) {
|
||||
if (distance === 0) {
|
||||
distance = 0.1;
|
||||
dx = distance;
|
||||
}
|
||||
|
||||
if (this.overlapAvoidanceFactor < 1 && node.shape.radius) {
|
||||
distance = Math.max(0.1 + (this.overlapAvoidanceFactor * node.shape.radius), distance - node.shape.radius);
|
||||
}
|
||||
|
||||
// the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines
|
||||
// it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce
|
||||
let gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance,3);
|
||||
let fx = dx * gravityForce;
|
||||
let fy = dy * gravityForce;
|
||||
|
||||
this.physicsBody.forces[node.id].x += fx;
|
||||
this.physicsBody.forces[node.id].y += fy;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
|
||||
*
|
||||
* @param {Array.<Node>} nodes
|
||||
* @param {Array.<number>} nodeIndices
|
||||
* @returns {{root: {centerOfMass: {x: number, y: number}, mass: number, range: {minX: number, maxX: number, minY: number, maxY: number}, size: number, calcSize: number, children: {data: null}, maxWidth: number, level: number, childrenCount: number}}} BarnesHutTree
|
||||
* @private
|
||||
*/
|
||||
_formBarnesHutTree(nodes, nodeIndices) {
|
||||
let node;
|
||||
let nodeCount = nodeIndices.length;
|
||||
|
||||
let minX = nodes[nodeIndices[0]].x;
|
||||
let minY = nodes[nodeIndices[0]].y;
|
||||
let maxX = nodes[nodeIndices[0]].x;
|
||||
let maxY = nodes[nodeIndices[0]].y;
|
||||
|
||||
// get the range of the nodes
|
||||
for (let i = 1; i < nodeCount; i++) {
|
||||
let node = nodes[nodeIndices[i]];
|
||||
let x = node.x;
|
||||
let y = node.y;
|
||||
if (node.options.mass > 0) {
|
||||
if (x < minX) {
|
||||
minX = x;
|
||||
}
|
||||
if (x > maxX) {
|
||||
maxX = x;
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y;
|
||||
}
|
||||
if (y > maxY) {
|
||||
maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
// make the range a square
|
||||
let sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
|
||||
if (sizeDiff > 0) {
|
||||
minY -= 0.5 * sizeDiff;
|
||||
maxY += 0.5 * sizeDiff;
|
||||
} // xSize > ySize
|
||||
else {
|
||||
minX += 0.5 * sizeDiff;
|
||||
maxX -= 0.5 * sizeDiff;
|
||||
} // xSize < ySize
|
||||
|
||||
|
||||
let minimumTreeSize = 1e-5;
|
||||
let rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX));
|
||||
let halfRootSize = 0.5 * rootSize;
|
||||
let centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
|
||||
|
||||
// construct the barnesHutTree
|
||||
let barnesHutTree = {
|
||||
root: {
|
||||
centerOfMass: {x: 0, y: 0},
|
||||
mass: 0,
|
||||
range: {
|
||||
minX: centerX - halfRootSize, maxX: centerX + halfRootSize,
|
||||
minY: centerY - halfRootSize, maxY: centerY + halfRootSize
|
||||
},
|
||||
size: rootSize,
|
||||
calcSize: 1 / rootSize,
|
||||
children: {data: null},
|
||||
maxWidth: 0,
|
||||
level: 0,
|
||||
childrenCount: 4
|
||||
}
|
||||
};
|
||||
this._splitBranch(barnesHutTree.root);
|
||||
|
||||
// place the nodes one by one recursively
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
node = nodes[nodeIndices[i]];
|
||||
if (node.options.mass > 0) {
|
||||
this._placeInTree(barnesHutTree.root, node);
|
||||
}
|
||||
}
|
||||
|
||||
// make global
|
||||
return barnesHutTree
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this updates the mass of a branch. this is increased by adding a node.
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @param {Node} node
|
||||
* @private
|
||||
*/
|
||||
_updateBranchMass(parentBranch, node) {
|
||||
let centerOfMass = parentBranch.centerOfMass;
|
||||
let totalMass = parentBranch.mass + node.options.mass;
|
||||
let totalMassInv = 1 / totalMass;
|
||||
|
||||
centerOfMass.x = centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
|
||||
centerOfMass.x *= totalMassInv;
|
||||
|
||||
centerOfMass.y = centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
|
||||
centerOfMass.y *= totalMassInv;
|
||||
|
||||
parentBranch.mass = totalMass;
|
||||
let biggestSize = Math.max(Math.max(node.height, node.radius), node.width);
|
||||
parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* determine in which branch the node will be placed.
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @param {Node} node
|
||||
* @param {boolean} skipMassUpdate
|
||||
* @private
|
||||
*/
|
||||
_placeInTree(parentBranch, node, skipMassUpdate) {
|
||||
if (skipMassUpdate != true || skipMassUpdate === undefined) {
|
||||
// update the mass of the branch.
|
||||
this._updateBranchMass(parentBranch, node);
|
||||
}
|
||||
|
||||
let range = parentBranch.children.NW.range;
|
||||
let region;
|
||||
if (range.maxX > node.x) { // in NW or SW
|
||||
if (range.maxY > node.y) {
|
||||
region = "NW";
|
||||
}
|
||||
else {
|
||||
region = "SW";
|
||||
}
|
||||
}
|
||||
else { // in NE or SE
|
||||
if (range.maxY > node.y) {
|
||||
region = "NE";
|
||||
}
|
||||
else {
|
||||
region = "SE";
|
||||
}
|
||||
}
|
||||
|
||||
this._placeInRegion(parentBranch, node, region);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* actually place the node in a region (or branch)
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @param {Node} node
|
||||
* @param {'NW'| 'NE' | 'SW' | 'SE'} region
|
||||
* @private
|
||||
*/
|
||||
_placeInRegion(parentBranch, node, region) {
|
||||
let children = parentBranch.children[region];
|
||||
|
||||
switch (children.childrenCount) {
|
||||
case 0: // place node here
|
||||
children.children.data = node;
|
||||
children.childrenCount = 1;
|
||||
this._updateBranchMass(children, node);
|
||||
break;
|
||||
case 1: // convert into children
|
||||
// if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
|
||||
// we move one node a little bit and we do not put it in the tree.
|
||||
if (children.children.data.x === node.x && children.children.data.y === node.y) {
|
||||
node.x += this.seededRandom();
|
||||
node.y += this.seededRandom();
|
||||
}
|
||||
else {
|
||||
this._splitBranch(children);
|
||||
this._placeInTree(children, node);
|
||||
}
|
||||
break;
|
||||
case 4: // place in branch
|
||||
this._placeInTree(children, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
|
||||
* after the split is complete.
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @private
|
||||
*/
|
||||
_splitBranch(parentBranch) {
|
||||
// if the branch is shaded with a node, replace the node in the new subset.
|
||||
let containedNode = null;
|
||||
if (parentBranch.childrenCount === 1) {
|
||||
containedNode = parentBranch.children.data;
|
||||
parentBranch.mass = 0;
|
||||
parentBranch.centerOfMass.x = 0;
|
||||
parentBranch.centerOfMass.y = 0;
|
||||
}
|
||||
parentBranch.childrenCount = 4;
|
||||
parentBranch.children.data = null;
|
||||
this._insertRegion(parentBranch, "NW");
|
||||
this._insertRegion(parentBranch, "NE");
|
||||
this._insertRegion(parentBranch, "SW");
|
||||
this._insertRegion(parentBranch, "SE");
|
||||
|
||||
if (containedNode != null) {
|
||||
this._placeInTree(parentBranch, containedNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function subdivides the region into four new segments.
|
||||
* Specifically, this inserts a single new segment.
|
||||
* It fills the children section of the parentBranch
|
||||
*
|
||||
* @param {Object} parentBranch
|
||||
* @param {'NW'| 'NE' | 'SW' | 'SE'} region
|
||||
* @private
|
||||
*/
|
||||
_insertRegion(parentBranch, region) {
|
||||
let minX, maxX, minY, maxY;
|
||||
let childSize = 0.5 * parentBranch.size;
|
||||
switch (region) {
|
||||
case "NW":
|
||||
minX = parentBranch.range.minX;
|
||||
maxX = parentBranch.range.minX + childSize;
|
||||
minY = parentBranch.range.minY;
|
||||
maxY = parentBranch.range.minY + childSize;
|
||||
break;
|
||||
case "NE":
|
||||
minX = parentBranch.range.minX + childSize;
|
||||
maxX = parentBranch.range.maxX;
|
||||
minY = parentBranch.range.minY;
|
||||
maxY = parentBranch.range.minY + childSize;
|
||||
break;
|
||||
case "SW":
|
||||
minX = parentBranch.range.minX;
|
||||
maxX = parentBranch.range.minX + childSize;
|
||||
minY = parentBranch.range.minY + childSize;
|
||||
maxY = parentBranch.range.maxY;
|
||||
break;
|
||||
case "SE":
|
||||
minX = parentBranch.range.minX + childSize;
|
||||
maxX = parentBranch.range.maxX;
|
||||
minY = parentBranch.range.minY + childSize;
|
||||
maxY = parentBranch.range.maxY;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
parentBranch.children[region] = {
|
||||
centerOfMass: {x: 0, y: 0},
|
||||
mass: 0,
|
||||
range: {minX: minX, maxX: maxX, minY: minY, maxY: maxY},
|
||||
size: 0.5 * parentBranch.size,
|
||||
calcSize: 2 * parentBranch.calcSize,
|
||||
children: {data: null},
|
||||
maxWidth: 0,
|
||||
level: parentBranch.level + 1,
|
||||
childrenCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//--------------------------- DEBUGGING BELOW ---------------------------//
|
||||
|
||||
|
||||
/**
|
||||
* This function is for debugging purposed, it draws the tree.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {string} color
|
||||
* @private
|
||||
*/
|
||||
_debug(ctx, color) {
|
||||
if (this.barnesHutTree !== undefined) {
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
this._drawBranch(this.barnesHutTree.root, ctx, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function is for debugging purposes. It draws the branches recursively.
|
||||
*
|
||||
* @param {Object} branch
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {string} color
|
||||
* @private
|
||||
*/
|
||||
_drawBranch(branch, ctx, color) {
|
||||
if (color === undefined) {
|
||||
color = "#FF0000";
|
||||
}
|
||||
|
||||
if (branch.childrenCount === 4) {
|
||||
this._drawBranch(branch.children.NW, ctx);
|
||||
this._drawBranch(branch.children.NE, ctx);
|
||||
this._drawBranch(branch.children.SE, ctx);
|
||||
this._drawBranch(branch.children.SW, ctx);
|
||||
}
|
||||
ctx.strokeStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(branch.range.minX, branch.range.minY);
|
||||
ctx.lineTo(branch.range.maxX, branch.range.minY);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(branch.range.maxX, branch.range.minY);
|
||||
ctx.lineTo(branch.range.maxX, branch.range.maxY);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(branch.range.maxX, branch.range.maxY);
|
||||
ctx.lineTo(branch.range.minX, branch.range.maxY);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(branch.range.minX, branch.range.maxY);
|
||||
ctx.lineTo(branch.range.minX, branch.range.minY);
|
||||
ctx.stroke();
|
||||
|
||||
/*
|
||||
if (branch.mass > 0) {
|
||||
ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
|
||||
ctx.stroke();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default BarnesHutSolver;
|
||||
61
node_modules/vis/lib/network/modules/components/physics/CentralGravitySolver.js
generated
vendored
Normal file
61
node_modules/vis/lib/network/modules/components/physics/CentralGravitySolver.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Central Gravity Solver
|
||||
*/
|
||||
class CentralGravitySolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates forces for each node
|
||||
*/
|
||||
solve() {
|
||||
let dx, dy, distance, node;
|
||||
let nodes = this.body.nodes;
|
||||
let nodeIndices = this.physicsBody.physicsNodeIndices;
|
||||
let forces = this.physicsBody.forces;
|
||||
|
||||
for (let i = 0; i < nodeIndices.length; i++) {
|
||||
let nodeId = nodeIndices[i];
|
||||
node = nodes[nodeId];
|
||||
dx = -node.x;
|
||||
dy = -node.y;
|
||||
distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
this._calculateForces(distance, dx, dy, forces, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the forces based on the distance.
|
||||
* @param {number} distance
|
||||
* @param {number} dx
|
||||
* @param {number} dy
|
||||
* @param {Object<Node.id, vis.Node>} forces
|
||||
* @param {Node} node
|
||||
* @private
|
||||
*/
|
||||
_calculateForces(distance, dx, dy, forces, node) {
|
||||
let gravityForce = (distance === 0) ? 0 : (this.options.centralGravity / distance);
|
||||
forces[node.id].x = dx * gravityForce;
|
||||
forces[node.id].y = dy * gravityForce;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default CentralGravitySolver;
|
||||
37
node_modules/vis/lib/network/modules/components/physics/FA2BasedCentralGravitySolver.js
generated
vendored
Normal file
37
node_modules/vis/lib/network/modules/components/physics/FA2BasedCentralGravitySolver.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import CentralGravitySolver from "./CentralGravitySolver"
|
||||
|
||||
/**
|
||||
* @extends CentralGravitySolver
|
||||
*/
|
||||
class ForceAtlas2BasedCentralGravitySolver extends CentralGravitySolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
super(body, physicsBody, options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the forces based on the distance.
|
||||
*
|
||||
* @param {number} distance
|
||||
* @param {number} dx
|
||||
* @param {number} dy
|
||||
* @param {Object<Node.id, Node>} forces
|
||||
* @param {Node} node
|
||||
* @private
|
||||
*/
|
||||
_calculateForces(distance, dx, dy, forces, node) {
|
||||
if (distance > 0) {
|
||||
let degree = (node.edges.length + 1);
|
||||
let gravityForce = this.options.centralGravity * degree * node.options.mass;
|
||||
forces[node.id].x = dx * gravityForce;
|
||||
forces[node.id].y = dy * gravityForce;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ForceAtlas2BasedCentralGravitySolver;
|
||||
48
node_modules/vis/lib/network/modules/components/physics/FA2BasedRepulsionSolver.js
generated
vendored
Normal file
48
node_modules/vis/lib/network/modules/components/physics/FA2BasedRepulsionSolver.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import BarnesHutSolver from "./BarnesHutSolver"
|
||||
|
||||
/**
|
||||
* @extends BarnesHutSolver
|
||||
*/
|
||||
class ForceAtlas2BasedRepulsionSolver extends BarnesHutSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
super(body, physicsBody, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the forces based on the distance.
|
||||
*
|
||||
* @param {number} distance
|
||||
* @param {number} dx
|
||||
* @param {number} dy
|
||||
* @param {Node} node
|
||||
* @param {Object} parentBranch
|
||||
* @private
|
||||
*/
|
||||
_calculateForces(distance, dx, dy, node, parentBranch) {
|
||||
if (distance === 0) {
|
||||
distance = 0.1 * Math.random();
|
||||
dx = distance;
|
||||
}
|
||||
|
||||
if (this.overlapAvoidanceFactor < 1 && node.shape.radius) {
|
||||
distance = Math.max(0.1 + (this.overlapAvoidanceFactor * node.shape.radius), distance - node.shape.radius);
|
||||
}
|
||||
|
||||
let degree = (node.edges.length + 1);
|
||||
// the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines
|
||||
// it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce
|
||||
let gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance,2);
|
||||
let fx = dx * gravityForce;
|
||||
let fy = dy * gravityForce;
|
||||
|
||||
this.physicsBody.forces[node.id].x += fx;
|
||||
this.physicsBody.forces[node.id].y += fy;
|
||||
}
|
||||
}
|
||||
|
||||
export default ForceAtlas2BasedRepulsionSolver;
|
||||
81
node_modules/vis/lib/network/modules/components/physics/HierarchicalRepulsionSolver.js
generated
vendored
Normal file
81
node_modules/vis/lib/network/modules/components/physics/HierarchicalRepulsionSolver.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Hierarchical Repulsion Solver
|
||||
*/
|
||||
class HierarchicalRepulsionSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the forces the nodes apply on each other based on a repulsion field.
|
||||
* This field is linearly approximated.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
solve() {
|
||||
var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j;
|
||||
|
||||
var nodes = this.body.nodes;
|
||||
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
||||
var forces = this.physicsBody.forces;
|
||||
|
||||
// repulsing forces between nodes
|
||||
var nodeDistance = this.options.nodeDistance;
|
||||
|
||||
// we loop from i over all but the last entree in the array
|
||||
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j
|
||||
for (i = 0; i < nodeIndices.length - 1; i++) {
|
||||
node1 = nodes[nodeIndices[i]];
|
||||
for (j = i + 1; j < nodeIndices.length; j++) {
|
||||
node2 = nodes[nodeIndices[j]];
|
||||
|
||||
// nodes only affect nodes on their level
|
||||
if (node1.level === node2.level) {
|
||||
dx = node2.x - node1.x;
|
||||
dy = node2.y - node1.y;
|
||||
distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
var steepness = 0.05;
|
||||
if (distance < nodeDistance) {
|
||||
repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2);
|
||||
}
|
||||
else {
|
||||
repulsingForce = 0;
|
||||
}
|
||||
// normalize force with
|
||||
if (distance === 0) {
|
||||
distance = 0.01;
|
||||
}
|
||||
else {
|
||||
repulsingForce = repulsingForce / distance;
|
||||
}
|
||||
fx = dx * repulsingForce;
|
||||
fy = dy * repulsingForce;
|
||||
|
||||
forces[node1.id].x -= fx;
|
||||
forces[node1.id].y -= fy;
|
||||
forces[node2.id].x += fx;
|
||||
forces[node2.id].y += fy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default HierarchicalRepulsionSolver;
|
||||
118
node_modules/vis/lib/network/modules/components/physics/HierarchicalSpringSolver.js
generated
vendored
Normal file
118
node_modules/vis/lib/network/modules/components/physics/HierarchicalSpringSolver.js
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Hierarchical Spring Solver
|
||||
*/
|
||||
class HierarchicalSpringSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calculates the springforces on the nodes, accounting for the support nodes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
solve() {
|
||||
var edgeLength, edge;
|
||||
var dx, dy, fx, fy, springForce, distance;
|
||||
var edges = this.body.edges;
|
||||
var factor = 0.5;
|
||||
|
||||
var edgeIndices = this.physicsBody.physicsEdgeIndices;
|
||||
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
||||
var forces = this.physicsBody.forces;
|
||||
|
||||
// initialize the spring force counters
|
||||
for (let i = 0; i < nodeIndices.length; i++) {
|
||||
let nodeId = nodeIndices[i];
|
||||
forces[nodeId].springFx = 0;
|
||||
forces[nodeId].springFy = 0;
|
||||
}
|
||||
|
||||
|
||||
// forces caused by the edges, modelled as springs
|
||||
for (let i = 0; i < edgeIndices.length; i++) {
|
||||
edge = edges[edgeIndices[i]];
|
||||
if (edge.connected === true) {
|
||||
edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
|
||||
|
||||
dx = (edge.from.x - edge.to.x);
|
||||
dy = (edge.from.y - edge.to.y);
|
||||
distance = Math.sqrt(dx * dx + dy * dy);
|
||||
distance = distance === 0 ? 0.01 : distance;
|
||||
|
||||
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
|
||||
springForce = this.options.springConstant * (edgeLength - distance) / distance;
|
||||
|
||||
fx = dx * springForce;
|
||||
fy = dy * springForce;
|
||||
|
||||
if (edge.to.level != edge.from.level) {
|
||||
if (forces[edge.toId] !== undefined) {
|
||||
forces[edge.toId].springFx -= fx;
|
||||
forces[edge.toId].springFy -= fy;
|
||||
}
|
||||
if (forces[edge.fromId] !== undefined) {
|
||||
forces[edge.fromId].springFx += fx;
|
||||
forces[edge.fromId].springFy += fy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (forces[edge.toId] !== undefined) {
|
||||
forces[edge.toId].x -= factor * fx;
|
||||
forces[edge.toId].y -= factor * fy;
|
||||
}
|
||||
if (forces[edge.fromId] !== undefined) {
|
||||
forces[edge.fromId].x += factor * fx;
|
||||
forces[edge.fromId].y += factor * fy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalize spring forces
|
||||
springForce = 1;
|
||||
var springFx, springFy;
|
||||
for (let i = 0; i < nodeIndices.length; i++) {
|
||||
let nodeId = nodeIndices[i];
|
||||
springFx = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFx));
|
||||
springFy = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFy));
|
||||
|
||||
forces[nodeId].x += springFx;
|
||||
forces[nodeId].y += springFy;
|
||||
}
|
||||
|
||||
// retain energy balance
|
||||
var totalFx = 0;
|
||||
var totalFy = 0;
|
||||
for (let i = 0; i < nodeIndices.length; i++) {
|
||||
let nodeId = nodeIndices[i];
|
||||
totalFx += forces[nodeId].x;
|
||||
totalFy += forces[nodeId].y;
|
||||
}
|
||||
var correctionFx = totalFx / nodeIndices.length;
|
||||
var correctionFy = totalFy / nodeIndices.length;
|
||||
|
||||
for (let i = 0; i < nodeIndices.length; i++) {
|
||||
let nodeId = nodeIndices[i];
|
||||
forces[nodeId].x -= correctionFx;
|
||||
forces[nodeId].y -= correctionFy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default HierarchicalSpringSolver;
|
||||
84
node_modules/vis/lib/network/modules/components/physics/RepulsionSolver.js
generated
vendored
Normal file
84
node_modules/vis/lib/network/modules/components/physics/RepulsionSolver.js
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Repulsion Solver
|
||||
*/
|
||||
class RepulsionSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the forces the nodes apply on each other based on a repulsion field.
|
||||
* This field is linearly approximated.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
solve() {
|
||||
var dx, dy, distance, fx, fy, repulsingForce, node1, node2;
|
||||
|
||||
var nodes = this.body.nodes;
|
||||
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
||||
var forces = this.physicsBody.forces;
|
||||
|
||||
// repulsing forces between nodes
|
||||
var nodeDistance = this.options.nodeDistance;
|
||||
|
||||
// approximation constants
|
||||
var a = (-2 / 3) / nodeDistance;
|
||||
var b = 4 / 3;
|
||||
|
||||
// we loop from i over all but the last entree in the array
|
||||
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j
|
||||
for (let i = 0; i < nodeIndices.length - 1; i++) {
|
||||
node1 = nodes[nodeIndices[i]];
|
||||
for (let j = i + 1; j < nodeIndices.length; j++) {
|
||||
node2 = nodes[nodeIndices[j]];
|
||||
|
||||
dx = node2.x - node1.x;
|
||||
dy = node2.y - node1.y;
|
||||
distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// same condition as BarnesHutSolver, making sure nodes are never 100% overlapping.
|
||||
if (distance === 0) {
|
||||
distance = 0.1*Math.random();
|
||||
dx = distance;
|
||||
}
|
||||
|
||||
if (distance < 2 * nodeDistance) {
|
||||
if (distance < 0.5 * nodeDistance) {
|
||||
repulsingForce = 1.0;
|
||||
}
|
||||
else {
|
||||
repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness))
|
||||
}
|
||||
repulsingForce = repulsingForce / distance;
|
||||
|
||||
fx = dx * repulsingForce;
|
||||
fy = dy * repulsingForce;
|
||||
|
||||
forces[node1.id].x -= fx;
|
||||
forces[node1.id].y -= fy;
|
||||
forces[node2.id].x += fx;
|
||||
forces[node2.id].y += fy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default RepulsionSolver;
|
||||
94
node_modules/vis/lib/network/modules/components/physics/SpringSolver.js
generated
vendored
Normal file
94
node_modules/vis/lib/network/modules/components/physics/SpringSolver.js
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Spring Solver
|
||||
*/
|
||||
class SpringSolver {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(body, physicsBody, options) {
|
||||
this.body = body;
|
||||
this.physicsBody = physicsBody;
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calculates the springforces on the nodes, accounting for the support nodes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
solve() {
|
||||
let edgeLength, edge;
|
||||
let edgeIndices = this.physicsBody.physicsEdgeIndices;
|
||||
let edges = this.body.edges;
|
||||
let node1, node2, node3;
|
||||
|
||||
// forces caused by the edges, modelled as springs
|
||||
for (let i = 0; i < edgeIndices.length; i++) {
|
||||
edge = edges[edgeIndices[i]];
|
||||
if (edge.connected === true && edge.toId !== edge.fromId) {
|
||||
// only calculate forces if nodes are in the same sector
|
||||
if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) {
|
||||
if (edge.edgeType.via !== undefined) {
|
||||
edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
|
||||
node1 = edge.to;
|
||||
node2 = edge.edgeType.via;
|
||||
node3 = edge.from;
|
||||
|
||||
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
|
||||
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
|
||||
}
|
||||
else {
|
||||
// the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use
|
||||
// the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger.
|
||||
edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5: edge.options.length;
|
||||
this._calculateSpringForce(edge.from, edge.to, edgeLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the code actually performing the calculation for the function above.
|
||||
*
|
||||
* @param {Node} node1
|
||||
* @param {Node} node2
|
||||
* @param {number} edgeLength
|
||||
* @private
|
||||
*/
|
||||
_calculateSpringForce(node1, node2, edgeLength) {
|
||||
let dx = (node1.x - node2.x);
|
||||
let dy = (node1.y - node2.y);
|
||||
let distance = Math.max(Math.sqrt(dx * dx + dy * dy),0.01);
|
||||
|
||||
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
|
||||
let springForce = this.options.springConstant * (edgeLength - distance) / distance;
|
||||
|
||||
let fx = dx * springForce;
|
||||
let fy = dy * springForce;
|
||||
|
||||
// handle the case where one node is not part of the physcis
|
||||
if (this.physicsBody.forces[node1.id] !== undefined) {
|
||||
this.physicsBody.forces[node1.id].x += fx;
|
||||
this.physicsBody.forces[node1.id].y += fy;
|
||||
}
|
||||
|
||||
if (this.physicsBody.forces[node2.id] !== undefined) {
|
||||
this.physicsBody.forces[node2.id].x -= fx;
|
||||
this.physicsBody.forces[node2.id].y -= fy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SpringSolver;
|
||||
139
node_modules/vis/lib/network/modules/components/shared/ComponentUtil.js
generated
vendored
Normal file
139
node_modules/vis/lib/network/modules/components/shared/ComponentUtil.js
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Definitions for param's in jsdoc.
|
||||
* These are more or less global within Network. Putting them here until I can figure out
|
||||
* where to really put them
|
||||
*
|
||||
* @typedef {string|number} Id
|
||||
* @typedef {Id} NodeId
|
||||
* @typedef {Id} EdgeId
|
||||
* @typedef {Id} LabelId
|
||||
*
|
||||
* @typedef {{x: number, y: number}} point
|
||||
* @typedef {{left: number, top: number, width: number, height: number}} rect
|
||||
* @typedef {{x: number, y:number, angle: number}} rotationPoint
|
||||
* - point to rotate around and the angle in radians to rotate. angle == 0 means no rotation
|
||||
* @typedef {{nodeId:NodeId}} nodeClickItem
|
||||
* @typedef {{nodeId:NodeId, labelId:LabelId}} nodeLabelClickItem
|
||||
* @typedef {{edgeId:EdgeId}} edgeClickItem
|
||||
* @typedef {{edgeId:EdgeId, labelId:LabelId}} edgeLabelClickItem
|
||||
*/
|
||||
|
||||
let util = require("../../../../util");
|
||||
|
||||
/**
|
||||
* Helper functions for components
|
||||
* @class
|
||||
*/
|
||||
class ComponentUtil {
|
||||
/**
|
||||
* Determine values to use for (sub)options of 'chosen'.
|
||||
*
|
||||
* This option is either a boolean or an object whose values should be examined further.
|
||||
* The relevant structures are:
|
||||
*
|
||||
* - chosen: <boolean value>
|
||||
* - chosen: { subOption: <boolean or function> }
|
||||
*
|
||||
* Where subOption is 'node', 'edge' or 'label'.
|
||||
*
|
||||
* The intention of this method appears to be to set a specific priority to the options;
|
||||
* Since most properties are either bridged or merged into the local options objects, there
|
||||
* is not much point in handling them separately.
|
||||
* TODO: examine if 'most' in previous sentence can be replaced with 'all'. In that case, we
|
||||
* should be able to get rid of this method.
|
||||
*
|
||||
* @param {string} subOption option within object 'chosen' to consider; either 'node', 'edge' or 'label'
|
||||
* @param {Object} pile array of options objects to consider
|
||||
*
|
||||
* @return {boolean|function} value for passed subOption of 'chosen' to use
|
||||
*/
|
||||
static choosify(subOption, pile) {
|
||||
// allowed values for subOption
|
||||
let allowed = [ 'node', 'edge', 'label'];
|
||||
let value = true;
|
||||
|
||||
let chosen = util.topMost(pile, 'chosen');
|
||||
if (typeof chosen === 'boolean') {
|
||||
value = chosen;
|
||||
} else if (typeof chosen === 'object') {
|
||||
if (allowed.indexOf(subOption) === -1 ) {
|
||||
throw new Error('choosify: subOption \'' + subOption + '\' should be one of '
|
||||
+ "'" + allowed.join("', '") + "'");
|
||||
}
|
||||
|
||||
let chosenEdge = util.topMost(pile, ['chosen', subOption]);
|
||||
if ((typeof chosenEdge === 'boolean') || (typeof chosenEdge === 'function')) {
|
||||
value = chosenEdge;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the point falls within the given rectangle.
|
||||
*
|
||||
* @param {rect} rect
|
||||
* @param {point} point
|
||||
* @param {rotationPoint} [rotationPoint] if specified, the rotation that applies to the rectangle.
|
||||
* @returns {boolean} true if point within rectangle, false otherwise
|
||||
* @static
|
||||
*/
|
||||
static pointInRect(rect, point, rotationPoint) {
|
||||
if (rect.width <= 0 || rect.height <= 0) {
|
||||
return false; // early out
|
||||
}
|
||||
|
||||
if (rotationPoint !== undefined) {
|
||||
// Rotate the point the same amount as the rectangle
|
||||
var tmp = {
|
||||
x: point.x - rotationPoint.x,
|
||||
y: point.y - rotationPoint.y
|
||||
};
|
||||
|
||||
if (rotationPoint.angle !== 0) {
|
||||
// In order to get the coordinates the same, you need to
|
||||
// rotate in the reverse direction
|
||||
var angle = -rotationPoint.angle;
|
||||
|
||||
var tmp2 = {
|
||||
x: Math.cos(angle)*tmp.x - Math.sin(angle)*tmp.y,
|
||||
y: Math.sin(angle)*tmp.x + Math.cos(angle)*tmp.y
|
||||
};
|
||||
point = tmp2;
|
||||
} else {
|
||||
point = tmp;
|
||||
}
|
||||
|
||||
// Note that if a rotation is specified, the rectangle coordinates
|
||||
// are **not* the full canvas coordinates. They are relative to the
|
||||
// rotationPoint. Hence, the point coordinates need not be translated
|
||||
// back in this case.
|
||||
}
|
||||
|
||||
var right = rect.x + rect.width;
|
||||
var bottom = rect.y + rect.width;
|
||||
|
||||
return (
|
||||
rect.left < point.x &&
|
||||
right > point.x &&
|
||||
rect.top < point.y &&
|
||||
bottom > point.y
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if given value is acceptable as a label text.
|
||||
*
|
||||
* @param {*} text value to check; can be anything at this point
|
||||
* @returns {boolean} true if valid label value, false otherwise
|
||||
*/
|
||||
static isValidLabel(text) {
|
||||
// Note that this is quite strict: types that *might* be converted to string are disallowed
|
||||
return (typeof text === 'string' && text !== '');
|
||||
}
|
||||
}
|
||||
|
||||
export default ComponentUtil;
|
||||
799
node_modules/vis/lib/network/modules/components/shared/Label.js
generated
vendored
Normal file
799
node_modules/vis/lib/network/modules/components/shared/Label.js
generated
vendored
Normal file
@@ -0,0 +1,799 @@
|
||||
let util = require('../../../../util');
|
||||
let ComponentUtil = require('./ComponentUtil').default;
|
||||
let LabelSplitter = require('./LabelSplitter').default;
|
||||
|
||||
/**
|
||||
* @typedef {'bold'|'ital'|'boldital'|'mono'|'normal'} MultiFontStyle
|
||||
*
|
||||
* The allowed specifiers of multi-fonts.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{color:string, size:number, face:string, mod:string, vadjust:number}} MultiFontOptions
|
||||
*
|
||||
* The full set of options of a given multi-font.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Array.<object>} Pile
|
||||
*
|
||||
* Sequence of option objects, the order is significant.
|
||||
* The sequence is used to determine the value of a given option.
|
||||
*
|
||||
* Usage principles:
|
||||
*
|
||||
* - All search is done in the sequence of the pile.
|
||||
* - As soon as a value is found, the searching stops.
|
||||
* - prototypes are totally ignored. The idea is to add option objects used as prototypes
|
||||
* to the pile, in the correct order.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* List of special styles for multi-fonts
|
||||
* @private
|
||||
*/
|
||||
const multiFontStyle = ['bold', 'ital', 'boldital', 'mono'];
|
||||
|
||||
/**
|
||||
* A Label to be used for Nodes or Edges.
|
||||
*/
|
||||
class Label {
|
||||
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {Object} options
|
||||
* @param {boolean} [edgelabel=false]
|
||||
*/
|
||||
constructor(body, options, edgelabel = false) {
|
||||
this.body = body;
|
||||
this.pointToSelf = false;
|
||||
this.baseSize = undefined;
|
||||
this.fontOptions = {}; // instance variable containing the *instance-local* font options
|
||||
this.setOptions(options);
|
||||
this.size = {top: 0, left: 0, width: 0, height: 0, yLine: 0};
|
||||
this.isEdgeLabel = edgelabel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object} options the options of the parent Node-instance
|
||||
*/
|
||||
setOptions(options) {
|
||||
this.elementOptions = options; // Reference to the options of the parent Node-instance
|
||||
|
||||
this.initFontOptions(options.font);
|
||||
|
||||
if (ComponentUtil.isValidLabel(options.label)) {
|
||||
this.labelDirty = true;
|
||||
} else {
|
||||
// Bad label! Change the option value to prevent bad stuff happening
|
||||
options.label = '';
|
||||
}
|
||||
|
||||
if (options.font !== undefined && options.font !== null) { // font options can be deleted at various levels
|
||||
if (typeof options.font === 'string') {
|
||||
this.baseSize = this.fontOptions.size;
|
||||
}
|
||||
else if (typeof options.font === 'object') {
|
||||
let size = options.font.size;
|
||||
|
||||
if (size !== undefined) {
|
||||
this.baseSize = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init the font Options structure.
|
||||
*
|
||||
* Member fontOptions serves as an accumulator for the current font options.
|
||||
* As such, it needs to be completely separated from the node options.
|
||||
*
|
||||
* @param {Object} newFontOptions the new font options to process
|
||||
* @private
|
||||
*/
|
||||
initFontOptions(newFontOptions) {
|
||||
// Prepare the multi-font option objects.
|
||||
// These will be filled in propagateFonts(), if required
|
||||
util.forEach(multiFontStyle, (style) => {
|
||||
this.fontOptions[style] = {};
|
||||
});
|
||||
|
||||
// Handle shorthand option, if present
|
||||
if (Label.parseFontString(this.fontOptions, newFontOptions)) {
|
||||
this.fontOptions.vadjust = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy over the non-multifont options, if specified
|
||||
util.forEach(newFontOptions, (prop, n) => {
|
||||
if (prop !== undefined && prop !== null && typeof prop !== 'object') {
|
||||
this.fontOptions[n] = prop;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If in-variable is a string, parse it as a font specifier.
|
||||
*
|
||||
* Note that following is not done here and have to be done after the call:
|
||||
* - No number conversion (size)
|
||||
* - Not all font options are set (vadjust, mod)
|
||||
*
|
||||
* @param {Object} outOptions out-parameter, object in which to store the parse results (if any)
|
||||
* @param {Object} inOptions font options to parse
|
||||
* @return {boolean} true if font parsed as string, false otherwise
|
||||
* @static
|
||||
*/
|
||||
static parseFontString(outOptions, inOptions) {
|
||||
if (!inOptions || typeof inOptions !== 'string') return false;
|
||||
|
||||
let newOptionsArray = inOptions.split(" ");
|
||||
|
||||
outOptions.size = newOptionsArray[0].replace("px",'');
|
||||
outOptions.face = newOptionsArray[1];
|
||||
outOptions.color = newOptionsArray[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the width and height constraints based on 'nearest' value
|
||||
*
|
||||
* @param {Array} pile array of option objects to consider
|
||||
* @returns {object} the actual constraint values to use
|
||||
* @private
|
||||
*/
|
||||
constrain(pile) {
|
||||
// NOTE: constrainWidth and constrainHeight never set!
|
||||
// NOTE: for edge labels, only 'maxWdt' set
|
||||
// Node labels can set all the fields
|
||||
let fontOptions = {
|
||||
constrainWidth: false,
|
||||
maxWdt: -1,
|
||||
minWdt: -1,
|
||||
constrainHeight: false,
|
||||
minHgt: -1,
|
||||
valign: 'middle',
|
||||
}
|
||||
|
||||
let widthConstraint = util.topMost(pile, 'widthConstraint');
|
||||
if (typeof widthConstraint === 'number') {
|
||||
fontOptions.maxWdt = Number(widthConstraint);
|
||||
fontOptions.minWdt = Number(widthConstraint);
|
||||
} else if (typeof widthConstraint === 'object') {
|
||||
let widthConstraintMaximum = util.topMost(pile, ['widthConstraint', 'maximum']);
|
||||
if (typeof widthConstraintMaximum === 'number') {
|
||||
fontOptions.maxWdt = Number(widthConstraintMaximum);
|
||||
}
|
||||
let widthConstraintMinimum = util.topMost(pile, ['widthConstraint', 'minimum'])
|
||||
if (typeof widthConstraintMinimum === 'number') {
|
||||
fontOptions.minWdt = Number(widthConstraintMinimum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let heightConstraint = util.topMost(pile, 'heightConstraint');
|
||||
if (typeof heightConstraint === 'number') {
|
||||
fontOptions.minHgt = Number(heightConstraint);
|
||||
} else if (typeof heightConstraint === 'object') {
|
||||
let heightConstraintMinimum = util.topMost(pile, ['heightConstraint', 'minimum']);
|
||||
if (typeof heightConstraintMinimum === 'number') {
|
||||
fontOptions.minHgt = Number(heightConstraintMinimum);
|
||||
}
|
||||
let heightConstraintValign = util.topMost(pile, ['heightConstraint', 'valign']);
|
||||
if (typeof heightConstraintValign === 'string') {
|
||||
if ((heightConstraintValign === 'top')|| (heightConstraintValign === 'bottom')) {
|
||||
fontOptions.valign = heightConstraintValign;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fontOptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set options and update internal state
|
||||
*
|
||||
* @param {Object} options options to set
|
||||
* @param {Array} pile array of option objects to consider for option 'chosen'
|
||||
*/
|
||||
update(options, pile) {
|
||||
this.setOptions(options, true);
|
||||
this.propagateFonts(pile);
|
||||
util.deepExtend(this.fontOptions, this.constrain(pile));
|
||||
this.fontOptions.chooser = ComponentUtil.choosify('label', pile);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When margins are set in an element, adjust sizes is called to remove them
|
||||
* from the width/height constraints. This must be done prior to label sizing.
|
||||
*
|
||||
* @param {{top: number, right: number, bottom: number, left: number}} margins
|
||||
*/
|
||||
adjustSizes(margins) {
|
||||
let widthBias = (margins) ? (margins.right + margins.left) : 0;
|
||||
if (this.fontOptions.constrainWidth) {
|
||||
this.fontOptions.maxWdt -= widthBias;
|
||||
this.fontOptions.minWdt -= widthBias;
|
||||
}
|
||||
let heightBias = (margins) ? (margins.top + margins.bottom) : 0;
|
||||
if (this.fontOptions.constrainHeight) {
|
||||
this.fontOptions.minHgt -= heightBias;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Methods for handling options piles
|
||||
// Eventually, these will be moved to a separate class
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Add the font members of the passed list of option objects to the pile.
|
||||
*
|
||||
* @param {Pile} dstPile pile of option objects add to
|
||||
* @param {Pile} srcPile pile of option objects to take font options from
|
||||
* @private
|
||||
*/
|
||||
addFontOptionsToPile(dstPile, srcPile) {
|
||||
for (let i = 0; i < srcPile.length; ++i) {
|
||||
this.addFontToPile(dstPile, srcPile[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add given font option object to the list of objects (the 'pile') to consider for determining
|
||||
* multi-font option values.
|
||||
*
|
||||
* @param {Pile} pile pile of option objects to use
|
||||
* @param {object} options instance to add to pile
|
||||
* @private
|
||||
*/
|
||||
addFontToPile(pile, options) {
|
||||
if (options === undefined) return;
|
||||
if (options.font === undefined || options.font === null) return;
|
||||
|
||||
let item = options.font;
|
||||
pile.push(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collect all own-property values from the font pile that aren't multi-font option objectss.
|
||||
*
|
||||
* @param {Pile} pile pile of option objects to use
|
||||
* @returns {object} object with all current own basic font properties
|
||||
* @private
|
||||
*/
|
||||
getBasicOptions(pile) {
|
||||
let ret = {};
|
||||
|
||||
// Scans the whole pile to get all options present
|
||||
for (let n = 0; n < pile.length; ++n) {
|
||||
let fontOptions = pile[n];
|
||||
|
||||
// Convert shorthand if necessary
|
||||
let tmpShorthand = {};
|
||||
if (Label.parseFontString(tmpShorthand, fontOptions)) {
|
||||
fontOptions = tmpShorthand;
|
||||
}
|
||||
|
||||
util.forEach(fontOptions, (opt, name) => {
|
||||
if (opt === undefined) return; // multi-font option need not be present
|
||||
if (ret.hasOwnProperty(name)) return; // Keep first value we encounter
|
||||
|
||||
if (multiFontStyle.indexOf(name) !== -1) {
|
||||
// Skip multi-font properties but we do need the structure
|
||||
ret[name] = {};
|
||||
} else {
|
||||
ret[name] = opt;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value for given option for the given multi-font.
|
||||
*
|
||||
* All available option objects are trawled in the set order to construct the option values.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
* ## Traversal of pile for multi-fonts
|
||||
*
|
||||
* The determination of multi-font option values is a special case, because any values not
|
||||
* present in the multi-font options should by definition be taken from the main font options,
|
||||
* i.e. from the current 'parent' object of the multi-font option.
|
||||
*
|
||||
* ### Search order for multi-fonts
|
||||
*
|
||||
* 'bold' used as example:
|
||||
*
|
||||
* - search in option group 'bold' in local properties
|
||||
* - search in main font option group in local properties
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* @param {Pile} pile pile of option objects to use
|
||||
* @param {MultiFontStyle} multiName sub path for the multi-font
|
||||
* @param {string} option the option to search for, for the given multi-font
|
||||
* @returns {string|number} the value for the given option
|
||||
* @private
|
||||
*/
|
||||
getFontOption(pile, multiName, option) {
|
||||
let multiFont;
|
||||
|
||||
// Search multi font in local properties
|
||||
for (let n = 0; n < pile.length; ++n) {
|
||||
let fontOptions = pile[n];
|
||||
|
||||
if (fontOptions.hasOwnProperty(multiName)) {
|
||||
multiFont = fontOptions[multiName];
|
||||
if (multiFont === undefined || multiFont === null) continue;
|
||||
|
||||
// Convert shorthand if necessary
|
||||
// TODO: inefficient to do this conversion every time; find a better way.
|
||||
let tmpShorthand = {};
|
||||
if (Label.parseFontString(tmpShorthand, multiFont)) {
|
||||
multiFont = tmpShorthand;
|
||||
}
|
||||
|
||||
if (multiFont.hasOwnProperty(option)) {
|
||||
return multiFont[option];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Option is not mentioned in the multi font options; take it from the parent font options.
|
||||
// These have already been converted with getBasicOptions(), so use the converted values.
|
||||
if (this.fontOptions.hasOwnProperty(option)) {
|
||||
return this.fontOptions[option];
|
||||
}
|
||||
|
||||
// A value **must** be found; you should never get here.
|
||||
throw new Error("Did not find value for multi-font for property: '" + option + "'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return all options values for the given multi-font.
|
||||
*
|
||||
* All available option objects are trawled in the set order to construct the option values.
|
||||
*
|
||||
* @param {Pile} pile pile of option objects to use
|
||||
* @param {MultiFontStyle} multiName sub path for the mod-font
|
||||
* @returns {MultiFontOptions}
|
||||
* @private
|
||||
*/
|
||||
getFontOptions(pile, multiName) {
|
||||
let result = {};
|
||||
let optionNames = ['color', 'size', 'face', 'mod', 'vadjust']; // List of allowed options per multi-font
|
||||
|
||||
for (let i = 0; i < optionNames.length; ++i) {
|
||||
let mod = optionNames[i];
|
||||
result[mod] = this.getFontOption(pile, multiName, mod);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// End methods for handling options piles
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Collapse the font options for the multi-font to single objects, from
|
||||
* the chain of option objects passed (the 'pile').
|
||||
*
|
||||
* @param {Pile} pile sequence of option objects to consider.
|
||||
* First item in list assumed to be the newly set options.
|
||||
*/
|
||||
propagateFonts(pile) {
|
||||
let fontPile = []; // sequence of font objects to consider, order important
|
||||
|
||||
// Note that this.elementOptions is not used here.
|
||||
this.addFontOptionsToPile(fontPile, pile);
|
||||
this.fontOptions = this.getBasicOptions(fontPile);
|
||||
|
||||
// We set multifont values even if multi === false, for consistency (things break otherwise)
|
||||
for (let i = 0; i < multiFontStyle.length; ++i) {
|
||||
let mod = multiFontStyle[i];
|
||||
let modOptions = this.fontOptions[mod];
|
||||
let tmpMultiFontOptions = this.getFontOptions(fontPile, mod);
|
||||
|
||||
// Copy over found values
|
||||
util.forEach(tmpMultiFontOptions, (option, n) => {
|
||||
modOptions[n] = option;
|
||||
});
|
||||
|
||||
modOptions.size = Number(modOptions.size);
|
||||
modOptions.vadjust = Number(modOptions.vadjust);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main function. This is called from anything that wants to draw a label.
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {string} [baseline='middle']
|
||||
*/
|
||||
draw(ctx, x, y, selected, hover, baseline = 'middle') {
|
||||
// if no label, return
|
||||
if (this.elementOptions.label === undefined)
|
||||
return;
|
||||
|
||||
// check if we have to render the label
|
||||
let viewFontSize = this.fontOptions.size * this.body.view.scale;
|
||||
if (this.elementOptions.label && viewFontSize < this.elementOptions.scaling.label.drawThreshold - 1)
|
||||
return;
|
||||
|
||||
// This ensures that there will not be HUGE letters on screen
|
||||
// by setting an upper limit on the visible text size (regardless of zoomLevel)
|
||||
if (viewFontSize >= this.elementOptions.scaling.label.maxVisible) {
|
||||
viewFontSize = Number(this.elementOptions.scaling.label.maxVisible) / this.body.view.scale;
|
||||
}
|
||||
|
||||
// update the size cache if required
|
||||
this.calculateLabelSize(ctx, selected, hover, x, y, baseline);
|
||||
this._drawBackground(ctx);
|
||||
this._drawText(ctx, x, this.size.yLine, baseline, viewFontSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws the label background
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @private
|
||||
*/
|
||||
_drawBackground(ctx) {
|
||||
if (this.fontOptions.background !== undefined && this.fontOptions.background !== "none") {
|
||||
ctx.fillStyle = this.fontOptions.background;
|
||||
let size = this.getSize();
|
||||
ctx.fillRect(size.left, size.top, size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {string} [baseline='middle']
|
||||
* @param {number} viewFontSize
|
||||
* @private
|
||||
*/
|
||||
_drawText(ctx, x, y, baseline = 'middle', viewFontSize) {
|
||||
[x, y] = this._setAlignment(ctx, x, y, baseline);
|
||||
|
||||
ctx.textAlign = 'left';
|
||||
x = x - this.size.width / 2; // Shift label 1/2-distance to the left
|
||||
if ((this.fontOptions.valign) && (this.size.height > this.size.labelHeight)) {
|
||||
if (this.fontOptions.valign === 'top') {
|
||||
y -= (this.size.height - this.size.labelHeight) / 2;
|
||||
}
|
||||
if (this.fontOptions.valign === 'bottom') {
|
||||
y += (this.size.height - this.size.labelHeight) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// draw the text
|
||||
for (let i = 0; i < this.lineCount; i++) {
|
||||
let line = this.lines[i];
|
||||
if (line && line.blocks) {
|
||||
let width = 0;
|
||||
if (this.isEdgeLabel || this.fontOptions.align === 'center') {
|
||||
width += (this.size.width - line.width) / 2
|
||||
} else if (this.fontOptions.align === 'right') {
|
||||
width += (this.size.width - line.width)
|
||||
}
|
||||
for (let j = 0; j < line.blocks.length; j++) {
|
||||
let block = line.blocks[j];
|
||||
ctx.font = block.font;
|
||||
let [fontColor, strokeColor] = this._getColor(block.color, viewFontSize, block.strokeColor);
|
||||
if (block.strokeWidth > 0) {
|
||||
ctx.lineWidth = block.strokeWidth;
|
||||
ctx.strokeStyle = strokeColor;
|
||||
ctx.lineJoin = 'round';
|
||||
}
|
||||
ctx.fillStyle = fontColor;
|
||||
|
||||
if (block.strokeWidth > 0) {
|
||||
ctx.strokeText(block.text, x + width, y + block.vadjust);
|
||||
}
|
||||
ctx.fillText(block.text, x + width, y + block.vadjust);
|
||||
width += block.width;
|
||||
}
|
||||
y += line.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {string} baseline
|
||||
* @returns {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
_setAlignment(ctx, x, y, baseline) {
|
||||
// check for label alignment (for edges)
|
||||
// TODO: make alignment for nodes
|
||||
if (this.isEdgeLabel && this.fontOptions.align !== 'horizontal' && this.pointToSelf === false) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
let lineMargin = 2;
|
||||
if (this.fontOptions.align === 'top') {
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
y -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
|
||||
}
|
||||
else if (this.fontOptions.align === 'bottom') {
|
||||
ctx.textBaseline = 'hanging';
|
||||
y += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
|
||||
}
|
||||
else {
|
||||
ctx.textBaseline = 'middle';
|
||||
}
|
||||
}
|
||||
else {
|
||||
ctx.textBaseline = baseline;
|
||||
}
|
||||
return [x,y];
|
||||
}
|
||||
|
||||
/**
|
||||
* fade in when relative scale is between threshold and threshold - 1.
|
||||
* If the relative scale would be smaller than threshold -1 the draw function would have returned before coming here.
|
||||
*
|
||||
* @param {string} color The font color to use
|
||||
* @param {number} viewFontSize
|
||||
* @param {string} initialStrokeColor
|
||||
* @returns {Array.<string>} An array containing the font color and stroke color
|
||||
* @private
|
||||
*/
|
||||
_getColor(color, viewFontSize, initialStrokeColor) {
|
||||
let fontColor = color || '#000000';
|
||||
let strokeColor = initialStrokeColor || '#ffffff';
|
||||
if (viewFontSize <= this.elementOptions.scaling.label.drawThreshold) {
|
||||
let opacity = Math.max(0, Math.min(1, 1 - (this.elementOptions.scaling.label.drawThreshold - viewFontSize)));
|
||||
fontColor = util.overrideOpacity(fontColor, opacity);
|
||||
strokeColor = util.overrideOpacity(strokeColor, opacity);
|
||||
}
|
||||
return [fontColor, strokeColor];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @returns {{width: number, height: number}}
|
||||
*/
|
||||
getTextSize(ctx, selected = false, hover = false) {
|
||||
this._processLabel(ctx, selected, hover);
|
||||
return {
|
||||
width: this.size.width,
|
||||
height: this.size.height,
|
||||
lineCount: this.lineCount
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current dimensions of the label
|
||||
*
|
||||
* @return {rect}
|
||||
*/
|
||||
getSize() {
|
||||
let lineMargin = 2;
|
||||
let x = this.size.left; // default values which might be overridden below
|
||||
let y = this.size.top - 0.5*lineMargin; // idem
|
||||
|
||||
if (this.isEdgeLabel) {
|
||||
const x2 = -this.size.width * 0.5;
|
||||
|
||||
switch (this.fontOptions.align) {
|
||||
case 'middle':
|
||||
x = x2;
|
||||
y = -this.size.height * 0.5
|
||||
break;
|
||||
case 'top':
|
||||
x = x2;
|
||||
y = -(this.size.height + lineMargin);
|
||||
break;
|
||||
case 'bottom':
|
||||
x = x2;
|
||||
y = lineMargin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ret = {
|
||||
left : x,
|
||||
top : y,
|
||||
width : this.size.width,
|
||||
height: this.size.height,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {number} [x=0]
|
||||
* @param {number} [y=0]
|
||||
* @param {'middle'|'hanging'} [baseline='middle']
|
||||
*/
|
||||
calculateLabelSize(ctx, selected, hover, x = 0, y = 0, baseline = 'middle') {
|
||||
this._processLabel(ctx, selected, hover);
|
||||
this.size.left = x - this.size.width * 0.5;
|
||||
this.size.top = y - this.size.height * 0.5;
|
||||
this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.fontOptions.size;
|
||||
if (baseline === "hanging") {
|
||||
this.size.top += 0.5 * this.fontOptions.size;
|
||||
this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
|
||||
this.size.yLine += 4; // distance from node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {string} mod
|
||||
* @returns {{color, size, face, mod, vadjust, strokeWidth: *, strokeColor: (*|string|allOptions.edges.font.strokeColor|{string}|allOptions.nodes.font.strokeColor|Array)}}
|
||||
*/
|
||||
getFormattingValues(ctx, selected, hover, mod) {
|
||||
let getValue = function(fontOptions, mod, option) {
|
||||
if (mod === "normal") {
|
||||
if (option === 'mod' ) return "";
|
||||
return fontOptions[option];
|
||||
}
|
||||
|
||||
if (fontOptions[mod][option] !== undefined) { // Grumbl leaving out test on undefined equals false for ""
|
||||
return fontOptions[mod][option];
|
||||
} else {
|
||||
// Take from parent font option
|
||||
return fontOptions[option];
|
||||
}
|
||||
};
|
||||
|
||||
let values = {
|
||||
color : getValue(this.fontOptions, mod, 'color' ),
|
||||
size : getValue(this.fontOptions, mod, 'size' ),
|
||||
face : getValue(this.fontOptions, mod, 'face' ),
|
||||
mod : getValue(this.fontOptions, mod, 'mod' ),
|
||||
vadjust: getValue(this.fontOptions, mod, 'vadjust'),
|
||||
strokeWidth: this.fontOptions.strokeWidth,
|
||||
strokeColor: this.fontOptions.strokeColor
|
||||
};
|
||||
if (selected || hover) {
|
||||
if (mod === "normal" && (this.fontOptions.chooser === true) && (this.elementOptions.labelHighlightBold)) {
|
||||
values.mod = 'bold';
|
||||
} else {
|
||||
if (typeof this.fontOptions.chooser === 'function') {
|
||||
this.fontOptions.chooser(values, this.elementOptions.id, selected, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fontString = "";
|
||||
if (values.mod !== undefined && values.mod !== "") { // safeguard for undefined - this happened
|
||||
fontString += values.mod + " ";
|
||||
}
|
||||
fontString += values.size + "px " + values.face;
|
||||
|
||||
ctx.font = fontString.replace(/"/g, "");
|
||||
values.font = ctx.font;
|
||||
values.height = values.size;
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @returns {boolean}
|
||||
*/
|
||||
differentState(selected, hover) {
|
||||
return ((selected !== this.selectedState) || (hover !== this.hoverState));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This explodes the passed text into lines and determines the width, height and number of lines.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @param {string} inText the text to explode
|
||||
* @returns {{width, height, lines}|*}
|
||||
* @private
|
||||
*/
|
||||
_processLabelText(ctx, selected, hover, inText) {
|
||||
let splitter = new LabelSplitter(ctx, this, selected, hover);
|
||||
return splitter.process(inText);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This explodes the label string into lines and sets the width, height and number of lines.
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
* @private
|
||||
*/
|
||||
_processLabel(ctx, selected, hover) {
|
||||
|
||||
if(this.labelDirty === false && !this.differentState(selected,hover))
|
||||
return;
|
||||
|
||||
let state = this._processLabelText(ctx, selected, hover, this.elementOptions.label);
|
||||
|
||||
if ((this.fontOptions.minWdt > 0) && (state.width < this.fontOptions.minWdt)) {
|
||||
state.width = this.fontOptions.minWdt;
|
||||
}
|
||||
|
||||
this.size.labelHeight =state.height;
|
||||
if ((this.fontOptions.minHgt > 0) && (state.height < this.fontOptions.minHgt)) {
|
||||
state.height = this.fontOptions.minHgt;
|
||||
}
|
||||
|
||||
this.lines = state.lines;
|
||||
this.lineCount = state.lines.length;
|
||||
this.size.width = state.width;
|
||||
this.size.height = state.height;
|
||||
this.selectedState = selected;
|
||||
this.hoverState = hover;
|
||||
|
||||
this.labelDirty = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this label is visible
|
||||
*
|
||||
* @return {boolean} true if this label will be show, false otherwise
|
||||
*/
|
||||
visible() {
|
||||
if ((this.size.width === 0 || this.size.height === 0)
|
||||
|| this.elementOptions.label === undefined) {
|
||||
return false; // nothing to display
|
||||
}
|
||||
|
||||
let viewFontSize = this.fontOptions.size * this.body.view.scale;
|
||||
if (viewFontSize < this.elementOptions.scaling.label.drawThreshold - 1) {
|
||||
return false; // Too small or too far away to show
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default Label;
|
||||
238
node_modules/vis/lib/network/modules/components/shared/LabelAccumulator.js
generated
vendored
Normal file
238
node_modules/vis/lib/network/modules/components/shared/LabelAccumulator.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Callback to determine text dimensions, using the parent label settings.
|
||||
* @callback MeasureText
|
||||
* @param {text} text
|
||||
* @param {text} mod
|
||||
* @return {Object} { width, values} width in pixels and font attributes
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for Label which collects results of splitting labels into lines and blocks.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class LabelAccumulator {
|
||||
|
||||
/**
|
||||
* @param {MeasureText} measureText
|
||||
*/
|
||||
constructor(measureText) {
|
||||
this.measureText = measureText;
|
||||
this.current = 0;
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append given text to the given line.
|
||||
*
|
||||
* @param {number} l index of line to add to
|
||||
* @param {string} text string to append to line
|
||||
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
||||
* @private
|
||||
*/
|
||||
_add(l, text, mod = 'normal') {
|
||||
|
||||
if (this.lines[l] === undefined) {
|
||||
this.lines[l] = {
|
||||
width : 0,
|
||||
height: 0,
|
||||
blocks: []
|
||||
};
|
||||
}
|
||||
|
||||
// We still need to set a block for undefined and empty texts, hence return at this point
|
||||
// This is necessary because we don't know at this point if we're at the
|
||||
// start of an empty line or not.
|
||||
// To compensate, empty blocks are removed in `finalize()`.
|
||||
//
|
||||
// Empty strings should still have a height
|
||||
let tmpText = text;
|
||||
if (text === undefined || text === "") tmpText = " ";
|
||||
|
||||
// Determine width and get the font properties
|
||||
let result = this.measureText(tmpText, mod);
|
||||
let block = Object.assign({}, result.values);
|
||||
block.text = text;
|
||||
block.width = result.width;
|
||||
block.mod = mod;
|
||||
|
||||
if (text === undefined || text === "") {
|
||||
block.width = 0;
|
||||
}
|
||||
|
||||
this.lines[l].blocks.push(block);
|
||||
|
||||
// Update the line width. We need this for determining if a string goes over max width
|
||||
this.lines[l].width += block.width;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the width in pixels of the current line.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
curWidth() {
|
||||
let line = this.lines[this.current];
|
||||
if (line === undefined) return 0;
|
||||
|
||||
return line.width;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add text in block to current line
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
||||
*/
|
||||
append(text, mod = 'normal') {
|
||||
this._add(this.current, text, mod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add text in block to current line and start a new line
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
||||
*/
|
||||
newLine(text, mod = 'normal') {
|
||||
this._add(this.current, text, mod);
|
||||
this.current++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine and set the heights of all the lines currently contained in this instance
|
||||
*
|
||||
* Note that width has already been set.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
determineLineHeights() {
|
||||
for (let k = 0; k < this.lines.length; k++) {
|
||||
let line = this.lines[k];
|
||||
|
||||
// Looking for max height of blocks in line
|
||||
let height = 0;
|
||||
|
||||
if (line.blocks !== undefined) { // Can happen if text contains e.g. '\n '
|
||||
for (let l = 0; l < line.blocks.length; l++) {
|
||||
let block = line.blocks[l];
|
||||
|
||||
if (height < block.height) {
|
||||
height = block.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the full size of the label text, as determined by current lines and blocks
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
determineLabelSize() {
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
for (let k = 0; k < this.lines.length; k++) {
|
||||
let line = this.lines[k];
|
||||
|
||||
if (line.width > width) {
|
||||
width = line.width;
|
||||
}
|
||||
height += line.height;
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all empty blocks and empty lines we don't need
|
||||
*
|
||||
* This must be done after the width/height determination,
|
||||
* so that these are set properly for processing here.
|
||||
*
|
||||
* @returns {Array<Line>} Lines with empty blocks (and some empty lines) removed
|
||||
* @private
|
||||
*/
|
||||
removeEmptyBlocks() {
|
||||
let tmpLines = [];
|
||||
for (let k = 0; k < this.lines.length; k++) {
|
||||
let line = this.lines[k];
|
||||
|
||||
// Note: an empty line in between text has width zero but is still relevant to layout.
|
||||
// So we can't use width for testing empty line here
|
||||
if (line.blocks.length === 0) continue;
|
||||
|
||||
// Discard final empty line always
|
||||
if(k === this.lines.length - 1) {
|
||||
if (line.width === 0) continue;
|
||||
}
|
||||
|
||||
let tmpLine = {};
|
||||
Object.assign(tmpLine, line);
|
||||
tmpLine.blocks = [];
|
||||
|
||||
let firstEmptyBlock;
|
||||
let tmpBlocks = []
|
||||
for (let l = 0; l < line.blocks.length; l++) {
|
||||
let block = line.blocks[l];
|
||||
if (block.width !== 0) {
|
||||
tmpBlocks.push(block);
|
||||
} else {
|
||||
if (firstEmptyBlock === undefined) {
|
||||
firstEmptyBlock = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that there is *some* text present
|
||||
if (tmpBlocks.length === 0 && firstEmptyBlock !== undefined) {
|
||||
tmpBlocks.push(firstEmptyBlock);
|
||||
}
|
||||
|
||||
tmpLine.blocks = tmpBlocks;
|
||||
|
||||
tmpLines.push(tmpLine);
|
||||
}
|
||||
|
||||
return tmpLines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the sizes for all lines and the whole thing.
|
||||
*
|
||||
* @returns {{width: (number|*), height: (number|*), lines: Array}}
|
||||
*/
|
||||
finalize() {
|
||||
//console.log(JSON.stringify(this.lines, null, 2));
|
||||
|
||||
this.determineLineHeights();
|
||||
this.determineLabelSize();
|
||||
let tmpLines = this.removeEmptyBlocks();
|
||||
|
||||
|
||||
// Return a simple hash object for further processing.
|
||||
return {
|
||||
width : this.width,
|
||||
height: this.height,
|
||||
lines : tmpLines
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default LabelAccumulator;
|
||||
549
node_modules/vis/lib/network/modules/components/shared/LabelSplitter.js
generated
vendored
Normal file
549
node_modules/vis/lib/network/modules/components/shared/LabelSplitter.js
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
let LabelAccumulator = require('./LabelAccumulator').default;
|
||||
let ComponentUtil = require('./ComponentUtil').default;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for Label which explodes the label text into lines and blocks within lines
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class LabelSplitter {
|
||||
|
||||
/**
|
||||
* @param {CanvasRenderingContext2D} ctx Canvas rendering context
|
||||
* @param {Label} parent reference to the Label instance using current instance
|
||||
* @param {boolean} selected
|
||||
* @param {boolean} hover
|
||||
*/
|
||||
constructor(ctx, parent, selected, hover) {
|
||||
this.ctx = ctx;
|
||||
this.parent = parent;
|
||||
|
||||
|
||||
/**
|
||||
* Callback to determine text width; passed to LabelAccumulator instance
|
||||
*
|
||||
* @param {String} text string to determine width of
|
||||
* @param {String} mod font type to use for this text
|
||||
* @return {Object} { width, values} width in pixels and font attributes
|
||||
*/
|
||||
let textWidth = (text, mod) => {
|
||||
if (text === undefined) return 0;
|
||||
|
||||
// TODO: This can be done more efficiently with caching
|
||||
let values = this.parent.getFormattingValues(ctx, selected, hover, mod);
|
||||
|
||||
let width = 0;
|
||||
if (text !== '') {
|
||||
// NOTE: The following may actually be *incorrect* for the mod fonts!
|
||||
// This returns the size with a regular font, bold etc. may
|
||||
// have different sizes.
|
||||
let measure = this.ctx.measureText(text);
|
||||
width = measure.width;
|
||||
}
|
||||
|
||||
return {width, values: values};
|
||||
};
|
||||
|
||||
|
||||
this.lines = new LabelAccumulator(textWidth);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split passed text of a label into lines and blocks.
|
||||
*
|
||||
* # NOTE
|
||||
*
|
||||
* The handling of spacing is option dependent:
|
||||
*
|
||||
* - if `font.multi : false`, all spaces are retained
|
||||
* - if `font.multi : true`, every sequence of spaces is compressed to a single space
|
||||
*
|
||||
* This might not be the best way to do it, but this is as it has been working till now.
|
||||
* In order not to break existing functionality, for the time being this behaviour will
|
||||
* be retained in any code changes.
|
||||
*
|
||||
* @param {string} text text to split
|
||||
* @returns {Array<line>}
|
||||
*/
|
||||
process(text) {
|
||||
if (!ComponentUtil.isValidLabel(text)) {
|
||||
return this.lines.finalize();
|
||||
}
|
||||
|
||||
var font = this.parent.fontOptions;
|
||||
|
||||
// Normalize the end-of-line's to a single representation - order important
|
||||
text = text.replace(/\r\n/g, '\n'); // Dos EOL's
|
||||
text = text.replace(/\r/g, '\n'); // Mac EOL's
|
||||
|
||||
// Note that at this point, there can be no \r's in the text.
|
||||
// This is used later on splitStringIntoLines() to split multifont texts.
|
||||
|
||||
let nlLines = String(text).split('\n');
|
||||
let lineCount = nlLines.length;
|
||||
|
||||
if (font.multi) {
|
||||
// Multi-font case: styling tags active
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
let blocks = this.splitBlocks(nlLines[i], font.multi);
|
||||
// Post: Sequences of tabs and spaces are reduced to single space
|
||||
|
||||
if (blocks === undefined) continue;
|
||||
|
||||
if (blocks.length === 0) {
|
||||
this.lines.newLine("");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (font.maxWdt > 0) {
|
||||
// widthConstraint.maximum defined
|
||||
//console.log('Running widthConstraint multi, max: ' + this.fontOptions.maxWdt);
|
||||
for (let j = 0; j < blocks.length; j++) {
|
||||
let mod = blocks[j].mod;
|
||||
let text = blocks[j].text;
|
||||
this.splitStringIntoLines(text, mod, true);
|
||||
}
|
||||
} else {
|
||||
// widthConstraint.maximum NOT defined
|
||||
for (let j = 0; j < blocks.length; j++) {
|
||||
let mod = blocks[j].mod;
|
||||
let text = blocks[j].text;
|
||||
this.lines.append(text, mod);
|
||||
}
|
||||
}
|
||||
|
||||
this.lines.newLine();
|
||||
}
|
||||
} else {
|
||||
// Single-font case
|
||||
if (font.maxWdt > 0) {
|
||||
// widthConstraint.maximum defined
|
||||
// console.log('Running widthConstraint normal, max: ' + this.fontOptions.maxWdt);
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
this.splitStringIntoLines(nlLines[i]);
|
||||
}
|
||||
} else {
|
||||
// widthConstraint.maximum NOT defined
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
this.lines.newLine(nlLines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.lines.finalize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* normalize the markup system
|
||||
*
|
||||
* @param {boolean|'md'|'markdown'|'html'} markupSystem
|
||||
* @returns {string}
|
||||
*/
|
||||
decodeMarkupSystem(markupSystem) {
|
||||
let system = 'none';
|
||||
if (markupSystem === 'markdown' || markupSystem === 'md') {
|
||||
system = 'markdown';
|
||||
} else if (markupSystem === true || markupSystem === 'html') {
|
||||
system = 'html'
|
||||
}
|
||||
return system;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Array}
|
||||
*/
|
||||
splitHtmlBlocks(text) {
|
||||
let blocks = [];
|
||||
|
||||
// TODO: consolidate following + methods/closures with splitMarkdownBlocks()
|
||||
// NOTE: sequences of tabs and spaces are reduced to single space; scan usage of `this.spacing` within method
|
||||
let s = {
|
||||
bold: false,
|
||||
ital: false,
|
||||
mono: false,
|
||||
spacing: false,
|
||||
position: 0,
|
||||
buffer: "",
|
||||
modStack: []
|
||||
};
|
||||
|
||||
s.mod = function() {
|
||||
return (this.modStack.length === 0) ? 'normal' : this.modStack[0];
|
||||
};
|
||||
|
||||
s.modName = function() {
|
||||
if (this.modStack.length === 0)
|
||||
return 'normal';
|
||||
else if (this.modStack[0] === 'mono')
|
||||
return 'mono';
|
||||
else {
|
||||
if (s.bold && s.ital) {
|
||||
return 'boldital';
|
||||
} else if (s.bold) {
|
||||
return 'bold';
|
||||
} else if (s.ital) {
|
||||
return 'ital';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
|
||||
if (this.spacing) {
|
||||
this.add(" ");
|
||||
this.spacing = false;
|
||||
}
|
||||
if (this.buffer.length > 0) {
|
||||
blocks.push({ text: this.buffer, mod: this.modName() });
|
||||
this.buffer = "";
|
||||
}
|
||||
};
|
||||
|
||||
s.add = function(text) {
|
||||
if (text === " ") {
|
||||
s.spacing = true;
|
||||
}
|
||||
if (s.spacing) {
|
||||
this.buffer += " ";
|
||||
this.spacing = false;
|
||||
}
|
||||
if (text != " ") {
|
||||
this.buffer += text;
|
||||
}
|
||||
};
|
||||
|
||||
while (s.position < text.length) {
|
||||
let ch = text.charAt(s.position);
|
||||
if (/[ \t]/.test(ch)) {
|
||||
if (!s.mono) {
|
||||
s.spacing = true;
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
} else if (/</.test(ch)) {
|
||||
if (!s.mono && !s.bold && /<b>/.test(text.substr(s.position,3))) {
|
||||
s.emitBlock();
|
||||
s.bold = true;
|
||||
s.modStack.unshift("bold");
|
||||
s.position += 2;
|
||||
} else if (!s.mono && !s.ital && /<i>/.test(text.substr(s.position,3))) {
|
||||
s.emitBlock();
|
||||
s.ital = true;
|
||||
s.modStack.unshift("ital");
|
||||
s.position += 2;
|
||||
} else if (!s.mono && /<code>/.test(text.substr(s.position,6))) {
|
||||
s.emitBlock();
|
||||
s.mono = true;
|
||||
s.modStack.unshift("mono");
|
||||
s.position += 5;
|
||||
} else if (!s.mono && (s.mod() === 'bold') && /<\/b>/.test(text.substr(s.position,4))) {
|
||||
s.emitBlock();
|
||||
s.bold = false;
|
||||
s.modStack.shift();
|
||||
s.position += 3;
|
||||
} else if (!s.mono && (s.mod() === 'ital') && /<\/i>/.test(text.substr(s.position,4))) {
|
||||
s.emitBlock();
|
||||
s.ital = false;
|
||||
s.modStack.shift();
|
||||
s.position += 3;
|
||||
} else if ((s.mod() === 'mono') && /<\/code>/.test(text.substr(s.position,7))) {
|
||||
s.emitBlock();
|
||||
s.mono = false;
|
||||
s.modStack.shift();
|
||||
s.position += 6;
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
} else if (/&/.test(ch)) {
|
||||
if (/</.test(text.substr(s.position,4))) {
|
||||
s.add("<");
|
||||
s.position += 3;
|
||||
} else if (/&/.test(text.substr(s.position,5))) {
|
||||
s.add("&");
|
||||
s.position += 4;
|
||||
} else {
|
||||
s.add("&");
|
||||
}
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
s.position++
|
||||
}
|
||||
s.emitBlock();
|
||||
return blocks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Array}
|
||||
*/
|
||||
splitMarkdownBlocks(text) {
|
||||
let blocks = [];
|
||||
|
||||
// TODO: consolidate following + methods/closures with splitHtmlBlocks()
|
||||
// NOTE: sequences of tabs and spaces are reduced to single space; scan usage of `this.spacing` within method
|
||||
let s = {
|
||||
bold: false,
|
||||
ital: false,
|
||||
mono: false,
|
||||
beginable: true,
|
||||
spacing: false,
|
||||
position: 0,
|
||||
buffer: "",
|
||||
modStack: []
|
||||
};
|
||||
|
||||
s.mod = function() {
|
||||
return (this.modStack.length === 0) ? 'normal' : this.modStack[0];
|
||||
};
|
||||
|
||||
s.modName = function() {
|
||||
if (this.modStack.length === 0)
|
||||
return 'normal';
|
||||
else if (this.modStack[0] === 'mono')
|
||||
return 'mono';
|
||||
else {
|
||||
if (s.bold && s.ital) {
|
||||
return 'boldital';
|
||||
} else if (s.bold) {
|
||||
return 'bold';
|
||||
} else if (s.ital) {
|
||||
return 'ital';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
|
||||
if (this.spacing) {
|
||||
this.add(" ");
|
||||
this.spacing = false;
|
||||
}
|
||||
if (this.buffer.length > 0) {
|
||||
blocks.push({ text: this.buffer, mod: this.modName() });
|
||||
this.buffer = "";
|
||||
}
|
||||
};
|
||||
|
||||
s.add = function(text) {
|
||||
if (text === " ") {
|
||||
s.spacing = true;
|
||||
}
|
||||
if (s.spacing) {
|
||||
this.buffer += " ";
|
||||
this.spacing = false;
|
||||
}
|
||||
if (text != " ") {
|
||||
this.buffer += text;
|
||||
}
|
||||
};
|
||||
|
||||
while (s.position < text.length) {
|
||||
let ch = text.charAt(s.position);
|
||||
if (/[ \t]/.test(ch)) {
|
||||
if (!s.mono) {
|
||||
s.spacing = true;
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
s.beginable = true
|
||||
} else if (/\\/.test(ch)) {
|
||||
if (s.position < text.length+1) {
|
||||
s.position++;
|
||||
ch = text.charAt(s.position);
|
||||
if (/ \t/.test(ch)) {
|
||||
s.spacing = true;
|
||||
} else {
|
||||
s.add(ch);
|
||||
s.beginable = false;
|
||||
}
|
||||
}
|
||||
} else if (!s.mono && !s.bold && (s.beginable || s.spacing) && /\*/.test(ch)) {
|
||||
s.emitBlock();
|
||||
s.bold = true;
|
||||
s.modStack.unshift("bold");
|
||||
} else if (!s.mono && !s.ital && (s.beginable || s.spacing) && /\_/.test(ch)) {
|
||||
s.emitBlock();
|
||||
s.ital = true;
|
||||
s.modStack.unshift("ital");
|
||||
} else if (!s.mono && (s.beginable || s.spacing) && /`/.test(ch)) {
|
||||
s.emitBlock();
|
||||
s.mono = true;
|
||||
s.modStack.unshift("mono");
|
||||
} else if (!s.mono && (s.mod() === "bold") && /\*/.test(ch)) {
|
||||
if ((s.position === text.length-1) || /[.,_` \t\n]/.test(text.charAt(s.position+1))) {
|
||||
s.emitBlock();
|
||||
s.bold = false;
|
||||
s.modStack.shift();
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
} else if (!s.mono && (s.mod() === "ital") && /\_/.test(ch)) {
|
||||
if ((s.position === text.length-1) || /[.,*` \t\n]/.test(text.charAt(s.position+1))) {
|
||||
s.emitBlock();
|
||||
s.ital = false;
|
||||
s.modStack.shift();
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
} else if (s.mono && (s.mod() === "mono") && /`/.test(ch)) {
|
||||
if ((s.position === text.length-1) || (/[.,*_ \t\n]/.test(text.charAt(s.position+1)))) {
|
||||
s.emitBlock();
|
||||
s.mono = false;
|
||||
s.modStack.shift();
|
||||
} else {
|
||||
s.add(ch);
|
||||
}
|
||||
} else {
|
||||
s.add(ch);
|
||||
s.beginable = false;
|
||||
}
|
||||
s.position++
|
||||
}
|
||||
s.emitBlock();
|
||||
return blocks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Explodes a piece of text into single-font blocks using a given markup
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {boolean|'md'|'markdown'|'html'} markupSystem
|
||||
* @returns {Array.<{text: string, mod: string}>}
|
||||
* @private
|
||||
*/
|
||||
splitBlocks(text, markupSystem) {
|
||||
let system = this.decodeMarkupSystem(markupSystem);
|
||||
if (system === 'none') {
|
||||
return [{
|
||||
text: text,
|
||||
mod: 'normal'
|
||||
}]
|
||||
} else if (system === 'markdown') {
|
||||
return this.splitMarkdownBlocks(text);
|
||||
} else if (system === 'html') {
|
||||
return this.splitHtmlBlocks(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {boolean} true if text length over the current max with
|
||||
* @private
|
||||
*/
|
||||
overMaxWidth(text) {
|
||||
let width = this.ctx.measureText(text).width;
|
||||
return (this.lines.curWidth() + width > this.parent.fontOptions.maxWdt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the longest part of the sentence which still fits in the
|
||||
* current max width.
|
||||
*
|
||||
* @param {Array} words Array of strings signifying a text lines
|
||||
* @return {number} index of first item in string making string go over max
|
||||
* @private
|
||||
*/
|
||||
getLongestFit(words) {
|
||||
let text = '';
|
||||
let w = 0;
|
||||
|
||||
while (w < words.length) {
|
||||
let pre = (text === '') ? '' : ' ';
|
||||
let newText = text + pre + words[w];
|
||||
|
||||
if (this.overMaxWidth(newText)) break;
|
||||
text = newText;
|
||||
w++;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the longest part of the string which still fits in the
|
||||
* current max width.
|
||||
*
|
||||
* @param {Array} words Array of strings signifying a text lines
|
||||
* @return {number} index of first item in string making string go over max
|
||||
*/
|
||||
getLongestFitWord(words) {
|
||||
let w = 0;
|
||||
|
||||
while (w < words.length) {
|
||||
if (this.overMaxWidth(words.slice(0,w))) break;
|
||||
w++;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split the passed text into lines, according to width constraint (if any).
|
||||
*
|
||||
* The method assumes that the input string is a single line, i.e. without lines break.
|
||||
*
|
||||
* This method retains spaces, if still present (case `font.multi: false`).
|
||||
* A space which falls on an internal line break, will be replaced by a newline.
|
||||
* There is no special handling of tabs; these go along with the flow.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} [mod='normal']
|
||||
* @param {boolean} [appendLast=false]
|
||||
* @private
|
||||
*/
|
||||
splitStringIntoLines(str, mod = 'normal', appendLast = false) {
|
||||
// Still-present spaces are relevant, retain them
|
||||
str = str.replace(/^( +)/g, '$1\r');
|
||||
str = str.replace(/([^\r][^ ]*)( +)/g, '$1\r$2\r');
|
||||
let words = str.split('\r');
|
||||
|
||||
while (words.length > 0) {
|
||||
let w = this.getLongestFit(words);
|
||||
|
||||
if (w === 0) {
|
||||
// Special case: the first word is already larger than the max width.
|
||||
let word = words[0];
|
||||
|
||||
// Break the word to the largest part that fits the line
|
||||
let x = this.getLongestFitWord(word);
|
||||
this.lines.newLine(word.slice(0, x), mod);
|
||||
|
||||
// Adjust the word, so that the rest will be done next iteration
|
||||
words[0] = word.slice(x);
|
||||
} else {
|
||||
// skip any space that is replaced by a newline
|
||||
let newW = w;
|
||||
if (words[w - 1] === ' ') {
|
||||
w--;
|
||||
} else if (words[newW] === ' ') {
|
||||
newW++;
|
||||
}
|
||||
|
||||
let text = words.slice(0, w).join("");
|
||||
|
||||
if (w == words.length && appendLast) {
|
||||
this.lines.append(text, mod);
|
||||
} else {
|
||||
this.lines.newLine(text, mod);
|
||||
}
|
||||
|
||||
// Adjust the word, so that the rest will be done next iteration
|
||||
words = words.slice(newW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LabelSplitter;
|
||||
Reference in New Issue
Block a user