/*
* NanoGraph, a small footprint java graph drawing component
*
* Copyright 2004 Jeroen van Grondelle
* 2013 Xander Uiterlinden
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.nanograph.drawing;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.nanograph.drawing.background.Background;
import org.nanograph.drawing.docking.DockingStrategy;
import org.nanograph.drawing.docking.IntersectionDockingStrategy;
import org.nanograph.drawing.edgerenderer.ArrowEdgeRenderer;
import org.nanograph.drawing.edgerenderer.EdgeRenderer;
import org.nanograph.drawing.graphicsadapter.GraphicsAdapter;
import org.nanograph.drawing.layout.LayoutAlgorithm;
import org.nanograph.drawing.noderenderer.DefaultNodeRenderer;
import org.nanograph.drawing.noderenderer.NodeRenderer;
import org.nanograph.model.GraphModel;
/**
* The NanoGraph class implements the actual graph drawing for all NanoGraph
* components, by inspecting the GraphModel interface and invoking edge and node
* renderers.
*
* @author Jeroen van Grondelle
*/
public class NanoGraph {
private GraphModel model = null;
private List highlightNodeSet = new ArrayList();
private LayoutAlgorithm layoutAlgorithm = null;
private Background background = null;
private NodeRenderer defaultNodeRenderer = new DefaultNodeRenderer();
private HashMap nodeRenderers = new HashMap();
private HashMap dockingStrategies = new HashMap();
private HashMap edgeRenderers = new HashMap();
private EdgeRenderer defaultEdgeRenderer = new ArrowEdgeRenderer();
private DockingStrategy defaultDockingStrategy = new IntersectionDockingStrategy();
private Object selectedEdge = null;
private boolean renderEdges = true;
private boolean renderEdgesBehindNodes = true;
/**
* @param renderEdges
* The renderEdges to set.
*/
public void setRenderEdges(boolean renderEdges) {
this.renderEdges = renderEdges;
}
public NanoGraph() {
}
public List getHighlightNodeSet() {
return highlightNodeSet;
}
public void setHighlightNodeSet(List highlightNodeSet) {
this.highlightNodeSet = highlightNodeSet;
}
public Object getSelectedEdge() {
return selectedEdge;
}
public void setSelectedEdge(Object selectedEdge) {
this.selectedEdge = selectedEdge;
}
public void setModel(GraphModel model) {
this.model = model;
if (layoutAlgorithm != null) {
layoutAlgorithm.layout(this);
}
}
public GraphModel getModel() {
return model;
}
public void setLayout(LayoutAlgorithm layout) {
this.layoutAlgorithm = layout;
if (model != null && layout != null) {
layoutAlgorithm.layout(this);
}
}
public void paintGraph(GraphicsAdapter gc) {
// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
// RenderingHints.VALUE_ANTIALIAS_ON);
if (model == null) {
return;
}
calculateBounds(gc);
if (background != null) {
background.drawBackground(gc, getGraphWidth(), getGraphHeight());
}
if (renderEdges && renderEdgesBehindNodes) {
for (int t = 0; t < model.getNodeCount(); t++) {
paintEdgesForNode(gc, model.getNode(t));
}
}
Object node = null;
// render nodes
for (int t = 0; t < model.getNodeCount(); t++) {
node = model.getNode(t);
if (highlightNodeSet != null && highlightNodeSet.contains(node)) {
getNodeRenderer(node).renderSelected(gc, node, model.getLocation(node));
} else {
getNodeRenderer(node).render(gc, node, model.getLocation(node));
}
if (renderEdges && !renderEdgesBehindNodes) {
paintEdgesForNode(gc, node);
}
}
}
private void paintEdgesForNode(GraphicsAdapter gc, Object node) {
Object destinationNode;
Object edge;
Object type;
Point2D fromLocation;
Point2D toLocation;
Point2D dockingPointStart;
Point2D dockingPointEnd;
for (int r = 0; r < model.getEdgeTypeCount(node); r++) {
type = model.getEdgeType(node, r);
for (int s = 0; s < model.getEdgeCount(node, type); s++) {
edge = model.getEdge(node, type, s);
fromLocation = model.getLocation(node);
destinationNode = model.getDestinationNode(node, type, s);
toLocation = model.getLocation(destinationNode);
dockingPointStart = getDockingStrategy(node).getDockingPoint(getNodeRenderer(node).getNodeBounds(gc, node, fromLocation), toLocation);
dockingPointEnd = getDockingStrategy(destinationNode).getDockingPoint(getNodeRenderer(destinationNode).getNodeBounds(gc, destinationNode, toLocation), model.getLocation(node));
if (edge.equals(selectedEdge)) {
getEdgeRenderer(edge).renderSelected(gc, edge, dockingPointStart, dockingPointEnd);
} else {
getEdgeRenderer(edge).render(gc, edge, dockingPointStart, dockingPointEnd);
}
}
}
}
public void paintOutline(GraphicsAdapter gc) {
if (model == null) {
return;
}
calculateBounds(gc);
Object node = null, type = null;
for (int t = 0; t < model.getNodeCount(); t++) {
node = model.getNode(t);
for (int r = 0; r < model.getEdgeTypeCount(node); r++) {
type = model.getEdgeType(node, r);
for (int s = 0; s < model.getEdgeCount(node, type); s++) {
// draw line
Point2D from = model.getLocation(node);
Point2D to = model.getLocation(model.getDestinationNode(node, type, s));
gc.drawLine(from.getX(), from.getY(), to.getX(), to.getY());
}
}
}
for (int t = 0; t < model.getNodeCount(); t++) {
node = model.getNode(t);
Rectangle2D rect = getNodeBounds(node);
if (rect != null) {
gc.fillRectangle(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
}
}
// //////////////////////////////
// renderers
public void registerNodeRenderer(Class c, NodeRenderer r) {
nodeRenderers.put(c, r);
}
public void registerEdgeRenderer(Class c, EdgeRenderer e) {
edgeRenderers.put(c, e);
}
public void registerDockingStrategy(Class c, DockingStrategy s) {
dockingStrategies.put(c, s);
}
public void removeAllRegisteredDockingStrategies() {
dockingStrategies.clear();
}
private DockingStrategy getDockingStrategy(Object node) {
if (dockingStrategies.get(node.getClass()) == null)
return defaultDockingStrategy;
else
return (DockingStrategy) dockingStrategies.get(node.getClass());
}
private NodeRenderer getNodeRenderer(Object node) {
if (nodeRenderers.get(node.getClass()) == null)
return defaultNodeRenderer;
else
return (NodeRenderer) nodeRenderers.get(node.getClass());
}
private EdgeRenderer getEdgeRenderer(Object edge) {
EdgeRenderer result = (EdgeRenderer) edgeRenderers.get(edge.getClass());
return (result != null ? result : defaultEdgeRenderer);
}
// ////////////////////////////////////
// Bounds functions
Map bounds = new HashMap();
int width = 0, height = 0;
/**
* Refresh all node bounds in the bounds cache
*
* @param g
*/
public void calculateBounds(GraphicsAdapter g) {
bounds.clear();
Rectangle2D r = null;
width = 0;
height = 0;
for (int t = 0; t < model.getNodeCount(); t++) {
// calculate bound
Object n = model.getNode(t);
r = getNodeRenderer(n).getNodeBounds(g, n, model.getLocation(n));
// storfe bound in cache
bounds.put(n, r);
// update graph size
if (r.getMaxX() + 25 > width)
width = (int) r.getMaxX() + 25;
if (r.getMaxY() + 25 > height)
height = (int) r.getMaxY() + 25;
}
if (background != null && getBackground().getBounds(g) != null) {
if (getBackground().getBounds(g).getWidth() > width) {
width = (int) getBackground().getBounds(g).getWidth();
}
if (getBackground().getBounds(g).getHeight() > height) {
height = (int) getBackground().getBounds(g).getHeight();
}
}
}
public Rectangle2D getNodeBounds(Object node) {
Object o = bounds.get(node);
return o != null ? (Rectangle2D) o : null;
}
public Object getNodeForLocation(Point2D p2d) {
for (int t = model.getNodeCount(); t > 0; t--) {
Object n = model.getNode(t - 1);
if (getNodeBounds(n) != null) {
if (getNodeBounds(n).contains(p2d)) {
return n;
}
}
}
return null;
}
public Collection getNodesForBounds(Rectangle2D box) {
Collection result = new HashSet();
for (int t = 0; t < model.getNodeCount(); t++) {
Object n = model.getNode(t);
Rectangle2D nodeBounds = getNodeBounds(n);
if (nodeBounds != null && box.intersects(nodeBounds)) {
result.add(n);
}
}
return result;
}
public Rectangle2D getBoundsForNodes(Collection nodes) {
if (nodes.isEmpty())
return null;
Iterator iter = nodes.iterator();
Rectangle2D result = getNodeBounds(iter.next());
while (iter.hasNext()) {
Object n = iter.next();
Rectangle2D boundsForNode = getNodeBounds(n);
if (boundsForNode != null) {
result.add(getNodeBounds(n));
}
}
return result;
}
public int getGraphWidth() {
return width;
}
public int getGraphHeight() {
return height;
}
// ////////////////////////
// edge selection
/**
* Returns the edge closest to the point
*
* @param p2d
* @return edge closest to p2d
*/
public Object getEdgeForLocation(Point2D p2d) {
for (int t = 0; t < model.getNodeCount(); t++) {
Object fromNode = model.getNode(t);
Point2D centerOfFrom = getCenterOfNode(fromNode);
for (int r = 0; r < model.getEdgeTypeCount(fromNode); r++) {
Object type = model.getEdgeType(fromNode, r);
for (int s = 0; s < model.getEdgeCount(fromNode, type); s++) {
Object edge = model.getEdge(fromNode, type, s);
Object destinationNode = model.getDestinationNode(fromNode, type, s);
Point2D centerOfTo = getCenterOfNode(destinationNode);
Rectangle2D nodeBounds = getNodeBounds(fromNode);
if (nodeBounds != null) {
Point2D from = getDockingStrategy(fromNode).getDockingPoint(nodeBounds, centerOfTo);
Point2D to = getDockingStrategy(destinationNode).getDockingPoint(getNodeBounds(destinationNode), centerOfFrom);
if (distFromEdgeToPoint(from, to, p2d) < 10) {
return edge;
}
}
}
}
}
return null;
}
/**
* @param theNode
* @return Point2D of the center of the node
*/
private Point2D getCenterOfNode(Object theNode) {
if (theNode != null) {
Rectangle2D nodeBounds = getNodeBounds(theNode);
if (nodeBounds != null) {
double centerXOfFromNode = nodeBounds.getX() + (nodeBounds.getWidth() / 2);
double centerYOfFromNode = nodeBounds.getY() + (nodeBounds.getHeight() / 2);
return new Point2D.Double(centerXOfFromNode, centerYOfFromNode);
}
}
return new Point2D.Double(0.0d, 0.0d);
}
/**
* @param from
* edge starting point
* @param to
* edge ending point
* @param p2d
* point from which to calculate the distance
* @return the distance from edge to point
*
*/
public double distFromEdgeToPoint(Point2D from, Point2D to, Point2D p2d) {
double x1 = from.getX();
double y1 = from.getY();
double x2 = to.getX();
double y2 = to.getY();
double px = p2d.getX();
double py = p2d.getY();
if (px < Math.min(x1, x2) - 8 || px > Math.max(x1, x2) + 8 || py < Math.min(y1, y2) - 8 || py > Math.max(y1, y2) + 8) {
return Double.MAX_VALUE;
}
double dist = Double.MAX_VALUE;
if (x1 - x2 != 0)
dist = Math.abs((y2 - y1) / (x2 - x1) * (px - x1) + (y1 - py));
if (y1 - y2 != 0)
dist = Math.min(dist, Math.abs((x2 - x1) / (y2 - y1) * (py - y1) + (x1 - px)));
return dist;
}
// ////////////////////////
// getters and setters
public Background getBackground() {
return background;
}
public void setBackground(Background background) {
this.background = background;
}
public DockingStrategy getDefaultDockingStrategy() {
return defaultDockingStrategy;
}
public void setDefaultDockingStrategy(DockingStrategy defaultDockingStrategy) {
this.defaultDockingStrategy = defaultDockingStrategy;
}
public EdgeRenderer getDefaultEdgeRenderer() {
return defaultEdgeRenderer;
}
public void setDefaultEdgeRenderer(EdgeRenderer defaultEdgeRenderer) {
this.defaultEdgeRenderer = defaultEdgeRenderer;
}
public NodeRenderer getDefaultNodeRenderer() {
return defaultNodeRenderer;
}
public void setDefaultNodeRenderer(NodeRenderer defaultNodeRenderer) {
this.defaultNodeRenderer = defaultNodeRenderer;
}
public Collection getNodes() {
return bounds.keySet();
}
public boolean isRenderEdgesBehindNodes() {
return renderEdgesBehindNodes;
}
public void setRenderEdgesBehindNodes(boolean renderEdgesBehindNodes) {
this.renderEdgesBehindNodes = renderEdgesBehindNodes;
}
}