UNPKG

vis-network

Version:

A dynamic, browser-based visualization library.

217 lines (202 loc) 5.98 kB
import { EdgeBase } from "./edge-base"; import { EdgeFormattingValues, Label, EdgeOptions, Point, PointT, SelectiveRequired, VBody, VNode } from "./types"; /** * The Base Class for all Bezier edges. * Bezier curves are used to model smooth gradual curves in paths between nodes. */ export abstract class BezierEdgeBase<Via> extends EdgeBase<Via> { /** * 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) { super(options, body, labelModule); } /** * Compute additional point(s) the edge passes through. * * @returns Cartesian coordinates of the point(s) the edge passes through. */ protected abstract _getViaCoordinates(): Via; /** * Find the intersection between the border of the node and the edge. * * @remarks * This function uses binary search to look for the point where the bezier curve crosses the border of the node. * * @param nearNode - The node (either from or to node of the edge). * @param ctx - The context that will be used for rendering. * @param viaNode - Additional node(s) the edge passes through. * * @returns Cartesian coordinates of the intersection between the border of the node and the edge. */ protected _findBorderPositionBezier( nearNode: VNode, ctx: CanvasRenderingContext2D, viaNode: Via = this._getViaCoordinates() ): PointT { const maxIterations = 10; const threshold = 0.2; let from = false; let high = 1; let low = 0; let node = this.to; let pos: Point; let middle: number; if (nearNode.id === this.from.id) { node = this.from; from = true; } let iteration = 0; do { middle = (low + high) * 0.5; pos = this.getPoint(middle, viaNode); const angle = Math.atan2(node.y - pos.y, node.x - pos.x); const distanceToBorder = node.distanceToBorder(ctx, angle); const distanceToPoint = Math.sqrt( Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2) ); const 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; } while (low <= high && iteration < maxIterations); return { ...pos, t: middle }; } /** * Calculate the distance between a point (x3,y3) and a line segment from (x1,y1) to (x2,y2). * * @remarks * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment * * @param x1 - First end of the line segment on the x axis. * @param y1 - First end of the line segment on the y axis. * @param x2 - Second end of the line segment on the x axis. * @param y2 - Second end of the line segment on the y axis. * @param x3 - Position of the point on the x axis. * @param y3 - Position of the point on the y axis. * @param via - The control point for the edge. * * @returns The distance between the line segment and the point. */ protected _getDistanceToBezierEdge( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, via: Point ): number { // 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; } /** * Render a bezier curve between two nodes. * * @remarks * The method accepts zero, one or two control points. * Passing zero control points just draws a straight line. * * @param ctx - The context that will be used for rendering. * @param values - Style options for edge drawing. * @param viaNode1 - First control point for curve drawing. * @param viaNode2 - Second control point for curve drawing. */ protected _bezierCurve( ctx: CanvasRenderingContext2D, values: SelectiveRequired< EdgeFormattingValues, | "backgroundColor" | "backgroundSize" | "shadowColor" | "shadowSize" | "shadowX" | "shadowY" >, viaNode1?: Point, viaNode2?: Point ): void { ctx.beginPath(); ctx.moveTo(this.fromPoint.x, this.fromPoint.y); if (viaNode1 != null && viaNode1.x != null) { if (viaNode2 != null && viaNode2.x != null) { ctx.bezierCurveTo( viaNode1.x, viaNode1.y, viaNode2.x, viaNode2.y, this.toPoint.x, this.toPoint.y ); } else { 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 a background this.drawBackground(ctx, values); // draw shadow if enabled this.enableShadow(ctx, values); ctx.stroke(); this.disableShadow(ctx, values); } /** @inheritdoc */ public getViaNode(): Via { return this._getViaCoordinates(); } }