@doc hierarchy="GMLDOM">
Aspect for drawing orthogonal lines using only horizontal and vertical
line segments.
(c) SAP AG 2003-2006. All rights reserved.
Aspect OrthogonalLine for GmlDrawing;
inherit Line;
//////////////////////////////////////////////////////////////////////////////////////
// BASE PROPERTIES
<@doc>
Gets or sets the line corners' radius
This property controls the rounding of the corners of the orthogonal line.
By default, this property is set to 0 which results in square corners.
property cornerRadius = 0;
//////////////////////////////////////////////////////////////////////////////////////
// METHODS
override virtual method reroute()
if (this.diagram.bufferedReroute(this)) return;
// normalize pins (so that source pin is at origin and oriented at 0 degrees)
var P1 = this.srcpin; // source pin (origin)
var S1 = P1.shape;
var ox1=S1.ox, oy1=S1.oy, os1=S1.os;
var Sx = ox1+(S1.cx+P1.x)*os1;
var Sy = oy1+(S1.cy+P1.y)*os1;
var Sz = P1.rot;
var Sx1 = ox1+S1.x1*os1-Sx; // source shape bounds
var Sy1 = oy1+S1.y1*os1-Sy;
var Sx2 = ox1+S1.x2*os1-Sx;
var Sy2 = oy1+S1.y2*os1-Sy;
var P2 = this.trgpin; // target pin
var S2 = P2.shape;
var ox2=S2.ox, oy2=S2.oy, os2=S2.os;
var Tx = ox2+(S2.cx+P2.x)*os2-Sx;
var Ty = oy2+(S2.cy+P2.y)*os2-Sy;
var Tz = P2.rot;
var Tx1 = ox2+S2.x1*os2-Sx; // target shape bounds
var Ty1 = oy2+S2.y1*os2-Sy;
var Tx2 = ox2+S2.x2*os2-Sx;
var Ty2 = oy2+S2.y2*os2-Sy;
var f1=((Sz%2) == 1), f2=(Sz==1 || Sz==2);
var t, mx, my;
if (f1) {
t=Tx; Tx=Ty; Ty=t;
t=Sx1; Sx1=Sy1; Sy1=t; t=Sx2; Sx2=Sy2; Sy2=t;
t=Tx1; Tx1=Ty1; Ty1=t; t=Tx2; Tx2=Ty2; Ty2=t;
}
if (f2) {
Tx=-Tx; Ty=-Ty;
Sx1=-Sx1; Sy1=-Sy1; Sx2=-Sx2; Sy2=-Sy2;
Tx1=-Tx1; Ty1=-Ty1; Tx2=-Tx2; Ty2=-Ty2;
}
t=Math.max(Sx1,Sx2); Sx1=Math.min(Sx1,Sx2); Sx2=t;
t=Math.max(Sy1,Sy2); Sy1=Math.min(Sy1,Sy2); Sy2=t;
t=Math.max(Tx1,Tx2); Tx1=Math.min(Tx1,Tx2); Tx2=t;
t=Math.max(Ty1,Ty2); Ty1=Math.min(Ty1,Ty2); Ty2=t;
var mr=10, MR=40; // minimum and maximum shape margins (absolute)
var Sa=(-Sy1)/(Sy2-Sy1), Sm=Math.max(mr,MR*Sa)*os1; // source margins (relative and absolute)
var Ta=(Ty-Ty1)/(Ty2-Ty1), Tm=Math.max(mr,MR*Ta)*os2; // target margins (relative and absolute)
Tz = (Tz-Sz)%4;
if (Tz<0) Tz+=4;
// compute path layout
var path=[], contour=0;
// pins are in opposite directions
if (Tz == 2) {
var flag = (Ty>0);
if (Tx > 0) {
// pins are facing each other
if (Ty==0) {
// trivial path (1 segment)
contour = 11;
path.push('h',Tx);
} else {
// path without obstacles (3 segments)
contour = (flag ? 12 : 13);
mx = Tx*(flag ? 1-Sa : Sa);
path.push('h',mx,'v',Ty,'h',Tx-mx);
}
} else {
// pins are facing opposite directions
var my1=(flag ? Ty1 : Sy1), my2=(flag ? Sy2 : Ty2), mx1, mx2;
if (my1 > my2) {
// path snakes through vertical space between shapes (5 segments)
contour = (flag ? 14 : 15);
mx1 = (flag ? MR-Sm : Sm)*1.5;
mx2 = (MR*1.5-mx1);
my = my1+(my2-my1)*Sa;
} else {
// path encircles shapes (5 segments)
contour = 16;
mx2 = (flag ? MR-Sm : Sm)*1.5;
mx1 = Math.max(0,Tx2)+mx2;
my = (flag ? Ty2+(MR-Sm)*1.5 : Ty1-Sm*1.5);
}
path.push('h',mx1,'v',my,'h',Tx-(mx1+mx2),'v',Ty-my,'h',mx2);
}
// pins are in same direction
} else if (Tz == 0) {
if (Tx>0 && Ty1*Ty2<=0) {
// path with bypass for target shape (5 segments)
var flag = (Math.abs(Ty1) < Math.abs(Ty2)), mx1, mx2;
contour = (flag ? 21 : 22);
mx1 = Tx1*(flag ? Sa : 1-Sa);
mx2 = (flag ? MR-Sm : Sm)*1.5;
my = (flag ? Ty1-(MR-Sm)*1.5 : Ty2+Sm*1.5);
path.push('h',mx1,'v',my,'h',Tx-mx1+mx2,'v',Ty-my,'h',-mx2);
} else if (Tx=Sy1 && Ty<=Sy2) {
// path with bypass for source shape (5 segments)
var flag = (Math.abs(Sy1) < Math.abs(Sy2)), mx1, mx2;
contour = (flag ? 23 : 24);
mx1 = Tx+(Sx1-Tx)*(flag ? 1-Sa : Sa);
mx2 = Sm*1.5;
my = (flag ? Sy1-mx2 : Sy2+mx2);
path.push('h',mx2,'v',my,'h',mx1-mx2,'v',Ty-my,'h',Tx-mx1);
} else {
// path without obstacles (3 segments)
var flag = (Ty>0);
contour = (flag ? 25 : 26);
mx = (flag ? (MR-Sm) : Sm)*1.5+Math.max(Tx,0);
path.push('h',mx,'v',Ty,'h',Tx-mx);
}
// pins are perpendicular
} else {
var flag = ((Sz&1) ? (Tz==3) : (Tz==1));
var sign = (flag ? 1 : -1), my1=(flag ? Ty1 : Sy1), my2=(flag ? Sy2 : Ty2);
if (Tx>0 && sign*Ty>0) {
// path without obstacles (2 segments)
contour = 31;
path.push('h',Tx,'v',Ty);
} else if (Tx<=0 && my1>my2) {
// path snakes through vertical space between shapes (4 segments)
contour = (flag ? 32 : 33);
mx = (flag ? MR-Sm : Sm)*1.5;
my = my1+(my2-my1)*Sa;
path.push('h',mx,'v',my,'h',Tx-mx,'v',Ty-my);
} else if (Tx1>0) {
// path snakes through horizontal space between shapes (4 segments)
contour = (flag ? 34 : 35);
mx=Tx1*(flag ? Sa : 1-Sa);
my=(flag ? MR-sign*Sm : sign*Sm)*1.5;
path.push('h',mx,'v',Ty-my,'h',Tx-mx,'v',my);
} else {
// path encircles shapes (4 segments)
contour = (flag ? 36 : 37);
my=(flag ? Math.min(Sy1,Ty1)-Sm*1.5 : Math.max(Sy2,Ty2)+(MR-Sm)*1.5);
mx=Math.max(0,Tx2)+(flag ? Sm : MR-Sm)*1.5;
path.push('h',mx,'v',my,'h',Tx-mx,'v',Ty-my);
}
}
// rotate path to original orientation
for (var i=0, len=path.length, sign=(f2 ? -1 : 1); i= 3) { if (path[0]=='h') lx+=path[1]; else ly+=path[1]; }
if (ln >= 5) { if (path[2]=='h') lx+=path[3]; else ly+=path[3]; }
if (path[ln-1]=='h') {
lx += path[ln]/2;
} else {
if (ABS(path[ln]) >= lmin) {
ly += path[ln]/2;
la = SIGN(path[ln])*90;
} else if (ln >= 3 && ABS(path[ln-2]) >= lmin) {
lx -= path[ln-2]/2;
} else if (ln <= path.length-2 && ABS(path[ln+2]) >= lmin) {
ly += path[ln];
lx += path[ln+2]/2;
} else {
ly += path[ln]/2;
}
}
lx += p1.x;
ly += p1.y;
this.labelNode.setAttribute('x', lx);
this.labelNode.setAttribute('y', ly-3);
this.labelNode.setAttribute('transform', 'rotate('+la+' '+lx+' '+ly+')');
// apply round corners
var r=base.@cornerRadius||0;
if (r > 0) path = this.applyRoundCorners(path, r);
this.lineNode.setAttribute('d', 'M'+p1.x+','+p1.y+path.join(' '));
end
override virtual method getHandlesData()
var path=SPLIT(base.@path), len=path.length;
if (len <= 4) return null;
var p1=this.srcpin.getCP(this.trgpin), cx=p1.x, cy=p1.y, data={};
for (var i=0; i0) data['C'+(i/2)] = {cx:cx+dx/2, cy:cy+dy/2}
cx+=dx; cy+=dy;
}
return data;
end
override virtual method onHandleMove(ptr)
switch (ptr.phase) {
case #[PTR_START]:
ptr.scrollMode = #[PTR_AUTOSCROLL];
ptr.wire = SVG.createElement(this.diagram.linesLayer, 'path', {fill:'none', 'stroke':this.base.@strokeColor, 'stroke-dasharray':'4 3', 'pointer-events':'none'});
var path=SPLIT(base.@path), len=path.length;
var n=POS((ptr.source.getAttribute('handle')||'').charAt(1)), axis=path[2*(n-1)];
if (axis != 'h' && axis != 'v') { ptr.cancel=true; break; }
for (var i=0; i 0) path = this.applyRoundCorners(path, r);
ptr.wire.setAttribute('d', 'M'+p1.x+','+p1.y+path.join(' '));
this.diagram.moveHandle('C'+n, org.x+dx, org.y+dy);
break;
case #[PTR_FINISH]:
var path=ptr.path, controls=SPLITN(base.@controls), n=ptr.n;
controls[n] += ptr.d0;
// Silent update: (see Line.reroute)
this.diagram.silentUpdate(this, '@path', path.join(' '));
this.diagram.silentUpdate(this, '@controls', controls.join(' '));
this.reroute();
// fall-through
case #[PTR_CANCEL]:
SVG.removeElement(ptr.wire);
if (!ptr.isOverSource) SVG.setProperty(ptr.source, 'class', 'handleNormal');
var data=this.getHandlesData();
for (var k in data) {
this.diagram.moveHandle(k, data[k].cx, data[k].cy);
}
break;
}
end
virtual method applyRoundCorners(path, radius)
var R=[], p1=[], p2=[], len=path.length-2;
for (var i=0; i0 ? 0 : 1)+' '+d2+' '+d1);
}
p1[i+3] -= d2;
}
for (var len=p1.length; i