/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.graphs;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics;
/**
* This renderer is used for rendering the labels of the tree model nodes.
*
* @author Ingo Mierswa
*
* @param <V>
* the type for vertices
* @param <E>
* the type for edges
*/
public class TreeModelNodeLabelRenderer<V, E> implements Renderer.VertexLabel<V, E> {
/** Used for positioning the label inside of a node, */
public static class InsidePositioner implements Positioner {
@Override
public Position getPosition(float x, float y, Dimension d) {
int cx = d.width / 2;
int cy = d.height / 2;
if (x > cx && y > cy) {
return Position.NW;
}
if (x > cx && y < cy) {
return Position.SW;
}
if (x < cx && y > cy) {
return Position.NE;
}
return Position.SE;
}
}
/** Used for positioning the label outside of a node, */
public static class OutsidePositioner implements Positioner {
@Override
public Position getPosition(float x, float y, Dimension d) {
int cx = d.width / 2;
int cy = d.height / 2;
if (x > cx && y > cy) {
return Position.SE;
}
if (x > cx && y < cy) {
return Position.NE;
}
if (x < cx && y > cy) {
return Position.SW;
}
return Position.NW;
}
}
private static final int LEAF_LABEL_OFFSET_Y = -15;
protected Position position = Position.CNTR;
private Positioner positioner = new OutsidePositioner();
private TreeModelGraphCreator graphCreator;
public TreeModelNodeLabelRenderer(TreeModelGraphCreator graphCreator) {
this.graphCreator = graphCreator;
}
/**
* @return the position
*/
@Override
public Position getPosition() {
return position;
}
/**
* @param position
* the position to set
*/
@Override
public void setPosition(Position position) {
this.position = position;
}
public Component prepareRenderer(RenderContext<V, E> rc, VertexLabelRenderer graphLabelRenderer, Object value,
boolean isSelected, V vertex) {
return rc.getVertexLabelRenderer().<V> getVertexLabelRendererComponent(rc.getScreenDevice(), value,
rc.getVertexFontTransformer().transform(vertex), isSelected, vertex);
}
/**
* Labels the specified vertex with the specified label. Uses the font specified by this
* instance's <code>VertexFontFunction</code>. (If the font is unspecified, the existing font
* for the graphics context is used.) If vertex label centering is active, the label is centered
* on the position of the vertex; otherwise the label is offset slightly.
*/
@Override
public void labelVertex(RenderContext<V, E> rc, Layout<V, E> layout, V v, String label) {
Graph<V, E> graph = layout.getGraph();
if (rc.getVertexIncludePredicate().evaluate(Context.<Graph<V, E>, V> getInstance(graph, v)) == false) {
return;
}
Point2D pt = layout.transform(v);
pt = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, pt);
float x = (float) pt.getX();
float y = (float) pt.getY();
Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), label, rc.getPickedVertexState().isPicked(v),
v);
GraphicsDecorator g = rc.getGraphicsContext();
Dimension d = component.getPreferredSize();
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
Shape shape = rc.getVertexShapeTransformer().transform(v);
shape = xform.createTransformedShape(shape);
if (rc.getGraphicsContext() instanceof TransformingGraphics) {
BidirectionalTransformer transformer = ((TransformingGraphics) rc.getGraphicsContext()).getTransformer();
if (transformer instanceof ShapeTransformer) {
ShapeTransformer shapeTransformer = (ShapeTransformer) transformer;
shape = shapeTransformer.transform(shape);
}
}
Rectangle2D bounds = shape.getBounds2D();
Point p = null;
if (position == Position.AUTO) {
Dimension vvd = rc.getScreenDevice().getSize();
if (vvd.width == 0 || vvd.height == 0) {
vvd = rc.getScreenDevice().getPreferredSize();
}
p = getAnchorPoint(bounds, d, positioner.getPosition(x, y, vvd));
} else {
p = getAnchorPoint(bounds, d, position);
}
if (graphCreator.isLeaf((String) v)) {
p.setLocation(p.x, p.y + LEAF_LABEL_OFFSET_Y);
}
g.draw(component, rc.getRendererPane(), p.x, p.y, d.width, d.height, true);
}
protected Point getAnchorPoint(Rectangle2D vertexBounds, Dimension labelSize, Position position) {
double x;
double y;
int offset = 5;
switch (position) {
case N:
x = vertexBounds.getCenterX() - labelSize.width / 2.0d;
y = vertexBounds.getMinY() - offset - labelSize.height;
return new Point((int) x, (int) y);
case NE:
x = vertexBounds.getMaxX() + offset;
y = vertexBounds.getMinY() - offset - labelSize.height;
return new Point((int) x, (int) y);
case E:
x = vertexBounds.getMaxX() + offset;
y = vertexBounds.getCenterY() - labelSize.height / 2.0d;
return new Point((int) x, (int) y);
case SE:
x = vertexBounds.getMaxX() + offset;
y = vertexBounds.getMaxY() + offset;
return new Point((int) x, (int) y);
case S:
x = vertexBounds.getCenterX() - labelSize.width / 2.0d;
y = vertexBounds.getMaxY() + offset;
return new Point((int) x, (int) y);
case SW:
x = vertexBounds.getMinX() - offset - labelSize.width;
y = vertexBounds.getMaxY() + offset;
return new Point((int) x, (int) y);
case W:
x = vertexBounds.getMinX() - offset - labelSize.width;
y = vertexBounds.getCenterY() - labelSize.height / 2.0d;
return new Point((int) x, (int) y);
case NW:
x = vertexBounds.getMinX() - offset - labelSize.width;
y = vertexBounds.getMinY() - offset - labelSize.height;
return new Point((int) x, (int) y);
case CNTR:
x = vertexBounds.getCenterX() - labelSize.width / 2.0d;
y = vertexBounds.getCenterY() - labelSize.height / 2.0d;
return new Point((int) x, (int) y);
default:
return new Point();
}
}
/**
* @return the positioner
*/
@Override
public Positioner getPositioner() {
return positioner;
}
/**
* @param positioner
* the positioner to set
*/
@Override
public void setPositioner(Positioner positioner) {
this.positioner = positioner;
}
}