UNPKG

vis-network

Version:

A dynamic, browser-based visualization library.

212 lines (194 loc) 5.74 kB
import { BezierEdgeBase } from "./util/bezier-edge-base"; import { EdgeFormattingValues, Label, EdgeOptions, Point, PointT, SelectiveRequired, VBody, VNode } from "./util/types"; /** * 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 */ export class BezierEdgeDynamic extends BezierEdgeBase<Point> { public via: VNode = this.via; // constructor → super → super → setOptions → setupSupportNode private readonly _boundFunction: () => void; /** * Create a new instance. * * @param options - The options object of given edge. * @param body - The body of the network. * @param labelModule - Label module. */ public constructor(options: EdgeOptions, body: VBody, labelModule: Label) { //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 = (): void => { this.positionBezierNode(); }; this._body.emitter.on("_repositionBezierNodes", this._boundFunction); } /** @inheritdoc */ public setOptions(options: EdgeOptions): void { super.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(); } } /** @inheritdoc */ public connect(): void { 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 }); } } } /** @inheritdoc */ public cleanup(): boolean { 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; } /** * Create and add a support node if not already present. * * @remarks * 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. */ public setupSupportNode(): void { if (this.via === undefined) { const nodeId = "edgeId:" + this.id; const 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(); } } /** * Position bezier node. */ public positionBezierNode(): void { 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; } } /** @inheritdoc */ protected _line( ctx: CanvasRenderingContext2D, values: SelectiveRequired< EdgeFormattingValues, | "backgroundColor" | "backgroundSize" | "shadowColor" | "shadowSize" | "shadowX" | "shadowY" >, viaNode: VNode ): void { this._bezierCurve(ctx, values, viaNode); } /** @inheritdoc */ protected _getViaCoordinates(): Point { return this.via; } /** @inheritdoc */ public getViaNode(): Point { return this.via; } /** @inheritdoc */ public getPoint(position: number, viaNode: Point = this.via): Point { if (this.from === this.to) { const [cx, cy, cr] = this._getCircleData(); const a = 2 * Math.PI * (1 - position); return { x: cx + cr * Math.sin(a), y: cy + cr - cr * (1 - Math.cos(a)) }; } else { return { x: Math.pow(1 - position, 2) * this.fromPoint.x + 2 * position * (1 - position) * viaNode.x + Math.pow(position, 2) * this.toPoint.x, y: Math.pow(1 - position, 2) * this.fromPoint.y + 2 * position * (1 - position) * viaNode.y + Math.pow(position, 2) * this.toPoint.y }; } } /** @inheritdoc */ protected _findBorderPosition( nearNode: VNode, ctx: CanvasRenderingContext2D ): PointT { return this._findBorderPositionBezier(nearNode, ctx, this.via); } /** @inheritdoc */ protected _getDistanceToEdge( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number ): number { // x3,y3 is the point return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via); } }