vis-network
Version:
A dynamic, browser-based visualization library.
241 lines (214 loc) • 6.06 kB
text/typescript
import { BezierEdgeBase } from "./util/bezier-edge-base";
import {
EdgeFormattingValues,
Label,
EdgeOptions,
Point,
PointT,
SelectiveRequired,
VBody,
VNode
} from "./util/types";
/**
* A Static Bezier Edge. Bezier curves are used to model smooth gradual curves in paths between nodes.
*/
export class BezierEdgeStatic extends BezierEdgeBase<Point> {
/**
* 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);
}
/** @inheritdoc */
protected _line(
ctx: CanvasRenderingContext2D,
values: SelectiveRequired<
EdgeFormattingValues,
| "backgroundColor"
| "backgroundSize"
| "shadowColor"
| "shadowSize"
| "shadowX"
| "shadowY"
>,
viaNode: Point
): void {
this._bezierCurve(ctx, values, viaNode);
}
/** @inheritdoc */
public getViaNode(): Point {
return this._getViaCoordinates();
}
/**
* Compute the coordinates of the via node.
*
* @remarks
* We do not use the to and fromPoints here to make the via nodes the same as edges without arrows.
*
* @returns Cartesian coordinates of the via node.
*/
protected _getViaCoordinates(): Point {
// Assumption: x/y coordinates in from/to always defined
const factor = this.options.smooth.roundness;
const 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;
}
let xVia = this.from.x + stepX;
let 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;
}
}
return { x: xVia, 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;
}
return {
x: this.to.x + stepX,
y: this.to.y + stepY
};
} else if (type === "horizontal") {
let stepX = (1 - factor) * dx;
if (this.from.x < this.to.x) {
stepX = -stepX;
}
return {
x: this.to.x + stepX,
y: this.from.y
};
} else if (type === "vertical") {
let stepY = (1 - factor) * dy;
if (this.from.y < this.to.y) {
stepY = -stepY;
}
return {
x: this.from.x,
y: this.to.y + stepY
};
} else if (type === "curvedCW") {
dx = this.to.x - this.from.x;
dy = this.from.y - this.to.y;
const radius = Math.sqrt(dx * dx + dy * dy);
const pi = Math.PI;
const originalAngle = Math.atan2(dy, dx);
const myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi);
return {
x: this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle),
y: 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;
const radius = Math.sqrt(dx * dx + dy * dy);
const pi = Math.PI;
const originalAngle = Math.atan2(dy, dx);
const myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi);
return {
x: this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle),
y: 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;
}
let xVia = this.from.x + stepX;
let 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 };
}
}
/** @inheritdoc */
protected _findBorderPosition(
nearNode: VNode,
ctx: CanvasRenderingContext2D,
options: { via?: Point } = {}
): PointT {
return this._findBorderPositionBezier(nearNode, ctx, options.via);
}
/** @inheritdoc */
protected _getDistanceToEdge(
x1: number,
y1: number,
x2: number,
y2: number,
x3: number,
y3: number,
viaNode = this._getViaCoordinates()
) {
// x3,y3 is the point
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, viaNode);
}
/** @inheritdoc */
public getPoint(
position: number,
viaNode: Point = this._getViaCoordinates()
): Point {
const t = position;
const x =
Math.pow(1 - t, 2) * this.fromPoint.x +
2 * t * (1 - t) * viaNode.x +
Math.pow(t, 2) * this.toPoint.x;
const 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 };
}
}