/** * $Id: mxPerimeter.java,v 1.1 2012/11/15 13:26:46 gaudenz Exp $ * Copyright (c) 2007-2010, Gaudenz Alder, David Benson */ package com.mxgraph.view; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUtils; /** * Provides various perimeter functions to be used in a style * as the value of mxConstants.STYLE_PERIMETER. Alternately, the mxConstants. * PERIMETER_* constants can be used to reference a perimeter via the * mxStyleRegistry. */ public class mxPerimeter { /** * Defines the requirements for a perimeter function. */ public interface mxPerimeterFunction { /** * Implements a perimeter function. * * @param bounds Rectangle that represents the absolute bounds of the * vertex. * @param vertex Cell state that represents the vertex. * @param next Point that represents the nearest neighbour point on the * given edge. * @param 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. * @return Returns the perimeter point. */ mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal); } /** * Describes a rectangular perimeter for the given bounds. */ public static mxPerimeterFunction RectanglePerimeter = new mxPerimeterFunction() { /* (non-Javadoc) * @see com.mxgraph.view.mxPerimeter.mxPerimeterFunction#apply */ public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal) { double cx = bounds.getCenterX(); double cy = bounds.getCenterY(); double dx = next.getX() - cx; double dy = next.getY() - cy; double alpha = Math.atan2(dy, dx); mxPoint p = new mxPoint(); double pi = Math.PI; double pi2 = Math.PI / 2; double beta = pi2 - alpha; double t = Math.atan2(bounds.getHeight(), bounds.getWidth()); if (alpha < -pi + t || alpha > pi - t) { // Left edge p.setX(bounds.getX()); p.setY(cy - bounds.getWidth() * Math.tan(alpha) / 2); } else if (alpha < -t) { // Top Edge p.setY(bounds.getY()); p.setX(cx - bounds.getHeight() * Math.tan(beta) / 2); } else if (alpha < t) { // Right Edge p.setX(bounds.getX() + bounds.getWidth()); p.setY(cy + bounds.getWidth() * Math.tan(alpha) / 2); } else { // Bottom Edge p.setY(bounds.getY() + bounds.getHeight()); p.setX(cx + bounds.getHeight() * Math.tan(beta) / 2); } if (orthogonal) { if (next.getX() >= bounds.getX() && next.getX() <= bounds.getX() + bounds.getWidth()) { p.setX(next.getX()); } else if (next.getY() >= bounds.getY() && next.getY() <= bounds.getY() + bounds.getHeight()) { p.setY(next.getY()); } if (next.getX() < bounds.getX()) { p.setX(bounds.getX()); } else if (next.getX() > bounds.getX() + bounds.getWidth()) { p.setX(bounds.getX() + bounds.getWidth()); } if (next.getY() < bounds.getY()) { p.setY(bounds.getY()); } else if (next.getY() > bounds.getY() + bounds.getHeight()) { p.setY(bounds.getY() + bounds.getHeight()); } } return p; } }; /** * Describes an elliptic perimeter. */ public static mxPerimeterFunction EllipsePerimeter = new mxPerimeterFunction() { /* (non-Javadoc) * @see com.mxgraph.view.mxPerimeter.mxPerimeterFunction#apply */ public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal) { double x = bounds.getX(); double y = bounds.getY(); double a = bounds.getWidth() / 2; double b = bounds.getHeight() / 2; double cx = x + a; double cy = y + b; double px = next.getX(); double py = next.getY(); // Calculates straight line equation through // point and ellipse center y = d * x + h double dx = px - cx; double dy = 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.getHeight()) { double ty = py - cy; double tx = Math.sqrt(a * a * (1 - (ty * ty) / (b * b))); if (Double.isNaN(tx)) { tx = 0; } if (px <= x) { tx = -tx; } return new mxPoint(cx + tx, py); } if (px >= x && px <= x + bounds.getWidth()) { double tx = px - cx; double ty = Math.sqrt(b * b * (1 - (tx * tx) / (a * a))); if (Double.isNaN(ty)) { ty = 0; } if (py <= y) { ty = -ty; } return new mxPoint(px, cy + ty); } } // Calculates intersection double d = dy / dx; double h = cy - d * cx; double e = a * a * d * d + b * b; double f = -2 * cx * e; double g = a * a * d * d * cx * cx + b * b * cx * cx - a * a * b * b; double det = Math.sqrt(f * f - 4 * e * g); // Two solutions (perimeter points) double xout1 = (-f + det) / (2 * e); double xout2 = (-f - det) / (2 * e); double yout1 = d * xout1 + h; double yout2 = d * xout2 + h; double dist1 = Math.sqrt(Math.pow((xout1 - px), 2) + Math.pow((yout1 - py), 2)); double dist2 = Math.sqrt(Math.pow((xout2 - px), 2) + Math.pow((yout2 - py), 2)); // Correct solution double xout = 0; double yout = 0; if (dist1 < dist2) { xout = xout1; yout = yout1; } else { xout = xout2; yout = yout2; } return new mxPoint(xout, yout); } }; /** * Describes a rhombus (aka diamond) perimeter. */ public static mxPerimeterFunction RhombusPerimeter = new mxPerimeterFunction() { /* (non-Javadoc) * @see com.mxgraph.view.mxPerimeter.mxPerimeterFunction#apply */ public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal) { double x = bounds.getX(); double y = bounds.getY(); double w = bounds.getWidth(); double h = bounds.getHeight(); double cx = x + w / 2; double cy = y + h / 2; double px = next.getX(); double py = next.getY(); // 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 } } double tx = cx; double 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); } } }; /** * Describes a triangle perimeter. See RectanglePerimeter * for a description of the parameters. */ public static mxPerimeterFunction TrianglePerimeter = new mxPerimeterFunction() { /* (non-Javadoc) * @see com.mxgraph.view.mxPerimeter.mxPerimeterFunction#apply(com.mxgraph.utils.mxRectangle, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, boolean, com.mxgraph.utils.mxPoint) */ public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal) { Object direction = (vertex != null) ? mxUtils.getString( vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST; boolean vertical = direction.equals(mxConstants.DIRECTION_NORTH) || direction.equals(mxConstants.DIRECTION_SOUTH); double x = bounds.getX(); double y = bounds.getY(); double w = bounds.getWidth(); double h = bounds.getHeight(); double cx = x + w / 2; double cy = y + h / 2; mxPoint start = new mxPoint(x, y); mxPoint corner = new mxPoint(x + w, cy); mxPoint end = new mxPoint(x, y + h); if (direction.equals(mxConstants.DIRECTION_NORTH)) { start = end; corner = new mxPoint(cx, y); end = new mxPoint(x + w, y + h); } else if (direction.equals(mxConstants.DIRECTION_SOUTH)) { corner = new mxPoint(cx, y + h); end = new mxPoint(x + w, y); } else if (direction.equals(mxConstants.DIRECTION_WEST)) { start = new mxPoint(x + w, y); corner = new mxPoint(x, cy); end = new mxPoint(x + w, y + h); } // Compute angle double dx = next.getX() - cx; double dy = next.getY() - cy; double alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx); double t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w); boolean base = false; if (direction.equals(mxConstants.DIRECTION_NORTH) || direction.equals(mxConstants.DIRECTION_WEST)) { base = alpha > -t && alpha < t; } else { base = alpha < -Math.PI + t || alpha > Math.PI - t; } mxPoint result = null; if (base) { if (orthogonal && ((vertical && next.getX() >= start.getX() && next .getX() <= end.getX()) || (!vertical && next.getY() >= start.getY() && next.getY() <= end .getY()))) { if (vertical) { result = new mxPoint(next.getX(), start.getY()); } else { result = new mxPoint(start.getX(), next.getY()); } } else { if (direction.equals(mxConstants.DIRECTION_EAST)) { result = new mxPoint(x, y + h / 2 - w * Math.tan(alpha) / 2); } else if (direction.equals(mxConstants.DIRECTION_NORTH)) { result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2, y + h); } else if (direction.equals(mxConstants.DIRECTION_SOUTH)) { result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2, y); } else if (direction.equals(mxConstants.DIRECTION_WEST)) { result = new mxPoint(x + w, y + h / 2 + w * Math.tan(alpha) / 2); } } } else { if (orthogonal) { mxPoint pt = new mxPoint(cx, cy); if (next.getY() >= y && next.getY() <= y + h) { pt.setX((vertical) ? cx : ((direction .equals(mxConstants.DIRECTION_WEST)) ? x + w : x)); pt.setY(next.getY()); } else if (next.getX() >= x && next.getX() <= x + w) { pt.setX(next.getX()); pt.setY((!vertical) ? cy : ((direction .equals(mxConstants.DIRECTION_NORTH)) ? y + h : y)); } // Compute angle dx = next.getX() - pt.getX(); dy = next.getY() - pt.getY(); cx = pt.getX(); cy = pt.getY(); } if ((vertical && next.getX() <= x + w / 2) || (!vertical && next.getY() <= y + h / 2)) { result = mxUtils.intersection(next.getX(), next.getY(), cx, cy, start.getX(), start.getY(), corner.getX(), corner.getY()); } else { result = mxUtils.intersection(next.getX(), next.getY(), cx, cy, corner.getX(), corner.getY(), end.getX(), end.getY()); } } if (result == null) { result = new mxPoint(cx, cy); } return result; } }; /** * Describes a hexagon perimeter. See RectanglePerimeter * for a description of the parameters. */ public static mxPerimeterFunction HexagonPerimeter = new mxPerimeterFunction() { public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next, boolean orthogonal) { double x = bounds.getX(); double y = bounds.getY(); double w = bounds.getWidth(); double h = bounds.getHeight(); double cx = bounds.getCenterX(); double cy = bounds.getCenterY(); double px = next.getX(); double py = next.getY(); double dx = px - cx; double dy = py - cy; double alpha = -Math.atan2(dy, dx); double pi = Math.PI; double pi2 = Math.PI / 2; mxPoint result = new mxPoint(cx, cy); Object direction = (vertex != null) ? mxUtils.getString( vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST; boolean vertical = direction.equals(mxConstants.DIRECTION_NORTH) || direction.equals(mxConstants.DIRECTION_SOUTH); mxPoint a = new mxPoint(); mxPoint 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 - (int) (0.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x + w, y - (int) (0.25 * h)); } else if (py > y + 3 * h / 4) { a = new mxPoint(x - (int) (0.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x + w, y + (int) (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 - (int) (0.25 * h)); b = new mxPoint(x + (int) (1.5 * w), y + (int) (0.5 * h)); } else if (py > y + 3 * h / 4) { a = new mxPoint(x + (int) (1.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x, y + (int) (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 - (int) (0.25 * w), y + h); b = new mxPoint(x + (int) (0.5 * w), y - (int) (0.5 * h)); } else if (px > x + 3 * w / 4) { a = new mxPoint(x + (int) (0.5 * w), y - (int) (0.5 * h)); b = new mxPoint(x + (int) (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 - (int) (0.25 * w), y); b = new mxPoint(x + (int) (0.5 * w), y + (int) (1.5 * h)); } else if (px > x + 3 * w / 4) { a = new mxPoint(x + (int) (0.5 * w), y + (int) (1.5 * h)); b = new mxPoint(x + (int) (1.25 * w), y); } } } double tx = cx; double 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.getX(), next.getY(), a.getX(), a.getY(), b.getX(), b.getY()); } else { if (vertical) { double beta = Math.atan2(h / 4, w / 2); //Special cases where intersects with hexagon corners if (alpha == beta) { return new mxPoint(x + w, y + (int) (0.25 * h)); } else if (alpha == pi2) { return new mxPoint(x + (int) (0.5 * w), y); } else if (alpha == (pi - beta)) { return new mxPoint(x, y + (int) (0.25 * h)); } else if (alpha == -beta) { return new mxPoint(x + w, y + (int) (0.75 * h)); } else if (alpha == (-pi2)) { return new mxPoint(x + (int) (0.5 * w), y + h); } else if (alpha == (-pi + beta)) { return new mxPoint(x, y + (int) (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 - (int) (0.25 * h)); b = new mxPoint(x + (int) (1.5 * w), y + (int) (0.5 * h)); } else if ((alpha > pi2) && (alpha < (pi - beta))) { a = new mxPoint(x - (int) (0.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x + w, y - (int) (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 + (int) (1.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x, y + (int) (1.25 * h)); } else if ((alpha < -pi2) && (alpha > (-pi + beta))) { a = new mxPoint(x - (int) (0.5 * w), y + (int) (0.5 * h)); b = new mxPoint(x + w, y + (int) (1.25 * h)); } } else { double beta = Math.atan2(h / 2, w / 4); //Special cases where intersects with hexagon corners if (alpha == beta) { return new mxPoint(x + (int) (0.75 * w), y); } else if (alpha == (pi - beta)) { return new mxPoint(x + (int) (0.25 * w), y); } else if ((alpha == pi) || (alpha == -pi)) { return new mxPoint(x, y + (int) (0.5 * h)); } else if (alpha == 0) { return new mxPoint(x + w, y + (int) (0.5 * h)); } else if (alpha == -beta) { return new mxPoint(x + (int) (0.75 * w), y + h); } else if (alpha == (-pi + beta)) { return new mxPoint(x + (int) (0.25 * w), y + h); } if ((alpha > 0) && (alpha < beta)) { a = new mxPoint(x + (int) (0.5 * w), y - (int) (0.5 * h)); b = new mxPoint(x + (int) (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 - (int) (0.25 * w), y + h); b = new mxPoint(x + (int) (0.5 * w), y - (int) (0.5 * h)); } else if ((alpha < 0) && (alpha > -beta)) { a = new mxPoint(x + (int) (0.5 * w), y + (int) (1.5 * h)); b = new mxPoint(x + (int) (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 - (int) (0.25 * w), y); b = new mxPoint(x + (int) (0.5 * w), y + (int) (1.5 * h)); } } result = mxUtils.intersection(cx, cy, next.getX(), next.getY(), a.getX(), a.getY(), b.getX(), b.getY()); } if (result == null) { return new mxPoint(cx, cy); } return result; } }; }