/* 
 * This code is part of Project Waterloo from King's College London
 * 
 *
 * Copyright King's College London  2011-
 * 
 * @author Malcolm Lidierth, King's College London  [Contact]
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 */
package kcl.waterloo.annotation;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.SwingConstants;
import kcl.waterloo.defaults.GJDefaults;
import kcl.waterloo.graphics.GJGraphInterface;
import kcl.waterloo.marker.GJMarker;

/**
 * GJAnnotation is an abstract class that provides concrete internal classes to
 * annotate parentGraph containers and static factory methods to construct them.
 *
 * Annotations can be added to parentGraph containers by calling the container's
 * {@code add} method (in which case the position of the annotation will be set
 * in the coordinate space of that container's View) or by calling the
 * {@code add} method of a parentGraph layer in the View of a container (in
 * which case the position of the annotation will be set in that layer's
 * coordinate space).
 *
 *
 * @author Malcolm Lidierth, King's College London  [Contact]
 */
public abstract class GJAnnotation
        implements GJAnnotationInterface {

    /**
     * Specifies the parentGraph whose coordinate space is to be used to
     * position this annotation. If null, the View will be used,
     */
    private GJGraphInterface parentGraph = null;
    /**
     * text to render.
     */
    private String text = "";
    /**
     * GJMarker instance - used as a wrapper for a Path2D object.
     */
    private GJMarker marker = null;
    private GJMarker extra = null;
    /**
     * Stroke to be used for drawing.
     */
    private Stroke lineStroke = null;
    /**
     * Line color to be used for drawing.
     */
    private Paint lineColor = (Paint) GJDefaults.getMap().get("GJAnnotation.lineColor");
    /**
     * text color to be used for drawing.
     */
    private Paint textColor = (Paint) GJDefaults.getMap().get("GJAnnotation.textColor");
    /**
     * text color to be used for drawing.
     */
    private Paint textBackground = (Paint) GJDefaults.getMap().get("GJAnnotation.textBackground");
    /**
     * fill color to be used. Null for no fill.
     */
    private Paint fill = (Paint) GJDefaults.getMap().get("GJAnnotation.fill");
    /**
     * For text annotations only, sets the left hand X-position
     */
    private double xData = 0;
    /**
     * For text annotations only, sets the Y-position
     */
    private double yData = 0;
    /**
     * User-settable fromString used to identity this annotation.
     */
    private String name = "";
    /**
     * Font used for text.
     */
    private Font font = new Font("Sans Serif", Font.PLAIN, 10);
    /**
     * For line annotations, {@code lineTextPosition} sets the position of the
     * text relative to the bounding rectangle of the line.
     * 

* Recognized values are:

*

* SwingConstants.NORTH - centers the text at the top

*

* SwingConstants.NORTH_EAST - centers the text at the top-right corner

*

* SwingConstants.NORTH_WEST - centers the text at the top-left corner

*

* SwingConstants.SOUTH - centers the text at the bottom

*

* SwingConstants.SOUTH_EAST - centers the text at the bottom-right * corner

*

* SwingConstants.SOUTH_WEST - centers the text at the bottom-left * corner

*

* SwingConstants.LEADNG - puts the text to the left of center

*

* SwingConstants.TRAILING - puts the text to the right of center

* */ private int lineTextPosition = SwingConstants.TOP; /** * Constructor. * */ public GJAnnotation() { } /** * Returns a clone of the Path2D object. * * @return the marker */ @Override public Path2D getPath() { if (marker == null) { return null; } else { return getMarker().getPath(); } } /** * Sets the Path2D object. * * @param p */ @Override public void setPath(Path2D p) { setMarker(new GJMarker(p)); } /** * Returns the parentGraph reference for setting the coordinate system for * this annotation. * * @return the plot */ @Override public GJGraphInterface getParentGraph() { return parentGraph; } /** * Sets the Path2D object. * * @param p */ @Override public void setExtra(GJMarker p) { extra = p; } /** * Returns the parentGraph reference for setting the coordinate system for * this annotation. * * @return the plot */ @Override public GJMarker getExtra() { return extra; } /** * setParentGraph method. This is called when an annotation is added using * the {@code add} method of a parentGraph - which is recommended - so need * not be called directly by the user. * * @param gr */ @Override public void setParentGraph(GJGraphInterface gr) { parentGraph = gr; } /** * @return the lineStroke */ @Override public Stroke getLineStroke() { return lineStroke; } /** * */ @Override public void setLineStroke(Stroke LineStroke) { this.lineStroke = LineStroke; } /** * @return the lineColor */ @Override public Paint getLineColor() { return lineColor; } /** */ @Override public void setLineColor(Paint LineColor) { this.lineColor = LineColor; } /** * @return the Font */ @Override public Font getFont() { return font; } /** * @param Font the Font to set */ @Override public void setFont(Font Font) { this.font = Font; } /** * @return the fill */ @Override public Paint getFill() { return fill; } /** */ @Override public void setFill(Paint Fill) { this.fill = Fill; } /** * @return the marker */ @Override public GJMarker getMarker() { return marker; } /** */ @Override public void setMarker(GJMarker Marker) { this.marker = Marker; } /** * @return the name */ @Override public String getName() { return name; } /** */ @Override public void setName(String Name) { this.name = Name; } /** * @return the text */ @Override public String getText() { return text; } /** */ @Override public void setText(String Text) { this.text = Text; } /** * @return the X */ @Override public double getXData() { return xData; } /** * @param X the X to set */ @Override public void setXData(double X) { this.xData = X; } /** * @return the Y */ @Override public double getYData() { return yData; } /** * @param Y the Y to set */ @Override public void setYData(double Y) { this.yData = Y; } /** * @return the textColor */ @Override public Paint getTextColor() { return textColor; } /** * Sets the textColor * */ @Override public void setTextColor(Paint TextColor) { this.textColor = TextColor; } /** * @return the lineTextPosition */ public int getLineTextPosition() { return lineTextPosition; } /** * @param pos the lineTextPosition to set */ public void setLineTextPosition(int pos) { this.lineTextPosition = pos; } @Override public void remove() { if (this.getParentGraph() != null && this.getParentGraph().getGraphContainer() != null) { this.getParentGraph().getGraphContainer(). getAnnotations().remove(this); } } @Override public void remove(String name) { if (this.getParentGraph() != null && this.getParentGraph().getGraphContainer() != null) { for (GJAnnotationInterface a : this.getParentGraph().getGraphContainer().getAnnotations()) { if (a.getName().matches(name)) { a.remove(); } } } } /** * paintAnnotation method. This has public scope to allow it to be called * from the parentGraph container's paint methods. When using a standard * Project Waterloo parentGraph container where annotations have been added * using the {@code add} method, this is done automatically. * * @param g2 the graphics object to render to * @param af an AffineTransform instance with scale and translate set to * position this annotation correctly in the container using the * parentGraph's coordinate system. */ @Override public void paintAnnotation(Graphics2D g2, AffineTransform af) { FontMetrics metrics; Rectangle2D lineBounds; Path2D p; if (lineStroke == null) { lineStroke = new BasicStroke(1f); } float X = 0, Y = 0; if (this instanceof TextAnnotation) { if (!text.isEmpty()) { g2.setFont(font); metrics = g2.getFontMetrics(font); lineBounds = metrics.getStringBounds(text, g2); Point2D pt = af.transform(new Point2D.Double(getXData(), getYData()), null); if (textBackground != null || lineColor != null) { Rectangle2D r = new Rectangle2D.Double(pt.getX() - 5, pt.getY() - lineBounds.getBounds2D().getHeight() - 3, lineBounds.getBounds2D().getWidth() + 10, lineBounds.getBounds2D().getHeight() + 10); GJMarker marker2 = new GJMarker(r); p = marker2.getPath(); if (textBackground != null) { g2.setPaint(textBackground); g2.fill(p); } if (lineColor != null) { g2.setPaint(getLineColor()); g2.setStroke(getLineStroke()); g2.draw(p); } } g2.setPaint(getTextColor()); g2.setStroke(getLineStroke()); g2.drawString(text, (float) pt.getX(), (float) pt.getY()); } } else if (this instanceof ShapeAnnotation) { p = this.getPath(); p.transform(af); Paint f = getFill(); if (f != null) { g2.setPaint(f); g2.fill(p); } g2.setPaint(getLineColor()); g2.setStroke(getLineStroke()); g2.draw(p); if (!text.isEmpty()) { g2.setFont(font); metrics = g2.getFontMetrics(); lineBounds = metrics.getStringBounds(text, g2); X = (float) (p.getBounds2D().getCenterX() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getCenterY() + metrics.getLineMetrics(text, g2).getDescent()); g2.drawString(text, X, Y); } } else if (this instanceof LineAnnotation || this instanceof ArrowAnnotation) { p = this.getPath(); p.transform(af); g2.setPaint(getLineColor()); g2.setStroke(getLineStroke()); g2.draw(p); if (this instanceof ArrowAnnotation && fill != null) { // Fill the arrowhead Path2D p2 = getExtra().getPath(); p2.transform(af); g2.setPaint(fill); g2.fill(p2); } if (!text.isEmpty()) { g2.setFont(font); metrics = g2.getFontMetrics(); lineBounds = metrics.getStringBounds(text, g2); if (lineTextPosition == SwingConstants.NORTH) { X = (float) (p.getBounds2D().getCenterX() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() - metrics.getLineMetrics(text, g2).getHeight()); g2.drawString(text, X, Y); } else if (lineTextPosition == SwingConstants.NORTH_EAST) { X = (float) (p.getBounds2D().getX() + p.getBounds().getWidth() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() - metrics.getLineMetrics(text, g2).getHeight()); g2.drawString(text, X, Y); } else if (lineTextPosition == SwingConstants.NORTH_WEST) { X = (float) (p.getBounds().getX() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() - metrics.getLineMetrics(text, g2).getHeight()); g2.drawString(text, X, Y); } else if (lineTextPosition == SwingConstants.SOUTH) { X = (float) (p.getBounds2D().getCenterX() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() + p.getBounds2D().getHeight() + metrics.getLineMetrics(text, g2).getHeight()); g2.drawString(text, X, Y); } else if (lineTextPosition == SwingConstants.SOUTH_EAST) { X = (float) (p.getBounds2D().getX() + p.getBounds().getWidth() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() + p.getBounds2D().getHeight() + metrics.getLineMetrics(text, g2).getHeight()); g2.drawString(text, X, Y); } else if (lineTextPosition == SwingConstants.SOUTH_WEST) { X = (float) (p.getBounds2D().getX() - lineBounds.getCenterX()); Y = (float) (p.getBounds2D().getY() + p.getBounds2D().getHeight() + metrics.getLineMetrics(text, g2).getHeight()); } else if (lineTextPosition == SwingConstants.TRAILING) { X = (float) (p.getBounds2D().getCenterX() + 5); Y = (float) (p.getBounds2D().getCenterY() - lineBounds.getCenterY()); } else if (lineTextPosition == SwingConstants.LEADING) { X = (float) (p.getBounds2D().getCenterX() - lineBounds.getWidth() - 5); Y = (float) (p.getBounds2D().getCenterY() - lineBounds.getCenterY()); } if (textBackground != null || lineColor != null) { Rectangle2D r = new Rectangle2D.Double(X - 1, Y - metrics.getLineMetrics(text, g2).getHeight() + metrics.getLineMetrics(text, g2).getDescent() - 1, lineBounds.getWidth() + 1, lineBounds.getHeight() + 1); GJMarker marker2 = new GJMarker(r); p = marker2.getPath(); if (textBackground != null) { g2.setPaint(textBackground); g2.fill(p); } if (lineColor != null) { g2.setPaint(getLineColor()); g2.setStroke(new BasicStroke(1f)); g2.draw(p); } } g2.setPaint(getTextColor()); g2.drawString(text, X, Y); } } } /** * Static method to create a line between {x1,y1} and {x2,y2}. * * @param x1 * @param y1 * @param x2 * @param y2 * @return the the annotation */ public static LineAnnotation createLine(double x1, double y1, double x2, double y2) { return new LineAnnotation(x1, y1, x2, y2); } /** * Static method to create a line between {x1,y1} and {x2,y2}. * * @param x1 * @param y1 * @param x2 * @param y2 * @param szx * @param szy * @return the annotation */ public static ArrowAnnotation createArrow(double x1, double y1, double x2, double y2, double szx, double szy) { return new ArrowAnnotation(x1, y1, x2, y2, szx, szy); } /** * * @param x1 * @param y1 * @param x2 * @param y2 * @param x3 * @param y3 * @param szx * @param szy * @return the annotation */ public static ArrowAnnotation createArrow(double x1, double y1, double x2, double y2, double x3, double y3, double szx, double szy) { return new ArrowAnnotation(x1, y1, x2, y2, x3, y3, szx, szy); } /** * Static method to create a line between {x1,y1} and {x2,y2}. * * @param x1 * @param y1 * @param x2 * @param y2 * @param x3 * @param y3 * @return the LineAnnotation */ public static LineAnnotation createLine(double x1, double y1, double x2, double y2, double x3, double y3) { return new LineAnnotation(x1, y1, x2, y2, x3, y3); } /** * Static method to create a rectangle where {X,Y} is the top left corner * and w and h are the width and height. * * @param x * @param y * @param w * @param h * @return a GJAnnotation$ShapeAnnotation */ public static ShapeAnnotation createBox(double x, double y, double w, double h) { return new ShapeAnnotation(new Rectangle2D.Double(x, y, w, h)); } /** * Static method to create an ellipse where {X,Y} is the top left corner and * w and h are the width and height of the bounding rectangle. * * @param x * @param y * @param w * @param h * @return a GJAnnotation$ShapeAnnotation */ public static ShapeAnnotation createEllipse(double x, double y, double w, double h) { Shape s = new Ellipse2D.Double(x, y, w, h); return new ShapeAnnotation(s); } /** * Creates an annotation using the specified object which should implement * the {code Shape} interface. * * @param s a Shape. * @return a GJAnnotation$ShapeAnnotation */ public static ShapeAnnotation createShape(Shape s) { return new ShapeAnnotation(s); } public static TextAnnotation createText(double x, double y, String s) { return new TextAnnotation(x, y, s); } /** * @return the textBackground */ public Paint getTextBackground() { return textBackground; } /** * @param textBackground the textBackground to set */ public void setTextBackground(Paint textBackground) { this.textBackground = textBackground; } /** * Internal class for line annotations */ public static class LineAnnotation extends GJAnnotation { /** * Default constructor */ public LineAnnotation() { } /** * Creates a line between the point x1,y1 and x2,y2. * * @param x1 * @param y1 * @param x2 * @param y2 */ public LineAnnotation(double x1, double y1, double x2, double y2) { Path2D p = new Path2D.Double(); p.moveTo(x1, y1); p.lineTo(x2, y2); setPath(p); } /** * Create a line using quadTo(x1,y1,x2,y2,x3,y3). * * @param x1 * @param y1 * @param x2 * @param y2 * @param x3 * @param y3 */ public LineAnnotation(double x1, double y1, double x2, double y2, double x3, double y3) { Path2D p = new Path2D.Double(); p.moveTo(x1, y1); p.quadTo(x2, y2, x3, y3); setPath(p); } } /** * Internal class for arrow annotations */ public static class ArrowAnnotation extends GJAnnotation { public ArrowAnnotation() { } public ArrowAnnotation(double x1, double y1, double x2, double y2, double szx, double szy) { Path2D p = new Path2D.Double(); p.moveTo(x1, y1); p.lineTo(x2, y2); setPath(p); double dX = x2 - x1; double dY = y2 - y1; Shape arrowhead = GJMarker.makeArrow(x2, y2, szx, szy, true); AffineTransform af = new AffineTransform(); af.setToRotation(Math.atan2(dY, dX), x2, y2); arrowhead = af.createTransformedShape(arrowhead); p.append(arrowhead, false); setExtra(new GJMarker(arrowhead)); } public ArrowAnnotation(double x1, double y1, double x2, double y2, double x3, double y3, double szx, double szy) { Path2D p = new Path2D.Double(); p.moveTo(x1, y1); p.quadTo(x2, y2, x3, y3); setPath(p); double dX = x3 - x2; double dY = y3 - y2; Shape arrowhead = GJMarker.makeArrow(x3, y3, szx, szy, true); AffineTransform af = new AffineTransform(); af.setToRotation(Math.atan2(dY, dX), x3, y3); arrowhead = af.createTransformedShape(arrowhead); p.append(arrowhead, false); setExtra(new GJMarker(arrowhead)); } } /** * Internal class for shape annotations */ public static class ShapeAnnotation extends GJAnnotation { public ShapeAnnotation() { } public ShapeAnnotation(Shape s) { setMarker(new GJMarker(s)); } } /** * Internal class for text annotations */ public static class TextAnnotation extends GJAnnotation { public TextAnnotation() { } public TextAnnotation(double x, double y, String text) { setXData(x); setYData(y); setText(text); } } }

Ads help maintain this website.