UNPKG

mxgraph

Version:

mxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

922 lines (859 loc) 20.5 kB
/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ var mxPerimeter = { /** * Class: mxPerimeter * * Provides various perimeter functions to be used in a style * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for * rectangle, circle, rhombus and triangle are available. * * Example: * * (code) * <add as="perimeter">mxPerimeter.RectanglePerimeter</add> * (end) * * Or programmatically: * * (code) * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; * (end) * * When adding new perimeter functions, it is recommended to use the * mxPerimeter-namespace as follows: * * (code) * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal) * { * var x = 0; // Calculate x-coordinate * var y = 0; // Calculate y-coordainte * * return new mxPoint(x, y); * } * (end) * * The new perimeter should then be registered in the <mxStyleRegistry> as follows: * (code) * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter); * (end) * * The custom perimeter above can now be used in a specific vertex as follows: * * (code) * model.setStyle(vertex, 'perimeter=customPerimeter'); * (end) * * Note that the key of the <mxStyleRegistry> entry for the function should * be used in string values, unless <mxGraphView.allowEval> is true, in * which case you can also use mxPerimeter.CustomPerimeter for the value in * the cell style above. * * Or it can be used for all vertices in the graph as follows: * * (code) * var style = graph.getStylesheet().getDefaultVertexStyle(); * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter; * (end) * * Note that the object can be used directly when programmatically setting * the value, but the key in the <mxStyleRegistry> should be used when * setting the value via a key, value pair in a cell style. * * The parameters are explained in <RectanglePerimeter>. * * Function: RectanglePerimeter * * Describes a rectangular perimeter for the given bounds. * * Parameters: * * bounds - <mxRectangle> that represents the absolute bounds of the * vertex. * vertex - <mxCellState> that represents the vertex. * next - <mxPoint> that represents the nearest neighbour point on the * given edge. * orthogonal - Boolean that specifies if the orthogonal projection onto * the perimeter should be returned. If this is false then the intersection * of the perimeter and the line between the next and the center point is * returned. */ RectanglePerimeter: function (bounds, vertex, next, orthogonal) { var cx = bounds.getCenterX(); var cy = bounds.getCenterY(); var dx = next.x - cx; var dy = next.y - cy; var alpha = Math.atan2(dy, dx); var p = new mxPoint(0, 0); var pi = Math.PI; var pi2 = Math.PI/2; var beta = pi2 - alpha; var t = Math.atan2(bounds.height, bounds.width); if (alpha < -pi + t || alpha > pi - t) { // Left edge p.x = bounds.x; p.y = cy - bounds.width * Math.tan(alpha) / 2; } else if (alpha < -t) { // Top Edge p.y = bounds.y; p.x = cx - bounds.height * Math.tan(beta) / 2; } else if (alpha < t) { // Right Edge p.x = bounds.x + bounds.width; p.y = cy + bounds.width * Math.tan(alpha) / 2; } else { // Bottom Edge p.y = bounds.y + bounds.height; p.x = cx + bounds.height * Math.tan(beta) / 2; } if (orthogonal) { if (next.x >= bounds.x && next.x <= bounds.x + bounds.width) { p.x = next.x; } else if (next.y >= bounds.y && next.y <= bounds.y + bounds.height) { p.y = next.y; } if (next.x < bounds.x) { p.x = bounds.x; } else if (next.x > bounds.x + bounds.width) { p.x = bounds.x + bounds.width; } if (next.y < bounds.y) { p.y = bounds.y; } else if (next.y > bounds.y + bounds.height) { p.y = bounds.y + bounds.height; } } return p; }, /** * Function: EllipsePerimeter * * Describes an elliptic perimeter. See <RectanglePerimeter> * for a description of the parameters. */ EllipsePerimeter: function (bounds, vertex, next, orthogonal) { var x = bounds.x; var y = bounds.y; var a = bounds.width / 2; var b = bounds.height / 2; var cx = x + a; var cy = y + b; var px = next.x; var py = next.y; // Calculates straight line equation through // point and ellipse center y = d * x + h var dx = parseInt(px - cx); var dy = parseInt(py - cy); if (dx == 0 && dy != 0) { return new mxPoint(cx, cy + b * dy / Math.abs(dy)); } else if (dx == 0 && dy == 0) { return new mxPoint(px, py); } if (orthogonal) { if (py >= y && py <= y + bounds.height) { var ty = py - cy; var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0; if (px <= x) { tx = -tx; } return new mxPoint(cx+tx, py); } if (px >= x && px <= x + bounds.width) { var tx = px - cx; var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0; if (py <= y) { ty = -ty; } return new mxPoint(px, cy+ty); } } // Calculates intersection var d = dy / dx; var h = cy - d * cx; var e = a * a * d * d + b * b; var f = -2 * cx * e; var g = a * a * d * d * cx * cx + b * b * cx * cx - a * a * b * b; var det = Math.sqrt(f * f - 4 * e * g); // Two solutions (perimeter points) var xout1 = (-f + det) / (2 * e); var xout2 = (-f - det) / (2 * e); var yout1 = d * xout1 + h; var yout2 = d * xout2 + h; var dist1 = Math.sqrt(Math.pow((xout1 - px), 2) + Math.pow((yout1 - py), 2)); var dist2 = Math.sqrt(Math.pow((xout2 - px), 2) + Math.pow((yout2 - py), 2)); // Correct solution var xout = 0; var yout = 0; if (dist1 < dist2) { xout = xout1; yout = yout1; } else { xout = xout2; yout = yout2; } return new mxPoint(xout, yout); }, /** * Function: RhombusPerimeter * * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter> * for a description of the parameters. */ RhombusPerimeter: function (bounds, vertex, next, orthogonal) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var cx = x + w / 2; var cy = y + h / 2; var px = next.x; var py = next.y; // Special case for intersecting the diamond's corners if (cx == px) { if (cy > py) { return new mxPoint(cx, y); // top } else { return new mxPoint(cx, y + h); // bottom } } else if (cy == py) { if (cx > px) { return new mxPoint(x, cy); // left } else { return new mxPoint(x + w, cy); // right } } var tx = cx; var ty = cy; if (orthogonal) { if (px >= x && px <= x + w) { tx = px; } else if (py >= y && py <= y + h) { ty = py; } } // In which quadrant will the intersection be? // set the slope and offset of the border line accordingly if (px < cx) { if (py < cy) { return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy); } else { return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy); } } else if (py < cy) { return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy); } else { return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy); } }, /** * Function: TrianglePerimeter * * Describes a triangle perimeter. See <RectanglePerimeter> * for a description of the parameters. */ TrianglePerimeter: function (bounds, vertex, next, orthogonal) { var direction = (vertex != null) ? vertex.style[mxConstants.STYLE_DIRECTION] : null; var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH; var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var cx = x + w / 2; var cy = y + h / 2; var start = new mxPoint(x, y); var corner = new mxPoint(x + w, cy); var end = new mxPoint(x, y + h); if (direction == mxConstants.DIRECTION_NORTH) { start = end; corner = new mxPoint(cx, y); end = new mxPoint(x + w, y + h); } else if (direction == mxConstants.DIRECTION_SOUTH) { corner = new mxPoint(cx, y + h); end = new mxPoint(x + w, y); } else if (direction == mxConstants.DIRECTION_WEST) { start = new mxPoint(x + w, y); corner = new mxPoint(x, cy); end = new mxPoint(x + w, y + h); } var dx = next.x - cx; var dy = next.y - cy; var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx); var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w); var base = false; if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_WEST) { base = alpha > -t && alpha < t; } else { base = alpha < -Math.PI + t || alpha > Math.PI - t; } var result = null; if (base) { if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) || (!vertical && next.y >= start.y && next.y <= end.y))) { if (vertical) { result = new mxPoint(next.x, start.y); } else { result = new mxPoint(start.x, next.y); } } else { if (direction == mxConstants.DIRECTION_NORTH) { result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2, y + h); } else if (direction == mxConstants.DIRECTION_SOUTH) { result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2, y); } else if (direction == mxConstants.DIRECTION_WEST) { result = new mxPoint(x + w, y + h / 2 + w * Math.tan(alpha) / 2); } else { result = new mxPoint(x, y + h / 2 - w * Math.tan(alpha) / 2); } } } else { if (orthogonal) { var pt = new mxPoint(cx, cy); if (next.y >= y && next.y <= y + h) { pt.x = (vertical) ? cx : ( (direction == mxConstants.DIRECTION_WEST) ? x + w : x); pt.y = next.y; } else if (next.x >= x && next.x <= x + w) { pt.x = next.x; pt.y = (!vertical) ? cy : ( (direction == mxConstants.DIRECTION_NORTH) ? y + h : y); } // Compute angle dx = next.x - pt.x; dy = next.y - pt.y; cx = pt.x; cy = pt.y; } if ((vertical && next.x <= x + w / 2) || (!vertical && next.y <= y + h / 2)) { result = mxUtils.intersection(next.x, next.y, cx, cy, start.x, start.y, corner.x, corner.y); } else { result = mxUtils.intersection(next.x, next.y, cx, cy, corner.x, corner.y, end.x, end.y); } } if (result == null) { result = new mxPoint(cx, cy); } return result; }, /** * Function: HexagonPerimeter * * Describes a hexagon perimeter. See <RectanglePerimeter> * for a description of the parameters. */ HexagonPerimeter: function (bounds, vertex, next, orthogonal) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var cx = bounds.getCenterX(); var cy = bounds.getCenterY(); var px = next.x; var py = next.y; var dx = px - cx; var dy = py - cy; var alpha = -Math.atan2(dy, dx); var pi = Math.PI; var pi2 = Math.PI / 2; var result = new mxPoint(cx, cy); var direction = (vertex != null) ? mxUtils.getValue( vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST; var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH; var a = new mxPoint(); var b = new mxPoint(); //Only consider corrects quadrants for the orthogonal case. if ((px < x) && (py < y) || (px < x) && (py > y + h) || (px > x + w) && (py < y) || (px > x + w) && (py > y + h)) { orthogonal = false; } if (orthogonal) { if (vertical) { //Special cases where intersects with hexagon corners if (px == cx) { if (py <= y) { return new mxPoint(cx, y); } else if (py >= y + h) { return new mxPoint(cx, y + h); } } else if (px < x) { if (py == y + h / 4) { return new mxPoint(x, y + h / 4); } else if (py == y + 3 * h / 4) { return new mxPoint(x, y + 3 * h / 4); } } else if (px > x + w) { if (py == y + h / 4) { return new mxPoint(x + w, y + h / 4); } else if (py == y + 3 * h / 4) { return new mxPoint(x + w, y + 3 * h / 4); } } else if (px == x) { if (py < cy) { return new mxPoint(x, y + h / 4); } else if (py > cy) { return new mxPoint(x, y + 3 * h / 4); } } else if (px == x + w) { if (py < cy) { return new mxPoint(x + w, y + h / 4); } else if (py > cy) { return new mxPoint(x + w, y + 3 * h / 4); } } if (py == y) { return new mxPoint(cx, y); } else if (py == y + h) { return new mxPoint(cx, y + h); } if (px < cx) { if ((py > y + h / 4) && (py < y + 3 * h / 4)) { a = new mxPoint(x, y); b = new mxPoint(x, y + h); } else if (py < y + h / 4) { a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x + w, y - Math.floor(0.25 * h)); } else if (py > y + 3 * h / 4) { a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x + w, y + Math.floor(1.25 * h)); } } else if (px > cx) { if ((py > y + h / 4) && (py < y + 3 * h / 4)) { a = new mxPoint(x + w, y); b = new mxPoint(x + w, y + h); } else if (py < y + h / 4) { a = new mxPoint(x, y - Math.floor(0.25 * h)); b = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h)); } else if (py > y + 3 * h / 4) { a = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x, y + Math.floor(1.25 * h)); } } } else { //Special cases where intersects with hexagon corners if (py == cy) { if (px <= x) { return new mxPoint(x, y + h / 2); } else if (px >= x + w) { return new mxPoint(x + w, y + h / 2); } } else if (py < y) { if (px == x + w / 4) { return new mxPoint(x + w / 4, y); } else if (px == x + 3 * w / 4) { return new mxPoint(x + 3 * w / 4, y); } } else if (py > y + h) { if (px == x + w / 4) { return new mxPoint(x + w / 4, y + h); } else if (px == x + 3 * w / 4) { return new mxPoint(x + 3 * w / 4, y + h); } } else if (py == y) { if (px < cx) { return new mxPoint(x + w / 4, y); } else if (px > cx) { return new mxPoint(x + 3 * w / 4, y); } } else if (py == y + h) { if (px < cx) { return new mxPoint(x + w / 4, y + h); } else if (py > cy) { return new mxPoint(x + 3 * w / 4, y + h); } } if (px == x) { return new mxPoint(x, cy); } else if (px == x + w) { return new mxPoint(x + w, cy); } if (py < cy) { if ((px > x + w / 4) && (px < x + 3 * w / 4)) { a = new mxPoint(x, y); b = new mxPoint(x + w, y); } else if (px < x + w / 4) { a = new mxPoint(x - Math.floor(0.25 * w), y + h); b = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h)); } else if (px > x + 3 * w / 4) { a = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h)); b = new mxPoint(x + Math.floor(1.25 * w), y + h); } } else if (py > cy) { if ((px > x + w / 4) && (px < x + 3 * w / 4)) { a = new mxPoint(x, y + h); b = new mxPoint(x + w, y + h); } else if (px < x + w / 4) { a = new mxPoint(x - Math.floor(0.25 * w), y); b = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h)); } else if (px > x + 3 * w / 4) { a = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h)); b = new mxPoint(x + Math.floor(1.25 * w), y); } } } var tx = cx; var ty = cy; if (px >= x && px <= x + w) { tx = px; if (py < cy) { ty = y + h; } else { ty = y; } } else if (py >= y && py <= y + h) { ty = py; if (px < cx) { tx = x + w; } else { tx = x; } } result = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y); } else { if (vertical) { var beta = Math.atan2(h / 4, w / 2); //Special cases where intersects with hexagon corners if (alpha == beta) { return new mxPoint(x + w, y + Math.floor(0.25 * h)); } else if (alpha == pi2) { return new mxPoint(x + Math.floor(0.5 * w), y); } else if (alpha == (pi - beta)) { return new mxPoint(x, y + Math.floor(0.25 * h)); } else if (alpha == -beta) { return new mxPoint(x + w, y + Math.floor(0.75 * h)); } else if (alpha == (-pi2)) { return new mxPoint(x + Math.floor(0.5 * w), y + h); } else if (alpha == (-pi + beta)) { return new mxPoint(x, y + Math.floor(0.75 * h)); } if ((alpha < beta) && (alpha > -beta)) { a = new mxPoint(x + w, y); b = new mxPoint(x + w, y + h); } else if ((alpha > beta) && (alpha < pi2)) { a = new mxPoint(x, y - Math.floor(0.25 * h)); b = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h)); } else if ((alpha > pi2) && (alpha < (pi - beta))) { a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x + w, y - Math.floor(0.25 * h)); } else if (((alpha > (pi - beta)) && (alpha <= pi)) || ((alpha < (-pi + beta)) && (alpha >= -pi))) { a = new mxPoint(x, y); b = new mxPoint(x, y + h); } else if ((alpha < -beta) && (alpha > -pi2)) { a = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x, y + Math.floor(1.25 * h)); } else if ((alpha < -pi2) && (alpha > (-pi + beta))) { a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h)); b = new mxPoint(x + w, y + Math.floor(1.25 * h)); } } else { var beta = Math.atan2(h / 2, w / 4); //Special cases where intersects with hexagon corners if (alpha == beta) { return new mxPoint(x + Math.floor(0.75 * w), y); } else if (alpha == (pi - beta)) { return new mxPoint(x + Math.floor(0.25 * w), y); } else if ((alpha == pi) || (alpha == -pi)) { return new mxPoint(x, y + Math.floor(0.5 * h)); } else if (alpha == 0) { return new mxPoint(x + w, y + Math.floor(0.5 * h)); } else if (alpha == -beta) { return new mxPoint(x + Math.floor(0.75 * w), y + h); } else if (alpha == (-pi + beta)) { return new mxPoint(x + Math.floor(0.25 * w), y + h); } if ((alpha > 0) && (alpha < beta)) { a = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h)); b = new mxPoint(x + Math.floor(1.25 * w), y + h); } else if ((alpha > beta) && (alpha < (pi - beta))) { a = new mxPoint(x, y); b = new mxPoint(x + w, y); } else if ((alpha > (pi - beta)) && (alpha < pi)) { a = new mxPoint(x - Math.floor(0.25 * w), y + h); b = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h)); } else if ((alpha < 0) && (alpha > -beta)) { a = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h)); b = new mxPoint(x + Math.floor(1.25 * w), y); } else if ((alpha < -beta) && (alpha > (-pi + beta))) { a = new mxPoint(x, y + h); b = new mxPoint(x + w, y + h); } else if ((alpha < (-pi + beta)) && (alpha > -pi)) { a = new mxPoint(x - Math.floor(0.25 * w), y); b = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h)); } } result = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y); } if (result == null) { return new mxPoint(cx, cy); } return result; } };