/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2007 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@sfu.ca) * Simon Fraser University (SFU), B.C., Canada */ package ccvisu; import java.awt.Color; import java.io.PrintWriter; import java.text.DateFormat; import java.util.Date; /***************************************************************** * Writer for layouts in VRML format. * @version $Revision$; $Date$ * @author Dirk Beyer *****************************************************************/ public class WriterDataGraphicsVRML extends WriterDataGraphics { private PrintWriter out; private float scalePos; /** * Constructor. * @param graph Graph representation, contains the positions of the vertices. * @param out Output stream writer. * @param minVert Diameter of the smallest vertex. * @param fontSize Font size of vertex annotations. * @param backColor Background color. * @param blackCircle If true, draw black circle around each vertex. * @param showEdges If true, draw the edges between the vertices (if possible). * @param scalePos Scaling factor for the layout to adjust to drawing area. */ public WriterDataGraphicsVRML(GraphData graph, PrintWriter out, float minVert, int fontSize, Color backColor, boolean blackCircle, boolean showEdges, boolean openURL, float scalePos) { super(graph, minVert, fontSize, backColor, blackCircle, showEdges, openURL); this.out = out; this.scalePos = scalePos; } /***************************************************************** * Writes the layout in graphics format VRML. *****************************************************************/ public void write() { int size = (int) (1000 * scalePos); // Header. out.print( "#VRML V2.0 utf8 " + endl + "# " + endl + "# Generated by CCVisu, a tool for visual graph clustering " + endl + "# and general force-directed graph layout. " + endl + "# " + DateFormat.getDateTimeInstance().format(new Date()) + " " + endl + " " + endl + " PROTO TRANSFORM_Sphere [ " + endl + " field SFVec3f translation 0 0 0 " + endl + " field SFVec3f scale 1 1 1 " + endl + " field SFColor diffuseColor 0.5 0.5 0.5 " + endl + " field SFString name \"\" " + endl + " field SFVec3f textTranslate 0 0 1 " + endl + " field SFFloat textSize " + fontSize + endl + " ] { " + endl + " Transform { " + endl + " translation IS translation " + endl + " children [ " + endl + " Transform { " + endl + " scale IS scale " + endl + " children [ " + endl + " DEF sensor0 TouchSensor {} " + endl + " Shape { " + endl + " geometry Sphere { radius 0.5 } " + endl + " appearance Appearance { " + endl + " material Material { " + endl + " ambientIntensity 0.3 " + endl + " diffuseColor IS diffuseColor " + endl + " } " + endl + " } " + endl + " } " + endl + " ] " + endl + " } " + endl + " Billboard { " + endl + " axisOfRotation 0 0 0 " + endl + " children [ " + endl + " Transform { " + endl + " translation IS textTranslate " + endl + " children [ " + endl + " Shape { " + endl + " geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }" + endl + " appearance Appearance { " + endl + " material Material { " + endl + " ambientIntensity 0.3 " + endl + " diffuseColor 0 0 0 " + endl + " } " + endl + " } " + endl + " } " + endl + " ] " + endl + " } " + endl + " ] " + endl + " } " + endl + " DEF script0 Script { " + endl + " eventIn SFBool isOver " + endl + " eventIn SFBool touchTime " + endl + " eventOut MFString trans " + endl + " field SFBool labelDisplayed FALSE " + endl + " field SFString label IS name " + endl + " url [ \"javascript: " + endl + " function isOver(value) { " + endl + " if (value) { " + endl + " trans[0] = label; " + endl + " } else { " + endl + " if (!labelDisplayed) trans[0] = ''; " + endl + " } " + endl + " } " + endl + " function touchTime() { " + endl + " if (labelDisplayed) { " + endl + " trans[0] = ''; " + endl + " labelDisplayed = false; " + endl + " } else { " + endl + " trans[0] = label; " + endl + " labelDisplayed = true; " + endl ); if(openURL){ out.println( " Browser.loadURL(new MFString(label), new MFString('target=DUMMY'));" ); } out.print( " } " + endl + " }\" " + endl + " ] " + endl + " } " + endl + " ] " + endl + " } " + endl + " ROUTE sensor0.isOver TO script0.isOver " + endl + " ROUTE sensor0.touchTime TO script0.touchTime " + endl + " ROUTE script0.trans TO label0.set_string " + endl + " } " + endl + endl + " PROTO TRANSFORM_Cylinder [ " + endl + " field SFVec3f translation 0 0 0 " + endl + " field SFRotation rotation 0 0 0 0" + endl + " field SFFloat height 1 " + endl + " field SFColor diffuseColor 0.5 0.5 0.5 " + endl + " field SFString name \"\" " + endl + " field SFVec3f textTranslate 0 0 1 " + endl + " field SFFloat textSize " + fontSize + endl + " ] { " + endl + " Transform { " + endl + " translation IS translation " + endl + " children [ " + endl + " Transform { " + endl + " rotation IS rotation " + endl + " children [ " + endl + " DEF sensor0 TouchSensor {} " + endl + " Shape { " + endl + " geometry Cylinder { " + endl + " height IS height " + endl + " radius 0.5 " + endl + " top FALSE " + endl + " side TRUE " + endl + " bottom FALSE " + endl + " } " + endl + " appearance Appearance { " + endl + " material Material { " + endl + " ambientIntensity 0.3 " + endl + " diffuseColor IS diffuseColor " + endl + " } " + endl + " } " + endl + " } " + endl + " ] "+ endl + " } " + endl + " Billboard { " + endl + " axisOfRotation 0 0 0 " + endl + " children [ " + endl + " Transform { " + endl + " translation IS textTranslate " + endl + " children [ " + endl + " Shape { " + endl + " geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }" + endl + " appearance Appearance { " + endl + " material Material { " + endl + " ambientIntensity 0.3 " + endl + " diffuseColor 0 0 0 " + endl + " } " + endl + " } " + endl + " } " + endl + " ] " + endl + " } " + endl + " ] " + endl + " } " + endl + " DEF script0 Script { " + endl + " eventIn SFBool isOver " + endl + " eventIn SFBool touchTime " + endl + " eventOut MFString trans " + endl + " field SFBool labelDisplayed FALSE " + endl + " field SFString label IS name " + endl + " url [ \"javascript: " + endl + " function isOver(value) { " + endl + " if (value) { " + endl + " trans[0] = label; " + endl + " } else { " + endl + " if (!labelDisplayed) trans[0] = ''; " + endl + " } " + endl + " } " + endl + " function touchTime() { " + endl + " if (labelDisplayed) { " + endl + " trans[0] = ''; " + endl + " labelDisplayed = false; " + endl + " } else { " + endl + " trans[0] = label; " + endl + " labelDisplayed = true; " + endl + " } " + endl + " }\" " + endl + " ] " + endl + " } " + endl + " ] "+ endl + " } " + endl + " ROUTE sensor0.isOver TO script0.isOver " + endl + " ROUTE sensor0.touchTime TO script0.touchTime " + endl + " ROUTE script0.trans TO label0.set_string " + endl + " } " + endl+ endl + "Background { skyColor .8 .8 .8 }" + endl ); // Body. writeGraphicsLayout(size); } /***************************************************************** * Write graphics layout. * @param size Size of output area (e.g., number of pixel). *****************************************************************/ public void writeGraphicsLayout(int size) { float xPosMin = 1000000; float xPosMax = -1000000; float yPosMin = 1000000; float yPosMax = -1000000; float zPosMin = 1000000; float zPosMax = -1000000; for (int i = 0; i < graph.vertices.size(); ++i) { if ( graph.vertices.get(i).showVertex ) { xPosMin = Math.min(xPosMin, graph.pos[i][0]); xPosMax = Math.max(xPosMax, graph.pos[i][0]); yPosMin = Math.min(yPosMin, graph.pos[i][1]); yPosMax = Math.max(yPosMax, graph.pos[i][1]); zPosMin = Math.min(zPosMin, graph.pos[i][2]); zPosMax = Math.max(zPosMax, graph.pos[i][2]); } } float layoutDist; layoutDist = Math.max(xPosMax - xPosMin, yPosMax - yPosMin); layoutDist = Math.max(layoutDist, zPosMax - zPosMin); float xOffset = - xPosMin + 0.05f * layoutDist; float yOffset = - yPosMin + 0.05f * layoutDist; float zOffset = - zPosMin + 0.05f * layoutDist; float scale = 0.9f * size / layoutDist; int x = (int)(((xPosMin+xPosMax)/2 + xOffset) * scale); int y = (int)(((yPosMin+yPosMax)/2 + yOffset) * -scale + size); int z = (int)((zPosMax + Math.max((xPosMax-xPosMin),(yPosMax-yPosMin)) * 1.3 + zOffset) * scale); out.print( "Viewpoint { " + endl + " position "+ x +" "+ y +" "+ z + endl + " orientation 0 0 1 0 " + endl + " description \"origin\" " + endl + "} " + endl + endl ); // Draw the edges. if(showEdges && !graph.edges.isEmpty()){ int end = graph.edges.size(); for (int i = 0; i < end; ++i) { GraphEdgeInt e = graph.edges.get(i); writeEdge( i, (int)((graph.pos[e.x][0]+ xOffset) * scale), (int)((graph.pos[e.x][1]+ yOffset) * -scale + size), (int)((graph.pos[e.x][2]+ zOffset) * scale), (int)((graph.pos[e.y][0]+ xOffset) * scale), (int)((graph.pos[e.y][1]+ yOffset) * -scale + size), (int)((graph.pos[e.y][2]+ zOffset) * scale)); } } // Draw the vertices. for (int i = 0; i < graph.vertices.size(); ++i) { GraphVertex curVertex = graph.vertices.get(i); if (!curVertex.showName) { if (curVertex.showVertex) { int radius = (int) Math.max(Math.pow(curVertex.degree, 0.5) * minVert, minVert); int xPos = (int) ((graph.pos[i][0] + xOffset) * scale); int yPos = (int) ((graph.pos[i][1] + yOffset) * -scale + size); int zPos = (int) ((graph.pos[i][2] + zOffset) * scale); writeVertex(curVertex, xPos, yPos, zPos, radius); } } } // Draw the annotated vertices. for (int i = 0; i < graph.vertices.size(); ++i) { GraphVertex curVertex = graph.vertices.get(i); if (curVertex.showName) { if (curVertex.showVertex) { int dia = (int) Math.max(Math.pow(curVertex.degree, 0.5) * minVert, minVert); int xPos = (int) ((graph.pos[i][0] + xOffset) * scale); int yPos = (int) ((graph.pos[i][1] + yOffset) * -scale + size); int zPos = (int) ((graph.pos[i][2] + zOffset) * scale); writeVertex(curVertex, xPos, yPos, zPos, dia); } } } } /** * Writes a vertex in VRML format. * @param curVertex 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 curVertex, int xPos, int yPos, int zPos, int radius) { String name = curVertex.name; //remove double quote if(name.startsWith("\"") && name.endsWith("\"")){ name = name.substring(1,name.length()-1); } out.print( "TRANSFORM_Sphere { translation " + xPos + " " + yPos + " " + zPos + " " + "scale " + 2*radius + " " + 2*radius + " " + 2*radius + " " + "diffuseColor " + curVertex.color.getRed() + " " + curVertex.color.getGreen() + " " + curVertex.color.getBlue() + " " + "name " + "\"" + name + "\" textTranslate "+ 2*radius + " "+ -fontSize/2 +" " + 2*radius + " } " + endl ); } /** * Writes an edge. * @param index index of the 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. */ public void writeEdge(int index, int xPos1, int yPos1, int zPos1, int xPos2, int yPos2, int zPos2){ String edgeName = graph.edges.get(index).relName; int dx = xPos2-xPos1; int dy = yPos2-yPos1; int dz = zPos2-zPos1; float dist = (float) Math.sqrt(dx*dx + dy*dy + dz*dz); // It does not make sense to draw an edge of length zero. if (dist == 0) { return; } /* float rotx = dy; float roty = -dx; float rotz = 0.0f; */ float rotx = dz; float roty = 0.0f; float rotz = -dx; float norm = (float)Math.sqrt(rotx*rotx + roty*roty + rotz*rotz); float rotw = (float)Math.asin(norm/dist); if(dy < 0){ rotw = -rotw; } out.print( "TRANSFORM_Cylinder { translation " + (xPos1+xPos2)/2 + " " + (yPos1+yPos2)/2 + " " + (zPos1+zPos2)/2 + " " + "rotation " + rotx + " " + roty + " " + rotz + " " + rotw + " " + "height " + dist + " " + "diffuseColor 0 0 0 " + "name " + "\"" + edgeName + "\" " + "textTranslate 0 0 "+ fontSize +" } " + endl ); } };