/*
* CCVisu is a tool for visual graph clustering
* and general force-directed graph layout.
* This file is part of CCVisu.
*
* Copyright (C) 2005-2012 Dirk Beyer
*
* CCVisu 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.
*
* CCVisu 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 CCVisu; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please find the GNU Lesser General Public License in file
* license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
*
* Dirk Beyer (firstname.lastname@uni-passau.de)
* University of Passau, Bavaria, Germany
*/
package org.sosy_lab.ccvisu.writers;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Stroke;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.sosy_lab.ccvisu.DisplayCriteria;
import org.sosy_lab.ccvisu.NameHandler;
import org.sosy_lab.ccvisu.Options;
import org.sosy_lab.ccvisu.Options.OptionsEnum;
import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.graph.GraphEdge;
import org.sosy_lab.ccvisu.graph.GraphVertex;
import org.sosy_lab.ccvisu.graph.GraphVertex.Shape;
import org.sosy_lab.ccvisu.graph.GraphicLayoutInfo;
import org.sosy_lab.ccvisu.graph.Group;
import org.sosy_lab.ccvisu.graph.NameVisibility;
import org.sosy_lab.ccvisu.graph.Position;
import org.sosy_lab.ccvisu.ui.FrameDisplay;
import org.sosy_lab.util.Colors;
/**
* Writer for displaying the layout on the screen device.
*/
public class WriterDataLayoutDISP extends WriterDataLayout {
private List<Set<String>> xMap;
private List<Set<String>> yMap;
private List<List<Set<GraphEdge>>> edgeMap;
//write-color (computed once and stored)
private Color frontColor;
// Temporarily associated during callback from ScreenDisplay.
private Graphics graphics;
private int insetleft;
private int insetbottom;
private int xSize = 0;
private int ySize = 0;
// Ashgan added the following data field, which is used to
// filter the display of vertices and edges
private DisplayCriteria dispFilter = null;
private final FrameDisplay display;
/**
* Constructor.
* @param graph Graph representation, contains the positions of the vertices.
*/
public WriterDataLayoutDISP(PrintWriter out, GraphData graph, Container frame, Options options) {
super(out, graph, options);
assert options.frame != null;
// Adjust frontColor.
adjustFrontColor();
display = new FrameDisplay(this, frame);
graph.addOnGraphChangeListener(display);
if (display == null) {
System.err.println("Runtime error: Could not open ScreenDisplay.");
System.exit(1);
}
// Ashgan added the following line
dispFilter = new DisplayCriteria(graph);
}
/**
* Nothing to do here.
* The constructor initializes the ScreenDisplay (frame and canvas),
* and that calls back to the methods below
* (writeDISP, writeLAY, toggleVertexNames, getVertexNames).
*/
@Override
public void write() {
}
/**
* Writes the layout on the screen device (DISP output format).
* Call-back method, invoked from <code>ScreenDisplay</code>.
* @param size Size of the output drawing area.
* @param graphics The drawing area of the canvas.
* @param xCanvasSize Width of the canvas.
* @param yCanvasSize Height of the canvas.
* @param insetleft Left inset of the drawing frame.
* @param insetbottom Bottom inset of the drawing frame.
*/
public void writeDISP(Dimension size, Graphics graphics, int xCanvasSize,
int yCanvasSize, int insetleft, int insetbottom) {
this.graphics = graphics;
this.insetbottom = insetbottom;
this.insetleft = insetleft;
// Maps for getting the vertices at mouse positions.
xMap = new ArrayList<Set<String>>(xCanvasSize);
yMap = new ArrayList<Set<String>>(yCanvasSize);
for (int i = 0; i < xCanvasSize; ++i) {
xMap.add(new TreeSet<String>());
}
for (int i = 0; i < yCanvasSize; ++i) {
yMap.add(new TreeSet<String>());
}
// Maps for getting the edges at mouse positions.
if (options.getOption(OptionsEnum.showEdges).getBool()) {
if (xSize == xCanvasSize && ySize == yCanvasSize) {
for (int x = 0; x < xCanvasSize; ++x) {
List<Set<GraphEdge>> lVec = edgeMap.get(x);
for (int y = 0; y < yCanvasSize; ++y) {
lVec.get(y).clear();
}
}
} else {
edgeMap = new ArrayList<List<Set<GraphEdge>>>(xCanvasSize);
for (int x = 0; x < xCanvasSize; ++x) {
List<Set<GraphEdge>> lVec = new ArrayList<Set<GraphEdge>>(yCanvasSize);
edgeMap.add(lVec);
for (int y = 0; y < yCanvasSize; ++y) {
lVec.add(new TreeSet<GraphEdge>());
}
}
xSize = xCanvasSize;
ySize = yCanvasSize;
}
}
List<GraphVertex> emptyVertices = new ArrayList<GraphVertex>();
List<GraphEdge> emptyEdges = new ArrayList<GraphEdge>();
// Write edges only
writeGraphicsLayout(emptyVertices, graph.getEdges(), size);
// Draw the vertices, group by group, groups on top of normal vertices.
Iterator<Group> it = graph.getGroups().iterator();
while (it.hasNext()) {
Group group = it.next();
if (group.isVisible()) {
writeGraphicsLayout(group.getNodes(), emptyEdges, size);
}
}
GraphicLayoutInfo graphicLayoutInfo = calculateOffsetAndScale(size);
// Draw the group specific information (except default group)
for (Group group : graph.getGroups()) {
if (group == graph.getDefaultGroup()) {
continue;
}
if (group.isVisible() && group.isDrawInfo()) {
Position position = graphicLayoutInfo.mapToOriginal(new Position(group.getX(),
group.getY(), 0));
int x = (int) (position.x + insetleft);
int y = (int) (position.y - insetbottom);
int left = x - 5;
int right = x + 5;
int top = y - 5;
int bottom = y + 5;
graphics.setColor(Color.BLACK);
graphics.drawLine(left, top, right, bottom);
graphics.drawLine(right, top, left, bottom);
graphics.setColor(group.getColor());
graphics.drawLine(left, y, right, y);
graphics.drawLine(x, top, x, bottom);
int radius = (int) (group.getAverageRadius()
* graphicLayoutInfo.getScale().x);
int diam = (radius + radius);
graphics.drawOval(x - radius, y - radius, diam, diam);
graphics.setColor(Color.BLACK);
}
}
if (options.writerDataGraphicsDISPListener != null) {
options.writerDataGraphicsDISPListener.onUpdateImage(size,
graphicLayoutInfo, graphics);
}
}
/**
* Writes a vertex on screen.
* @param vertex The vertex object, to access vertex attributes.
* @param xPos x coordinate of the vertex.
* @param yPos y coordinate of the vertex.
* @param zPos z coordinate of the vertex.
* @param Width Width of the vertex.
* @param height Height of the vertex.
*/
@Override
public void writeVertex(GraphVertex vertex, int xPos, int yPos, int zPos,
int width, int height, Color color) {
assert (vertex.isShowVertex());
assert (!vertex.isAuxiliary());
// Correction for inset.left and inset.bottom
xPos = xPos + insetleft;
yPos = yPos - insetbottom;
int startX = xPos - width;
int startY = yPos - height;
// Draw the vertex.
int radius = width;
int diam = 2 * width;
graphics.setColor(color);
if ((vertex.getShape() == Shape.BOX)
|| (vertex.getShape() == Shape.FIXED_SIZE_BOX)) {
// BOX
graphics.fillRect(startX, startY, diam, diam);
if (options.getOption(OptionsEnum.blackCircle).getBool()) {
graphics.setColor(Colors.get(options.getOption(OptionsEnum.ringColor).getString()));
graphics.drawRect(startX, startY, diam, diam);
}
} else if (vertex.getShape() == Shape.DISC) {
// DISC
graphics.fillOval(startX, startY, diam, diam);
if (options.getOption(OptionsEnum.blackCircle).getBool()) {
graphics.setColor(Colors.get(options.getOption(OptionsEnum.ringColor).getString()));
graphics.drawOval(startX, startY, diam, diam);
}
graphics.setColor(frontColor);
} else if (vertex.getShape() == Shape.RBOX) {
// RBox
float d = diam;
graphics.fillRoundRect(startX, startY, diam, diam,
(int) (3 * d / 4), (int) (3 * d / 4));
// graphics.fillPolygon(xs, ys, 8);
if (options.getOption(OptionsEnum.blackCircle).getBool()) {
graphics.setColor(Colors.get(options.getOption(OptionsEnum.ringColor).getString()));
// graphics.drawPolygon(xs, ys, 8);
}
} else if (vertex.getShape() == Shape.FILLER_RECT) {
// FILLER_RECT
graphics.fillRect(startX, startY, height, width);
if (options.getOption(OptionsEnum.blackCircle).getBool()) {
graphics.setColor(Colors.get(options.getOption(OptionsEnum.ringColor).getString()));
graphics.drawRect(startX, startY, height, width);
}
} else {
// UNKNOWN
assert (false);
}
// Draw a little loop if the vertex has a self-loop.
if (vertex.hasSelfLoop() && options.getOption(OptionsEnum.showEdges).getBool()) {
graphics.drawOval(startX, startY, (int) ((1.8) * diam),
(int) ((1.8) * diam + 4));
}
if (vertex.isShowName()) {
// Draw annotation.
// Use inverted background color for the annotation.
graphics.setColor(frontColor);
String vertexLabel = vertex.getLabel();
if (options.getOption(OptionsEnum.shortNames).getBool()) {
vertexLabel = NameHandler.shortenName(vertexLabel);
}
graphics.drawString(vertexLabel, xPos + radius + 3, yPos + 3);
/*
* if (options.getOption(OptionsEnum.enableFeatureVisu).getBool()) {
* int res = Statistics.calculateProximityDegreeExtended(curVertex, graph);
* String resStr = "(proximity degree: " + res + ")";
* int lYPos = yPos + 3 + options.getOption(OptionsEnum.fontSize).getInt();
* mGraphics.drawString(resStr, xPos + radius + 3, lYPos);
* }
*/
}
try {
// For interactive annotation: Store vertex names at their positions in the maps.
int endX = Math.min(xPos + radius, xMap.size() - 1);
for (int pos = Math.max(startX, 0); pos <= endX; ++pos) {
xMap.get(pos).add(vertex.getName());
}
int endY = Math.min(yPos + radius, yMap.size() - 1);
for (int pos = Math.max(startY, 0); pos <= endY; ++pos) {
yMap.get(pos).add(vertex.getName());
}
} catch (Exception e) {
// Tolerate exception (it's because of the concurrent thread).
}
}
/**
* Writes an edge.
* @param edge an edge in graph.edges
* @param xPos1 x coordinate of the first point.
* @param yPos1 y coordinate of the first point.
* @param zPos1 z coordinate of the first point.
* @param xPos2 x coordinate of the second point.
* @param yPos2 y coordinate of the second point.
* @param zPos2 z coordinate of the second point.
*/
@Override
public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
int xPos2, int yPos2, int zPos2) {
assert (!edge.isAuxiliaryEdge());
// reflexive edges are not allowed by specification
if (xPos1 == xPos2 && yPos1 == yPos2) { return; }
// Correction for inset.left
xPos1 = xPos1 + insetleft;
xPos2 = xPos2 + insetleft;
// Correction for inset.bottom
yPos1 = yPos1 - insetbottom;
yPos2 = yPos2 - insetbottom;
// Draw
graphics.setColor(edge.getColor());
paintArrow(graphics, xPos1, yPos1, xPos2, yPos2, edge.isDashed());
// Draw the annotation
if (edge.showName()) {
int xPos = (xPos1 + xPos2 + options.getOption(OptionsEnum.fontSize).getInt()) / 2;
int yPos = (yPos1 + yPos2 + options.getOption(OptionsEnum.fontSize).getInt()) / 2;
graphics.drawString(edge.getRelName(), xPos, yPos);
}
// For interactive annotation: Store edges at their positions in the maps.
// Using Bresenham's line algorithm.
boolean steep = Math.abs(yPos2 - yPos1) > Math.abs(xPos2 - xPos1);
if (steep) {
int tmp = xPos1;
xPos1 = yPos1;
yPos1 = tmp;
tmp = xPos2;
xPos2 = yPos2;
yPos2 = tmp;
}
if (xPos1 > xPos2) {
int tmp = xPos1;
xPos1 = xPos2;
xPos2 = tmp;
tmp = yPos1;
yPos1 = yPos2;
yPos2 = tmp;
}
int deltaX = xPos2 - xPos1;
int deltaY = Math.abs(yPos2 - yPos1);
float error = 0;
float deltaError = ((float) deltaY) / deltaX;
int y = yPos1;
int ystep;
if (yPos1 < yPos2) {
ystep = 1;
} else {
ystep = -1;
}
for (int x = xPos1; x <= xPos2; x++) {
try {
if (steep) {
if (y >= 0 && y < xSize && x >= 0 && x < ySize) {
edgeMap.get(y).get(x).add(edge);
}
} else {
if (x >= 0 && x < xSize && y >= 0 && y < ySize) {
edgeMap.get(x).get(y).add(edge);
}
}
error += deltaError;
if (error >= 0.5) {
y += ystep;
error -= 1.0;
}
} catch (Exception e) {
// Tolerate exception (it's because of the concurrent thread).
}
}
}
/**
* Marks all vertices whose node names match the given regular expression.
* Call-back method, invoked from within ScreenDisplay.
* @param regEx Regular expression.
*/
public void markVertices(String regEx) {
for (GraphVertex vertex : graph.getVertices()) {
if (vertex.getName().matches(regEx)) {
vertex.setColor(Color.red);
vertex.setShowName(NameVisibility.Priority.FIND, true);
} else {
vertex.resetColor();
vertex.unsetShowName(NameVisibility.Priority.FIND);
}
}
}
/**
* Resets all marked vertices.
*/
public void resetMarkedVertices() {
for (GraphVertex vertex : graph.getVertices()) {
vertex.resetColor();
vertex.unsetShowName(NameVisibility.Priority.FIND);
}
}
/**
* Toggle the showName flag of the vertices and edges at the given position.
* Call-back method, invoked from within ScreenDisplay.
* @param point coordinates of the vertex.
* @return number of names toggled
*/
public int toggleNames(Point point) {
int xPos = (int) point.getX();
int yPos = (int) point.getY();
int nb = 0;
Set<String> tmp = new TreeSet<String>(xMap.get(xPos));
tmp.retainAll(yMap.get(yPos));
for (String name : tmp) {
nb++;
GraphVertex vertex = graph.getVertexByName(name);
vertex.setShowName(NameVisibility.Priority.MANUAL, !vertex.isShowName());
}
// edges
if (options.getOption(OptionsEnum.showEdges).getBool()) {
Set<GraphEdge> edgesIndex = edgeMap.get(xPos).get(yPos);
for (GraphEdge edge : edgesIndex) {
nb++;
edge.setShowName(!edge.showName());
}
}
return nb;
}
/**
* Compute list of names of the vertices and edges at the given position.
* Call-back method, invoked from within ScreenDisplay.
* @param point coordinates.
*/
public Set<String> getNames(Point point, Boolean includeEdges) {
int xPos = (int) point.getX();
int yPos = (int) point.getY();
Set<String> tmp = new TreeSet<String>();
try {
if (xPos >= xMap.size() || yPos >= yMap.size()) {
return tmp;
}
tmp.addAll(xMap.get(xPos));
tmp.retainAll(yMap.get(yPos));
} catch (Exception e) {
// Tolerate exception (it's because of the concurrent thread).
}
// Edges
if ((includeEdges) && (options.getOption(OptionsEnum.showEdges).getBool())) {
Set<GraphEdge> edgesIndex = edgeMap.get(xPos).get(yPos);
for (GraphEdge edge : edgesIndex) {
tmp.add(edge.getRelName());
}
}
return tmp;
}
/**
* Restrict the set of vertices displayed on the screen to
* the vertices within the given rectangular (i.e., zoom).
* Call-back method, invoked from within ScreenDisplay.
* @param topLeft coordinates of the top left corner of the rectangular.
* @param bottomRight coordinates of the bottom right corner of the rectangular.
*/
public void restrictShowedVertices(Point topLeft, Point bottomRight) {
Set<String> xNodes = new TreeSet<String>();
int end = (int) Math.min(bottomRight.getX(), xMap.size());
int minX = Math.max(0, (int) topLeft.getX());
for (int i = minX; i < end; i++) {
xNodes.addAll(xMap.get(i));
}
end = (int) Math.min(bottomRight.getY(), yMap.size());
int minY = Math.max(0, (int) topLeft.getY());
Set<String> yNodes = new TreeSet<String>();
for (int i = minY; i < end; i++) {
yNodes.addAll(yMap.get(i));
}
Set<String> nodesToKeep = xNodes;
nodesToKeep.retainAll(yNodes);
for (int i = 0; i < graph.getVertices().size(); i++) {
GraphVertex vertex = graph.getVertices().get(i);
if (!nodesToKeep.contains(vertex.getName())) {
vertex.setShowVertex(false);
}
}
// Ashgan starts
// Every time the canvas view changes, the
// undo list in the associated display filter instance
// should also be updated
this.dispFilter.resetUndoList(graph.getVertices());
// Ashgan ends
}
/**
* Reset vertex restriction that was set by restrictShowedVertices.
* Call-back method, invoked from within ScreenDisplay.
*/
public void resetRestriction() {
// Handle vertex options.
for (GraphVertex vertex : graph.getVertices()) {
// hideSource (do not show vertex if it is source of an edge).
if (options.getOption(OptionsEnum.hideSource).getBool() && vertex.isSource()) {
vertex.setShowVertex(false);
} else {
vertex.setShowVertex(true);
}
}
// Ashgan starts
// Every time the canvas view changes, the
// undo list in the associated display filter instance
// should also be updated
this.dispFilter.resetUndoList(graph.getVertices());
// Ashgan ends
}
/**
* Gets the local graph representation (layout).
* Call-back method, invoked from within ScreenDisplay.
* @return Graph/layout representation to switch to.
*/
public GraphData getGraphData() {
return graph;
}
/**
* adjust frontColor
*/
public void adjustFrontColor() {
Color backColor = options.backColor.get();
frontColor = new Color(0xffffffff - backColor.getRGB());
//problem when using gray: colors too close => hard to read
//ignore alpha
if (Math.abs(frontColor.getRed() - backColor.getRed()) < 10
&& Math.abs(frontColor.getBlue() - backColor.getBlue()) < 10
&& Math.abs(frontColor.getGreen() - backColor.getGreen()) < 10) {
frontColor = Color.BLACK;
}
}
/**
* the color of the text
* @return the color of the text
*/
public Color getWriteColor() {
return frontColor;
}
/**
* Open the name of what is under the cursor as if it is an URL.
* @param point Coordinates
*/
public void openURL(Point point) {
// TODO: Simplify!
String targets = getNames(point, false).toString();
int lenght = targets.length();
if (lenght <= 2) {
return;
}
StringTokenizer stringTokenizer = new StringTokenizer(targets.substring(1, lenght - 1));
if (stringTokenizer.hasMoreTokens()) {
String url = stringTokenizer.nextToken();
if ((url.length() > 0 && url.charAt(0) == '"') && url.endsWith("\"")) {
url = url.substring(1, url.length() - 1);
}
if (options.getOption(OptionsEnum.browser).getString().equals("")) {
if (!guessBrowser(url)) {
System.err.println("Unable to find browser");
return;
}
} else {
String cmd[] = { options.getOption(OptionsEnum.browser).getString(), url };
System.err.println("opening: " + url);
try {
Runtime rt = Runtime.getRuntime();
rt.exec(cmd);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
private boolean guessBrowser(String url) {
String[] possibility = { "firefox", "mozilla", "opera", "safari",
"iexplorer", "epiphany", "konqueror" };
for (int i = 0; i < possibility.length; i++) {
options.getOption(OptionsEnum.browser).set(possibility[i]);
String cmd[] = { options.getOption(OptionsEnum.browser).getString(), url };
try {
Runtime rt = Runtime.getRuntime();
rt.exec(cmd);
return true;
} catch (Throwable t) {
}
}
options.getOption(OptionsEnum.browser).set("");
return false;
}
/**
* Draws an arrow from one 2d-coordinate to another.
*/
private void paintArrow(Graphics g, int x0, int y0, int x1, int y1,
boolean dashed) {
if (dashed) {
Stroke drawingStroke = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL, 0, new float[] { 9 }, 0);
Graphics2D g2d = (Graphics2D) g;
Stroke oldStroke = g2d.getStroke();
g2d.setStroke(drawingStroke);
// Draw line.
g2d.drawLine(x0, y0, x1, y1);
g2d.setStroke(oldStroke);
} else {
g.drawLine(x0, y0, x1, y1);
}
// Draw arrow head.
int[] aps = paintArrow(x0, y0, x1, y1);
g.drawLine(aps[0], aps[1], aps[2], aps[3]);
g.drawLine(aps[4], aps[5], aps[6], aps[7]);
}
/**
* @return the display
*/
public FrameDisplay getDisplay() {
return display;
}
// Ashgan starts
/**
* @param filterType <br>
* 0: Display unchanged vertices<br>
* 1: Display removed vertices<br>
* 2: Display added vertices
*/
public void filterVertices(int filterType) {
dispFilter.filterVertices(graph, filterType, true);
}
/**
* @param filterType <br>
* 0: Display unchanged edges<br>
* 1: Display removed edges<br>
* 2: Display added edges
*/
public void filterEdges(int filterType) {
dispFilter.filterEdges(graph, filterType, true);
}
public void filterOff() {
dispFilter.resetView(graph.getVertices());
}
// Ashgan ends
}