/*
* 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.Color;
import java.awt.Dimension;
import java.io.PrintWriter;
import java.util.List;
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.Position;
/**
* Writer for graphical output of layout data.
*/
public abstract class WriterDataLayout extends WriterData {
protected final Options options;
/**
* @param graph Graph representation, contains the positions of the vertices.
*/
public WriterDataLayout(PrintWriter out, GraphData graph, Options options) {
super(out, graph);
this.options = options;
}
/**
* Writes the layout data in a graphics format.
*/
@Override
abstract public void write();
public GraphicLayoutInfo calculateOffsetAndScale(Dimension size) {
boolean considerAuxVertices = options.getOption(OptionsEnum.considerAuxVertices).getBool();
Position minPos = Position.min(graph.getVertices(), true, considerAuxVertices);
Position maxPos = Position.max(graph.getVertices(), true, considerAuxVertices);
float width;
float height;
final int OFFSET_FROM_BORDERS = calculateVertexRadius(options.graph.getMaxDegree());
if (options.getOption(OptionsEnum.stretchGraphToBounds).getBool()) {
width = maxPos.x - minPos.x;
height = maxPos.y - minPos.y;
} else {
width = Position.width(maxPos, minPos);
height = width;
}
if (width == 0) {
width = 1;
}
if (height == 0) {
height = 1;
}
int sizeX = (int) size.getWidth();
int sizeY = (int) size.getHeight();
Position offset = new Position();
offset.x = Position.add(Position.mult(minPos, -1), 0.05f * width).x;
offset.y = Position.add(Position.mult(minPos, -1), 0.05f * height).y;
// Flip y-coordinate.
Position scale = new Position(1, -1, 1);
scale.x *= (0.9f * sizeX / width);
if (options.getOption(OptionsEnum.stretchGraphToBounds).getBool()) {
scale.y *= (0.9f * sizeY / (height + OFFSET_FROM_BORDERS));
} else {
scale.y *= (0.9f * sizeY / height);
}
Position maxPosScaled = Position.add(maxPos, offset);
maxPosScaled.mult(scale);
if (options.getOption(OptionsEnum.stretchGraphToBounds).getBool()) {
sizeY = (int) Math.abs(maxPosScaled.y) + OFFSET_FROM_BORDERS;
} else {
// do nothing
}
return new GraphicLayoutInfo(width, height, sizeY, offset, scale, maxPosScaled);
}
/**
* Write graphics layout.
* @param size Size of output area (e.g., number of pixel).
*/
public void writeGraphicsLayout(List<GraphVertex> vertices, List<GraphEdge> edges, int size) {
writeGraphicsLayout(vertices, edges, new Dimension(size, size));
}
public void writeGraphicsLayout(List<GraphVertex> vertices,
List<GraphEdge> edges, Dimension size) {
GraphicLayoutInfo graphicLayoutInfo = calculateOffsetAndScale(size);
int sizeX = (int) size.getWidth();
int sizeY = (int) size.getHeight();
// Draw the edges.
for (GraphEdge edge : edges) {
if (options.getOption(OptionsEnum.showEdges).getBool() || edge.showEdge()) {
// Only draw the edge if it and both of its incident vertices are visible
if ((edge.getSource().isShowVertex() || edge.getTarget().isShowVertex()) && !edge.isAuxiliaryEdge()) {
Position sourcePos = graphicLayoutInfo.mapToOriginal(edge.getSource().getPosition());
Position targetPos = graphicLayoutInfo.mapToOriginal(edge.getTarget().getPosition());
writeEdge(edge, (int) sourcePos.x, (int) sourcePos.y, (int) sourcePos.z,
(int) targetPos.x, (int) targetPos.y, (int) targetPos.z);
}
}
}
// Draw the vertices.
// First draw the vertices that are not annotated (put them to background).
for (GraphVertex vertex : vertices) {
if (vertex.isShowVertex() && !(options.getOption(OptionsEnum.hideSource).getBool()
&& vertex.isSource()) && !vertex.isAuxiliary()
&& !vertex.isShowName()) {
calculateAndWriteVertex(sizeX, sizeY, graphicLayoutInfo, vertex);
}
}
// Draw the annotated vertices.
// Second draw the annotated vertices (put them to foreground).
for (GraphVertex vertex : vertices) {
if (vertex.isShowVertex() && !(options.getOption(OptionsEnum.hideSource).getBool()
&& vertex.isSource()) && !vertex.isAuxiliary()
&& vertex.isShowName()) {
calculateAndWriteVertex(sizeX, sizeY, graphicLayoutInfo, vertex);
}
}
}
private void calculateAndWriteVertex(int sizeX, int sizeY,
GraphicLayoutInfo graphicLayoutInfo, GraphVertex vertex) {
Position position = graphicLayoutInfo.mapToOriginal(vertex.getPosition());
// Determine color for vertex.
Color color = vertex.getColor();
if (options.getOption(OptionsEnum.depDegreeColor).getBool()) {
float redValue = vertex.getDegreeOut() / graph.getMaxOutdegree();
color = new Color(redValue, 1 - redValue, 0);
}
if (vertex.getShape() == Shape.FIXED_SIZE_BOX) {
int radius = 2;
writeVertex(vertex, (int) position.x, (int) position.y, (int) position.z, radius, color);
} else if (vertex.getShape() == Shape.FILLER_RECT) {
int width = Math.max(1, Math.round(sizeX / graphicLayoutInfo.getWidth()));
int height = Math.max(1, Math.round(sizeY / graphicLayoutInfo.getHeight()));
writeVertex(vertex, (int) position.x, (int) position.y, (int) position.z, width, height, color);
} else {
int radius = calculateVertexRadius(vertex.getDegree());
writeVertex(vertex, (int) position.x, (int) position.y, (int) position.z, radius, color);
}
}
private int calculateVertexRadius(float degree) {
return (int) Math.max(Math.pow(degree, 0.5) * options.getOption(OptionsEnum.minVert).getFloat(),
options.getOption(OptionsEnum.minVert).getFloat());
}
/**
* Writes a vertex.
*
* @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 radius Radius of the vertex.
*/
public void writeVertex(GraphVertex vertex, int xPos, int yPos, int zPos,
int radius, Color color) {
writeVertex(vertex, xPos, yPos, zPos, radius, radius, color);
}
abstract public void writeVertex(GraphVertex vertex, int xPos, int yPos,
int zPos, int width, int height, Color color);
/**
* 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.
*/
abstract public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
int xPos2, int yPos2, int zPos2);
/**
* Calculates the two angled lines that form the arrow head.
*/
protected int[] paintArrow(int x0, int y0, int x1, int y1) {
int[] arrowPoints = new int[8];
double angleFactor = 0.5;
double tipLocPercentage = 0.9 - options.getOption(OptionsEnum.minVert).getFloat() * 0.02;
double xMid = x0 + tipLocPercentage * (x1 - x0);
double yMid = y0 + tipLocPercentage * (y1 - y0);
double deltaX = xMid - x0;
double deltaY = yMid - y0;
deltaX *= angleFactor;
deltaY *= angleFactor;
double diaLength = (Math.sqrt(deltaX * deltaX + deltaY * deltaY) * Math.sqrt(2));
// (deltaY, -deltaX)
double x2 = (x0 + deltaY - xMid) / diaLength;
double y2 = (y0 - deltaX - yMid) / diaLength;
// (-deltaY, deltaX)
double x3 = (x0 - deltaY - xMid) / diaLength;
double y3 = (y0 + deltaX - yMid) / diaLength;
double arrowTipSizeFactor = 5;
double xt2 = xMid + arrowTipSizeFactor * x2;
double yt2 = yMid + arrowTipSizeFactor * y2;
double xt3 = xMid + arrowTipSizeFactor * x3;
double yt3 = yMid + arrowTipSizeFactor * y3;
arrowPoints[0] = (int) Math.round(xMid);
arrowPoints[1] = (int) Math.round(yMid);
arrowPoints[2] = (int) Math.round(xt2);
arrowPoints[3] = (int) Math.round(yt2);
arrowPoints[4] = (int) Math.round(xMid);
arrowPoints[5] = (int) Math.round(yMid);
arrowPoints[6] = (int) Math.round(xt3);
arrowPoints[7] = (int) Math.round(yt3);
return arrowPoints;
}
/**
* @return the options
*/
public Options getOptions() {
return options;
}
}