。java可以直接调用这个类实现画图功能,但是使用这个类的前提是你的电脑已经装了GraphViz软件,你可以在http://www.graphviz.org/Gallery.php下载windows版本,装完后,找到dot.exe所在的路径,我电脑上的是D:\Program Files\Graphviz2.30\bin\dot.exe,
将GraphViz.java中的这一路径改成你电脑上的路径,基本上就可以用了。
package Graphoutput;
// 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. *
* *
******************************************************************************
*/
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.InputStreamReader;
/**
* <dl>
* <dt>Purpose: GraphViz Java API
* <dd>
*
* <dt>Description:
* <dd> With this Java class you can simply call dot
* from your Java programs
* <dt>Example usage:
* <dd>
* <pre>
* 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());
*
* String type = "gif";
* File out = new File("out." + type); // out.gif in this example
* gv.writeGraphToFile( gv.getGraph( gv.getDotSource(), type ), out );
* </pre>
* </dd>
*
* </dl>
*
* @version v0.4, 2011/02/05 (February) -- Patch of Keheliya Gallaba is added. Now you
* can specify the type of the output file: gif, dot, fig, pdf, ps, svg, png, etc.
* @version v0.3, 2010/11/29 (November) -- Windows support + ability
* to read the graph from a text file
* @version v0.2, 2010/07/22 (July) -- bug fix
* @version v0.1, 2003/12/04 (December) -- first release
* @author Laszlo Szathmary (<a href="jabba.laci@gmail.com">jabba.laci@gmail.com</a>)
*/
public class GraphViz
{
/**
* The dir. where temporary files will be created.
*/
//private static String TEMP_DIR = "/tmp"; // Linux
private static String TEMP_DIR = "c:/temp"; // Windows
/**
* Where is your dot program located? It will be called externally.
*/
// private static String DOT = "/usr/bin/dot"; // Linux
private static String DOT = "D:\\Program Files\\Graphviz2.30\\bin\\dot.exe"; // Windows
/**
* The source of the graph written in dot language.
*/
private StringBuilder graph = new StringBuilder();
/**
* Constructor: creates a new GraphViz object that will contain
* a graph.
*/
public GraphViz() {
}
/**
* Returns the graph's source description in dot language.
* @return Source of the graph in dot language.
*/
public String getDotSource() {
return graph.toString();
}
/**
* 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.
* @param dot_source Source of the graph to be drawn.
* @param type Type of the output image to be produced, e.g.: gif, dot, fig, pdf, ps, svg, png.
* @return A byte array containing the image of the graph.
*/
public byte[] getGraph(String dot_source, String type)
{
File dot;
byte[] img_stream = null;
try {
dot = writeDotSourceToFile(dot_source);
if (dot != null)
{
img_stream = get_img_stream(dot, type);
if (dot.delete() == false)
System.err.println("Warning: " + dot.getAbsolutePath() + " could not be deleted!");
return img_stream;
}
return null;
} catch (java.io.IOException ioe) { return null; }
}
/**
* 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) { ioe.printStackTrace();return -1; }
return 1;
}
/**
* It will call the external dot program, and return the image in
* binary format.
* @param dot Source of the graph (in dot language).
* @param type Type of the output image to be produced, e.g.: gif, dot, fig, pdf, ps, svg, png.
* @return The image of the graph in .gif format.
*/
private byte[] get_img_stream(File dot, String type)
{
File img;
byte[] img_stream = null;
try {
img = File.createTempFile("graph_", "."+type, new File(GraphViz.TEMP_DIR));
Runtime rt = Runtime.getRuntime();
// patch by Mike Chenault
String[] args = {DOT, "-T"+type, dot.getAbsolutePath(), "-o", img.getAbsolutePath()};
Process p = rt.exec(args);
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 (img.delete() == false)
System.err.println("Warning: " + img.getAbsolutePath() + " could not be deleted!");
}
catch (java.io.IOException ioe) {
System.err.println("Error: in I/O processing of tempfile in dir " + GraphViz.TEMP_DIR+"\n");
System.err.println(" or in calling external command");
ioe.printStackTrace();
}
catch (java.lang.InterruptedException ie) {
System.err.println("Error: the execution of the external program was interrupted");
ie.printStackTrace();
}
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.
*/
public File writeDotSourceToFile(String str) throws java.io.IOException
{
File temp;
try {
temp = File.createTempFile("graph_", ".dot.tmp", new File(GraphViz.TEMP_DIR));
FileWriter fout = new FileWriter(temp);
fout.write(str);
fout.close();
}
catch (Exception e) {
System.err.println("Error: I/O error while writing the dot source to temp file!");
return null;
}
return temp;
}
/**
* Returns a string that is used to start a graph.
* @return A string to open a graph.
*/
public String start_graph() {
return "digraph G {" ;
}
/**
* Returns a string that is used to end a graph.
* @return A string to close a graph.
*/
public String end_graph() {
return "}";
}
/**
* Read a DOT graph from a text file.
*
* @param input Input text file containing the DOT graph
* source.
*/
public void readSource(String input)
{
StringBuilder sb = new StringBuilder();
try
{
FileInputStream fis = new FileInputStream(input);
DataInputStream dis = new DataInputStream(fis);
BufferedReader br = new BufferedReader(new InputStreamReader(dis));
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
dis.close();
}
catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
this.graph = sb;
}
} // end of class GraphViz
通过下面这个类调用graphViz.java
import java.io.File;
public class Proba
{
public static void main(String[] args)
{
Proba p = new Proba();
p.start();
// p.start2();
}
/**
* Construct a DOT graph in memory, convert it
* to image and store the image in the file system.
*/
private void start()
{
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());
String type = "gif";
// String type = "dot";
// String type = "fig"; // open with xfig
// String type = "pdf";
// String type = "ps";
// String type = "svg"; // open with inkscape
// String type = "png";
// String type = "plain";
File out = new File("/tmp/out." + type); // Linux
// File out = new File("c:/eclipse.ws/graphviz-java-api/out." + type); // Windows
gv.writeGraphToFile( gv.getGraph( gv.getDotSource(), type ), out );
}
/**
* Read the DOT source from a file,
* convert to image and store the image in the file system.
*/
private void start2()
{
// String dir = "/home/jabba/eclipse2/laszlo.sajat/graphviz-java-api"; // Linux
// String input = dir + "/sample/simple.dot";
String input = "c:/eclipse.ws/graphviz-java-api/sample/simple.dot"; // Windows
GraphViz gv = new GraphViz();
gv.readSource(input);
System.out.println(gv.getDotSource());
String type = "gif";
// String type = "dot";
// String type = "fig"; // open with xfig
// String type = "pdf";
// String type = "ps";
// String type = "svg"; // open with inkscape
// String type = "png";
// String type = "plain";
File out = new File("/tmp/simple." + type); // Linux
// File out = new File("c:/eclipse.ws/graphviz-java-api/tmp/simple." + type); // Windows
gv.writeGraphToFile( gv.getGraph( gv.getDotSource(), type ), out );
}
}
Graphviz介绍
graphviz是贝尔实验室几个计算机牛人设计的一个开源 的图表(计算机科学中数据结构中的图)可视化项目,主要用C语言实现,主要实现了一些图布局算法。通过这些算法,可以将图中的节点在画布上比较均匀的分 布,缩短节点之间的边长,并且尽量的减少边的交叉。
graphviz提供命令式的绘图方式,它提供一个dot语言 用来编写绘图脚本,然后对这个脚本进行解析,分析出其中的定点,边以及子图,然后根据属性进行绘制。具体的可以看一个例子,这个例子来自官方的文档。
1. digraph G {
2. main -> parse -> execute;
3. main -> init;
4. main -> cleanup;
5. execute -> make_string;
6. execute -> printf
7. init -> make_string;
8. main -> printf;
9. execute -> compare;
10. }
digraph指定该图是一个有向图(directed graph),->表示一条边,main,parse,execute等是顶点,运行出来的效果很好看,如下图:
需要注意的是,我在这个dot脚本中没有指定任何的关于图的位置的信息,布局器会自动的根据图形的类型进行布局,并最终展现出来。
再来看一个比较复杂,并且是程序员经常使用的功能,数据结构图:
1. digraph g {
2. node [shape = record,height=.1];
3. "<f0> |<f1> G|<f2> "];
4. "<f0> |<f1> E|<f2> "];
5. "<f0> |<f1> B|<f2> "];
6. "<f0> |<f1> F|<f2> "];
7. "<f0> |<f1> R|<f2> "];
8. "<f0> |<f1> H|<f2> "];
9. "<f0> |<f1> Y|<f2> "];
10. "<f0> |<f1> A|<f2> "];
11. "<f0> |<f1> C|<f2> "];
12. "node0":f2 -> "node4":f1;
13. "node0":f0 -> "node1":f1;
14. "node1":f0 -> "node2":f1;
15. "node1":f2 -> "node3":f1;
16. "node2":f2 -> "node8":f1;
17. "node2":f0 -> "node7":f1;
18. "node4":f2 -> "node6":f1;
19. "node4":f0 -> "node5":f1;
20. }
运行后的效果如下图所示:
不知道其他的程序员怎样,反正我对命令行情有独钟,比较喜欢这一类的工具。最早接触的计算机系统正是一个没有图形系统的BSD,由此对命令行的,没 有界面的程序都特别感兴趣。
相关的想法
自从使用了graphviz以后,一直想着把这个好东西移植到java下来,大概的思想跟graphviz类似:
- 解析dot脚本,生成图的对象,这个图中包括节点,边以及子图等对象,这些对象上都绑定着相应的属性
- 将解析出来的图对象发送给layout engine进行处理,layout engine可以选择布局策略,比如流布局等
- 从layout engine中得到布局后的图对象,并交给image engine处理,得到最终结果,负责展示或者保存等
dot 的语法定义比较简单,我已经用javacc构造了一个dot的分析器,现在可以从dot文件中构建出图对象出来,不过还需要进一步完善,可以看看这个 BNF定义:
1. graph -> [strict] (digraph|graph) id '{' stmt-list '}'
2. stmt-list -> [stmt [';'] [stmt-list] ]
3. stmt -> attr-stmt | node-stmt | edge-stmt | subgraph | id '=' id
4. attr-stmt -> (graph | node | edge) attr-list
5. attr-list -> '[' [a-list] ']' [attr-list]
6. a-list -> id '=' id [','][a-list]
7. node-stmt -> node-id [attr-list]
8. node-id -> id [port]
9. port -> port-location [port-angle] | port-angle [port-location]
10. port-location -> ':' id | ':' '(' id ',' id ')'
11. port-angle ->'@' id
12. edge-stmt -> (node-id | subgraph) edgeRHS [attr-list]
13. edgeRHS -> edgeop (node-id | subgraph) [edgeRHS]
14. subgraph -> [subgraph id] '{' stmt-list '}' | subgraph id
(最近老是感觉时间不够用,有很多有意思的项目要做,比如要完善前几天说的那个bbms(Bus Based Message Service), 再比如修改用Swing和Smack做一个jabber的客户端jTalk,都是很有搞头的,唉,扯远了。)
当然graphviz的功能不至于此,它提供一个lib,可以用来将绘图引擎嵌入在自己的应用中。这是一个很有意义的事,我们可以不必掌握布局部分 的复杂算法,把精力放在业务逻辑部分,将最后的图对象交给这个引擎来处理即可。当然,如果你正好和我一样,想了解其神奇的布局算法,不妨翻翻它的源码,欢 迎交流之至。
我在周末分析了下graphviz的内部结构,并建立了一个java的项目jraph ,用来做简单的移植,主要是学习之用,其实,Java的图库还是相当丰富的,比如JGraph ,JGraphT,Prefuse,TouchGraph等等,用来做项目当然是很便捷的,但是我还是比较喜欢看着一个算法被自己实现的过程,特别是这种 比较神奇的算法,哈哈。
预期中的API如下:
1. public static void main(String[] args){
2. new GCodeParser("file.g");
3. GraphSet gs = parser.parse();
4. GraphSet layouted =
5. new GraphLayoutEngine(gs).layout();
6. ImageEngine imgEngine =
7. new GraphImageEngine(layouted);
8. imgEngine.export(0);
9. }
同graphviz一样,先调用分析器构造出图的集合GraphSet(包括多个图,每个图中包括Vertex,Edge,及SubGraph), 然后进行布局,最后将通过布局后的GraphSet绘制结果。目前完成了框架的设计部分,分析器部分基本完成,layout部分只实现了一个策略,即force-based 布局算法,不过layout engine被设计成可以插拔的模型,如果有了新的算法实现,可以很容易的整合起来。
dot.exe -Tpng -o new.png -Kdot new.dot