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

198 lines
6.1 KiB

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.
*
* @class BezierEdgeDynamic
* @extends BezierEdgeBase
*/
class BezierEdgeDynamic extends BezierEdgeBase {
/**
* @param {Object} options
* @param {Object} body
* @param {Label} labelModule
* @constructor BezierEdgeDynamic
*/
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 {vis.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 {vis.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 {vis.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;