edu.uci.ics.jung.visualization.picking.ShapePickSupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jung-visualization Show documentation
Show all versions of jung-visualization Show documentation
Core visualization support for the JUNG project
The newest version!
/*
* Copyright (c) 2005, The JUNG Authors
*
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
* Created on Mar 11, 2005
*
*/
package edu.uci.ics.jung.visualization.picking;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
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.graph.util.Pair;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationServer;
/**
* A GraphElementAccessor
that returns elements whose Shape
* contains the specified pick point or region.
*
* @author Tom Nelson
*
*/
public class ShapePickSupport implements GraphElementAccessor {
/**
* The available picking heuristics:
*
* Style.CENTERED
: returns the element whose
* center is closest to the pick point.
* Style.LOWEST
: returns the first such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the bottom",
* that is, the one which is rendered first.)
* Style.HIGHEST
: returns the last such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the top",
* that is, the one which is rendered last.)
*
*
*/
public static enum Style { LOWEST, CENTERED, HIGHEST };
protected float pickSize;
/**
* The VisualizationServer
in which the
* this instance is being used for picking. Used to
* retrieve properties such as the layout, renderer,
* vertex and edge shapes, and coordinate transformations.
*/
protected VisualizationServer vv;
/**
* The current picking heuristic for this instance. Defaults
* to CENTERED
.
*/
protected Style style = Style.CENTERED;
/**
* Creates a ShapePickSupport
for the vv
* VisualizationServer, with the specified pick footprint and
* the default pick style.
* The VisualizationServer
is used to access
* properties of the current visualization (layout, renderer,
* coordinate transformations, vertex/edge shapes, etc.).
* @param vv source of the current Layout
.
* @param pickSize the size of the pick footprint for line edges
*/
public ShapePickSupport(VisualizationServer vv, float pickSize) {
this.vv = vv;
this.pickSize = pickSize;
}
/**
* Create a ShapePickSupport
for the specified
* VisualizationServer
with a default pick footprint.
* of size 2.
* @param vv the visualization server used for rendering
*/
public ShapePickSupport(VisualizationServer vv) {
this.vv = vv;
this.pickSize = 2;
}
/**
* Returns the style of picking used by this instance.
* This specifies which of the elements, among those
* whose shapes contain the pick point, is returned.
* The available styles are:
*
* Style.CENTERED
: returns the element whose
* center is closest to the pick point.
* Style.LOWEST
: returns the first such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the bottom",
* that is, the one which is rendered first.)
* Style.HIGHEST
: returns the last such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the top",
* that is, the one which is rendered last.)
*
*
* @return the style of picking used by this instance
*/
public Style getStyle() {
return style;
}
/**
* Specifies the style of picking to be used by this instance.
* This specifies which of the elements, among those
* whose shapes contain the pick point, will be returned.
* The available styles are:
*
* Style.CENTERED
: returns the element whose
* center is closest to the pick point.
* Style.LOWEST
: returns the first such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the bottom",
* that is, the one which is rendered first.)
* Style.HIGHEST
: returns the last such element
* encountered. (If the element collection has a consistent
* ordering, this will also be the element "on the top",
* that is, the one which is rendered last.)
*
* @param style the style to set
*/
public void setStyle(Style style) {
this.style = style;
}
/**
* Returns the vertex, if any, whose shape contains (x, y).
* If (x,y) is contained in more than one vertex's shape, returns
* the vertex whose center is closest to the pick point.
*
* @param layout the layout instance that records the positions for all vertices
* @param x the x coordinate of the pick point
* @param y the y coordinate of the pick point
* @return the vertex whose shape contains (x,y), and whose center is closest to the pick point
*/
public V getVertex(Layout layout, double x, double y) {
V closest = null;
double minDistance = Double.MAX_VALUE;
Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW,
new Point2D.Double(x,y));
x = ip.getX();
y = ip.getY();
while(true) {
try {
for(V v : getFilteredVertices(layout)) {
Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
// get the vertex location
Point2D p = layout.apply(v);
if(p == null) continue;
// transform the vertex location to screen coords
p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
double ox = x - p.getX();
double oy = y - p.getY();
if(shape.contains(ox, oy)) {
if(style == Style.LOWEST) {
// return the first match
return v;
} else if(style == Style.HIGHEST) {
// will return the last match
closest = v;
} else {
// return the vertex closest to the
// center of a vertex shape
Rectangle2D bounds = shape.getBounds2D();
double dx = bounds.getCenterX() - ox;
double dy = bounds.getCenterY() - oy;
double dist = dx * dx + dy * dy;
if (dist < minDistance) {
minDistance = dist;
closest = v;
}
}
}
}
break;
} catch(ConcurrentModificationException cme) {}
}
return closest;
}
/**
* Returns the vertices whose layout coordinates are contained in
* Shape
.
* The shape is in screen coordinates, and the graph vertices
* are transformed to screen coordinates before they are tested
* for inclusion.
* @return the Collection
of vertices whose layout
* coordinates are contained in shape
.
*/
public Collection getVertices(Layout layout, Shape shape) {
Set pickedVertices = new HashSet();
// remove the view transform from the rectangle
shape = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, shape);
while(true) {
try {
for(V v : getFilteredVertices(layout)) {
Point2D p = layout.apply(v);
if(p == null) continue;
p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
if(shape.contains(p)) {
pickedVertices.add(v);
}
}
break;
} catch(ConcurrentModificationException cme) {}
}
return pickedVertices;
}
/**
* Returns an edge whose shape intersects the 'pickArea' footprint of the passed
* x,y, coordinates.
*
* @param layout the context in which the location is defined
* @param x the x coordinate of the location
* @param y the y coordinate of the location
* @return an edge whose shape intersects the pick area centered on the location {@code (x,y)}
*/
public E getEdge(Layout layout, double x, double y) {
Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y));
x = ip.getX();
y = ip.getY();
// as a Line has no area, we can't always use edgeshape.contains(point) so we
// make a small rectangular pickArea around the point and check if the
// edgeshape.intersects(pickArea)
Rectangle2D pickArea =
new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
E closest = null;
double minDistance = Double.MAX_VALUE;
while(true) {
try {
for(E e : getFilteredEdges(layout)) {
Shape edgeShape = getTransformedEdgeShape(layout, e);
if (edgeShape == null)
continue;
// because of the transform, the edgeShape is now a GeneralPath
// see if this edge is the closest of any that intersect
if(edgeShape.intersects(pickArea)) {
float cx=0;
float cy=0;
float[] f = new float[6];
PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null);
if(pi.isDone()==false) {
pi.next();
pi.currentSegment(f);
cx = f[0];
cy = f[1];
if(pi.isDone()==false) {
pi.currentSegment(f);
cx = f[0];
cy = f[1];
}
}
float dx = (float) (cx - x);
float dy = (float) (cy - y);
float dist = dx * dx + dy * dy;
if (dist < minDistance) {
minDistance = dist;
closest = e;
}
}
}
break;
} catch(ConcurrentModificationException cme) {}
}
return closest;
}
/**
* Retrieves the shape template for e
and
* transforms it according to the positions of its endpoints
* in layout
.
* @param layout the Layout
which specifies
* e
's endpoints' positions
* @param e the edge whose shape is to be returned
* @return the transformed shape
*/
private Shape getTransformedEdgeShape(Layout layout, E e) {
Pair pair = layout.getGraph().getEndpoints(e);
V v1 = pair.getFirst();
V v2 = pair.getSecond();
boolean isLoop = v1.equals(v2);
Point2D p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v1));
Point2D p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v2));
if(p1 == null || p2 == null)
return null;
float x1 = (float) p1.getX();
float y1 = (float) p1.getY();
float x2 = (float) p2.getX();
float y2 = (float) p2.getY();
// translate the edge to the starting vertex
AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e);
if(isLoop) {
// make the loops proportional to the size of the vertex
Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2);
Rectangle2D s2Bounds = s2.getBounds2D();
xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
// move the loop so that the nadir is centered in the vertex
xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
} else {
float dx = x2 - x1;
float dy = y2 - y1;
// rotate the edge to the angle between the vertices
double theta = Math.atan2(dy,dx);
xform.rotate(theta);
// stretch the edge to span the distance between the vertices
float dist = (float) Math.sqrt(dx*dx + dy*dy);
xform.scale(dist, 1.0f);
}
// transform the edge to its location and dimensions
edgeShape = xform.createTransformedShape(edgeShape);
return edgeShape;
}
protected Collection getFilteredVertices(Layout layout) {
if(verticesAreFiltered()) {
Collection unfiltered = layout.getGraph().getVertices();
Collection filtered = new LinkedHashSet();
for(V v : unfiltered) {
if(isVertexRendered(Context.,V>getInstance(layout.getGraph(),v))) {
filtered.add(v);
}
}
return filtered;
} else {
return layout.getGraph().getVertices();
}
}
protected Collection getFilteredEdges(Layout layout) {
if(edgesAreFiltered()) {
Collection unfiltered = layout.getGraph().getEdges();
Collection filtered = new LinkedHashSet();
for(E e : unfiltered) {
if(isEdgeRendered(Context.,E>getInstance(layout.getGraph(),e))) {
filtered.add(e);
}
}
return filtered;
} else {
return layout.getGraph().getEdges();
}
}
/**
* Quick test to allow optimization of getFilteredVertices()
.
* @return true
if there is an active vertex filtering
* mechanism for this visualization, false
otherwise
*/
protected boolean verticesAreFiltered() {
Predicate,V>> vertexIncludePredicate =
vv.getRenderContext().getVertexIncludePredicate();
return vertexIncludePredicate != null &&
vertexIncludePredicate.equals(Predicates.alwaysTrue()) == false;
}
/**
* Quick test to allow optimization of getFilteredEdges()
.
* @return true
if there is an active edge filtering
* mechanism for this visualization, false
otherwise
*/
protected boolean edgesAreFiltered() {
Predicate,E>> edgeIncludePredicate =
vv.getRenderContext().getEdgeIncludePredicate();
return edgeIncludePredicate != null &&
edgeIncludePredicate.equals(Predicates.alwaysTrue()) == false;
}
/**
* Returns true
if this vertex in this graph is included
* in the collections of elements to be rendered, and false
otherwise.
* @param context the vertex and graph to be queried
* @return true
if this vertex is
* included in the collections of elements to be rendered, false
* otherwise.
*/
protected boolean isVertexRendered(Context,V> context) {
Predicate,V>> vertexIncludePredicate =
vv.getRenderContext().getVertexIncludePredicate();
return vertexIncludePredicate == null || vertexIncludePredicate.apply(context);
}
/**
* Returns true
if this edge and its endpoints
* in this graph are all included in the collections of
* elements to be rendered, and false
otherwise.
* @param context the edge and graph to be queried
* @return true
if this edge and its endpoints are all
* included in the collections of elements to be rendered, false
* otherwise.
*/
protected boolean isEdgeRendered(Context,E> context) {
Predicate,V>> vertexIncludePredicate =
vv.getRenderContext().getVertexIncludePredicate();
Predicate,E>> edgeIncludePredicate =
vv.getRenderContext().getEdgeIncludePredicate();
Graph g = context.graph;
E e = context.element;
boolean edgeTest = edgeIncludePredicate == null || edgeIncludePredicate.apply(context);
Pair endpoints = g.getEndpoints(e);
V v1 = endpoints.getFirst();
V v2 = endpoints.getSecond();
boolean endpointsTest = vertexIncludePredicate == null ||
(vertexIncludePredicate.apply(Context.,V>getInstance(g,v1)) &&
vertexIncludePredicate.apply(Context.,V>getInstance(g,v2)));
return edgeTest && endpointsTest;
}
/**
* Returns the size of the edge picking area.
* The picking area is square; the size is specified as the length of one
* side, in view coordinates.
* @return the size of the edge picking area
*/
public float getPickSize() {
return pickSize;
}
/**
* Sets the size of the edge picking area.
* @param pickSize the length of one side of the (square) picking area, in view coordinates
*/
public void setPickSize(float pickSize) {
this.pickSize = pickSize;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy