/** * 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. */ // NOTE: When a typedef is isolated in a separate comment block, an actual description is generated for it, // using the rest of the commenting in the code block. Usage of typedef in other comments then // link to there. TIL. // // Also noteworthy, all typedef's set up in this manner are collected in a single, global page 'global.html'. // In other words, it doesn't matter *where* the typedef's are defined in the code. // // // TODO: add descriptive commenting to given typedef's /** * @typedef {{type:string, point:Point, angle:number, length:number}} ArrowData * * Object containing instantiation data for a given endpoint. */ /** * @typedef {{x:number, y:number}} Point * * A point in view-coordinates. */ /** * 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 {Array} points * @param {ArrowData} arrowData * @static */ static transform(points, arrowData) { if (!(points instanceof Array)) { points = [points]; } var x = arrowData.point.x; var y = arrowData.point.y; var angle = arrowData.angle var length = arrowData.length; for(var i = 0; i < points.length; ++i) { var p = points[i]; var xt = p.x * Math.cos(angle) - p.y * Math.sin(angle); var 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 {CanvasRenderingContext2D} ctx * @param {Array.} points * @static */ static drawPath(ctx, points) { ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); for(var i = 1; i < points.length; ++i) { ctx.lineTo(points[i].x, points[i].y); } ctx.closePath(); } } /** * Drawing methods for the arrow endpoint. * @extends EndPoint */ class Arrow extends EndPoint { /** * Draw this shape at the end of a line. * * @param {CanvasRenderingContext2D} ctx * @param {ArrowData} arrowData * @static */ static draw(ctx, arrowData) { // 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 var 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 circle endpoint. */ class Circle { /** * Draw this shape at the end of a line. * * @param {CanvasRenderingContext2D} ctx * @param {ArrowData} arrowData * @static */ static draw(ctx, arrowData) { var point = {x:-0.4, y:0}; EndPoint.transform(point, arrowData); ctx.circle(point.x, point.y, arrowData.length*0.4); } } /** * Drawing methods for the endpoints. */ class EndPoints { /** * Draw an endpoint * * @param {CanvasRenderingContext2D} ctx * @param {ArrowData} arrowData * @static */ static draw(ctx, arrowData) { var type; if (arrowData.type) { type = arrowData.type.toLowerCase(); } switch (type) { case 'circle': Circle.draw(ctx, arrowData); break; case 'arrow': // fall-through default: Arrow.draw(ctx, arrowData); } } } export default EndPoints;