org.metacsp.utility.UI.CurvedArrow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of meta-csp-framework Show documentation
Show all versions of meta-csp-framework Show documentation
A Java API for Meta-CSP based reasoning
package org.metacsp.utility.UI;
/*
* JFLAP - Formal Languages and Automata Package
*
*
* Susan H. Rodger
* Computer Science Department
* Duke University
* August 27, 2009
* Copyright (c) 2002-2009
* All rights reserved.
* JFLAP is open source software. Please see the LICENSE for terms.
*
*/
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
/**
* This is a simple class for storing and drawing a curved line with possible
* arrow heads on it.
*
* @author Thomas Finley
*/
public class CurvedArrow {
/**
* Instantiates a CurvedArrow
object.
*
* @param x1
* the x coordinate of the start point
* @param y1
* the y coordinate of the start point
* @param x2
* the x coordinate of the end point
* @param y2
* the y coordinate of the end point
* @param curvy
* the curvi-ness factor; 0 will create a straight line; 1 and -1
* are rather curvy
*/
public CurvedArrow(int x1, int y1, int x2, int y2, float curvy) {
curve = new QuadCurve2D.Float();
start = new Point();
end = new Point();
control = new Point();
setStart(x1, y1);
setEnd(x2, y2);
setCurvy(curvy);
refreshCurve();
}
/**
* Instantiates a CurvedArrow
object.
*
* @param start
* the start point
* @param end
* the end point
* @param curvy
* the curvi-ness factor; 0 will create a straight line; 1 and -1
* are rather curvy
*/
public CurvedArrow(Point start, Point end, float curvy) {
curve = new QuadCurve2D.Float();
setStart(start);
setEnd(end);
control = new Point();
setCurvy(curvy);
refreshCurve();
}
/**
* Sets the start point.
*
* @param x1
* the x coordinate of the start point
* @param y1
* the y coordinate of the start point
*/
public void setStart(int x1, int y1) {
start.x = x1;
start.y = y1;
needsRefresh = true;
}
/**
* Sets the start point.
*
* @param start
* the new start point
*/
public void setStart(Point start) {
this.start = start;
needsRefresh = true;
}
/**
* Sets the end point.
*
* @param x2
* the x coordinate of the end point
* @param y2
* the y coordinate of the end point
*/
public void setEnd(int x2, int y2) {
end.x = x2;
end.y = y2;
needsRefresh = true;
}
/**
* Sets the end point.
*
* @param end
* the new end point
*/
public void setEnd(Point end) {
this.end = end;
needsRefresh = true;
}
/**
* Sets the "curvy-ness" factor.
*
* @param curvy
* the new curvy factor
*/
public void setCurvy(float curvy) {
this.curvy = curvy;
needsRefresh = true;
}
/**
* Draws the arrow on the indicated graphics environment.
*
* @param g
* the graphics to draw this arrow upon
*/
public void draw(Graphics2D g) {
if (needsRefresh)
refreshCurve();
g.draw(curve); // Draws the main part of the arrow.
drawArrow(g, end, control); // Draws the arrow head.
drawText(g);
}
public void drawControlPoint(Graphics2D g){ //adjust later to center of circle = focus point
g.drawOval((int)curve.getCtrlX() - 5, (int)curve.getCtrlY() - 5, 10,10);
}
/**
* Draws a highlight of the curve.
*
* @param g
* the graphics to draw the highlight of the curve upon
*/
public void drawHighlight(Graphics2D g) {
if (needsRefresh)
refreshCurve();
Graphics2D g2 = (Graphics2D) g.create();
g2.setStroke(new java.awt.BasicStroke(6.0f));
g2.setColor(HIGHLIGHT_COLOR);
g2.draw(curve);
g2.transform(affineToText);
g2.fill(bounds);
g2.dispose();
}
/**
* Draws the text on the high point of the arc. The text drawn is none other
* than the label for this object, as retrieved from getLabel
.
*
* @param g
* the graphics object to draw the text upon
*/
public void drawText(Graphics2D g) {
// We don't want to corrupt the graphics environs with our
// affine transforms!
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.transform(affineToText);
// What about the text label?
FontMetrics metrics = g2.getFontMetrics();
bounds = metrics.getStringBounds(getLabel(), g2);
// Will the label appear to be upside down?
boolean upsideDown = end.x < start.x;
float dx = (float) bounds.getWidth() / 2.0f;
float dy = (curvy < 0.0f) ^ upsideDown ? metrics.getAscent() : -metrics
.getDescent();
bounds.setRect(bounds.getX() - dx, bounds.getY() + dy, bounds
.getWidth(), bounds.getHeight());
for (int i = 0; i < label.length(); i += CHARS_PER_STEP) {
String sublabel = label.substring(i, Math.min(i + CHARS_PER_STEP,
label.length()));
g2.drawString(sublabel, -dx, dy);
dx -= metrics.getStringBounds(sublabel, g2).getWidth();
}
// g2.drawString(label, -dx, dy);
g2.dispose();
/*
* if (GRAPHICS == null) { GRAPHICS = g.create(); METRICS =
* GRAPHICS.getFontMetrics(); }
*/
}
/**
* Sets the label that will be drawn on the high arc point.
*
* @param label
* the new label for the arrow
*/
public void setLabel(String label) {
this.label = label;
// if (GRAPHICS == null) return;
bounds = METRICS.getStringBounds(getLabel(), GRAPHICS);
boolean upsideDown = end.x < start.x;
float dx = (float) bounds.getWidth() / 2.0f;
float dy = (curvy < 0.0f) ^ upsideDown ? METRICS.getAscent() : -METRICS
.getDescent();
bounds.setRect(bounds.getX() - dx, bounds.getY() + dy, bounds
.getWidth(), bounds.getHeight());
//System.out.println("Setting label" + label);
}
/**
* Returns the label for this arrow.
*
* @return the label for this arrow
*/
public String getLabel() {
return this.label;
}
/**
* Draws an arrow head on the graphics object. The arrow geometry is based
* on the point of its head as well as another point, which the arrow is
* defined as facing away from. This arrow head has no body.
*
* @param g
* the graphics object to draw upon
* @param head
* the point that is the point of the head of the arrow
* @param away
* the point opposite from where the arrow is pointing, a point
* along the line segment extending from the head backwards from
* the head if this were an arrow with a line trailing the head
*/
private void drawArrow(Graphics g, Point head, Point away) {
int endX, endY;
double angle = Math.atan2((double) (away.x - head.x),
(double) (away.y - head.y));
angle += ARROW_ANGLE;
endX = ((int) (Math.sin(angle) * ARROW_LENGTH)) + head.x;
endY = ((int) (Math.cos(angle) * ARROW_LENGTH)) + head.y;
g.drawLine(head.x, head.y, endX, endY);
angle -= 2 * ARROW_ANGLE;
endX = ((int) (Math.sin(angle) * ARROW_LENGTH)) + head.x;
endY = ((int) (Math.cos(angle) * ARROW_LENGTH)) + head.y;
g.drawLine(head.x, head.y, endX, endY);
}
/**
* Refreshes the curve object.
*/
public void refreshCurve() {
// System.out.println("Curve refreshing");
needsRefresh = false;
double lengthx = end.x - start.x;
double lengthy = end.y - start.y;
double centerx = ((double) (start.x + end.x)) / 2.0;
double centery = ((double) (start.y + end.y)) / 2.0;
double length = Math.sqrt(lengthx * lengthx + lengthy * lengthy);
double factorx = length == 0.0 ? 0.0 : lengthx / length;
double factory = length == 0.0 ? 0.0 : lengthy / length;
control.x = (int) (centerx + curvy * HEIGHT * factory);
control.y = (int) (centery - curvy * HEIGHT * factorx);
high.x = (int) (centerx + curvy * HEIGHT * factory / 2.0);
high.y = (int) (centery - curvy * HEIGHT * factorx / 2.0);
curve.setCurve((float) start.x, (float) start.y, (float) control.x,
(float) control.y, (float) end.x, (float) end.y);
affineToText = new AffineTransform();
affineToText.translate(high.x, high.y);
affineToText.rotate(Math.atan2(lengthy, lengthx));
if (end.x < start.x)
affineToText.rotate(Math.PI);
}
/**
* Returns the bounds.
*
* @return the rectangular bounds for this curved arrow
*/
public Rectangle2D getBounds() {
if (needsRefresh)
refreshCurve();
Rectangle2D b = curve.getBounds();
Area area = new Area(bounds);
area.transform(affineToText);
b.add(area.getBounds());
return b;
}
/**
* Determines if a point is on/near the curved arrow.
*
* @param point
* the point to check
* @param fudge
* the radius around the point that should be checked for the
* presence of the curve
* @return true if the point is on the curve within a certain
* fudge factor, false otherwise
*/
public boolean isNear(Point point, int fudge) {
if (needsRefresh)
refreshCurve();
try {
if (bounds.contains(affineToText.inverseTransform(point, null)))
return true;
} catch (java.awt.geom.NoninvertibleTransformException e) {
} catch (NullPointerException e) {
System.err.println(e + " : " + bounds + " : " + affineToText);
return false;
}
return intersects(point, fudge, curve);
}
/**
* Checks if something is on the line. If it appears to be, then it
* subdivides the curve into halves and tries again recursively until the
* flatness of the curve is less than the fudge. Frankly, I am a fucking
* genius. I am one of two people in this department that could have
* possibly thought of this.
*
* @param point
* the point to check intersection
* @param fudge
* the "fudge" factor
* @param c
* the curve we're checking for intersection with
* @return true if the point is on the curve within a certain
* fudge factor, false otherwise
*/
private boolean intersects(Point point, int fudge, QuadCurve2D.Float c) {
if (!c.intersects(point.x - fudge, point.y - fudge, fudge << 1,
fudge << 1))
return false;
if (c.getFlatness() < fudge)
return true;
QuadCurve2D.Float f1 = new QuadCurve2D.Float(), f2 = new QuadCurve2D.Float();
c.subdivide(f1, f2);
return intersects(point, fudge, f1) || intersects(point, fudge, f2);
}
public QuadCurve2D getCurve(){
return curve;
}
/** The start, end, and single control points. */
protected Point start, end, control;
/** The high point of the arc. */
private Point high = new Point();
/** The "curvy-ness" factor. */
protected float curvy;
/**
* The quad-curve that controls the shape of the long part of the arrow.
*/
protected QuadCurve2D.Float curve;
/**
* true
if the curve needs to be refreshed, false
* otherwise.
*/
protected boolean needsRefresh = true;
/** Arrow flags. */
protected boolean startArrow = false, endArrow = false;
/** The label for this arrow. */
protected String label = "";
/** The angle for the arrow heads. */
private static double ARROW_ANGLE = Math.PI / 10;
/** The length of the arrow head edges. */
private static double ARROW_LENGTH = 15;
/** The affine transform for "turning" text. */
private static AffineTransform AFFINE_TURN_180;
/** The stored bounds. */
protected java.awt.geom.Rectangle2D bounds = new java.awt.Rectangle(0, 0);
/** The affine transform for getting us to the text space. */
protected AffineTransform affineToText;
/** The number of characters to draw in each step. */
private static final int CHARS_PER_STEP = 4;
/** A graphics object. */
protected static Graphics GRAPHICS = null;
/** A font metrics object. */
protected static FontMetrics METRICS;
static {
AFFINE_TURN_180 = new AffineTransform();
AFFINE_TURN_180.rotate(Math.PI);
BufferedImage image = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
GRAPHICS = image.getGraphics();
METRICS = GRAPHICS.getFontMetrics();
}
/** The high factor of a control point. */
private static double HEIGHT = 30.0;
public static java.awt.Color HIGHLIGHT_COLOR = new java.awt.Color(255, 0, 0, 128);
}