big cmap refactoring
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user