/* * 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.io.PrintWriter; 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; /** * Writer for layouts in SVG format (Scalable Vector Graphs, XML, W3C). */ public class WriterDataLayoutSVG extends WriterDataLayout { /** used to build unique id for the edges */ private int edgeNumber = 0; /** * Constructor. */ public WriterDataLayoutSVG(PrintWriter out, GraphData graph, Options opt) { super(out, graph, opt); } /** * Writes the layout in graphics format SVG. */ @Override public void write() { int size = (int) (1000 * options.getOption(OptionsEnum.scalePos).getFloat()); // Header out.println("<?xml version=\"1.0\" standalone=\"no\"?>"); out.println(); out.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\""); out.println(" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"); out.println(); out.println("<!-- Generated by " + Options.toolDescription()); out.println(" " + Options.currentDateTime() + " -->"); out.println(); out.println("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""); out.println(" xmlns:xlink=\"http://www.w3.org/1999/xlink\""); out.println(" width=\"100%\" height=\"100%\""); out.println(" viewBox=\"0 0 " + size + " " + size + "\">"); out.println(); out.println(" <title>Visualization " + options.getOption(OptionsEnum.inputName).getString() + "</title>"); out.println(); out.println(" <script><![CDATA["); out.println(); out.println(" /*"); out.println(" * Returns the innermost SVG object that triggered the event"); out.println(" * and that has a non-null id attribute. This can be either the event's"); out.println(" * target itself, or its parent node, or the first ancestor of the target"); out.println(" * with a non-null id."); out.println(" *"); out.println(" * Input Parameters:"); out.println(" * evt - The JavaScript object describing the triggering event."); out.println(" * Return Value:"); out.println(" * The target node or first target's ancestor with a non-null id."); out.println(" */"); out.println(" function get_target(evt) {"); out.println(" var target = evt.target;"); out.println(" while (target && !target.getAttribute('id')) {"); out.println(" target = target.parentNode;"); out.println(" }"); out.println(" return target;"); out.println(" }"); out.println(); out.println(" /*"); out.println(" * Adds or removes a duplicate of the text object that"); out.println(" * corresponds to the circle that was clicked on / pointed to:"); out.println(" * The circle node is the evt.target."); out.println(" * If the text node is already there, the node gets removed."); out.println(" * Otherwise the text node is cloned. The cloned node's id is changed."); out.println(" * The cloned node is appended to the 'contents' group."); out.println(" *"); out.println(" * Input Parameters:"); out.println(" * evt - JavaScript object describing the triggering event."); out.println(" * postfix - String to distinguish between click and move."); out.println(" */"); out.println(" function annot_toggle(evt, postfix) {"); out.println(" // Retrieve node of object that was clicked on."); out.println(" var target = get_target(evt);"); out.println(" var svgdoc = target.ownerDocument;"); out.println(); out.println(" // Retrieve annotation node."); out.println(" var annotNode = svgdoc.getElementById(target.getAttribute('id')"); out.println(" + '__text' + postfix);"); out.println(" // Check whether annotation is already set."); out.println(" if (annotNode) {"); out.println(" // Remove object's node from its group node."); out.println(" var groupnode = annotNode.parentNode;"); out.println(" groupnode.removeChild (annotNode);"); out.println(" } else {"); out.println(" // Clone the text object and set its attributes."); out.println(" var text = svgdoc.getElementById(target.getAttribute('id') + '__text');"); out.println(" annotNode = text.cloneNode(true);"); out.println(" annotNode.setAttribute('id', target.getAttribute('id') + '__text' + postfix);"); out.println(" // Retrieve the node for 'contents' group."); out.println(" var contents = svgdoc.getElementById('contents');"); out.println(" // Insert the cloned object into the contents group node."); out.println(" contents.parentNode.appendChild(annotNode);"); out.println(" window.status = target.getAttribute('id');"); out.println(" }"); out.println(" }"); out.println(); out.println(" ]]></script>"); out.println(); // Body writeGraphicsLayout(graph.getVertices(), graph.getEdges(), size); // Footer out.println("</svg>"); } /** * Writes a vertex in SVG format. * @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) { String name = vertex.getName(); // remove double quote if ((name.length() > 0 && name.charAt(0) == '"') && name.endsWith("\"")) { name = name.substring(1, name.length() - 1); } // Prepare color string for vertex. // Convert to hex RGB value and get rid of the alpha value. String colorHex = Integer.toHexString(color.getRGB() & 0x00FFFFFF); // Fill up to 6 digits. colorHex = "000000" + colorHex; colorHex = colorHex.substring(colorHex.length() - 6, colorHex.length()); // Prepare color string for annotation text. Color textColor = new Color(0xffffffff - options.backColor.get().getRGB()); // Convert to hex RGB value and get rid of the alpha value. String textColorHex = Integer.toHexString(textColor.getRGB() & 0x00FFFFFF); // Fill up to 6 digits. textColorHex = "000000" + textColorHex; textColorHex = textColorHex.substring(textColorHex.length() - 6, textColorHex.length()); // Write the definition for the annotation. out.println(" <defs> <text id=\"" + name + "__text\" " + "x=\"" + (xPos + width + 3) + "\" " + "y=\"" + (yPos + 3) + "\" " + "style=\"font-family: helvetica, sans-serif;font-size:" + options.getOption(OptionsEnum.fontSize).getInt() + "pt;fill:#" + textColorHex + "\"> " + name + (vertex.hasSelfLoop() ? "-LOOP" : "") + " </text> </defs>"); // Write graphical objects. out.println(" <g id=\"contents\">"); // Write vertex. String strokeString = ""; if (options.getOption(OptionsEnum.blackCircle).getBool()) { strokeString = " stroke=\"" + options.getOption(OptionsEnum.ringColor).getString() + "\""; } //link information if (options.getOption(OptionsEnum.openURL).getBool()) { out.println(" <a xlink:href=\"" + name + "\">"); } out.println(" <circle id=\"" + name + "\" " + "cx=\"" + xPos + "\" " + "cy=\"" + yPos + "\" " + "r=\"" + width + "\" " + "fill=\"#" + colorHex + "\"" + strokeString + " onmouseover=\"annot_toggle(evt, '_move')\"" + " onmouseout=\"annot_toggle(evt, '_move')\"" + " onclick=\"annot_toggle(evt, '_click')\"" + " />"); if (options.getOption(OptionsEnum.openURL).getBool()) { out.println(" </a>"); } // Write annotation, only if required. if (vertex.isShowName()) { out.println(" <text id=\"" + name + "__text_click\" " + "x=\"" + (xPos + width + 3) + "\" " + "y=\"" + (yPos + 3) + "\" " + "style=\"font-family: helvetica, sans-serif;font-size:" + options.getOption(OptionsEnum.fontSize).getInt() + "pt;fill:#" + textColorHex + "\"> " + name + " </text>"); } out.println(" </g>"); } /** * 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) { String edgeName = edge.getRelName(); // reflexive edges are not allowed by specification // (currently not defined how they should be drawn) if (xPos1 == xPos2 && yPos1 == yPos2) { return; } // Prepare color string for edge and annotation text. // Move this to initialization in main perhaps: // Color textColor = new Color(0xffffffff - options.backColor.get().getRGB()); // Convert to hex RGB value and get rid of the alpha value. String colorHex = Integer.toHexString(edge.getColor().getRGB() & 0x00FFFFFF); // Fill up to 6 digits. colorHex = "000000" + colorHex; colorHex = colorHex.substring(colorHex.length() - 6, colorHex.length()); int fontSize = options.getOption(OptionsEnum.fontSize).getInt(); // Write the definition for the annotation. out.println(" <defs> <text id=\"" + edgeName + edgeNumber + "__text\" " + "x=\"" + ((xPos1 + xPos2) / 2 + fontSize + 3) + "\" " + "y=\"" + ((yPos1 + yPos2) / 2 + fontSize + 3) + "\" " + "style=\"font-family: helvetica, sans-serif;" + "font-size:" + fontSize + "pt;" + "fill:#" + colorHex + "\"> " + edgeName + " </text> </defs>"); // Write graphical objects. out.println(" <g id=\"contents\" stroke=\"#" + colorHex + "\">"); // Draw the line. out.println("<line id=\"" + edgeName + edgeNumber + "\" " + "style=\"stroke-width:1;fill:none;\" " + "y1=\"" + yPos1 + "\" " + "y2=\"" + yPos2 + "\" " + "x1=\"" + xPos1 + "\" " + "x2=\"" + xPos2 + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\"" + " onmouseout=\"annot_toggle(evt, '_move')\"" + " onclick=\"annot_toggle(evt, '_click')\" />"); // Draw the arrow head. int[] arr = paintArrow(xPos1, yPos1, xPos2, yPos2); out.println("<line id=\"" + edgeName + edgeNumber + "_tip1" + "\" " + "style=\"stroke-width:1;fill:none;\" " + "y1=\"" + arr[1] + "\" " + "y2=\"" + arr[3] + "\" " + "x1=\"" + arr[0] + "\" " + "x2=\"" + arr[2] + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\"" + " onmouseout=\"annot_toggle(evt, '_move')\"" + " onclick=\"annot_toggle(evt, '_click')\" />"); out.println("<line id=\"" + edgeName + edgeNumber + "_tip2" + "\" " + "style=\"stroke-width:1;fill:none;\" " + "y1=\"" + arr[5] + "\" " + "y2=\"" + arr[7] + "\" " + "x1=\"" + arr[4] + "\" " + "x2=\"" + arr[6] + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\"" + " onmouseout=\"annot_toggle(evt, '_move')\"" + " onclick=\"annot_toggle(evt, '_click')\" />"); // Write annotation, only if required. if (options.getOption(OptionsEnum.showAllLabels).getBool()) { out.println(" <text id=\"" + edgeName + edgeNumber + "__text\" " + "x=\"" + ((xPos1 + xPos2) / 2 + fontSize + 3) + "\" " + "y=\"" + ((yPos1 + yPos2) / 2 + fontSize + 3) + "\" " + "style=\"font-family: helvetica, sans-serif;" + "font-size:" + fontSize + "pt\"> " + edgeName + " </text> "); } out.println(" </g>"); edgeNumber++; } }