/*
* NodeShapePainter.java
*
* Copyright (C) 2006-2014 Andrew Rambaut
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package figtree.treeviewer.painters;
import figtree.treeviewer.TreePane;
import figtree.treeviewer.decorators.ContinuousScale;
import figtree.treeviewer.decorators.Decorator;
import jebl.evolution.graphs.Node;
import java.awt.*;
import java.awt.geom.*;
/**
* @author Andrew Rambaut
* @version $Id$
*
* $HeadURL$
*
* $LastChangedBy$
* $LastChangedDate$
* $LastChangedRevision$
*/
public class NodeShapePainter extends NodePainter {
public static final String FIXED = "Fixed";
public static final double MAX_SIZE = 4.0;
public static final double MIN_SIZE = 0.0;
public enum ShapeType {
CIRCLE("Circle"),
RECTANGLE("Rectangle"),
DIAMOND("Diamond");
ShapeType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
private final String name;
}
public enum ScaleType {
WIDTH("Width"),
AREA("Area");
ScaleType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
private final String name;
}
public NodeShapePainter() {
}
public void setTreePane(TreePane treePane) {
this.treePane = treePane;
}
public Rectangle2D calibrate(Graphics2D g2, Node node) {
Line2D shapePath = treePane.getTreeLayoutCache().getNodeShapePath(node);
if (shapePath != null) {
double x1 = shapePath.getX1();
double y1 = shapePath.getY1();
Shape nodeShape = createNodeShape(node, x1, y1);
if (nodeShape != null) {
return nodeShape.getBounds2D();
}
}
return null;
}
public void setShapeType(ShapeType shapeType) {
this.shapeType = shapeType;
firePainterChanged();
}
public void setScaleType(ScaleType scaleType) {
this.scaleType = scaleType;
firePainterChanged();
}
public void setNodeType(boolean external, boolean internal) {
this.external = external;
this.internal = internal;
firePainterChanged();
}
public boolean isExternal() {
return external;
}
public boolean isInternal() {
return internal;
}
public double getPreferredWidth() {
return 1.0;
}
public double getPreferredHeight() {
return 1.0;
}
public double getHeightBound() {
return 1.0;
}
/**
* The bounds define the shapeType of the nodeBar so just draw it
* @param g2
* @param node
*/
public void paint(Graphics2D g2, Node node, Point2D point, AffineTransform transform) {
Shape nodeShape = createNodeShape(node, point.getX(), point.getY());
nodeShape = transform.createTransformedShape(nodeShape);
Paint paint = getForeground();
if (colourDecorator != null) {
colourDecorator.setItem(node);
paint = colourDecorator.getPaint(paint);
}
g2.setPaint(paint);
g2.fill(nodeShape);
if (outlineStroke > 0.0F) {
g2.setPaint(outlinePaint);
g2.setStroke(new BasicStroke(outlineStroke));
g2.draw(nodeShape);
}
}
/**
* The bounds define the shapeType of the nodeBar so just draw it
* @param g2
* @param node
* @param justification
* @param bounds
*/
public void paint(Graphics2D g2, Node node, Justification justification, Rectangle2D bounds) {
throw new UnsupportedOperationException("This version of paint is not used in NodeShapePainter");
}
public void setMaxSize(double maxSize) {
this.maxSize = maxSize;
firePainterChanged();
}
public void setMinSize(double minSize) {
this.minSize = minSize;
firePainterChanged();
}
public void setSizeAttribute(String sizeAttribute) {
this.sizeAttribute = sizeAttribute;
sizeScale = new ContinuousScale(sizeAttribute, treePane.getTree().getNodes());
firePainterChanged();
}
public void setColourDecorator(Decorator colourDecorator) {
this.colourDecorator = colourDecorator;
firePainterChanged();
}
public void setOutline(final float outlineStroke, final Paint outlinePaint) {
this.outlineStroke = outlineStroke;
this.outlinePaint = outlinePaint;
firePainterChanged();
}
private Shape createNodeShape(Node node, double x, double y) {
double size = maxSize;
if (sizeAttribute != null && !sizeAttribute.equals(FIXED)) {
Object valueObject = node.getAttribute(sizeAttribute);
double value = sizeScale.getValue(valueObject);
if (scaleType == ScaleType.AREA) {
double minArea = getShapeArea(minSize);
double maxArea = getShapeArea(maxSize + minSize);
size = getShapeWidth(((maxArea - minArea) * value) + minArea);
} else {
size = minSize + (maxSize * value);
}
}
if (size > 0.0) {
switch (shapeType) {
case CIRCLE:
return new Ellipse2D.Double(x - (size * 0.5), y - (size * 0.5), size, size);
case RECTANGLE:
return new Rectangle2D.Double(x - (size * 0.5), y - (size * 0.5), size, size);
case DIAMOND:
Shape shape = new Rectangle2D.Double(x - (size * 0.5), y - (size * 0.5), size, size);
return AffineTransform.getRotateInstance(Math.PI / 4.0, x, y).createTransformedShape(shape);
}
throw new IllegalArgumentException("Unknown node shapeType type");
}
return null;
}
private double getShapeArea(double width) {
switch (shapeType) {
case CIRCLE:
double radius = width * 0.5;
return Math.PI * radius * radius;
case RECTANGLE:
case DIAMOND:
return width * width;
}
throw new IllegalArgumentException("Unknown node shapeType type");
}
private double getShapeWidth(double area) {
switch (shapeType) {
case CIRCLE:
return 2.0 * Math.sqrt(area / Math.PI);
case RECTANGLE:
case DIAMOND:
return Math.sqrt(area);
}
throw new IllegalArgumentException("Unknown node shapeType type");
}
private double maxSize = MAX_SIZE;
private double minSize = MIN_SIZE;
private ShapeType shapeType = ShapeType.CIRCLE;
private ScaleType scaleType = ScaleType.WIDTH;
private String sizeAttribute = null;
private boolean external = true;
private boolean internal = true;
private Decorator colourDecorator = null;
private ContinuousScale sizeScale = null;
private float outlineStroke = 0.5f;
private Paint outlinePaint = Color.black;
private TreePane treePane;
}