自建GraphViz API

本文使用PHP实现了一种与Google GraphViz Charts参数兼容的API,相较于Google GraphViz Charts API的优点是支持中文。

环境搭建

基于Debian,需要安装GraphViz及gd库

apt-get install php5
apt-get install graphviz
apt-get install php5-gd
root@server1:/var/www/gv# dot -V
dot - graphviz version 2.26.3 (20100126.1600)

API代码

<?php
/**
 * graphviz api
 *
 * $Id api.php  i@annhe.net 2015-6-8 $
 **/
$host = $_SERVER['HTTP_HOST'];
$siteurl = "http://" . $host . "/gv/"; 
$cht = "dot";
$chl = "graph {fontname=\"SimSun\";node[shape=box];a[label=\"nothing to do~\"];}";
$error = "error.png";
$engine = array("dot", "neato", "fdp", "sfdp", "twopi", "circo");
$imgtype = "png"; 
$imgtype_arr = array("png", "gif", "jpeg");

if(isset($_GET['cht'])) {
	$cht = $_GET['cht'];
	$arr = explode(':', $cht);
	if(!array_key_exists("1", $arr)) {
		$cht = "dot";
	} else {
		$cht = $arr['1'];
	}
	if(!in_array($cht, $engine)) {
		$cht = "dot";
	}
}
if(isset($_GET['chl'])) {
	$chl = $_GET['chl'];
}

if(isset($_GET['chof'])) {
	$imgtype = $_GET['chof'];
	if(!in_array($imgtype, $imgtype_arr)) {
		$imgtype = "png";
	}
}

$gvname = md5($chl) . $cht;
$gvpath = "./gv/" . $gvname . ".gv";
$imgpath = "img/" . $gvname . "." .  $imgtype;


$file = fopen("$gvpath", "w");
$encode = mb_detect_encoding($chl, array("ASCII","UTF-8","GB2312", "GBK", "EUC-CN"));

if($encode != "UTF-8") { 
	$chl = iconv("$encode", "UTF-8", $chl);
}
$chl = str_replace(">", ">", $chl);
$chl = str_replace("<", "<", $chl);
$chl = str_replace("&quot;", "\"", $chl);
fwrite($file, "$chl");
fclose($file);
if(!file_exists($imgpath)) {
	exec("$cht -T$imgtype $gvpath -o $imgpath",$out, $ret);
	if($ret != 0) {
		$imgpath = $error;		
		$imgtype = "png";
	}
}

$imgstrout = "image$imgtype(imagecreatefrom$imgtype('$imgpath'));";
header("Content-Type: image/$imgtype; charset=UTF-8");
eval($imgstrout);
?>

演示

参数与Google GraphViz Charts一致,及

  • cht=gv[:<opt_engine>]  ,  可选参数  , 如果没有,这默认为gv:dot。其他可用引擎有 neato twopi circo fdp sfdp (比Google GraphViz Charts多一个sfdp)
  • chl=<DOT_string>,  必选参数。支持中文
  • chof=<png|gif|jpeg>, 可选参数,默认为png

Discuz插件形式的演示:http://www.tecbbs.com/forum.php?mod=viewthread&tid=6724

WordPress尚未做成插件,暂时直接URL插入图片

GraphViz API演示

GraphViz API演示

中文图片

GraphViz中文演示

问题

直接返回图片内容

API一开始返回的是图片URL,这样程序需要有2次HTTP请求才能确定图片链接,会严重拖慢网页加载速度。因此改为直接返回图片内容,只需要一次HTTP请求。

$imgstrout = "image$imgtype(imagecreatefrom$imgtype('$imgpath'));";
header("Content-Type: image/$imgtype; charset=UTF-8");
eval($imgstrout);

eval()函数将字符串作为PHP代码来执行。

Gif not recognized问题

如果API搭建在Centos上,可能需要rpm方式安装GraphViz,yum方式的GraphViz版本太旧,不支持gif输出。 需要安装 graphviz-gd。

[root@HADOOP-215 gv]# dot -Tgif 05acaa447bc10f15991d19580ccc0d41dot.gv -o test.gif
Format: "gif" not recognized. Use one of: canon cmap cmapx cmapx_np dot eps fig gv imap imap_np ismap pic plain plain-ext pov ps ps2 
svg svgz tk vml vmlz xdot xdot1.2 xdot1.4[root@HADOOP-215 gv]# yum install graphviz-gd
Loaded plugins: fastestmirror, priorities
Loading mirror speeds from cached hostfile

=====================================================================================================================================
 Package                              Arch                    Version                         Repository                        Size
=====================================================================================================================================
Installing:
 graphviz-gd                          x86_64                  2.38.0-1.el6                    graphviz-stable                  6.6 k
Installing for dependencies:
 graphviz-plugins-gd                  x86_64                  2.38.0-1.el6                    graphviz-stable                   20 k

Transaction Summary
=====================================================================================================================================
Install       2 Package(s)
[root@HADOOP-215 gv]# dot -Tgif 05acaa447bc10f15991d19580ccc0d41dot.gv -o test.gif
[root@HADOOP-215 gv]# ls
05acaa447bc10f15991d19580ccc0d41circo.gv  05acaa447bc10f15991d19580ccc0d41fdp.gv  test.gif
05acaa447bc10f15991d19580ccc0d41dot.gv    76fadd7c49a49f7a08d97a34c111d534dot.gv

另外,GraphViz 的repo文件链接是:wget http://www.graphviz.org/graphviz-rhel.repo,下载之后可以直接yum安装最新稳定版本的GraphViz。

urlencode问题

dot源码需要进行urlencode,但是考虑到和Google GraphViz Charts保持一致,只进行部分字符的encode

$texcode = str_replace('#', "%23", $texcode); //'#' 需要urlencode

如果不encode,获得的dot源码不完整

digraph example3 { Server1 -> Server2 Server2 -> Server3 Server3 -> Server1 Server1 [shape=box, label="Server1\nWeb Server", fillcolo
r="

完整的代码是

digraph example3 { Server1 -> Server2 Server2 -> Server3 Server3 -> Server1 Server1 [shape=box, label="Server1\nWeb Server", fillcolo
r="#ABACBA", style=filled] Server2 [shape=triangle, label="Server2\nApp Server", fillcolor="#DDBCBC", style=filled] Server3 [shape=circle, label="Server3\nDatabase Server", fillcolor="#FFAA22",style=filled] }

中文字体问题

需要安装中文字体,否则中文将乱码,可以直接拷贝其他机器的wqy-microhei字体。

cd /usr/share/fonts/
tar zxvf wqy.tar.gz 
rm -f wqy.tar.gz 
mv usr/share/fonts/wqy-microhei/ .
cd wqy-microhei/

$_GET解码问题

PHP官网明确指出:超全局变量 $_GET 和 $_REQUEST 已经被解码了。对 $_GET 或 $_REQUEST 里的元素使用 urldecode() 将会导致不可预计和危险的结果。

本文实现的API会将dot源码保持为文件,文件名使用dot源码的md5值

$gvname = md5($chl) . $cht;

由于$_GET已经经过了urldecode,所以客户端程序使用dot源码文件名时也需要urldecode,这样才能获取正确的文件名

$p = str_replace("&gt;", ">", $texcode);
$p = str_replace("&lt;", "<", $p);
$p = str_replace("&quot;", "\"", $p);
$gv_md5 = md5(urldecode($p)) . $engine . ".gv";

2.38中文问题

Debian7和Centos6.5软件源里的GraphViz都是2.26版本的,目前(15年6月)最新稳定版本是2.38,手贱升级到了2.38,发现该版本子图中文显示有问题。上文演示的图片,代码如下:

digraph idp_modules{
rankdir = TB;
fontname = "Microsoft YaHei";
fontsize = 12;
  
node [ fontname = "Microsoft YaHei", fontsize = 12, shape = "record" ]; 
edge [ fontname = "Microsoft YaHei", fontsize = 12 ];
  
     subgraph cluster_sl{
         label="IDP支持层";
         bgcolor="mintcream";
         node [shape="Mrecord", color="skyblue", style="filled"];
         network_mgr [label="网络管理器"];
         log_mgr [label="日志管理器"];
         module_mgr [label="模块管理器"];
         conf_mgr [label="配置管理器"];
         db_mgr [label="数据库管理器"];
     };
  
     subgraph cluster_md{
         label="可插拔模块集";
         bgcolor="lightcyan";
         node [color="chartreuse2", style="filled"];
         mod_dev [label="开发支持模块"];
         mod_dm [label="数据建模模块"];
         mod_dp [label="部署发布模块"];
     };
  
mod_dp -> mod_dev [label="依赖..."];
mod_dp -> mod_dm [label="依赖..."];
mod_dp -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
mod_dev -> mod_dm [label="依赖..."];
mod_dev -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
mod_dm -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
}

使用2.38生成的图片

subgraph_zh

subgraph_zh

网上搜索的结果,一个说是引号问题

macbook darwin下安装了graphviz 2.38.0。
画有向图digraph的时候,如果label中包含中文字符,生成的图片中会缺文字。
网上的解释是fontname没有设置的原因,可设置fontname后还是会出现同样的问题。

例如:
digraph Geely {
node [shape=record,fontname="SimSun.ttf"];
edge [fontname="SimSun.ttf"];

jlwy [label="吉利万源"];
jlzy [label="吉利兆园"];
volv [label="沃尔沃"];

jlwy -> jlzy [label="87.65%"];
jlzy -> volv [label="100%"];

}

$ dot -Tpng geely.dot -o geely.png
$ open geely.png

生成的图像中,连接线上的百分比有,但节点中的内容是空的。

偶然原因,在引号中的中文前加了个空格,字符就能显示出来了,可能是个bug吧,总之问题解决了。
volv [label=" 沃尔沃"];

按照此方法能够正常显示图片。

另一个说是shape问题

可是子图里面的节点却依然是空(不是乱码,只是没有内容)。于是我查找半天,最终发现罪魁祸首是:
zongshu_title [label = "研究综述" shape=record]
看到了吧?就是最后这句shape=record。为什么有问题?不知道。但是把它删去,一点不改变原先的图形结构,中文却又都出来了。

按照此方法中文是可以显示了,但是形状效果都没了

subgraph_zh

subgraph_zh

参考资料

[1]. http://php.net/manual/zh/function.urldecode.php
[2]. http://php.net/manual/zh/function.imagecreatefromjpeg.php
[3]. http://blog.sina.com.cn/s/blog_b09d4602010195ky.html
[4]. 2.38中文问题 http://wshuyi.github.io/2014/05/03/graphvizchinesewindows/
[5]. 2.38中文问题 http://blog.csdn.net/spacecraft/article/details/25163293

One thought on “自建GraphViz API

  1. Pingback: Discuz插件处理大型dot源码 | 知行近思

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注