org.integratedmodelling.utils.graph.GraphViz Maven / Gradle / Ivy
The newest version!
/**
* Copyright 2011 The ARIES Consortium (http://www.ariesonline.org) and
* www.integratedmodelling.org.
This file is part of Thinklab.
Thinklab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
Thinklab 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with Thinklab. If not, see .
*/
package org.integratedmodelling.utils.graph;
//GraphViz.java - a simple API to call dot from Java programs
/*$Id$*/
/*
******************************************************************************
* *
* (c) Copyright 2003 Laszlo Szathmary *
* *
* This program 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. *
* *
* This program 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 this program; if not, write to the Free Software Foundation, *
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
* *
******************************************************************************
*
* Modified by Ferdinando Villa, Ecoinformatics Collaboratory, UVM
* @date June 18, 2007
*/
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Hashtable;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.utils.DisplayImage;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabIOException;
import org.jgrapht.DirectedGraph;
/**
*
* - Purpose: GraphViz Java API
*
-
*
*
- Description:
*
- With this Java class you can simply call dot
* from your Java programs
*
- Example usage:
*
-
*
* GraphViz gv = new GraphViz();
* gv.addln(gv.start_graph());
* gv.addln("A -> B;");
* gv.addln("A -> C;");
* gv.addln(gv.end_graph());
* System.out.println(gv.getDotSource());
*
* File out = new File("out.gif");
* gv.writeGraphToFile(gv.getGraph(gv.getDotSource()), out);
*
*
*
*
*
* @version v0.1, 2003/12/04 (Decembre)
* @author Laszlo Szathmary ([email protected])
*
* Modified by Ferdinando Villa, Ecoinformatics Collaboratory, UVM
* @date June 18, 2007
*/
public class GraphViz {
private String makeImageMap = null;
private String imageMap = "";
private String outputFormat = "png";
// these will be filled in only if layout() is called
int width;
int height;
public static interface NodePropertiesProvider {
/*
* node shapes
*/
public final static String BOX = "box";
public final static String ELLIPSE = "ellipse";
public final static String HEXAGON = "hexagon";
public final static String OCTAGON = "octagon";
public final static String RECTANGLE = "rectangle";
public final static String COMPONENT = "component";
public final static String PARALLELOGRAM = "parallelogram";
public final static String BOX3D = "box3d";
public final static String FOLDER = "folder";
public final static String TAB = "tab";
public final static String DIAMOND = "diamond";
public final static String CDS = "cds";
public String getNodeId(N o);
public String getNodeShape(N o);
public int getNodeWidth(N o);
public int getNodeHeight(N o);
public String getEdgeColor(E e);
public String getEdgeLabel(E e);
}
public class NodeLayout {
int x;
int y;
int w;
int h;
String node;
}
Hashtable nodes = new Hashtable();
/*
* Call this with a URL pattern if you want an imagemap to be generated for the graph.
* @param The URL pattern that you want to attach to each node. Use %s in the pattern
* where you want the node name to go. If the node name has spaces in them, they will be replaced by
* underscores in the resulting URL.
*/
public void makeImageMap(String urlPattern) {
makeImageMap = urlPattern;
}
/*
* If makeImageMap(true) has been called and getGraph() executed, returns the
* image map ready for inclusion in HTML. Otherwise returns an empty string.
* @return
*/
public String getImageMap() {
return imageMap;
}
/**
* Set the output format to the passed string. It must be understood in the -T option
* of graphviz executables, and it also becomes the filename extension. Use with caution,
* no checking is done.
*
* @param outputFormat
*/
public void setOutputFormat(String outputFormat) {
this.outputFormat = outputFormat;
}
static private String getDefaultDotPath(String exe) {
String ret = "/usr/bin/" + exe;
switch (KLAB.CONFIG.getOS()) {
case WIN:
ret = exe + ".exe";
break;
case MACOS:
/* TODO check where it goes */
ret = "/usr/bin/" + exe;
break;
case UNIX:
/* TODO check where common rpm distributions put it */
ret = "/usr/bin/" + exe;
break;
}
return ret;
}
private String DOT = null;
/**
* The source of the graph written in dot language.
*/
private StringBuffer graph = new StringBuffer();
/**
* Constructor: creates a new GraphViz object that will contain
* a graph.
*/
public GraphViz() {
this("dot");
}
/**
* COnstructor that allows to specify the program you want to use.
* @param graphvizProgram
*/
public GraphViz(String graphvizProgram) {
DOT = KLAB.CONFIG.getProperties()
.getProperty(graphvizProgram + ".path", getDefaultDotPath(graphvizProgram));
// if (! (new File(DOT).exists()))
// throw new ThinklabResourceNotFoundException(DOT);
}
/**
* Returns the graph's source description in dot language.
* @return Source of the graph in dot language.
*/
public String getDotSource() {
return graph.toString();
}
/**
* Add a node to the graph.
* @param node
* @param options
*/
public void addNode(String node, String options) {
String s = "\tnode ";
String a = options == null ? "" : options;
if (makeImageMap != null) {
// String nnode = node.replace(' ', '_');
// PrintfFormat fmt = new PrintfFormat(makeImageMap);
// a +=
// (a.length() > 0 ? "," : "") +
// "URL=\"" +
// fmt.tostr(nnode) +
// "\"";
}
/* add attributes if any */
if (a.length() > 0) {
s += " [" + a + "] ";
}
s += "\"" + node + "\";";
addln(s);
}
/**
* Add a directed edge between passed nodes.
* @param vertexName
* @param vertexName2
*/
public void addDirectedEdge(String vertexName, String vertexName2, String options) {
addln("\t\"" + vertexName + "\" -> \"" + vertexName2 + "\""
+ (options == null ? "" : ("[" + options + "]")));
}
public void loadGraph(DirectedGraph graph, NodePropertiesProvider npp) {
loadGraph(graph, npp, false);
}
/**
* Load a knowledge graph into graphviz representation. Do what you want after that.
* Use reverse if the relationship shows the inverse of the intended semantics.
*
* @param graph
*/
public void loadGraph(DirectedGraph graph, NodePropertiesProvider npp, boolean reverse) {
addln(start_graph("fontsize=8", "fontname=\"sanserif\"", "overlap=\"scale\""));
for (Object s : graph.vertexSet()) {
addNode(npp.getNodeId(s), "shape=" + npp.getNodeShape(s)
+ ", fontname=sanserif, fontsize=8, margin=\"0.055\"");
}
for (Object e : graph.edgeSet()) {
Object s = graph.getEdgeSource(e);
Object t = graph.getEdgeTarget(e);
addDirectedEdge((reverse ? npp.getNodeId(t) : npp.getNodeId(s)), (reverse ? npp.getNodeId(s)
: npp.getNodeId(t)), ("label=\"" + npp.getEdgeLabel(e) + "\", color=\""
+ npp.getEdgeColor(e) + "\", fontname=sanserif, fontsize=8"));
}
addln(end_graph());
}
public void show() throws KlabException {
try {
File o = File.createTempFile("gdi", "gif");
writeGraphToFile(createImage(writeDotSourceToFile()), o);
DisplayImage display = new DisplayImage(o.toURI().toURL());
display.setVisible(true);
} catch (IOException e) {
throw new KlabIOException(e);
}
}
/**
* Adds a string to the graph's source (without newline).
*/
public void add(String line) {
graph.append(line);
}
/**
* Adds a string to the graph's source (with newline).
*/
public void addln(String line) {
graph.append(line + "\n");
}
/**
* Adds a newline to the graph's source.
*/
public void addln() {
graph.append('\n');
}
/**
* Returns the graph as an image in binary format.
* @return A byte array containing the image of the graph.
* @throws KlabException
*/
public byte[] getGraph() throws KlabException {
byte[] img_stream = null;
File dot = writeDotSourceToFile();
if (dot != null)
img_stream = createImage(dot);
return img_stream;
}
/**
* Writes the graph's image in a file.
* @param img A byte array containing the image of the graph.
* @param file Name of the file to where we want to write.
* @return Success: 1, Failure: -1
*/
public int writeGraphToFile(byte[] img, String file) {
File to = new File(file);
return writeGraphToFile(img, to);
}
/**
* Writes the graph's image in a file.
* @param img A byte array containing the image of the graph.
* @param to A File object to where we want to write.
* @return Success: 1, Failure: -1
*/
public int writeGraphToFile(byte[] img, File to) {
try {
FileOutputStream fos = new FileOutputStream(to);
fos.write(img);
fos.close();
} catch (java.io.IOException ioe) {
return -1;
}
return 1;
}
/**
* Use graphviz to generate a layout for the nodes and return a Layout object
* that describes it. After that is done, the size of the diagram and coordinates
* of all nodes can be retrieved using getWidth(), getHeight() and getNode[X/Y]().
*
* Coordinates are top to bottom, left to right if the passed parameter is true,
* bottom to top otherwise.
*/
public void layout(NodePropertiesProvider npp, boolean top2bottom) throws KlabException {
try {
File img = File.createTempFile("graph_", ".txt");
File dot = writeDotSourceToFile();
Runtime rt = Runtime.getRuntime();
String cmd = DOT;
cmd += (top2bottom ? " -Tplain -y -o" : " -Tplain -o") + img.getAbsolutePath();
cmd += " " + dot.getAbsolutePath();
Process p = rt.exec(cmd);
p.waitFor();
/* parse layout file and set positions */
parseLayout(img);
if (img.delete() == false || dot.delete() == false)
System.err.println("Warning: " + img.getAbsolutePath() + " could not be deleted!");
} catch (Exception ioe) {
throw new KlabIOException(ioe);
}
}
private void parseLayout(File f) throws IOException {
FileInputStream fstream = new FileInputStream(f);
DataInputStream in = new DataInputStream(fstream);
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
String[] tokens = line.split("\\s+");
if (tokens.length < 2)
continue;
if (tokens[0].equals("graph")) {
width = (int) (Float.parseFloat(tokens[2]) * 72);
height = (int) (Float.parseFloat(tokens[3]) * 72);
} else if (tokens[0].equals("node")) {
NodeLayout nl = new NodeLayout();
nl.node = tokens[1];
nl.x = (int) (Float.parseFloat(tokens[2]) * 72);
nl.y = (int) (Float.parseFloat(tokens[3]) * 72);
nl.w = (int) (Float.parseFloat(tokens[4]) * 72);
nl.h = (int) (Float.parseFloat(tokens[5]) * 72);
nodes.put(nl.node, nl);
} else if (tokens[0].equals("edge")) {
/* ignore for now */
}
}
in.close();
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getNodeWidth(String node) {
return nodes.get(node).w;
}
public int getNodeHeight(String node) {
return nodes.get(node).h;
}
public int getNodeX(String node) {
return nodes.get(node).x;
}
public int getNodeY(String node) {
return nodes.get(node).y;
}
public Set getNodeNames() {
return nodes.keySet();
}
/**
* It will call the external dot program, and return the image in
* binary format.
* @param dot Source of the graph (in dot language).
* @return The image of the graph in .gif format.
* @throws KlabException
*/
public byte[] createImage(File dot) throws KlabException {
File img = null;
File imap = null;
byte[] img_stream = null;
try {
img = File.createTempFile("graph_", "." + outputFormat);
imap = File.createTempFile("imap_", ".txt");
String temp = img.getAbsolutePath();
Runtime rt = Runtime.getRuntime();
String cmd = DOT;
if (makeImageMap != null) {
cmd += " -Tcmap -o" + imap.getAbsolutePath();
}
cmd += " -T" + outputFormat + " -o" + img.getAbsolutePath();
cmd += " " + dot.getAbsolutePath();
Process p = rt.exec(cmd);
p.waitFor();
FileInputStream in = new FileInputStream(img.getAbsolutePath());
img_stream = new byte[in.available()];
in.read(img_stream);
// Close it if we need to
if (in != null)
in.close();
if (makeImageMap != null) {
FileUtils.readFileToString(imap);
}
if (img.delete() == false)
System.err.println("Warning: " + img.getAbsolutePath() + " could not be deleted!");
} catch (Exception ioe) {
throw new KlabIOException(ioe);
}
return img_stream;
}
/**
* Writes the source of the graph in a file, and returns the written file
* as a File object.
* @param str Source of the graph (in dot language).
* @return The file (as a File object) that contains the source of the graph.
*/
private File writeDotSourceToFile() throws KlabIOException {
File temp;
try {
temp = File.createTempFile("graph_", ".dot.tmp");
FileWriter fout = new FileWriter(temp);
fout.write(getDotSource());
fout.close();
} catch (Exception e) {
throw new KlabIOException(e);
}
return temp;
}
/**
* Returns a string that is used to start a graph.
* @return A string to open a graph.
*/
public String start_graph(String... options) {
String ret = "digraph G {";
if (options != null) {
for (String o : options) {
ret += "\n\t" + o;
}
}
return ret;
}
/**
* Returns a string that is used to end a graph.
* @return A string to close a graph.
*/
public String end_graph() {
return "}";
}
}