495 lines
12 KiB
JavaScript
495 lines
12 KiB
JavaScript
/**
|
|
* Copyright (c) 2006-2015, JGraph Ltd
|
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
|
*/
|
|
/**
|
|
* Class: mxArrowConnector
|
|
*
|
|
* Extends <mxShape> to implement an new rounded arrow shape with support for
|
|
* waypoints and double arrows. (The shape is used to represent edges, not
|
|
* vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
|
|
* in <mxCellRenderer>.
|
|
*
|
|
* Constructor: mxArrowConnector
|
|
*
|
|
* Constructs a new arrow shape.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* points - Array of <mxPoints> that define the points. This is stored in
|
|
* <mxShape.points>.
|
|
* fill - String that defines the fill color. This is stored in <fill>.
|
|
* stroke - String that defines the stroke color. This is stored in <stroke>.
|
|
* strokewidth - Optional integer that defines the stroke width. Default is
|
|
* 1. This is stored in <strokewidth>.
|
|
* arrowWidth - Optional integer that defines the arrow width. Default is
|
|
* <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
|
|
* spacing - Optional integer that defines the spacing between the arrow shape
|
|
* and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
|
|
* <spacing>.
|
|
* endSize - Optional integer that defines the size of the arrowhead. Default
|
|
* is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
|
|
*/
|
|
function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
|
|
{
|
|
mxShape.call(this);
|
|
this.points = points;
|
|
this.fill = fill;
|
|
this.stroke = stroke;
|
|
this.strokewidth = (strokewidth != null) ? strokewidth : 1;
|
|
this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
|
|
this.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
|
|
this.startSize = mxConstants.ARROW_SIZE / 5;
|
|
this.endSize = mxConstants.ARROW_SIZE / 5;
|
|
};
|
|
|
|
/**
|
|
* Extends mxShape.
|
|
*/
|
|
mxUtils.extend(mxArrowConnector, mxShape);
|
|
|
|
/**
|
|
* Variable: useSvgBoundingBox
|
|
*
|
|
* Allows to use the SVG bounding box in SVG. Default is false for performance
|
|
* reasons.
|
|
*/
|
|
mxArrowConnector.prototype.useSvgBoundingBox = true;
|
|
|
|
/**
|
|
* Function: isRoundable
|
|
*
|
|
* Hook for subclassers.
|
|
*/
|
|
mxArrowConnector.prototype.isRoundable = function()
|
|
{
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Variable: resetStyles
|
|
*
|
|
* Overrides mxShape to reset spacing.
|
|
*/
|
|
mxArrowConnector.prototype.resetStyles = function()
|
|
{
|
|
mxShape.prototype.resetStyles.apply(this, arguments);
|
|
|
|
this.arrowSpacing = mxConstants.ARROW_SPACING;
|
|
};
|
|
|
|
/**
|
|
* Overrides apply to get smooth transition from default start- and endsize.
|
|
*/
|
|
mxArrowConnector.prototype.apply = function(state)
|
|
{
|
|
mxShape.prototype.apply.apply(this, arguments);
|
|
|
|
if (this.style != null)
|
|
{
|
|
this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
|
|
this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: augmentBoundingBox
|
|
*
|
|
* Augments the bounding box with the edge width and markers.
|
|
*/
|
|
mxArrowConnector.prototype.augmentBoundingBox = function(bbox)
|
|
{
|
|
mxShape.prototype.augmentBoundingBox.apply(this, arguments);
|
|
|
|
var w = this.getEdgeWidth();
|
|
|
|
if (this.isMarkerStart())
|
|
{
|
|
w = Math.max(w, this.getStartArrowWidth());
|
|
}
|
|
|
|
if (this.isMarkerEnd())
|
|
{
|
|
w = Math.max(w, this.getEndArrowWidth());
|
|
}
|
|
|
|
bbox.grow((w / 2 + this.strokewidth) * this.scale);
|
|
};
|
|
|
|
/**
|
|
* Function: paintEdgeShape
|
|
*
|
|
* Paints the line shape.
|
|
*/
|
|
mxArrowConnector.prototype.paintEdgeShape = function(c, pts)
|
|
{
|
|
// Geometry of arrow
|
|
var strokeWidth = this.strokewidth;
|
|
|
|
if (this.outline)
|
|
{
|
|
strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
|
|
}
|
|
|
|
var startWidth = this.getStartArrowWidth() + strokeWidth;
|
|
var endWidth = this.getEndArrowWidth() + strokeWidth;
|
|
var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
|
|
var openEnded = this.isOpenEnded();
|
|
var markerStart = this.isMarkerStart();
|
|
var markerEnd = this.isMarkerEnd();
|
|
var spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;
|
|
var startSize = this.startSize + strokeWidth;
|
|
var endSize = this.endSize + strokeWidth;
|
|
var isRounded = this.isArrowRounded();
|
|
|
|
// Base vector (between first points)
|
|
var pe = pts[pts.length - 1];
|
|
|
|
// Finds first non-overlapping point
|
|
var i0 = 1;
|
|
|
|
while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)
|
|
{
|
|
i0++;
|
|
}
|
|
|
|
var dx = pts[i0].x - pts[0].x;
|
|
var dy = pts[i0].y - pts[0].y;
|
|
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (dist == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Computes the norm and the inverse norm
|
|
var nx = dx / dist;
|
|
var nx2, nx1 = nx;
|
|
var ny = dy / dist;
|
|
var ny2, ny1 = ny;
|
|
var orthx = edgeWidth * ny;
|
|
var orthy = -edgeWidth * nx;
|
|
|
|
// Stores the inbound function calls in reverse order in fns
|
|
var fns = [];
|
|
|
|
if (isRounded)
|
|
{
|
|
c.setLineJoin('round');
|
|
}
|
|
else if (pts.length > 2)
|
|
{
|
|
// Only mitre if there are waypoints
|
|
c.setMiterLimit(1.42);
|
|
}
|
|
|
|
c.begin();
|
|
|
|
var startNx = nx;
|
|
var startNy = ny;
|
|
|
|
if (markerStart && !openEnded)
|
|
{
|
|
this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
|
|
}
|
|
else
|
|
{
|
|
var outStartX = pts[0].x + orthx / 2 + spacing * nx;
|
|
var outStartY = pts[0].y + orthy / 2 + spacing * ny;
|
|
var inEndX = pts[0].x - orthx / 2 + spacing * nx;
|
|
var inEndY = pts[0].y - orthy / 2 + spacing * ny;
|
|
|
|
if (openEnded)
|
|
{
|
|
c.moveTo(outStartX, outStartY);
|
|
|
|
fns.push(function()
|
|
{
|
|
c.lineTo(inEndX, inEndY);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
c.moveTo(inEndX, inEndY);
|
|
c.lineTo(outStartX, outStartY);
|
|
}
|
|
}
|
|
|
|
var dx1 = 0;
|
|
var dy1 = 0;
|
|
var dist1 = 0;
|
|
|
|
for (var i = 0; i < pts.length - 2; i++)
|
|
{
|
|
// Work out in which direction the line is bending
|
|
var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);
|
|
|
|
dx1 = pts[i+2].x - pts[i+1].x;
|
|
dy1 = pts[i+2].y - pts[i+1].y;
|
|
|
|
dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
|
|
if (dist1 != 0)
|
|
{
|
|
nx1 = dx1 / dist1;
|
|
ny1 = dy1 / dist1;
|
|
|
|
var tmp1 = nx * nx1 + ny * ny1;
|
|
var tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);
|
|
|
|
// Work out the normal orthogonal to the line through the control point and the edge sides intersection
|
|
nx2 = (nx + nx1);
|
|
ny2 = (ny + ny1);
|
|
|
|
var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);
|
|
|
|
if (dist2 != 0)
|
|
{
|
|
nx2 = nx2 / dist2;
|
|
ny2 = ny2 / dist2;
|
|
|
|
// Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
|
|
var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
|
|
var angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);
|
|
|
|
var outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;
|
|
var outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;
|
|
var inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;
|
|
var inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;
|
|
|
|
if (pos == 0 || !isRounded)
|
|
{
|
|
// If the two segments are aligned, or if we're not drawing curved sections between segments
|
|
// just draw straight to the intersection point
|
|
c.lineTo(outX, outY);
|
|
|
|
(function(x, y)
|
|
{
|
|
fns.push(function()
|
|
{
|
|
c.lineTo(x, y);
|
|
});
|
|
})(inX, inY);
|
|
}
|
|
else if (pos == -1)
|
|
{
|
|
var c1x = inX + ny * edgeWidth;
|
|
var c1y = inY - nx * edgeWidth;
|
|
var c2x = inX + ny1 * edgeWidth;
|
|
var c2y = inY - nx1 * edgeWidth;
|
|
c.lineTo(c1x, c1y);
|
|
c.quadTo(outX, outY, c2x, c2y);
|
|
|
|
(function(x, y)
|
|
{
|
|
fns.push(function()
|
|
{
|
|
c.lineTo(x, y);
|
|
});
|
|
})(inX, inY);
|
|
}
|
|
else
|
|
{
|
|
c.lineTo(outX, outY);
|
|
|
|
(function(x, y)
|
|
{
|
|
var c1x = outX - ny * edgeWidth;
|
|
var c1y = outY + nx * edgeWidth;
|
|
var c2x = outX - ny1 * edgeWidth;
|
|
var c2y = outY + nx1 * edgeWidth;
|
|
|
|
fns.push(function()
|
|
{
|
|
c.quadTo(x, y, c1x, c1y);
|
|
});
|
|
fns.push(function()
|
|
{
|
|
c.lineTo(c2x, c2y);
|
|
});
|
|
})(inX, inY);
|
|
}
|
|
|
|
nx = nx1;
|
|
ny = ny1;
|
|
}
|
|
}
|
|
}
|
|
|
|
orthx = edgeWidth * ny1;
|
|
orthy = - edgeWidth * nx1;
|
|
|
|
if (markerEnd && !openEnded)
|
|
{
|
|
this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
|
|
}
|
|
else
|
|
{
|
|
c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);
|
|
|
|
var inStartX = pe.x - spacing * nx1 - orthx / 2;
|
|
var inStartY = pe.y - spacing * ny1 - orthy / 2;
|
|
|
|
if (!openEnded)
|
|
{
|
|
c.lineTo(inStartX, inStartY);
|
|
}
|
|
else
|
|
{
|
|
c.moveTo(inStartX, inStartY);
|
|
|
|
fns.splice(0, 0, function()
|
|
{
|
|
c.moveTo(inStartX, inStartY);
|
|
});
|
|
}
|
|
}
|
|
|
|
for (var i = fns.length - 1; i >= 0; i--)
|
|
{
|
|
fns[i]();
|
|
}
|
|
|
|
if (openEnded)
|
|
{
|
|
c.end();
|
|
c.stroke();
|
|
}
|
|
else
|
|
{
|
|
c.close();
|
|
c.fillAndStroke();
|
|
}
|
|
|
|
// Workaround for shadow on top of base arrow
|
|
c.setShadow(false);
|
|
|
|
// Need to redraw the markers without the low miter limit
|
|
c.setMiterLimit(4);
|
|
|
|
if (isRounded)
|
|
{
|
|
c.setLineJoin('flat');
|
|
}
|
|
|
|
if (pts.length > 2)
|
|
{
|
|
// Only to repaint markers if no waypoints
|
|
// Need to redraw the markers without the low miter limit
|
|
c.setMiterLimit(4);
|
|
if (markerStart && !openEnded)
|
|
{
|
|
c.begin();
|
|
this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
|
|
c.stroke();
|
|
c.end();
|
|
}
|
|
|
|
if (markerEnd && !openEnded)
|
|
{
|
|
c.begin();
|
|
this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
|
|
c.stroke();
|
|
c.end();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: paintMarker
|
|
*
|
|
* Paints the marker.
|
|
*/
|
|
mxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)
|
|
{
|
|
var widthArrowRatio = edgeWidth / arrowWidth;
|
|
var orthx = edgeWidth * ny / 2;
|
|
var orthy = -edgeWidth * nx / 2;
|
|
|
|
var spaceX = (spacing + size) * nx;
|
|
var spaceY = (spacing + size) * ny;
|
|
|
|
if (initialMove)
|
|
{
|
|
c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
|
|
}
|
|
else
|
|
{
|
|
c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
|
|
}
|
|
|
|
c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
|
|
c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
|
|
c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
|
|
c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
|
|
}
|
|
|
|
/**
|
|
* Function: isArrowRounded
|
|
*
|
|
* Returns wether the arrow is rounded
|
|
*/
|
|
mxArrowConnector.prototype.isArrowRounded = function()
|
|
{
|
|
return this.isRounded;
|
|
};
|
|
|
|
/**
|
|
* Function: getStartArrowWidth
|
|
*
|
|
* Returns the width of the start arrow
|
|
*/
|
|
mxArrowConnector.prototype.getStartArrowWidth = function()
|
|
{
|
|
return mxConstants.ARROW_WIDTH;
|
|
};
|
|
|
|
/**
|
|
* Function: getEndArrowWidth
|
|
*
|
|
* Returns the width of the end arrow
|
|
*/
|
|
mxArrowConnector.prototype.getEndArrowWidth = function()
|
|
{
|
|
return mxConstants.ARROW_WIDTH;
|
|
};
|
|
|
|
/**
|
|
* Function: getEdgeWidth
|
|
*
|
|
* Returns the width of the body of the edge
|
|
*/
|
|
mxArrowConnector.prototype.getEdgeWidth = function()
|
|
{
|
|
return mxConstants.ARROW_WIDTH / 3;
|
|
};
|
|
|
|
/**
|
|
* Function: isOpenEnded
|
|
*
|
|
* Returns whether the ends of the shape are drawn
|
|
*/
|
|
mxArrowConnector.prototype.isOpenEnded = function()
|
|
{
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Function: isMarkerStart
|
|
*
|
|
* Returns whether the start marker is drawn
|
|
*/
|
|
mxArrowConnector.prototype.isMarkerStart = function()
|
|
{
|
|
return (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);
|
|
};
|
|
|
|
/**
|
|
* Function: isMarkerEnd
|
|
*
|
|
* Returns whether the end marker is drawn
|
|
*/
|
|
mxArrowConnector.prototype.isMarkerEnd = function()
|
|
{
|
|
return (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);
|
|
}; |