blob: 1205953f4c3c130e701d9848497f6fbe221f49b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* 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 2.1 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.workbench.models.graph.dot;
import static java.lang.Float.parseFloat;
import static net.sf.taverna.t2.workbench.models.graph.Graph.Alignment.HORIZONTAL;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import net.sf.taverna.t2.workbench.models.graph.Graph;
import net.sf.taverna.t2.workbench.models.graph.GraphController;
import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
import net.sf.taverna.t2.workbench.models.graph.GraphElement;
import net.sf.taverna.t2.workbench.models.graph.GraphNode;
import org.apache.log4j.Logger;
/**
* Lays out a graph from a DOT layout.
*
* @author David Withers
*/
public class GraphLayout implements DOTParserVisitor {
private static final Logger logger = Logger.getLogger(GraphLayout.class);
private static final int BORDER = 10;
private Rectangle bounds;
private Rectangle requiredBounds;
private GraphController graphController;
private int xOffset;
private int yOffset;
public Rectangle layoutGraph(GraphController graphController, Graph graph,
String laidOutDot, Rectangle requiredBounds) throws ParseException {
this.graphController = graphController;
this.requiredBounds = requiredBounds;
bounds = null;
xOffset = 0;
yOffset = 0;
logger.debug(laidOutDot);
DOTParser parser = new DOTParser(new StringReader(laidOutDot));
parser.parse().jjtAccept(this, graph);
// int xOffset = (bounds.width - bounds.width) / 2;
// int yOffset = (bounds.height - bounds.height) / 2;
return new Rectangle(xOffset, yOffset, bounds.width, bounds.height);
}
@Override
public Object visit(SimpleNode node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTParse node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTGraph node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTStatementList node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTStatement node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTAttributeStatement node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTNodeStatement node, Object data) {
GraphElement element = graphController.getElement(removeQuotes(node
.getName()));
if (element != null)
return node.childrenAccept(this, element);
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTNodeId node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTPort node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTEdgeStatement node, Object data) {
StringBuilder id = new StringBuilder();
id.append(removeQuotes(node.getName()));
if (node.getPort() != null) {
id.append(":");
id.append(removeQuotes(node.getPort()));
}
if (node.children != null)
for (Node child : node.children)
if (child instanceof ASTEdgeRHS) {
NamedNode rhsNode = (NamedNode) child.jjtAccept(this, data);
id.append("->");
id.append(removeQuotes(rhsNode.getName()));
if (rhsNode.getPort() != null) {
id.append(":");
id.append(removeQuotes(rhsNode.getPort()));
}
}
GraphElement element = graphController.getElement(id.toString());
if (element != null)
return node.childrenAccept(this, element);
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTSubgraph node, Object data) {
GraphElement element = graphController.getElement(removeQuotes(
node.getName()).substring("cluster_".length()));
if (element != null)
return node.childrenAccept(this, element);
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTEdgeRHS node, Object data) {
return node;
}
@Override
public Object visit(ASTAttributeList node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTAList node, Object data) {
if (data instanceof Graph) {
Graph graph = (Graph) data;
if ("bb".equalsIgnoreCase(node.getName())) {
Rectangle rect = getRectangle(node.getValue());
if (rect.width == 0 && rect.height == 0) {
rect.width = 500;
rect.height = 500;
}
if (bounds == null) {
bounds = calculateBounds(rect);
rect = bounds;
}
graph.setSize(rect.getSize());
graph.setPosition(rect.getLocation());
} else if ("lp".equalsIgnoreCase(node.getName())) {
if (bounds != null)
graph.setLabelPosition(getPoint(node.getValue()));
}
} else if (data instanceof GraphNode) {
GraphNode graphNode = (GraphNode) data;
if ("width".equalsIgnoreCase(node.getName()))
graphNode.setSize(new Dimension(getSize(node.getValue()),
graphNode.getHeight()));
else if ("height".equalsIgnoreCase(node.getName()))
graphNode.setSize(new Dimension(graphNode.getWidth(),
getSize(node.getValue())));
else if ("pos".equalsIgnoreCase(node.getName())) {
Point position = getPoint(node.getValue());
position.x = position.x - (graphNode.getWidth() / 2);
position.y = position.y - (graphNode.getHeight() / 2);
graphNode.setPosition(position);
} else if ("rects".equalsIgnoreCase(node.getName())) {
List<Rectangle> rectangles = getRectangles(node.getValue());
List<GraphNode> sinkNodes = graphNode.getSinkNodes();
if (graphController.getAlignment().equals(HORIZONTAL)) {
Rectangle rect = rectangles.remove(0);
graphNode.setSize(rect.getSize());
graphNode.setPosition(rect.getLocation());
} else {
Rectangle rect = rectangles.remove(sinkNodes.size());
graphNode.setSize(rect.getSize());
graphNode.setPosition(rect.getLocation());
}
Point origin = graphNode.getPosition();
for (GraphNode sinkNode : sinkNodes) {
Rectangle rect = rectangles.remove(0);
rect.setLocation(rect.x - origin.x, rect.y - origin.y);
sinkNode.setSize(rect.getSize());
sinkNode.setPosition(rect.getLocation());
}
for (GraphNode sourceNode : graphNode.getSourceNodes()) {
Rectangle rect = rectangles.remove(0);
rect.setLocation(rect.x - origin.x, rect.y - origin.y);
sourceNode.setSize(rect.getSize());
sourceNode.setPosition(rect.getLocation());
}
}
} else if (data instanceof GraphEdge) {
GraphEdge graphEdge = (GraphEdge) data;
if ("pos".equalsIgnoreCase(node.getName()))
graphEdge.setPath(getPath(node.getValue()));
}
return node.childrenAccept(this, data);
}
private Rectangle calculateBounds(Rectangle bounds) {
bounds = new Rectangle(bounds);
bounds.width += BORDER;
bounds.height += BORDER;
Rectangle newBounds = new Rectangle(bounds);
double ratio = bounds.width / (float) bounds.height;
double requiredRatio = requiredBounds.width
/ (float) requiredBounds.height;
// adjust the bounds so they match the aspect ration of the required bounds
if (ratio > requiredRatio)
newBounds.height = (int) (ratio / requiredRatio * bounds.height);
else if (ratio < requiredRatio)
newBounds.width = (int) (requiredRatio / ratio * bounds.width);
xOffset = (newBounds.width - bounds.width) / 2;
yOffset = (newBounds.height - bounds.height) / 2;
// adjust the bounds and so they are not less than the required bounds
if (newBounds.width < requiredBounds.width) {
xOffset += (requiredBounds.width - newBounds.width) / 2;
newBounds.width = requiredBounds.width;
}
if (newBounds.height < requiredBounds.height) {
yOffset += (requiredBounds.height - newBounds.height) / 2;
newBounds.height = requiredBounds.height;
}
// adjust the offset for the border
xOffset += BORDER / 2;
yOffset += BORDER / 2;
return newBounds;
}
private List<Point> getPath(String value) {
List<Point> path = new ArrayList<>();
for (String point : removeQuotes(value).split(" ")) {
String[] coords = point.split(",");
if (coords.length == 2) {
int x = (int) parseFloat(coords[0]) + xOffset;
int y = (int) parseFloat(coords[1]) + yOffset;
path.add(new Point(x, flipY(y)));
}
}
return path;
}
private int flipY(int y) {
return bounds.height - y;
}
private List<Rectangle> getRectangles(String value) {
List<Rectangle> rectangles = new ArrayList<>();
String[] rects = value.split(" ");
for (String rectangle : rects)
rectangles.add(getRectangle(rectangle));
return rectangles;
}
private Rectangle getRectangle(String value) {
String[] coords = removeQuotes(value).split(",");
Rectangle rectangle = new Rectangle();
rectangle.x = (int) parseFloat(coords[0]);
rectangle.y = (int) parseFloat(coords[3]);
rectangle.width = (int) parseFloat(coords[2]) - rectangle.x;
rectangle.height = rectangle.y - (int) parseFloat(coords[1]);
rectangle.x += xOffset;
rectangle.y += yOffset;
if (bounds != null)
rectangle.y = flipY(rectangle.y);
else
rectangle.y = rectangle.height - rectangle.y;
return rectangle;
}
private Point getPoint(String value) {
String[] coords = removeQuotes(value).split(",");
return new Point(xOffset + (int) parseFloat(coords[0]), flipY(yOffset
+ (int) parseFloat(coords[1])));
}
private int getSize(String value) {
return (int) (parseFloat(removeQuotes(value)) * 72);
}
private String removeQuotes(String value) {
String result = value.trim();
if (result.startsWith("\""))
result = result.substring(1);
if (result.endsWith("\""))
result = result.substring(0, result.length() - 1);
result = result.replaceAll("\\\\", "");
return result;
}
}