/** * Keep diagram endpoints and connector controls geometrically related. * Derived from ionstage/cmap 0.1.3, (c) 2015 iOnStage, MIT License. */ import { Connector, DrawingLink as Link, DrawingNode as Node } from "./drawing-components.js"; class Relation { constructor() { } prop(initialValue) { var cache = initialValue; return function(value) { if (typeof value === 'undefined') return cache; cache = value; }; } update() { } } class Triple extends Relation { constructor(props) { super(); this.link = this.prop(props.link); this.sourceNode = this.prop(props.sourceNode || null); this.targetNode = this.prop(props.targetNode || null); this.skipNextUpdate = this.prop(false); this.nodePositionsCache = this.prop({}); } update(changedComponent) { if (this.skipNextUpdate()) { this.skipNextUpdate(false); return; } var link = this.link(); var sourceNode = this.sourceNode(); var targetNode = this.targetNode(); if (changedComponent instanceof Node) this.updateNode(link, sourceNode, targetNode, changedComponent); else if (changedComponent instanceof Link) this.updateLink(link, sourceNode, targetNode); } updateNode(link, sourceNode, targetNode, changedNode) { if (sourceNode && targetNode) this.rotateLink(link, sourceNode, targetNode, changedNode); else this.shiftLink(link, sourceNode, targetNode, changedNode); this.updateNodePositionsCache(); } rotateLink(link, sourceNode, targetNode, changedNode) { var cache = this.nodePositionsCache(); var sncx = cache.sncx; var sncy = cache.sncy; var tncx = cache.tncx; var tncy = cache.tncy; var lcx = link.cx(); var lcy = link.cy(); var ts_dx = tncx - sncx; var ts_dy = tncy - sncy; var cs_dx = lcx - sncx; var cs_dy = lcy - sncy; var ts_rad0 = Math.atan2(ts_dy, ts_dx); var cs_rad0 = Math.atan2(cs_dy, cs_dx); // changed node position if (changedNode === sourceNode) { sncx = sourceNode.cx(); sncy = sourceNode.cy(); } else if (changedNode === targetNode) { tncx = targetNode.cx(); tncy = targetNode.cy(); } // center positions of two nodes are equal if (cs_rad0 === 0) { link.cx((sncx + tncx) / 2); link.cy((sncy + tncy) / 2); return; } var ts_d0 = Math.sqrt(ts_dx * ts_dx + ts_dy * ts_dy); var cs_d0 = Math.sqrt(cs_dx * cs_dx + cs_dy * cs_dy); var ts_cs_rad = ts_rad0 - cs_rad0; ts_dx = tncx - sncx; ts_dy = tncy - sncy; var ts_rad1 = Math.atan2(ts_dy, ts_dx); var cs_rad1 = ts_rad1 - ts_cs_rad; var ts_d1 = Math.sqrt(ts_dx * ts_dx + ts_dy * ts_dy); var d_rate = (ts_d0 !== 0) ? ts_d1 / ts_d0 : 1; var cs_d1 = cs_d0 * d_rate; lcx = sncx + cs_d1 * Math.cos(cs_rad1); lcy = sncy + cs_d1 * Math.sin(cs_rad1); link.cx(lcx); link.cy(lcy); } shiftLink(link, sourceNode, targetNode, changedNode) { var cache = this.nodePositionsCache(); var ncx = changedNode.cx(); var ncy = changedNode.cy(); if (changedNode === sourceNode) { link.targetX(link.targetX() + (ncx - cache.sncx)); link.targetY(link.targetY() + (ncy - cache.sncy)); } else if (changedNode === targetNode) { link.sourceX(link.sourceX() + (ncx - cache.tncx)); link.sourceY(link.sourceY() + (ncy - cache.tncy)); } } updateLink(link, sourceNode, targetNode) { var lx, ly, p; if (sourceNode) { // connect link to source node lx = targetNode ? link.cx() : link.targetX(); ly = targetNode ? link.cy() : link.targetY(); p = this.connectedPoint(sourceNode, lx, ly); link.sourceX(p.x); link.sourceY(p.y); } if (targetNode) { // connect link to target node lx = sourceNode ? link.cx() : link.sourceX(); ly = sourceNode ? link.cy() : link.sourceY(); p = this.connectedPoint(targetNode, lx, ly); link.targetX(p.x); link.targetY(p.y); } if (!sourceNode || !targetNode) { // link content moves to midpoint link.cx((link.sourceX() + link.targetX()) / 2); link.cy((link.sourceY() + link.targetY()) / 2); } } updateLinkAngle(radians) { var link = this.link(); var sourceNode = this.sourceNode(); var targetNode = this.targetNode(); var ldx = link.targetX() - link.sourceX(); var ldy = link.targetY() - link.sourceY(); var d = Math.sqrt(ldx * ldx + ldy * ldy); var connectedNode = sourceNode || targetNode; var cx = connectedNode.cx(); var cy = connectedNode.cy(); var lx = cx + d * Math.cos(radians); var ly = cy + d * Math.sin(radians); var p = this.connectedPoint(connectedNode, lx, ly); if (connectedNode === sourceNode) link.straighten(p.x, p.y, lx + p.x - cx, ly + p.y - cy); else if (connectedNode === targetNode) link.straighten(lx + p.x - cx, ly + p.y - cy, p.x, p.y); } updateNodePositionsCache() { var sourceNode = this.sourceNode(); var targetNode = this.targetNode(); var cache = this.nodePositionsCache(); if (sourceNode) { cache.sncx = sourceNode.cx(); cache.sncy = sourceNode.cy(); } if (targetNode) { cache.tncx = targetNode.cx(); cache.tncy = targetNode.cy(); } } connectedPoint(node, lx, ly) { var nx = node.x(); var ny = node.y(); var nwidth = node.width(); var nheight = node.height(); var ncx = node.cx(); var ncy = node.cy(); var alpha = Math.atan2(ly - ncy, lx - ncx); var beta = Math.PI / 2 - alpha; var t = Math.atan2(nheight, nwidth); var x, y; // left edge if (alpha < t - Math.PI || alpha > Math.PI - t) { x = nx; y = ncy - nwidth * Math.tan(alpha) / 2; } // top edge else if (alpha < -t) { x = ncx - nheight * Math.tan(beta) / 2; y = ny; } // right edge else if (alpha < t) { x = nx + nwidth; y = ncy + nwidth * Math.tan(alpha) / 2; } // bottom edge else { x = ncx + nheight * Math.tan(beta) / 2; y = ny + nheight; } var x0, y0, l, ex, ey; var r = node.borderRadius(); var atCorner = false; // top-left corner if (x < nx + r && y < ny + r) { x0 = nx + r; y0 = ny + r; atCorner = true; } // top-right corner else if (x > nx + nwidth - r && y < ny + r) { x0 = nx + nwidth - r; y0 = ny + r; atCorner = true; } // bottom-left corner else if (x < nx + r && y > ny + nheight - r) { x0 = nx + r; y0 = ny + nheight - r; atCorner = true; } // bottom-right corner else if (x > nx + nwidth - r && y > ny + nheight - r) { x0 = nx + nwidth - r; y0 = ny + nheight - r; atCorner = true; } if (atCorner) { l = Math.sqrt((x0 - x) * (x0 - x) + (y0 - y) * (y0 - y)); ex = (x0 - x) / l; ey = (y0 - y) / l; x = x0 - r * ex; y = y0 - r * ey; } return { x: x, y: y }; } } class LinkConnectorRelation extends Relation { constructor(props) { super(); this.type = this.prop(props.type); this.link = this.prop(props.link); this.connector = this.prop(props.connector); } isConnected(isConnected) { var color = isConnected ? Connector.COLOR_CONNECTED : Connector.COLOR_UNCONNECTED; this.connector().color(color); } update(changedComponent) { var type = this.type(); var link = this.link(); var connector = this.connector(); if (changedComponent === link) { connector.x(link[type + 'X']()); connector.y(link[type + 'Y']()); } } } export { LinkConnectorRelation, Triple as DrawingTriple };