/**
* $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;
}
};
}