/* * 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; } }