vis-network
Version:
A dynamic, browser-based visualization library.
461 lines (426 loc) • 12.1 kB
text/typescript
/** ============================================================================
* 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`.
* ============================================================================= */
import { ArrowData, Point } from "./types";
/**
* 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 points - The point(s) to be transformed.
* @param arrowData - The data determining the result of the transformation.
*/
public static transform(points: Point | Point[], arrowData: ArrowData): void {
if (!Array.isArray(points)) {
points = [points];
}
const x = arrowData.point.x;
const y = arrowData.point.y;
const angle = arrowData.angle;
const length = arrowData.length;
for (let i = 0; i < points.length; ++i) {
const p = points[i];
const xt = p.x * Math.cos(angle) - p.y * Math.sin(angle);
const 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 ctx - The path will be rendered into this context.
* @param points - The points of the path.
*/
public static drawPath(ctx: CanvasRenderingContext2D, points: Point[]): void {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; ++i) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
}
}
/**
* Drawing methods for the arrow endpoint.
*/
class Arrow extends EndPoint {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const 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 crow endpoint.
*/
class Crow {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const points = [
{ x: -1, y: 0 },
{ x: 0, y: 0.3 },
{ x: -0.4, y: 0 },
{ x: 0, y: -0.3 }
];
EndPoint.transform(points, arrowData);
EndPoint.drawPath(ctx, points);
}
}
/**
* Drawing methods for the curve endpoint.
*/
class Curve {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const point = { x: -0.4, y: 0 };
EndPoint.transform(point, arrowData);
// Update endpoint style for drawing transparent arc.
ctx.strokeStyle = ctx.fillStyle;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
// Define curve endpoint as semicircle.
const pi = Math.PI;
const startAngle = arrowData.angle - pi / 2;
const endAngle = arrowData.angle + pi / 2;
ctx.beginPath();
ctx.arc(
point.x,
point.y,
arrowData.length * 0.4,
startAngle,
endAngle,
false
);
ctx.stroke();
}
}
/**
* Drawing methods for the inverted curve endpoint.
*/
class InvertedCurve {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const point = { x: -0.3, y: 0 };
EndPoint.transform(point, arrowData);
// Update endpoint style for drawing transparent arc.
ctx.strokeStyle = ctx.fillStyle;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
// Define inverted curve endpoint as semicircle.
const pi = Math.PI;
const startAngle = arrowData.angle + pi / 2;
const endAngle = arrowData.angle + (3 * pi) / 2;
ctx.beginPath();
ctx.arc(
point.x,
point.y,
arrowData.length * 0.4,
startAngle,
endAngle,
false
);
ctx.stroke();
}
}
/**
* Drawing methods for the trinagle endpoint.
*/
class Triangle {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const points = [{ x: 0.02, y: 0 }, { x: -1, y: 0.3 }, { x: -1, y: -0.3 }];
EndPoint.transform(points, arrowData);
EndPoint.drawPath(ctx, points);
}
}
/**
* Drawing methods for the inverted trinagle endpoint.
*/
class InvertedTriangle {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const points = [{ x: 0, y: 0.3 }, { x: 0, y: -0.3 }, { x: -1, y: 0 }];
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 ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
const 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 ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
/*
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();
*/
const 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 box endpoint.
*/
class Box {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
const points = [
{ x: 0, y: 0.3 },
{ x: 0, y: -0.3 },
{ x: -0.6, y: -0.3 },
{ x: -0.6, y: 0.3 }
];
EndPoint.transform(points, arrowData);
EndPoint.drawPath(ctx, points);
}
}
/**
* Drawing methods for the diamond endpoint.
*/
class Diamond {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
const points = [
{ x: 0, y: 0 },
{ x: -0.5, y: -0.3 },
{ x: -1, y: 0 },
{ x: -0.5, y: 0.3 }
];
EndPoint.transform(points, arrowData);
EndPoint.drawPath(ctx, points);
}
}
/**
* Drawing methods for the vee endpoint.
*/
class Vee {
/**
* Draw this shape at the end of a line.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
// 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
const points = [
{ x: -1, y: 0.3 },
{ x: -0.5, y: 0 },
{ x: -1, y: -0.3 },
{ x: 0, y: 0 }
];
EndPoint.transform(points, arrowData);
EndPoint.drawPath(ctx, points);
}
}
/**
* Drawing methods for the endpoints.
*/
export class EndPoints {
/**
* Draw an endpoint.
*
* @param ctx - The shape will be rendered into this context.
* @param arrowData - The data determining the shape.
*/
public static draw(
ctx: CanvasRenderingContext2D,
arrowData: ArrowData
): void {
let type;
if (arrowData.type) {
type = arrowData.type.toLowerCase();
}
switch (type) {
case "circle":
Circle.draw(ctx, arrowData);
break;
case "box":
Box.draw(ctx, arrowData);
break;
case "crow":
Crow.draw(ctx, arrowData);
break;
case "curve":
Curve.draw(ctx, arrowData);
break;
case "diamond":
Diamond.draw(ctx, arrowData);
break;
case "inv_curve":
InvertedCurve.draw(ctx, arrowData);
break;
case "triangle":
Triangle.draw(ctx, arrowData);
break;
case "inv_triangle":
InvertedTriangle.draw(ctx, arrowData);
break;
case "bar":
Bar.draw(ctx, arrowData);
break;
case "vee":
Vee.draw(ctx, arrowData);
break;
case "arrow": // fall-through
default:
Arrow.draw(ctx, arrowData);
}
}
}