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