常见问题
如果您在这里找不到问题的答案,请在Graphviz论坛中提问。
- Contributions
- FaqPSdefs 用于ISO-latin1字体的PostScript定义 (由 Enrique Vidal 提供)
常规
在哪里可以查看控制dot或neato的所有属性列表?
参见Graph Attributes。此外还包含有关command-line usage和output formats的信息。
在哪里可以讨论Graphviz?
请在Graphviz论坛中发布问题和评论
我正在尝试使布局更大。怎么做?
有多种方法可以增加布局的尺寸。在这样做时,需要决定是否同时增加节点和文本的大小。
一种方法是调整单个参数,如fontsize、nodesep和ranksep。例如,
digraph G {
graph [fontsize=24]
edge [fontsize=24]
node [fontsize=24]
ranksep = 1.5
nodesep = .25
edge [style="setlinewidth(3)"]
a -> b -> c
}如果这样做,请确保没有与冲突的图形大小设置相冲突,比如size="6,6",这会将所有内容重新缩放回较小尺寸。
如果您正在使用fdp或neato,增加边的长度将有助于扩展布局。
graph G {
layout="neato"
edge [len=3]
a -- { b c d }
}对于twopi和circo,还有其他参数如ranksep可以使用。请参阅graph attributes。
你也可以使用ratio属性。如果将size属性设置为所需的绘图尺寸,然后设置ratio=fill,节点位置会在x和y方向上分别缩放,直到绘图填满指定尺寸。请注意节点大小保持不变。如果改为设置ratio=expand,布局会在x和y方向上均匀放大,直到至少一个维度符合尺寸要求。
如果您指定了size属性但以感叹号(!)结尾,最终的图形将在x和y方向上均匀放大,直到至少一个维度符合尺寸要求。请注意所有内容都会被放大,包括文本和节点大小。
如果您正在使用PostScript,可以通过手动添加类似2 2 scale的命令来放大输出,该命令应在PostScript环境设置时添加。如果您的工具会查看此头部信息,请确保也调整BoundingBox。
如何在dot中连接或合并特定的边路径?
你可以尝试运行dot -Gconcentrate=true或者在你想要分割或连接边的地方引入自己绘制的虚拟节点(显示为小圆圈):
digraph G {
yourvirtualnode [shape=circle,width=.01,height=.01,label=""]
a -> yourvirtualnode [arrowhead=none]
yourvirtualnode -> {b;c}
}如何生成PDF格式的图形布局?
如果您的Graphviz版本支持cairo/pango,可以直接使用-Tpdf标志。
遗憾的是,这种方式无法处理嵌入式链接。
如果需要嵌入链接,或者没有安装cairo/pango,可以先生成PostScript输出,然后使用外部工具将其转换为PDF。例如,dot -Tps | epsf2pdf -o file.pdf。请注意URL标签会被保留,以便生成可点击的PDF对象。
如果您打算在某些文档准备系统(如pdflatex)中将图形用作PDF,使用-Tps2而非-Tps非常重要。一般来说,如果您确实需要PDF输出,即希望使用-Tpdf标志,建议在转换为PDF之前先使用-Tps2。
在下图中,阴影节点将包含错误的输出。
如何创建重复节点?
为具有重复标签的节点创建唯一标识。
digraph G {
node001 [label = "A"]
node002 [label = "A"]
node001 -> node002
}如何设置图表或集群标签而不让其传播到所有子集群?
在图的末尾(在右大括号之前)设置标签,在所有内容定义完成后。 (我们承认为非继承属性设置定义一些特殊语法似乎是可取的。)
如何在neato中绘制多条平行边?
当splines属性为false时(这是默认值),多重边会被绘制为一组简单弯曲边的纺锤形。不会尝试避开中间的节点。
当splines=true或polyline时,多边会以大致平行的样条曲线或多段线绘制。这依赖于节点之间没有重叠。
另一个有时足够使用的技巧是为边指定使用颜色列表的多种颜色。这将生成一组紧密平行的样条曲线,每条曲线使用其指定的颜色。阅读color属性以获取更多信息。
如何对称化(平衡)树状布局?
当树节点拥有偶数个子节点时,它不一定正好位于中间两个子节点的正上方。 如果您知道子节点的顺序,一个简单的技巧是引入新的不可见中间节点来重新平衡布局。 连接边也应设为不可见。例如:
digraph G {
a -> b0
xb [label="",width=.1,style=invis]
a -> xb [style=invis]
a -> b1
{rank=same b0 -> xb -> b1 [style=invis]}
b0 -> c0
xc [label="",width=.1,style=invis]
b0 -> xc [style=invis]
b0 -> c1
{rank=same c0 -> xc -> c1 [style=invis]}
}这个小技巧确实应该内置到我们的求解器中(并且使其独立于子节点的顺序,同时也能适用于树以外的其他布局)。
如何报告我发现的一个错误或问题?
您可以通过访问Graphviz的Issues页面来报告或查看Graphviz的错误和问题。
集群
如何在集群框之间创建边?
这仅在Graphviz 1.7及更高版本中有效。要在集群之间创建边,首先需要设置图属性compound=true。
然后,您可以将集群名称指定为边的逻辑头或尾。这将使连接两个节点的边被裁剪到给定集群周围框的外部。
例如,
digraph G {
compound=true; nodesep=1.0;
subgraph cluster_A {
a -> b; a -> c;
}
subgraph cluster_B {
d -> e; f -> e;
}
a -> e [ ltail=cluster_A, lhead=cluster_B ];
}有一条边从 cluster_A 指向 cluster_B。如果您改为这样声明
a -> e [ltail=cluster_A];
这为您提供了从cluster_A到节点e的一条边。或者您也可以直接指定一个lhead属性。如果指定的逻辑节点未定义集群,程序会发出警告。此外,如果将一个集群指定为边的逻辑头,则实际头节点必须包含在该集群中,而实际尾节点则不能包含在内。对于逻辑尾节点也会进行类似的检查。在这些情况下,边会像往常一样在实际节点之间绘制。
集群很难看清。
在集群中设置bgcolor=grey(或其他颜色)。
输出
如何获取高质量(抗锯齿)的输出?
最简单的方法是使用基于矢量的输出格式,如PDF、SVG或PostScript。此外,如果Graphviz具有cairo/pango后端,这将生成抗锯齿输出。
另一种方法是先在PostScript中生成布局(使用-Tps选项),然后在启用抗锯齿的情况下通过Ghostview运行。重要的命令行选项包括:
-dTextAlphaBits=4-dGraphicsAlphaBits=4(4是允许的最高级别抗锯齿 - 参见Ghostview文档)。
渲染光栅的完整命令行可能类似于:
$ gs -q -dNOPAUSE -dBATCH -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -sDEVICE=png16m -sOutputFile=file.png file.ps
在Mac OS X系统上,pixelglow移植版采用了苹果的Quartz渲染引擎,支持抗锯齿功能。同时它还为其用户界面提供了精美的文档容器。 (需要注意的是,若您的Mac配备3D显卡,则无法将Pixelglow Graphviz作为网页服务器或其他后台进程运行,因为Quartz需要占用该资源来加速渲染。)
我只能获取11x17的输出吗?
不是我们的问题!可能是您的打印机设置导致的。如果不相信,可以运行dot -Tps并查看BoundingBox头部信息。坐标单位是1/72英寸。
如何在标签中创建特殊符号和重音?
直接将符号(例如通过复制/粘贴)插入到您的dot源代码中,并保存为UTF-8格式,例如:
graph G {
yen [label="¥"]
}如果无法以UTF-8格式保存,请尝试使用HTML实体,例如¥表示日元符号¥。示例:
graph G {
yen [label="¥"]
}更一般地说,如何使用非ASCII字符集?
以下内容适用于Graphviz 2.8及更高版本。(在旧版Graphviz中,有时可以直接在输入流中使用Latin-1或其他UTF-8字符,但结果并不总是正确的。)
输入:基本思路是找到您想要的字符对应的Unicode值,然后在文本字符串"..."或类似HTML的标签<...>中输入该值。
例如,数学中的全称符号∀的值为0x2200。有几种方法可以将其插入文件中。一种方法是写出ASCII表示形式:&#,其中是该值的十进制表示。0x2200的十进制值是8704,因此该字符可以指定为∀。或者,Graphviz接受UTF-8编码的输入。对于全称符号,其UTF-8表示是3个字节,其十进制值为226 136 128。为了方便起见,您可能会使用您最喜欢的编辑器输入此内容,并根据您选择的字符集进行调整。然后,您可以使用iconv程序将图形从您的字符集映射到UTF-8或Latin-1。
我们也接受FaqSymbols中建议的拉丁-1字符的HTML符号名称。
例如,分币符号(Unicode和拉丁-1十进制值162)可以插入为¢
请注意,图文件必须始终是纯文本文档,而不是Word或其他富格式文件。任何未包含在"..."或<...>中的字符必须是普通ASCII字符。特别是所有DOT关键字如digraph或subgraph都必须是ASCII字符。
由于我们无法总是猜测编码,您应该将图的属性charset设置为UTF-8、Latin1(别名ISO-8859-1或ISO-IR-100)或用于繁体中文的Big-5。这可以在图文件中或命令行中完成。例如charset=Latin1。
输出:在最终渲染时,必须确保有一个包含您指定字符的字形的字体可用。该字体的选择取决于目标代码生成器。对于基于gd的光栅生成器(PNG、GIF等),您需要在运行Graphviz程序的机器上有一个TrueType或Type-1字体文件。如果Graphviz是用fontconfig库构建的,它将用于查找指定的字体。否则,Graphviz将在各种默认目录中查找字体。这些被搜索的目录包括由fontpath属性指定的目录、相关的环境或shell变量(参见fontpath条目)以及已知的系统字体目录。表格指出这些字形来自times.ttf字体。使用fontconfig时,很难指定这个字体。Times通常会被解析为Adobe Type1 times,它不包含该页面上看到的所有字形。)
对于PostScript,输入必须是UTF-8的ASCII子集或Latin-1编码。(我们曾寻求更通用的解决方案,但发现PostScript中每种字体类型对UTF-8和Unicode的处理方式都不同,我们没有时间逐个解决这些特殊情况。
对于SVG输出,我们直接将原始UTF-8(或其他编码)传递到生成的代码中。
如何创建自定义形状?
一种方法是使用HTML-like labels,可能结合嵌入式图像使用IMG属性。
正如dot用户指南中提到的,如果您想要真正自定义的形状,有几种方法可以实现这一点。目前,这些形状必须采用PostScript或图像文件格式,否则您需要修改源代码。一个严重的问题是,您无法创建在所有驱动程序和交互式前端(如Grappa)中都能工作的自定义形状。至少SVG具有交互式渲染器,而PostScript可以转换为PDF,后者也具备一些交互功能。
如果使用SVG(-Tsvg)、PostScript(-Tps,-Tps2)或其中一种光栅格式(-Tgif, -Tpng, 或 -Tjpg),您可以通过文件名将某些图像(例如图片)加载到节点中。例如:
yournode [image="yourface.gif"];
表示节点的内容在GIF文件yourface.gif中给出。image属性指定要使用的文件。
(还有一个已弃用的shapefile
属性。这与image类似,但节点形状将始终是一个方框。)
注意:在2006年3月11日之前的版本中,特别是1.12及更早的graphviz版本,还需要设置属性shape=custom。
使用 -Tsvg 时,image 必须指定包含 GIF、PNG 或 JPEG 位图文件的文件名。请注意,文件内容不会被复制到 SVG 输出中,
仅会引用文件名。因此,为了使 Graphviz 生成的 SVG 输出能正确显示,图像文件必须对 SVG 查看器可用。
使用PostScript时,image必须指定包含封装PostScript或位图文件的名称。文件内容会被复制到输出文件中。请注意,封装PostScript只会被复制一次。对图像内容的限制与External PostScript files中下面指定的限制相同。
对于位图输出,image是包含位图图像的文件名。该文件会被打开并复制(可能还会缩放)到输出绘图中。
这段代码仍处于初步阶段,我们已注意到索引色彩映射管理中存在一些色彩量化问题,目前正在努力理解和修正。(您可以使用-Gtruecolor=1尝试32位内部画布作为替代方案,但我们观察到图像会出现模糊(失真?)现象。)
当该软件作为网页服务器使用时,对图像文件的访问限制更为严格。请参阅SERVER_NAME。
如果使用PostScript驱动程序(-Tps),您可以将节点形状作为外部PostScript文件(如EPS(封装式PostScript))导入。至少,外部文件必须具有有效的BoundingBox头,并且不会对图形状态进行剧烈更改,因为我们没有安装包装器来抑制showpage等操作。
要导入外部PostScript文件,请按如下方式设置shape和shapefile属性:
somenode [shape=epsf, shapefile="yourfile.ps" ];
EPSF形状总是被裁剪到其边界框。
使用[shape=epsf, shapefile="yourfile.ps" ]的方式在很大程度上已被上一节描述的机制所取代,即使用[image="yourfile.ps" ]。
如果使用PostScript驱动程序(dot -Tps),您可以定义一个用于绘制形状的PostScript过程。该过程必须能够绘制可变大小的形状。
包含该定义的文件可以通过-l标志作为命令行参数加载:
$ dot -Tps -l yourPS.ps file.dot -o file.ps
在图形文件中,像这样调用形状:
somenode [shape=yourshape]
在file.ps文件中,对于非填充节点,yourshape过程将被这样调用:
[ 54 36 0 36 0 0 54 0 54 36 ] 4 false yourshape
当前颜色为节点的笔触颜色。该数组包含形状的边界多边形,第一个点在末尾重复,后跟点数。目前,形状始终为矩形。从左到右,数组中的点始终按逆时针方向排列,从右上顶点开始。顶点数后的布尔值(此处为false)表示节点的fill属性值。坐标均为绝对画布坐标。
对于具有fill=true的节点,上述对yourshape的调用之前会先执行以下操作
[ 54 36 0 36 0 0 54 0 54 36 ] 4 true yourshape
当前颜色是节点的fillcolor。
注意:在2005年9月23日之前的版本中,yourshape仅被调用一次,使用节点的填充值并将颜色设置为节点的笔触颜色。
例如,这里是一个合理的形状文件DFD.ps的内容,可以通过[shape=DFDbox]调用
/xdef {exch def} bind def
/DFDbox {
10 dict begin
/fflag xdef
/sides xdef
fflag % if shape is filled
{
aload pop
newpath
moveto
1 1 sides { pop lineto } for
closepath fill
}
{
aload pop
% draw the sides
newpath
moveto
1 1 sides {
2 mod 0 ne
{moveto} % even sides
{lineto currentpoint stroke moveto} % odd sides
ifelse
} for
}
ifelse
end
} bind def
这种自定义形状总是会被裁剪到其边界框内。如果有人愿意尝试并贡献代码,在shapes.c文件中的user_shape()函数里添加一个钩子来确定非矩形裁剪多边形(也许)应该不难。
请注意,默认情况下,边界框会围绕内容绘制,并且节点标签也会显示。如果您希望取消这些设置,请在节点中设置label=""和peripheries=0。
独立于驱动的自定义形状
如果不使用PostScript,您需要亲自动手修改源代码。其他代码生成器都不直接支持自定义节点形状。如果自定义形状需要是高级且与驱动程序无关的,那么您可以在shapes.c文件中添加特定形状的函数(方法),并在Shapes[]数组中添加相应的条目,将形状名称映射到方法。该文件的注释头部描述了方法接口。必须定义方法来初始化形状(通常将其大小调整到足以容纳其文本标签)、将端口名称绑定到坐标、测试点是否在形状实例内(用于边缘裁剪)、通过gvrender_engine_t结构提供的函数生成形状的代码,并返回一个框路径以访问节点内部的端口(如果它们可以存在)。
关于通过gvrender_engine_t可用的函数及Graphviz图形模型的更多信息,可在Graphviz库手册的第5节中找到。
行为类似于多边形的形状可以通过基本多边形方法进行引导;例如可以参考invtri或tab形状。
这类形状使用一个多边形描述符,其字段如下所列。
| 字段名称 | 描述 | 默认值 |
|---|---|---|
regular |
if a regular polygon | FALSE |
peripheries |
number of border peripheries | 1 |
sides |
number of sides (1 for curves) | 4 |
orientation |
angular rotation in degrees | 0 |
distortion |
trapezoidal distortion | 0 |
skew |
parallelogram distortion | 0 |
option |
fancy options: ROUNDED, DIAGONALS, AUXLABELS | 0 |
对于不是从通用多边形派生的形状,请参见record或epsf形状。
驱动程序相关的自定义形状
要实现一个特定驱动程序的形状(如GIF或PNG图标),您需要为驱动程序函数编写一个主体,该函数实现用户自定义形状。这包括为特定驱动程序提供library_shape函数(如果该函数尚不存在),并使用驱动程序的图形函数生成显示形状所需的图形操作。(Graphviz自带的驱动程序可以在插件目录中找到。)
用户形状函数基本上接收四个参数:
- 自定义形状名称字符串
- 形状边界多边形的绝对画布坐标
- 坐标数量(当前始终为4)
- 填充标志
剩下的就交给你了,但以防万一请先联系我们。
如何使用绘图图层(叠加层)?
如果设置了layers图属性,图形将以一系列彩色图层或覆盖层的形式呈现。(此颜色设置会覆盖其他所有设置。)layers
定义了一个图层名称列表,每个名称由一系列分隔字符分隔。标记可以是任何标识符或自然数,但保留字all除外。
默认情况下,分隔字符为冒号、空格和制表符,但可以通过layersep图属性来覆盖此设置。
节点、边或簇的layer属性可使其在指定图层中显示。其值表示来自图层图属性的一系列图层。
该属性通过由layerlistsep属性分隔的图层区间列表来指定。每个图层区间可以写作单个图层名称,或由layersep属性分隔符分隔的两个图层名称。关键字all表示所有可能的图层。若all
作为范围的一部分使用,则该范围表示以另一标记为边界的全部图层。因此,下例中边node2 -> node3的pvt:all图层对应pvt、test、new和ofc图层。而all:pvt,new,ofc则对应local、pvt、new和ofc图层。
例如,这个图表:
digraph G {
layers="local:pvt:test:new:ofc";
node1 [layer="pvt"];
node2 [layer="all"];
node3 [layer="pvt:ofc"]; /* pvt, test, new, and ofc */
node2 -> node3 [layer="pvt:all"]; /* same as pvt:ofc */
node2 -> node4 [layer=3]; /* same as test */
}生成如下所示的5个层级:
| 第一层 | 第二层 | 第三层 |
|---|---|---|
![]() |
![]() |
![]() |
| 第4层 | 第5层 |
|---|---|
![]() |
![]() |
在分层图中,如果某个给定节点(或边)没有分配层级,但相邻的边(或节点)有层级分配,则其层级会从这些相邻元素中推断出来。例如,在上面的示例中,node4仅出现在第3层,因为为其连接边指定了层级分配。但需要注意的是,如果一个没有层级属性的节点或边与另一个没有层级属性的边或节点相邻(或者该节点没有任何边),则该节点或边会出现在所有层级上。
要更改默认设置,使没有层属性的节点和边出现在所有层上,请插入
node [layer=all];
edge [layer=all];
在graph文件的开头。
该图可以有一个layerselect属性,用于指定应输出的图层。该值使用与layer属性相同的具体语法。
目前,将多层输出合并为单个输出文件的功能仅支持PostScript格式。不过,layerselect属性可用于选择任意格式输出的单个图层。
图层的颜色序列设置在数组layercolorseq中(至少在PostScript中如此)。第一个索引是1,每个元素都是一个三元素的颜色坐标数组。
可以通过设置此数组的值来创建自定义图层颜色。
待办事项
- 只需按层更改默认颜色,从而允许用户在需要时覆盖单个节点或边的颜色。
- 完全关闭图层着色,仅使用绘图本身固有的颜色。
- 强制给定子图中的节点/边采用特定属性。可能需要在libgraph解析器中添加钩子。支持这种语义会相当简单:对于该子图中的每个节点/边,为其分配与该图父级默认值不同的默认属性。需要避免的是以下示例中暴露的问题:
subgraph sub0 {
node [color=red];
a; b; c;
}
subgraph sub1 {
node [shape=diamond];
a; b; c;
}
我们不想仅仅因为sub1中的默认设置就将a、b、c重置为color=black。
如何在记录标签或其他标签中实现字体和颜色的更改?
这在记录形状中是不可能的。不过,您可以使用HTML-like labels来实现这一点。
在-Tplain格式中,样条曲线不会接触节点(缺少箭头)。
边被定义为主样条曲线,如果需要的话,箭头会实际连接到节点。如果没有指定箭头,绘制边样条时会在边和节点之间留下间隙。这原本是一个错误,但现在已固化为一个特性。一个解决方法是设置edge [dir=none]。由于边没有箭头,样条曲线会一直延伸到两个节点。
当rankdir=LR时,dot和neato中记录节点的绘制方式不同。
确实如此。dot -Grankdir=LR会旋转记录节点,使其顶层字段仍按层级横向排列。而在neato中rankdir=LR参数不起作用。
一个变通方案是使用HTML-like labels(它们不会旋转;缺点是必须用XML格式编写)。
通常我们建议用更通用的HTML-like标签来替代记录节点。
另一个解决方案是用大括号{ }包裹记录标签来旋转/取消旋转记录内容。
另请参阅Scott Berkun(微软公司)所著的《如何避免愚蠢的一致性》一文。
如何将大型图表打印到多页上?
page 属性如果被设置,会指示 Graphviz 将图形打印为指定大小的分页阵列。因此,图形
digraph G {
page="8.5,11";
...
}
将输出为8.5×11英寸的页面。打印时,这些页面可以拼接成完整图形的绘制。目前该功能仅支持PostScript输出格式。
另外,有多种工具和查看器可以处理大尺寸图片,并允许您提取适合页面大小的片段进行打印。 另请参阅viewport属性。
当我有一条红色边时,在PNG和GIF格式中显示为纯红色,但在渲染为JPEG时会出现黑色边框。
这是JPEG有损压缩算法的固有缺陷。JPEG不太适合用于线条绘图。建议考虑使用PNG格式。
有时在dotty中,右键点击会显示全局菜单,但无法选择任何项目。
检查NUMLOCK键是否关闭。这是一个已知的错误。
为什么dotty会报告合法dot文件的语法错误?
dotty工具已弃用,但保留此条目以帮助仍在使用的用户。
通常这个错误会报告为:
>> graph parser: syntax error near line 14
>> context: >>> <<< digraph G {
>> dotty.lefty: giving up on dot
>> dotty.lefty: graph that causes dot
>> dotty.lefty: to fail has been saved in file dottybug.dot
可能您的shell环境(如.alias或.profile)中存在一个命令,即使对于非交互式shell也会输出内容。 当这种情况发生时,这些字符会进入dot解析器的管道并导致此问题。一个简单的检查方法是看其他用户是否也存在相同问题。
如何去除dotty中边上的小圆圈("边柄")?
dotty工具已弃用,但保留此条目以帮助仍在使用的用户。
编辑文件dotty.lefty并将其中内容为'edgehandles' = 1;的行修改为'edgehandles' = 0;,该行大约在第110行附近。
我已经有了图中所有节点和边的坐标,只想使用dot、neato或dotty来渲染它。该怎么做?
将带有布局属性的图形放入dot文件中。然后运行neato -n2。例如:
$ neato -n2 -Tgif file.dot -o file.gif
请注意,如果边没有定义pos属性,neato将执行其通常的边路由操作。所有常见的后端属性(size、overlap、page等)都可用。
我已经有了所有节点的坐标,希望使用dot或neato来绘制边线路径。
运行 neato -n。这将添加必要的边信息。
我已经有了图中所有节点和边的坐标,只想用dotty来渲染它。怎么做?
如果你想先进行布局,可以使用-Txdot作为输出格式。Dotty会使用其中提供的布局信息。
与上述相同,但我只有节点坐标,没有边。
运行 neato -Txdot -n。这将添加必要的边信息。
如何制作客户端图像映射?
使用 -Tcmapx 命令行选项。更多详情请参阅 此处。
为什么我的服务器端地图没有被识别?我已经检查了HTML!
确保您的服务器已启用地图文件。例如,如果运行的是apache,请检查httpd.conf中是否有如下行:
AddHandler imap-file map
并且确保它没有被注释掉!
我已安装Debian Graphviz,在命令行下运行良好,但通过Apache执行Perl/CGI脚本时没有生成任何输出。
例如,代码 system("/usr/local/bin/dot -Tpng /tmp/tree.dot -o /tmp/tree.png"); 没有生成文件 /tmp/tree.png。
根据我们的了解,在Debian系统上,当从没有设置HOME的Apache cgi程序运行时,dot会在没有任何stdout或stderr消息的情况下终止。
解决方法是给Apache用户ID的环境提供一个HOME目录。
也有人建议使用Graphviz的Perl模块。
来自梦工厂的Greg Brauer指出了另一种可能性:问题原来在于我在运行dot之前没有关闭临时dot文件的文件描述符。Graphviz最终会获取一个新创建的空文件(在写入缓冲区刷新到文件之前),而dot会愉快地运行该空文件并生成一个空白的输出文件,且没有任何警告。
如何获得3D输出?
Graphviz 的作者们对于过度使用3D效果持保留态度。
尽管如此,dot -Tvrml可以生成VRML文件。它没有Z坐标布局 - 您需要自己在节点的z属性中指定Z坐标,边的Z坐标会被插值计算。如果有人能为更新、更有用的格式(OpenGL Performer场景图?Open Scene Graphs?Java3D程序?)贡献一个驱动程序,我们很乐意尝试。
neato 内部通过 dim 和 dimen 属性支持更高维度的布局,
例如 neato -Gdim=7。Graphviz 输出支持 2D 和 3D,但除非您将 neato 作为库调用并检查 ND_pos(n)[i],
其中 n 是指向相关节点的指针,否则无法获得更高维度的输出。
问题
如何避免neato中的节点重叠?
使用图形属性 overlap。
如何在neato中避免节点与边重叠?
使用overlap属性在节点之间留出空间,然后使用-Gsplines=true。
$ neato -Goverlap=... -Gsplines=true -Gsep=.1
sep参数表示节点与边之间的间距,以节点边界框的比例计算。也就是说,sep=.1意味着每个节点被视为比实际大小增加了1.1倍。实际数值可能需要一些调整。(别问为什么这个参数不是一个固定常量!)请注意,这个选项会显著降低neato的运行速度,因此应谨慎使用,并且仅适用于中等规模的图。
导致崩溃?
这种情况通常发生在Graphviz库使用某一版本的stdio库构建,而用户的程序使用另一版本编译时。
如果stdio的FILE结构不同,调用agread()会导致崩溃。这主要是Windows平台上的问题,因为我们提供的二进制版本是使用特定版本的Visual Studio构建的,而stdio会随Visual Studio版本变化而变化。如果用户尝试使用cygwin或类似工具,也可能因为使用了不兼容的stdio而导致此问题。
最简单的解决方案是将整个图形读入内存,并将指向该内存的指针传递给agmemread()。
如果无法实现(例如文件过大),您需要告诉agread()使用与您提供的流兼容的读取器。默认情况下,agread
假定您传递的是由编译Graphviz时使用的stdio版本生成的FILE*,并使用该版本的fgets来读取流。
要传入自定义的读取器,请使用第三个参数:
Agdisc_t mydisc;
Agodisc_t myiodisc;
mydisc.mem = NULL; // use system default
mydisc.id = NULL; // use system default
mydisc.io = &myiodisc;
myiodisc.afread = reader;
myiodisc.putstr = NULL; // only need to set if calling gvRender()
myiodisc.flush = NULL; // only need to set if calling gvRender()
reader函数的类型为
int (*reader) (void *chan, char *buf, int bufsize);
并且应该像read()系统调用一样工作。也就是说,它从流chan中读取数据,将字节存储在大小为bufsize的buf中,并返回读取的字节数。
对于类Unix的标准输入输出,可以使用
static int reader(void *chan, char *buf, int bufsize)
{
return fread(buf, 1, bufsize, (FILE*)chan);
}
然后,要读取图表,请使用:
FILE* fp = fopen ("mygraph.gv","r");
Agraph_t* g = agread (fp, &mydisc);
Neato 在某个示例上无限运行。
首先,您的图有多大?Neato是一个二次算法,大致相当于统计多维缩放。如果您输入一个包含数千个节点和边的图,它很容易花费数小时甚至数天时间。首先要检查的是运行neato -v以获取输出跟踪。如果您看到的数字通常越来越小,说明布局只是需要很长时间。您可以设置某些参数,例如epsilon或maxiter来缩短布局时间,但会牺牲布局质量。但如果您的图很大,谁会注意到呢?
或者,使用sfdp来布局图形(尽管目前sfdp不支持边长设置)。
如果您在neato或早期版本中使用mode=KK,优化过程可能会出现循环。如果您看到数字重复或上下波动,尤其是当您的图形较小时,这表明neato正在循环运行。对于1.13之后的版本,默认情况下不应发生这种情况。如果确实发生了,请将其报告为一个错误。
如果您使用的是早期版本的neato,或者使用了mode=KK,确实可能出现循环现象。这种循环对初始布局非常敏感。例如,通过使用start属性,
$ neato -Gstart=3
$ neato -Gstart=rand
循环很可能会消失。或者,您可以使用针对大型图形的参数来提前停止布局:
$ neato -Gepsilon=.01
$ neato -Gmaxiter=500
请注意,如果您有一个大型图,将边生成为样条曲线是一个立方算法,因此最好避免使用splines=true。
(此评论同样适用于circo、fdp和twopi。)
dot中的边标签放置效果不佳,或者布局非常混乱。
默认情况下,dot中的边标签被建模为虚拟节点。这保证了标签有足够的空间,但对于复杂图形,可能会严重扭曲布局。在这种情况下,用xlabel替换边标签可能是值得的。此时图形会先按照没有边标签的方式进行布局,在边路径确定后再添加标签。这样可以避免图形扭曲,但代价是可能出现边标签重叠的情况。
Dot 在某个示例上无限运行。
尝试 dot -v 来观察其进度。
请注意,有可能生成布局甚至解析复杂度与输入规模成平方关系的图表。例如,在dot中,
digraph G {
a -> b -> c -> .... -> x -> y -> z
a -> z
b -> z
c -> z
/* and so on... */
x -> z
}
作为排名图时,该图的总边长度(即布局时间)与节点数量呈平方关系。虽然您可能不会遇到以下情况,但通过在加载图后向节点和边追加属性,也有可能构造出解析时间与属性数量呈平方关系的图。例如:
digraph G {
/* really big graph goes here...with N+1 nodes */
n0 -> n1 -> ... -> nN;
n0 [attr0="whatever",
attr1="something else",
/* and so on with many more attributes */
attrM="something again"]
}
当一个属性首次出现时,每个对象的访问成本可能与之前声明的属性数量成正比。因此,上述操作的运行时间将是某个常数c的cN*O(M)。如果对此有任何顾虑,图应在声明节点或边之前先指定属性。实际上,这个问题可以忽略不计。
Twopi 在某个示例上无限运行。
如果你的图形很大(有数千条边),并且设置了splines=true,
拟合所有这些样条曲线会消耗大量计算周期!
Neato存在不必要的边交叉,或者错失了明显可以生成更美观布局的机会。
Neato及所有类似的虚拟物理模型算法都依赖于优化问题的启发式解决方案。解决方案越好,所需时间越长。 遗憾的是,这些启发式方法也可能陷入局部最小值。此外,它深受节点初始位置的影响。 很有可能,如果您再次运行neato,但使用不同的随机种子值或更多迭代次数,您将获得更好的布局。例如:
$ neato -Gstart=5 file.dot -Tps -o file.ps
$ neato -Gepsilon=.0000001 file.dot -Tps -o file.ps
在neato的默认应力主化模式下,使用-Gstart=self可以帮助生成更好的初始布局。
请注意,不能保证neato能为平面图生成平面布局,或展现图的所有或大部分对称性。
Webdot无法工作。
我们假设您正在使用Apache并已安装TCL。如果没有,可能最好直接使用webdot perl脚本。
要调试webdot,首先测试tclsh是否可以加载Tcldot共享库。尝试:
$ tclsh
% load $prefix/lib/graphviz/tcl/libtcldot.so.0
%
其中$prefix是graphviz的安装前缀;通常是/usr或/usr/local。
然后测试webdot是否可以从shell命令运行。(我们提供了一个辅助脚本scaffold.tcl或scaffold.sh来设置类似Apache提供的环境。)例如
$ scaffold.tcl >out.gif
can't read "LIBTCLDOT": no such variable
while executing
"file mtime $LIBTCLDOT"
invoked from within
"set t1 [file mtime $LIBTCLDOT]"
(file "cgi-bin/webdot" line 67)
invoked from within
"source cgi-bin/webdot
"
(file "scaffold.tcl" line 22)
以上情况强烈表明webdot未正确配置。
最后,测试webdot是否作为cgi-bin程序运行。使用一个简单的cgi-bin tcl脚本检查cgi-bin环境可能会有所帮助,例如:
#!/bin/env tclsh
puts "Content-type: text/plain"
puts ""
foreach e [lsort [array names env]] {puts "$e: $env($e)"}
将此脚本保存为.../cgi-bin/test.tcl,使其可执行,然后访问:http://localhost/cgi-bin/test.tcl
另外,如果您看到类似以下内容:
WebDot Error:
Response Code = 403
这通常意味着webdot运行成功,但无法从您提供的URL参数中获取远程图形。 原因可能是您的服务器位于防火墙之后,该防火墙阻止了webdot服务器,因此它无法获取图形文件。 您可以更改防火墙权限,将图形放在不同的服务器上,或者在本地安装webdot,这样就不需要远程服务器来获取图形数据。
如果有人能修改webdot,使其将图形内容作为cgi-bin参数接收,这样就不需要远程获取图形的权限,那就太好了。这个任务留给开源社区作为练习。
我在webdot中遇到"找不到字体"错误,或者文本标签缺失的问题。
首先,如果您的平台支持fontconfig,最新版本的graphviz将会使用它。有了fontconfig,这个错误应该不会发生,因此您可能需要检查是否有可用的graphviz升级版本,或者重新编译是否会添加fontconfig支持。
如果fontconfig不可用,graphviz会尝试自行将字体名称解析为字体路径,并使用DOTFONTPATH(或GDFONTPATH)来指定查找路径。
出于版权原因,Graphviz不附带自己的字体。在Windows机器上,它会自动搜索C:\Windows\Fonts目录。在Unix机器上,您需要设置一个包含TrueType字体的目录。您可以在此处获取一些字体的副本。
默认的DOTFONTPATH是:
#define DEFAULT_FONTPATH "/usr/X11R6/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/truetype:/usr/X11R6/lib/X11/fonts/TTF:/usr/share/fonts/TrueType:/usr/share/fonts/truetype:/usr/openwin/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/Type1"
如果您的字体在其他位置,那么您必须在webdot脚本中设置该目录,或者使用正确的DEFAULT_FONTPATH重新编译Graphviz(或者在您布局的每个图表中set fontpath="/your/font/directory",但这相当笨拙。)
你也可以尝试在/var/www/cgi-bin/webdot文件中注释掉这行代码 #set SIGNATURE "Graph by WebDot"。
graph bb 和 .png 图像之间的坐标转换是什么?
- bb在所有方向上扩展了4个图形单位(pad),以允许有限的线宽。
- 然后根据
-Gviewport、-Gsize、-Glandscape、-Gorientation选项进行缩放和/或旋转。在默认1:1的比例下,一个图形单位=1点(1/72英寸)。 - 然后,如果通过
-Gpage请求且输出格式支持的话,会进行分页处理。不过目前-Tpng渲染器还不支持此功能。 - 然后添加一个边距,
-Gmargin,以绝对单位(英寸)表示。上下边距可以独立于左右边距进行设置。 - 然后根据
-Gdpi、输出设备提供的dpi值或每个渲染器提供的默认值,将其转换为设备单位。x和y方向有独立的dpi值以支持非方形像素。某些渲染器会反转Y轴,需要偏移量将原点置于左上角。-Tpng的默认dpi为96dpi(近似大多数电脑显示器的分辨率),因此这里会出现96/72(4/3)的比例缩放。
在渲染器API中,插件可以选择坐标表示方式:
- 坐标以图形单位表示,以及由缩放、旋转和平移组成的复合变换数据。(由svg、cairo、ps渲染器使用)
- 坐标已预先转换为设备单位。




