123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- /**
- * 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);
- };
- __mxOutput.mxArrowConnector = typeof mxArrowConnector !== 'undefined' ? mxArrowConnector : undefined;
|