com.mxgraph.util.mxCurve Maven / Gradle / Ivy
/**
* $Id: mxCurve.java,v 1.53 2012-01-13 11:17:38 david Exp $
* Copyright (c) 2009-2012, JGraph Ltd
*/
package com.mxgraph.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
public class mxCurve
{
/**
* A collection of arrays of curve points
*/
protected Map points;
// Rectangle just completely enclosing branch and label/
protected double minXBounds = 10000000;
protected double maxXBounds = 0;
protected double minYBounds = 10000000;
protected double maxYBounds = 0;
/**
* An array of arrays of intervals. These intervals define the distance
* along the edge (0 to 1) that each point lies
*/
protected Map intervals;
/**
* The curve lengths of the curves
*/
protected Map curveLengths;
/**
* Defines the key for the central curve index
*/
public static String CORE_CURVE = "Center_curve";
/**
* Defines the key for the label curve index
*/
public static String LABEL_CURVE = "Label_curve";;
/**
* Indicates that an invalid position on a curve was requested
*/
public static mxLine INVALID_POSITION = new mxLine(new mxPoint(0, 0),
new mxPoint(1, 0));
/**
* Offset of the label curve from the curve the label curve is based on.
* If you wish to set this value, do so directly after creation of the curve.
* The first time the curve is used the label curve will be created with
* whatever value is contained in this variable. Changes to it after that point
* will have no effect.
*/
protected double labelBuffer = mxConstants.DEFAULT_LABEL_BUFFER;
/**
* The points this curve is drawn through. These are typically control
* points and are at distances from each other that straight lines
* between them do not describe a smooth curve. This class takes
* these guiding points and creates a finer set of internal points
* that visually appears to be a curve when linked by straight lines
*/
public List guidePoints = new ArrayList();
/**
* Whether or not the curve currently holds valid values
*/
protected boolean valid = false;
/**
*
*/
public void setLabelBuffer(double buffer)
{
labelBuffer = buffer;
}
/**
*
*/
public mxRectangle getBounds()
{
if (!valid)
{
createCoreCurve();
}
return new mxRectangle(minXBounds, minYBounds, maxXBounds - minXBounds,
maxYBounds - minYBounds);
}
/**
*
*/
public mxCurve()
{
}
/**
*
*/
public mxCurve(List points)
{
boolean nullPoints = false;
for (mxPoint point : points)
{
if (point == null)
{
nullPoints = true;
break;
}
}
if (!nullPoints)
{
guidePoints = new ArrayList(points);
}
}
/**
* Calculates the index of the lower point on the segment
* that contains the point distance along the
*/
protected int getLowerIndexOfSegment(String index, double distance)
{
double[] curveIntervals = getIntervals(index);
if (curveIntervals == null)
{
return 0;
}
int numIntervals = curveIntervals.length;
if (distance <= 0.0 || numIntervals < 3)
{
return 0;
}
if (distance >= 1.0)
{
return numIntervals - 2;
}
// Pick a starting index roughly where you expect the point
// to be
int testIndex = (int) (numIntervals * distance);
if (testIndex >= numIntervals)
{
testIndex = numIntervals - 1;
}
// The max and min indices tested so far
int lowerLimit = -1;
int upperLimit = numIntervals;
// It cannot take more than the number of intervals to find
// the correct segment
for (int i = 0; i < numIntervals; i++)
{
double segmentDistance = curveIntervals[testIndex];
double multiplier = 0.5;
if (distance < segmentDistance)
{
upperLimit = Math.min(upperLimit, testIndex);
multiplier = -0.5;
}
else if (distance > segmentDistance)
{
lowerLimit = Math.max(lowerLimit, testIndex);
}
else
{
// Values equal
if (testIndex == 0)
{
lowerLimit = 0;
upperLimit = 1;
}
else
{
lowerLimit = testIndex - 1;
upperLimit = testIndex;
}
}
int indexDifference = upperLimit - lowerLimit;
if (indexDifference == 1)
{
break;
}
testIndex = (int) (testIndex + indexDifference * multiplier);
if (testIndex == lowerLimit)
{
testIndex = lowerLimit + 1;
}
if (testIndex == upperLimit)
{
testIndex = upperLimit - 1;
}
}
if (lowerLimit != upperLimit - 1)
{
return -1;
}
return lowerLimit;
}
/**
* Returns a unit vector parallel to the curve at the specified
* distance along the curve. To obtain the angle the vector makes
* with (1,0) perform Math.atan(segVectorY/segVectorX).
* @param index the curve index specifying the curve to analyse
* @param distance the distance from start to end of curve (0.0...1.0)
* @return a unit vector at the specified point on the curve represented
* as a line, parallel with the curve. If the distance or curve is
* invalid, mxCurve.INVALID_POSITION
is returned
*/
public mxLine getCurveParallel(String index, double distance)
{
mxPoint[] pointsCurve = getCurvePoints(index);
double[] curveIntervals = getIntervals(index);
if (pointsCurve != null && pointsCurve.length > 0
&& curveIntervals != null && distance >= 0.0 && distance <= 1.0)
{
// If the curve is zero length, it will only have one point
// We can't calculate in this case
if (pointsCurve.length == 1)
{
mxPoint point = pointsCurve[0];
return new mxLine(point.getX(), point.getY(), new mxPoint(1, 0));
}
int lowerLimit = getLowerIndexOfSegment(index, distance);
mxPoint firstPointOfSeg = pointsCurve[lowerLimit];
double segVectorX = pointsCurve[lowerLimit + 1].getX()
- firstPointOfSeg.getX();
double segVectorY = pointsCurve[lowerLimit + 1].getY()
- firstPointOfSeg.getY();
double distanceAlongSeg = (distance - curveIntervals[lowerLimit])
/ (curveIntervals[lowerLimit + 1] - curveIntervals[lowerLimit]);
double segLength = Math.sqrt(segVectorX * segVectorX + segVectorY
* segVectorY);
double startPointX = firstPointOfSeg.getX() + segVectorX
* distanceAlongSeg;
double startPointY = firstPointOfSeg.getY() + segVectorY
* distanceAlongSeg;
mxPoint endPoint = new mxPoint(segVectorX / segLength, segVectorY
/ segLength);
return new mxLine(startPointX, startPointY, endPoint);
}
else
{
return INVALID_POSITION;
}
}
/**
* Returns a section of the curve as an array of points
* @param index the curve index specifying the curve to analyse
* @param start the start position of the curve segment (0.0...1.0)
* @param end the end position of the curve segment (0.0...1.0)
* @return a sequence of point representing the curve section or null
* if it cannot be calculated
*/
public mxPoint[] getCurveSection(String index, double start, double end)
{
mxPoint[] pointsCurve = getCurvePoints(index);
double[] curveIntervals = getIntervals(index);
if (pointsCurve != null && pointsCurve.length > 0
&& curveIntervals != null && start >= 0.0 && start <= 1.0
&& end >= 0.0 && end <= 1.0)
{
// If the curve is zero length, it will only have one point
// We can't calculate in this case
if (pointsCurve.length == 1)
{
mxPoint point = pointsCurve[0];
return new mxPoint[] { new mxPoint(point.getX(), point.getY()) };
}
int lowerLimit = getLowerIndexOfSegment(index, start);
mxPoint firstPointOfSeg = pointsCurve[lowerLimit];
double segVectorX = pointsCurve[lowerLimit + 1].getX()
- firstPointOfSeg.getX();
double segVectorY = pointsCurve[lowerLimit + 1].getY()
- firstPointOfSeg.getY();
double distanceAlongSeg = (start - curveIntervals[lowerLimit])
/ (curveIntervals[lowerLimit + 1] - curveIntervals[lowerLimit]);
mxPoint startPoint = new mxPoint(firstPointOfSeg.getX()
+ segVectorX * distanceAlongSeg, firstPointOfSeg.getY()
+ segVectorY * distanceAlongSeg);
List result = new ArrayList();
result.add(startPoint);
double current = start;
current = curveIntervals[++lowerLimit];
while (current <= end)
{
mxPoint nextPointOfSeg = pointsCurve[lowerLimit];
result.add(nextPointOfSeg);
current = curveIntervals[++lowerLimit];
}
// Add whatever proportion of the last segment has to
// be added to make the exactly end distance
if (lowerLimit > 0 && lowerLimit < pointsCurve.length
&& end > curveIntervals[lowerLimit - 1])
{
firstPointOfSeg = pointsCurve[lowerLimit - 1];
segVectorX = pointsCurve[lowerLimit].getX()
- firstPointOfSeg.getX();
segVectorY = pointsCurve[lowerLimit].getY()
- firstPointOfSeg.getY();
distanceAlongSeg = (end - curveIntervals[lowerLimit - 1])
/ (curveIntervals[lowerLimit] - curveIntervals[lowerLimit - 1]);
mxPoint endPoint = new mxPoint(firstPointOfSeg.getX()
+ segVectorX * distanceAlongSeg, firstPointOfSeg.getY()
+ segVectorY * distanceAlongSeg);
result.add(endPoint);
}
mxPoint[] resultArray = new mxPoint[result.size()];
return result.toArray(resultArray);
}
else
{
return null;
}
}
/**
* Returns whether or not the rectangle passed in hits any part of this
* curve.
* @param rect the rectangle to detect for a hit
* @return whether or not the rectangle hits this curve
*/
public boolean intersectsRect(Rectangle rect)
{
// To save CPU, we can test if the rectangle intersects the entire
// bounds of this curve
if (!getBounds().getRectangle().intersects(rect))
{
return false;
}
mxPoint[] pointsCurve = getCurvePoints(mxCurve.CORE_CURVE);
if (pointsCurve != null && pointsCurve.length > 1)
{
mxRectangle mxRect = new mxRectangle(rect);
// First check for any of the curve points lying within the
// rectangle, then for any of the curve segments intersecting
// with the rectangle sides
for (int i = 1; i < pointsCurve.length; i++)
{
if (mxRect.contains(pointsCurve[i].getX(),
pointsCurve[i].getY())
|| mxRect.contains(pointsCurve[i - 1].getX(),
pointsCurve[i - 1].getY()))
{
return true;
}
}
for (int i = 1; i < pointsCurve.length; i++)
{
if (mxRect.intersectLine(pointsCurve[i].getX(),
pointsCurve[i].getY(), pointsCurve[i - 1].getX(),
pointsCurve[i - 1].getY()) != null)
{
return true;
}
}
}
return false;
}
/**
* Returns the point at which this curve intersects the boundary of
* the given rectangle, if it does so. If it does not intersect,
* null is returned. If it intersects multiple times, the first
* intersection from the start end of the curve is returned.
*
* @param index the curve index specifying the curve to analyse
* @param rect the whose boundary is to be tested for intersection
* with this curve
* @return the point at which this curve intersects the boundary of
* the given rectangle, if it does so. If it does not intersect,
* null is returned.
*/
public mxPoint intersectsRectPerimeter(String index, mxRectangle rect)
{
mxPoint result = null;
mxPoint[] pointsCurve = getCurvePoints(index);
if (pointsCurve != null && pointsCurve.length > 1)
{
int crossingSeg = intersectRectPerimeterSeg(index, rect);
if (crossingSeg != -1)
{
result = intersectRectPerimeterPoint(index, rect, crossingSeg);
}
}
return result;
}
/**
* Returns the distance from the start of the curve at which this
* curve intersects the boundary of the given rectangle, if it does
* so. If it does not intersect, -1 is returned.
* If it intersects multiple times, the first intersection from
* the start end of the curve is returned.
*
* @param index the curve index specifying the curve to analyse
* @param rect the whose boundary is to be tested for intersection
* with this curve
* @return the distance along the curve from the start at which
* the intersection occurs
*/
public double intersectsRectPerimeterDist(String index, mxRectangle rect)
{
double result = -1;
mxPoint[] pointsCurve = getCurvePoints(index);
double[] curveIntervals = getIntervals(index);
if (pointsCurve != null && pointsCurve.length > 1)
{
int segIndex = intersectRectPerimeterSeg(index, rect);
mxPoint intersectPoint = null;
if (segIndex != -1)
{
intersectPoint = intersectRectPerimeterPoint(index, rect,
segIndex);
}
if (intersectPoint != null)
{
double startSegX = pointsCurve[segIndex - 1].getX();
double startSegY = pointsCurve[segIndex - 1].getY();
double distToStartSeg = curveIntervals[segIndex - 1]
* getCurveLength(index);
double intersectOffsetX = intersectPoint.getX() - startSegX;
double intersectOffsetY = intersectPoint.getY() - startSegY;
double lenToIntersect = Math.sqrt(intersectOffsetX
* intersectOffsetX + intersectOffsetY
* intersectOffsetY);
result = distToStartSeg + lenToIntersect;
}
}
return result;
}
/**
* Returns a point to move the input rectangle to, in order to
* attempt to place the rectangle away from the curve. NOTE: Curves
* are scaled, the input rectangle should be also.
* @param index the curve index specifying the curve to analyse
* @param rect the rectangle that is to be moved
* @param buffer the amount by which the rectangle is to be moved,
* beyond the dimensions of the rect
* @return the point to move the top left of the input rect to
* , otherwise null if no point can be determined
*/
public mxPoint collisionMove(String index, mxRectangle rect, double buffer)
{
int hitSeg = intersectRectPerimeterSeg(index, rect);
// Could test for a second hit (the rect exit, unless the same
// segment is entry and exit) and allow for that in movement.
if (hitSeg == -1)
{
return null;
}
else
{
mxPoint[] pointsCurve = getCurvePoints(index);
double x0 = pointsCurve[hitSeg - 1].getX();
double y0 = pointsCurve[hitSeg - 1].getY();
double x1 = pointsCurve[hitSeg].getX();
double y1 = pointsCurve[hitSeg].getY();
double x = rect.getX();
double y = rect.getY();
double width = rect.getWidth();
double height = rect.getHeight();
// Whether the intersection is one of the horizontal sides of the rect
@SuppressWarnings("unused")
boolean horizIncident = false;
mxPoint hitPoint = mxUtils.intersection(x, y, x + width, y, x0, y0, x1, y1);
if (hitPoint != null)
{
horizIncident = true;
}
else
{
hitPoint = mxUtils.intersection(x + width, y, x + width, y + height,
x0, y0, x1, y1);
}
if (hitPoint == null)
{
hitPoint = mxUtils.intersection(x + width, y + height, x, y + height,
x0, y0, x1, y1);
if (hitPoint != null)
{
horizIncident = true;
}
else
{
hitPoint = mxUtils.intersection(x, y, x, y + height, x0, y0, x1, y1);
}
}
if (hitPoint != null)
{
}
}
return null;
}
/**
* Utility method to determine within which segment the specified rectangle
* intersects the specified curve
*
* @param index the curve index specifying the curve to analyse
* @param rect the whose boundary is to be tested for intersection
* with this curve
* @return the point at which this curve intersects the boundary of
* the given rectangle, if it does so. If it does not intersect,
* -1 is returned
*/
protected int intersectRectPerimeterSeg(String index, mxRectangle rect)
{
return intersectRectPerimeterSeg(index, rect, 1);
}
/**
* Utility method to determine within which segment the specified rectangle
* intersects the specified curve. This method specifies which segment to
* start searching at.
*
* @param index the curve index specifying the curve to analyse
* @param rect the whose boundary is to be tested for intersection
* with this curve
* @param startSegment the segment to start searching at. To start at the
* beginning of the curve, use 1, not 0.
* @return the point at which this curve intersects the boundary of
* the given rectangle, if it does so. If it does not intersect,
* -1 is returned
*/
protected int intersectRectPerimeterSeg(String index, mxRectangle rect,
int startSegment)
{
mxPoint[] pointsCurve = getCurvePoints(index);
if (pointsCurve != null && pointsCurve.length > 1)
{
for (int i = startSegment; i < pointsCurve.length; i++)
{
if (rect.intersectLine(pointsCurve[i].getX(),
pointsCurve[i].getY(), pointsCurve[i - 1].getX(),
pointsCurve[i - 1].getY()) != null)
{
return i;
}
}
}
return -1;
}
/**
* Returns the point at which this curve segment intersects the boundary
* of the given rectangle, if it does so. If it does not intersect,
* null is returned.
*
* @param curveIndex the curve index specifying the curve to analyse
* @param rect the whose boundary is to be tested for intersection
* with this curve
* @param indexSeg the segments on this curve being checked
* @return the point at which this curve segment intersects the boundary
* of the given rectangle, if it does so. If it does not intersect,
* null is returned.
*/
protected mxPoint intersectRectPerimeterPoint(String curveIndex,
mxRectangle rect, int indexSeg)
{
mxPoint result = null;
mxPoint[] pointsCurve = getCurvePoints(curveIndex);
if (pointsCurve != null && pointsCurve.length > 1 && indexSeg >= 0
&& indexSeg < pointsCurve.length)
{
double p1X = pointsCurve[indexSeg - 1].getX();
double p1Y = pointsCurve[indexSeg - 1].getY();
double p2X = pointsCurve[indexSeg].getX();
double p2Y = pointsCurve[indexSeg].getY();
result = rect.intersectLine(p1X, p1Y, p2X, p2Y);
}
return result;
}
/**
* Calculates the position of an absolute in terms relative
* to this curve.
*
* @param absPoint the point whose relative point is to calculated
* @param index the index of the curve whom the relative position is to be
* calculated from
* @return an mxRectangle where the x is the distance along the curve
* (0 to 1), y is the orthogonal offset from the closest segment on the
* curve and (width, height) is an additional Cartesian offset applied
* after the other calculations
*/
public mxRectangle getRelativeFromAbsPoint(mxPoint absPoint, String index)
{
// Work out which segment the absolute point is closest to
mxPoint[] currentCurve = getCurvePoints(index);
double[] currentIntervals = getIntervals(index);
int closestSegment = 0;
double closestSegDistSq = 10000000;
mxLine segment = new mxLine(currentCurve[0], currentCurve[1]);
for (int i = 1; i < currentCurve.length; i++)
{
segment.setPoints(currentCurve[i - 1], currentCurve[i]);
double segDistSq = segment.ptSegDistSq(absPoint);
if (segDistSq < closestSegDistSq)
{
closestSegDistSq = segDistSq;
closestSegment = i - 1;
}
}
// Get the distance (squared) from the point to the
// infinitely extrapolated line created by the closest
// segment. If that value is the same as the distance
// to the segment then an orthogonal offset from some
// point on the line will intersect the point. If they
// are not equal, an additional cartesian offset is
// required
mxPoint startSegPt = currentCurve[closestSegment];
mxPoint endSegPt = currentCurve[closestSegment + 1];
mxLine closestSeg = new mxLine(startSegPt, endSegPt);
double lineDistSq = closestSeg.ptLineDistSq(absPoint);
double orthogonalOffset = Math.sqrt(Math.min(lineDistSq,
closestSegDistSq));
double segX = endSegPt.getX() - startSegPt.getX();
double segY = endSegPt.getY() - startSegPt.getY();
double segDist = Math.sqrt(segX * segX + segY * segY);
double segNormX = segX / segDist;
double segNormY = segY / segDist;
// The orthogonal offset could be in one of two opposite vectors
// Try both solutions, one will be closer to one of the segment
// end points (unless the point is on the line)
double candidateOffX1 = (absPoint.getX() - segNormY * orthogonalOffset)
- endSegPt.getX();
double candidateOffY1 = (absPoint.getY() + segNormX * orthogonalOffset)
- endSegPt.getY();
double candidateOffX2 = (absPoint.getX() + segNormY * orthogonalOffset)
- endSegPt.getX();
double candidateOffY2 = (absPoint.getY() - segNormX * orthogonalOffset)
- endSegPt.getY();
double candidateDist1 = (candidateOffX1 * candidateOffX1)
+ (candidateOffY1 * candidateOffY1);
double candidateDist2 = (candidateOffX2 * candidateOffX2)
+ (candidateOffY2 * candidateOffY2);
double orthOffsetPointX = 0;
double orthOffsetPointY = 0;
if (candidateDist2 < candidateDist1)
{
orthogonalOffset = -orthogonalOffset;
}
orthOffsetPointX = absPoint.getX() - segNormY * orthogonalOffset;
orthOffsetPointY = absPoint.getY() + segNormX * orthogonalOffset;
double distAlongEdge = 0;
double cartOffsetX = 0;
double cartOffsetY = 0;
// Don't compare for exact equality, there are often rounding errors
if (Math.abs(closestSegDistSq - lineDistSq) > 0.0001)
{
// The orthogonal offset does not move the point onto the
// segment. Work out an additional cartesian offset that moves
// the offset point onto the closest end point of the
// segment
// Not exact distances, but the equation holds
double distToStartPoint = Math.abs(orthOffsetPointX
- startSegPt.getX())
+ Math.abs(orthOffsetPointY - startSegPt.getY());
double distToEndPoint = Math
.abs(orthOffsetPointX - endSegPt.getX())
+ Math.abs(orthOffsetPointY - endSegPt.getY());
if (distToStartPoint < distToEndPoint)
{
distAlongEdge = currentIntervals[closestSegment];
cartOffsetX = orthOffsetPointX - startSegPt.getX();
cartOffsetY = orthOffsetPointY - startSegPt.getY();
}
else
{
distAlongEdge = currentIntervals[closestSegment + 1];
cartOffsetX = orthOffsetPointX - endSegPt.getX();
cartOffsetY = orthOffsetPointY - endSegPt.getY();
}
}
else
{
// The point, when orthogonally offset, lies on the segment
// work out what proportion along the segment, and therefore
// the entire curve, the offset point lies.
double segmentLen = Math.sqrt((endSegPt.getX() - startSegPt.getX())
* (endSegPt.getX() - startSegPt.getX())
+ (endSegPt.getY() - startSegPt.getY())
* (endSegPt.getY() - startSegPt.getY()));
double offsetLen = Math.sqrt((orthOffsetPointX - startSegPt.getX())
* (orthOffsetPointX - startSegPt.getX())
+ (orthOffsetPointY - startSegPt.getY())
* (orthOffsetPointY - startSegPt.getY()));
double proportionAlongSeg = offsetLen / segmentLen;
double segProportingDiff = currentIntervals[closestSegment + 1]
- currentIntervals[closestSegment];
distAlongEdge = currentIntervals[closestSegment]
+ segProportingDiff * proportionAlongSeg;
}
if (distAlongEdge > 1.0)
{
distAlongEdge = 1.0;
}
return new mxRectangle(distAlongEdge, orthogonalOffset, cartOffsetX,
cartOffsetY);
}
/**
* Creates the core curve that is based on the guide points passed into
* this class instance
*/
protected void createCoreCurve()
{
// Curve is marked invalid until all of the error situations have
// been checked
valid = false;
if (guidePoints == null || guidePoints.isEmpty())
{
return;
}
for (int i = 0; i < guidePoints.size(); i++)
{
if (guidePoints.get(i) == null)
{
return;
}
}
// Reset the cached bounds value
minXBounds = minYBounds = 10000000;
maxXBounds = maxYBounds = 0;
mxSpline spline = new mxSpline(guidePoints);
// Need the rough length of the spline, so we can get
// more samples for longer edges
double lengthSpline = spline.getLength();
// Check for errors in the spline calculation or zero length curves
if (Double.isNaN(lengthSpline) || !spline.checkValues()
|| lengthSpline < 1)
{
return;
}
mxSpline1D splineX = spline.getSplineX();
mxSpline1D splineY = spline.getSplineY();
double baseInterval = 12.0 / lengthSpline;
double minInterval = 1.0 / lengthSpline;
// Store the last two spline positions. If the next position is
// very close to where the extrapolation of the last two points
// then double the interval. This diviation is terms the "flatness".
// There is a range where the interval is kept the same, any
// variation from this range of flatness invokes a proportional
// adjustment to try to reenter the range without
// over compensating
double interval = baseInterval;
// These deviations are only tested against either
// dimension individually, working out the correct
// distance is too computationally intensive
double minDeviation = 0.15;
double maxDeviation = 0.3;
double preferedDeviation = (maxDeviation + minDeviation) / 2.0;
// x1, y1 are the position two iterations ago, x2, y2
// the position on the last iteration
double x1 = -1.0;
double x2 = -1.0;
double y1 = -1.0;
double y2 = -1.0;
// Store the change in interval amount between iterations.
// If it changes the extrapolation calculation must
// take this into account.
double intervalChange = 1;
List coreCurve = new ArrayList();
List coreIntervals = new ArrayList();
boolean twoLoopsComplete = false;
for (double t = 0; t <= 1.5; t += interval)
{
if (t > 1.0)
{
// Use the point regardless of the accuracy,
t = 1.0001;
mxPoint endControlPoint = guidePoints
.get(guidePoints.size() - 1);
mxPoint finalPoint = new mxPoint(endControlPoint.getX(),
endControlPoint.getY());
coreCurve.add(finalPoint);
coreIntervals.add(t);
updateBounds(endControlPoint.getX(), endControlPoint.getY());
break;
}
// Whether or not the accuracy of the current point is acceptable
boolean currentPointAccepted = true;
double newX = splineX.getFastValue(t);
double newY = splineY.getFastValue(t);
// Check if the last points are valid (indicated by
// dissimilar values)
// Check we're not in the first, second or last run
if (x1 != -1.0 && twoLoopsComplete && t != 1.0001)
{
// Work out how far the new spline point
// deviates from the extrapolation created
// by the last two points
double diffX = Math.abs(((x2 - x1) * intervalChange + x2)
- newX);
double diffY = Math.abs(((y2 - y1) * intervalChange + y2)
- newY);
// If either the x or y of the straight line
// extrapolation from the last two points
// is more than the 1D deviation allowed
// go back and re-calculate with a smaller interval
// It's possible that the edge has curved too fast
// for the algorithmn. If the interval is
// reduced to less than the minimum permitted
// interval, it may be that it's impossible
// to get within the deviation because of
// the extrapolation overshoot. The minimum
// interval is set to draw correctly for the
// vast majority of cases.
if ((diffX > maxDeviation || diffY > maxDeviation)
&& interval != minInterval)
{
double overshootProportion = maxDeviation
/ Math.max(diffX, diffY);
if (interval * overshootProportion <= minInterval)
{
// Set the interval
intervalChange = minInterval / interval;
}
else
{
// The interval can still be reduced, half
// the interval and go back and redo
// this iteration
intervalChange = overshootProportion;
}
t -= interval;
interval *= intervalChange;
currentPointAccepted = false;
}
else if (diffX < minDeviation && diffY < minDeviation)
{
intervalChange = 1.4;
interval *= intervalChange;
}
else
{
// Try to keep the deviation around the prefered value
double errorRatio = preferedDeviation
/ Math.max(diffX, diffY);
intervalChange = errorRatio / 4.0;
interval *= intervalChange;
}
if (currentPointAccepted)
{
x1 = x2;
y1 = y2;
x2 = newX;
y2 = newY;
}
}
else if (x1 == -1.0)
{
x1 = x2 = newX;
y1 = y2 = newY;
}
else if (x1 == x2 && y1 == y2)
{
x2 = newX;
y2 = newY;
twoLoopsComplete = true;
}
if (currentPointAccepted)
{
mxPoint newPoint = new mxPoint(newX, newY);
coreCurve.add(newPoint);
coreIntervals.add(t);
updateBounds(newX, newY);
}
}
if (coreCurve.size() < 2)
{
// A single point makes no sense, leave the curve as invalid
return;
}
mxPoint[] corePoints = new mxPoint[coreCurve.size()];
int count = 0;
for (mxPoint point : coreCurve)
{
corePoints[count++] = point;
}
points = new Hashtable();
curveLengths = new Hashtable();
points.put(CORE_CURVE, corePoints);
curveLengths.put(CORE_CURVE, lengthSpline);
double[] coreIntervalsArray = new double[coreIntervals.size()];
count = 0;
for (Double tempInterval : coreIntervals)
{
coreIntervalsArray[count++] = tempInterval.doubleValue();
}
intervals = new Hashtable();
intervals.put(CORE_CURVE, coreIntervalsArray);
valid = true;
}
/** Whether or not the label curve starts from the end target
* and traces to the start of the branch
* @return whether the label curve is reversed
*/
public boolean isLabelReversed()
{
if (valid)
{
mxPoint[] centralCurve = getCurvePoints(CORE_CURVE);
if (centralCurve != null)
{
double changeX = centralCurve[centralCurve.length - 1].getX()
- centralCurve[0].getX();
if (changeX < 0)
{
return true;
}
}
}
return false;
}
protected void createLabelCurve()
{
// Place the label on the "high" side of the vector
// joining the start and end points of the curve
mxPoint[] currentCurve = getBaseLabelCurve();
boolean labelReversed = isLabelReversed();
List labelCurvePoints = new ArrayList();
// Lower and upper curve start from the very ends
// of their curves, so given that their middle points
// are derived from the center of the central points
// they will contain one more point and both
// side curves contain the same end point
for (int i = 1; i < currentCurve.length; i++)
{
int currentIndex = i;
int lastIndex = i - 1;
if (labelReversed)
{
currentIndex = currentCurve.length - i - 1;
lastIndex = currentCurve.length - i;
}
mxPoint segStartPoint = currentCurve[currentIndex];
mxPoint segEndPoint = currentCurve[lastIndex];
double segVectorX = segEndPoint.getX() - segStartPoint.getX();
double segVectorY = segEndPoint.getY() - segStartPoint.getY();
double segVectorLength = Math.sqrt(segVectorX * segVectorX
+ segVectorY * segVectorY);
double normSegVectorX = segVectorX / segVectorLength;
double normSegVectorY = segVectorY / segVectorLength;
double centerSegX = (segEndPoint.getX() + segStartPoint.getX()) / 2.0;
double centerSegY = (segEndPoint.getY() + segStartPoint.getY()) / 2.0;
if (i == 1)
{
// Special case to work out the very end points at
// the start of the curve
mxPoint startPoint = new mxPoint(segEndPoint.getX()
- (normSegVectorY * labelBuffer), segEndPoint.getY()
+ (normSegVectorX * labelBuffer));
labelCurvePoints.add(startPoint);
updateBounds(startPoint.getX(), startPoint.getY());
}
double pointX = centerSegX - (normSegVectorY * labelBuffer);
double pointY = centerSegY + (normSegVectorX * labelBuffer);
mxPoint labelCurvePoint = new mxPoint(pointX, pointY);
updateBounds(pointX, pointY);
labelCurvePoints.add(labelCurvePoint);
if (i == currentCurve.length - 1)
{
// Special case to work out the very end points at
// the start of the curve
mxPoint endPoint = new mxPoint(segStartPoint.getX()
- (normSegVectorY * labelBuffer), segStartPoint.getY()
+ (normSegVectorX * labelBuffer));
labelCurvePoints.add(endPoint);
updateBounds(endPoint.getX(), endPoint.getY());
}
}
mxPoint[] tmpPoints = new mxPoint[labelCurvePoints.size()];
points.put(LABEL_CURVE, labelCurvePoints.toArray(tmpPoints));
populateIntervals(LABEL_CURVE);
}
/**
* Returns the curve the label curve is too be based on
*/
protected mxPoint[] getBaseLabelCurve()
{
return getCurvePoints(CORE_CURVE);
}
protected void populateIntervals(String index)
{
mxPoint[] currentCurve = points.get(index);
double[] newIntervals = new double[currentCurve.length];
double totalLength = 0.0;
newIntervals[0] = 0;
for (int i = 0; i < currentCurve.length - 1; i++)
{
double changeX = currentCurve[i + 1].getX()
- currentCurve[i].getX();
double changeY = currentCurve[i + 1].getY()
- currentCurve[i].getY();
double segLength = Math.sqrt(changeX * changeX + changeY * changeY);
// We initially fill the intervals with the total distance to
// the end of this segment then later normalize all the values
totalLength += segLength;
// The first index was populated before the loop (and is always 0)
newIntervals[i + 1] = totalLength;
}
// Normalize the intervals
for (int j = 0; j < newIntervals.length; j++)
{
if (j == newIntervals.length - 1)
{
// Make the final interval slightly over
// 1.0 so any analysis to find the lower
newIntervals[j] = 1.0001;
}
else
{
newIntervals[j] = newIntervals[j] / totalLength;
}
}
intervals.put(index, newIntervals);
curveLengths.put(index, totalLength);
}
/**
* Updates the existing curve using the points passed in.
* @param newPoints the new guide points
*/
public void updateCurve(List newPoints)
{
boolean pointsChanged = false;
// If any of the new points are null, ignore the list
for (mxPoint point : newPoints)
{
if (point == null)
{
return;
}
}
if (newPoints.size() != guidePoints.size())
{
pointsChanged = true;
}
else
{
// Check for a constant translation of all guide points. In that
// case apply the translation directly to all curves.
// Also check whether all of the translations are trivial
if (newPoints.size() == guidePoints.size() && newPoints.size() > 1
&& guidePoints.size() > 1)
{
boolean constantTranslation = true;
boolean trivialTranslation = true;
mxPoint newPoint0 = newPoints.get(0);
mxPoint oldPoint0 = guidePoints.get(0);
double transX = newPoint0.getX() - oldPoint0.getX();
double transY = newPoint0.getY() - oldPoint0.getY();
if (Math.abs(transX) > 0.01 || Math.abs(transY) > 0.01)
{
trivialTranslation = false;
}
for (int i = 1; i < newPoints.size(); i++)
{
double nextTransX = newPoints.get(i).getX()
- guidePoints.get(i).getX();
double nextTransY = newPoints.get(i).getY()
- guidePoints.get(i).getY();
if (Math.abs(transX - nextTransX) > 0.01
|| Math.abs(transY - nextTransY) > 0.01)
{
constantTranslation = false;
}
if (Math.abs(nextTransX) > 0.01
|| Math.abs(nextTransY) > 0.01)
{
trivialTranslation = false;
}
}
if (trivialTranslation)
{
pointsChanged = false;
}
else if (constantTranslation)
{
pointsChanged = false;
// Translate all stored points by the translation amounts
Collection curves = points.values();
// Update all geometry information held by the curve
// That is, all the curve points, the guide points
// and the cached bounds
for (mxPoint[] curve : curves)
{
for (int i = 0; i < curve.length; i++)
{
curve[i].setX(curve[i].getX() + transX);
curve[i].setY(curve[i].getY() + transY);
}
}
guidePoints = new ArrayList(newPoints);
minXBounds += transX;
minYBounds += transY;
maxXBounds += transX;
maxYBounds += transY;
}
else
{
pointsChanged = true;
}
}
}
if (pointsChanged)
{
guidePoints = new ArrayList(newPoints);
points = new Hashtable();
valid = false;
}
}
/**
* Obtains the points that make up the curve for the specified
* curve index. If that curve, or the core curve that other curves
* are based on have not yet been created, then they are lazily
* created. If creation is impossible, null is returned
* @param index the key specifying the curve
* @return the points making up that curve, or null
*/
public mxPoint[] getCurvePoints(String index)
{
if (validateCurve())
{
if (points.get(LABEL_CURVE) == null && index == LABEL_CURVE)
{
createLabelCurve();
}
return points.get(index);
}
return null;
}
public double[] getIntervals(String index)
{
if (validateCurve())
{
if (points.get(LABEL_CURVE) == null && index == LABEL_CURVE)
{
createLabelCurve();
}
return intervals.get(index);
}
return null;
}
public double getCurveLength(String index)
{
if (validateCurve())
{
if (intervals.get(index) == null)
{
createLabelCurve();
}
return curveLengths.get(index);
}
return 0;
}
/**
* Method must be called before any attempt to access curve information
* @return whether or not the curve may be used
*/
protected boolean validateCurve()
{
if (!valid)
{
createCoreCurve();
}
return valid;
}
/**
* Updates the total bounds of this curve, increasing any dimensions,
* if necessary, to fit in the specified point
*/
protected void updateBounds(double pointX, double pointY)
{
minXBounds = Math.min(minXBounds, pointX);
maxXBounds = Math.max(maxXBounds, pointX);
minYBounds = Math.min(minYBounds, pointY);
maxYBounds = Math.max(maxYBounds, pointY);
}
/**
* @return the guidePoints
*/
public List getGuidePoints()
{
return guidePoints;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy