常见问题解答

如果您在这里找不到您问题的答案,请在Graphviz 论坛中提问。


一般

在哪里可以看到控制 dot 或 neato 的所有属性列表?

参见 图形属性。还有一些关于 命令行使用输出格式 的信息。

在哪里可以讨论 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,增加边 len 将倾向于扩展布局。

graph G {
  layout="neato"
  edge [len=3]
  a -- { b c d }
}

对于 twopi 和 circo,可以使用其他参数,例如 ranksep。参见 图形属性.

您还可以使用 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 输出,然后使用外部转换器将 PostScript 转换为 PDF。例如,dot -Tps | epsf2pdf -o file.pdf。请注意,URL 标签会得到尊重,以允许可点击的 PDF 对象。

如果您的目的是在一些文档准备系统(例如 pdflatex)中使用图形作为 PDF,那么使用-Tps2而不是-Tps非常重要。通常,如果您确实想要 PDF 输出,也就是说,您希望有一个-Tpdf标志,在转换为 PDF 之前使用-Tps2

在下图中,阴影节点将包含错误的输出。

alt text

如何创建重复节点?

使用重复的标签创建唯一的节点。

digraph G {
  node001 [label = "A"]
  node002 [label = "A"]
  node001 -> node002
}

如何设置图形或簇标签,而不将其传播到所有子簇?

在图的末尾(在闭合大括号之前)设置标签,在所有内容定义之后。(我们承认,为非继承属性设置定义一些特殊语法似乎是可取的。)

如何在 neato 中绘制多个平行边?

当 splines 属性为 false 时(这是默认值),多边形被绘制成简单曲线边的纺锤形。没有尝试避免中间节点。

当 splines=true 或 polyline 时,多边形被绘制成大致平行的样条线或折线。这依赖于没有节点重叠。

另一个有时足够有效的技巧是使用颜色列表为边指定多种颜色。这将产生一组紧密平行的样条线,每条样条线都用其指定的颜色绘制。有关更多信息,请阅读颜色属性

如何使树布局对称(平衡)?

当一个树节点有偶数个子节点时,它不一定会位于两个中间子节点的正上方。如果您知道子节点的顺序,一个简单的技巧是引入新的、不可见的中间节点来重新平衡布局。连接边也应该是不可见的。例如

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问题页面来报告或查看 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_Acluster_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 端口使用 Apple 的 Quartz 渲染器,它支持抗锯齿。它还为其用户界面提供了一个漂亮的文档容器。(一个缺点是,如果您的 Mac 有 3D 图形,您无法将 Pixelglow Graphviz 作为 Web 服务器或其他后台进程运行,因为 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 表示:&#<nnn>;其中<nnn>是值的十进制表示。0x2200的十进制值为8704,因此可以将字符指定为&#8704;。或者,Graphviz 接受 UTF-8 编码的输入。对于全称符号,它的 UTF-8 表示是 3 个字节,其十进制值分别为226136128。为了方便起见,您可能希望使用您最喜欢的编辑器输入它,并将其调整到您选择的字符集。然后,您可以使用 iconv 程序将图从您的字符集映射到 UTF-8 或 Latin-1。

我们还接受 Latin-1 字符的 HTML 符号名称,如FaqSymbols中所建议的。例如,分符号(unicode 和 Latin-1 值十进制 162)可以插入为&cent;

请注意,图形文件必须始终是纯文本文档,而不是 Word 或其他富格式文件。任何未包含在"..."<...>中的字符必须是普通 ASCII 字符。特别是,所有 DOT 关键字(如digraphsubgraph)必须是 ASCII。

由于我们无法始终猜测编码,您应该将图形属性charset设置为UTF-8Latin1(别名 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 类标签,并可能与嵌入图像结合使用IMG属性。

正如dot 用户指南中提到的,如果您想要真正的自定义形状,有几种方法可以将其合并。目前,它们必须是 PostScript 或图像文件,或者您需要修改源代码。一个严重的问题是,您无法创建跨所有驱动程序和交互式前端(如 Grappa)使用的自定义形状。至少 SVG 有交互式渲染器,PostScript 可以转换为 PDF,PDF 也具有一些交互式功能。

外部图像文件

如果使用 SVG(-Tsvg)、PostScript(-Tps,-Tps2)或其中一种光栅格式(-Tgif, -Tpng, or -Tjpg),您可以通过文件名将某些图像(例如图片)加载到节点中。例如

   yournode [image="yourface.gif"];

表示节点的内容在 GIF 文件yourface.gif中给出。image属性指定要使用的文件。(还有一个已弃用的shapefile属性。这类似于 image,但节点形状将始终是一个框。)

注意:在 2006 年 3 月 11 日之前的版本中,特别是 1.12 graphviz 及更早版本,还需要设置属性shape=custom

使用-Tsvgimage必须给出包含 GIF、PNG 或 JPEG 位图文件的名称。请注意,文件的内容不会复制到 SVG 输出中,只有文件名。因此,为了使 Graphviz SVG 输出正确显示,图像文件必须可供 SVG 查看器使用。

使用 PostScript,image必须给出包含封装 PostScript 或位图的文件的名称。内容将复制到输出文件中。请注意,封装 PostScript 将只复制一次。对图像内容的限制与下面外部 PostScript 文件下指定的限制相同。

对于位图输出,image是包含位图图像的文件名。文件将被打开并复制(并可能被缩放)到输出图形中。

此代码仍处于初步阶段,我们注意到索引颜色映射管理中的颜色量化出现了一些问题,我们正在努力理解和纠正这些问题。(您可以使用-Gtruecolor=1尝试 32 位内部画布作为替代方案,但我们观察到图像中存在模糊(有损?)现象。)

当软件用作 Web 服务器时,对图像文件的访问权限更加严格。请参阅 SERVER_NAME。

外部 PostScript 文件

如果使用 PostScript 驱动程序(-Tps),您可以将节点形状导入为外部 PostScript 文件,例如 EPS(封装 PostScript)。最起码,外部文件必须具有有效的 BoundingBox 标题,并且不能对图形状态执行重大操作,因为我们不会安装包装器(例如,为了抑制 showpage)。

要导入外部 PostScript 文件,请设置shapeshapefile属性,如下所示

	somenode  [shape=epsf, shapefile="yourfile.ps" ];

EPSF 形状总是会被裁剪到它的边界框。

使用[shape=epsf, shapefile="yourfile.ps" ]在很大程度上被上一节中描述的机制所取代,使用[image="yourfile.ps" ]

外部 PostScript 过程

如果使用 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 节。

行为或多或少类似多边形的形状可以从基本多边形方法中引导;例如,请参见invtritab形状。此类形状使用多边形描述符,其字段列在下面。

字段名称 描述 默认值
regular 如果是规则多边形 FALSE
peripheries 边界外围数量 1
sides 边数(曲线为 1) 4
orientation 角度旋转(以度为单位) 0
distortion 梯形畸变 0
skew 平行四边形畸变 0
选项 奇特选项:ROUNDED、DIAGONALS、AUXLABELS 0

对于未从一般多边形派生的形状,请参见recordepsf形状。

与驱动程序相关的自定义形状

要实现特定于驱动程序的形状(如 GIF 或 PNG 图标),您需要为实现用户定义形状的驱动程序函数编写一个主体。这包括为特定驱动程序提供library_shape函数(如果不存在),并使用驱动程序的图形函数生成显示形状所需的图形操作。(随 Graphviz 提供的驱动程序可以在 plugins 目录中找到。)

用户形状函数基本上接收四个参数

  • 自定义形状名称字符串
  • 形状边界多边形的绝对画布坐标
  • 坐标数(目前始终为 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 个图层

图层 1 图层 2 图层 3
图层 4 图层 5

在分层图中,如果给定节点(或边)没有图层分配,但关联的边(节点)有,则其图层规范是从这些边(节点)推断出来的。例如,在上面的示例中,node4只出现在图层 3 上,因为其连接的边给出了图层分配。但是请注意,如果没有任何图层属性的节点或边与没有任何图层属性的边或节点相关联(或者这样的节点没有边),则该节点或边会出现在所有图层上。

要更改默认设置,以便没有图层属性的节点和边出现在所有图层上,请在图形文件开头插入

	node [layer=all];
	edge [layer=all];

该图形可以具有layerselect属性,该属性指定应发出哪些图层。该值使用与图层属性相同的具体语法。

目前,将多个图层输出到单个输出文件的功能只在 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 的标签来实现此目的。

-Tplain 格式中,样条线不接触节点(箭头丢失)。

边被指定为主样条线,并在必要时使用实际上与节点相接的箭头。如果没有给出箭头,绘制边样条线会在边和节点之间留出间隙。这是一个错误,现在已经固化为一个功能。解决方法是设置edge [dir=none]。由于边没有箭头,样条线规范将一直延伸到两个节点。

当 rankdir=LR 时,记录节点在 dot 和 neato 中的绘制方式不同。

确实如此。dot -Grankdir=LR 会旋转记录节点,以便其顶层字段仍然跨级别列出。rankdir=LR对 neato 无效。一个解决方法是使用类似 HTML 的标签(它们不会旋转;缺点是您必须用 XML 编写)。一般来说,我们建议用更通用的类似 HTML 的标签替换记录节点。另一种解决方法是将记录标签括在 { } 中,以旋转/取消旋转记录内容。另请参见 Scott Berkun(微软公司)的如何避免愚蠢的一致性。

如何将大型图形打印在多个页面上?

如果设置了page属性,它会告诉 Graphviz 将图形打印为给定大小的页面数组。因此,图形

digraph G {
  page="8.5,11";
  ...
}

将作为 8.5 英寸 x 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 会执行它通常执行的任何边路由。所有常见的后端属性(sizeoverlappage等)都可用。

我已经拥有了节点的所有坐标,我想让 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

据我们所知,当 dot 从 Apache cgi 程序运行时,如果没有设置 HOME,它会在 Debian 系统上退出,并且没有任何 stdout 或 stderr 消息。解决方法是在 Apache 用户 ID 的环境中提供一个 HOME 目录。

有人也建议使用 Graphviz 的 Perl 模块。

来自 Dreamworks 的 Greg Brauer 指出了另一种可能性:问题在于我在运行 dot 之前没有关闭临时 dot 文件上的文件描述符。Graphviz 最终会得到一个新创建的空文件,其中没有任何内容,直到写入文件的缓冲区被刷新。dot 会很乐意在上面运行,并生成一个空输出文件,没有任何警告。

如何获得 3D 输出?

Graphviz 作者对随意使用 3D 表示有疑虑。

尽管如此,dot -Tvrml仍然会生成 VRML 文件。没有 Z 坐标布局 - 您在节点的 z 属性中自行指定 Z 坐标,边的 Z 坐标是插值的。如果有人为更新、更有用的格式(OpenGL Performer 场景图?Open Scene Graphs?Java3D 程序?)贡献一个驱动程序,我们想尝试一下。

neato 通过dimdimen属性在内部支持更高维度的布局,例如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()

读取器函数的类型为

int (*reader) (void *chan, char *buf, int bufsize);

并且应该像 read() 系统调用一样工作。也就是说,它从流 chan 中读取,将字节存储在 buf 中,其大小为 bufsize,并返回它读取的字节数。

对于类似 Unix 的 stdio,可以使用

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 以获取输出跟踪。如果您看到的数字通常在变小,则布局只是花费了很长时间。您可以设置某些参数,例如 epsilonmaxiter 来缩短布局时间,但会以布局质量为代价。但如果您的图很大,谁会注意到呢?

或者,使用 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"]
}

当属性第一次出现时,会访问每个对象,可能的成本与先前声明的属性数量成正比。因此,上述代码的运行时间将是 cN*O(M),其中 c 是某个常数。如果对此有任何疑虑,则图应首先指定属性,然后声明节点或边。实际上,这个问题可以忽略不计。

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 命令运行。(对于 webdot,我们提供了一个帮助脚本 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 环境可能会有所帮助,方法是使用一个简单的 cgi-bin tcl 脚本,例如

#!/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 中缺少文本标签。

首先,graphviz 的最新版本将在您的平台上使用 fontconfig(如果可用)。使用 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" 行。

图形 bb 和 .png 图像之间的坐标转换是什么?

  • bb 在所有方向(pad)上扩展了 4 个图形单位,以允许有限的线宽。
  • 然后根据 -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 渲染器使用)
  • 预先转换为设备单位的坐标。
最后修改日期:2024 年 7 月 28 日:将所有 Hugo 'ref' 替换为 'relref'(bbef86a)