UP | HOME

使用 dot 画图工具

Table of Contents

1 Graphviz 画图工具和 dot 语言

Graphviz 是一个由 AT&T 实验室启动的开源工具包,用于绘制 dot 语言脚本描述的图形。 类似微软的 visio,但是他和 visio 也有很大的不同,他是用代码绘图的,使用一种名 为 dot 的语言绘图,对于绘制复杂的流程图,类图等非常好用。 这种设计使得用户更关 注于逻辑关系,实现 "所思即所得"。Graphviz 的自动布局功能,无需人为干预就可以做 到 "最小化连线交叉"。 关于 dot 画图的一个非常好的文章见 Drawing graphs with dot

2 基本画图

dot 可以生成 GIF, PNG, SVG, PDF 和 PostScript 格式的图片。dot 语言画图的类别可 以分成以下两类:

  • digraph 有向图
  • graph 无向图

2.1 图的基本元素

每种图中包含以下常见要素:

  • node 节点
  • edge
  • subgraph 子图
  • attr 属性
digraph basicGraph {
  main -> parse -> execute;
  main -> init;
  main -> cleanup;
  execute -> make_string;
  execute -> printf
  init -> make_string;
  main -> printf;
  execute -> compare;
}
  • dot 语言中每个变量表示一个节点
  • -> 表示连接边
  • 使用 dot 命令编译生成 PNG 图片,示例如下:
dot -Tpng fig.dot -o fig.png

basic-graph.png

2.2 图的属性

在绘制图的时候一般需要根据需求来设置节点和边的属性,如下例子中

digraph graphAttrs {
  size ="4,4";
  main [shape=box];   /* this is a comment */
  main -> parse [weight=8];
  parse -> execute;
  main -> init [style=dotted];
  main -> cleanup;
  execute -> { make_string; printf}
  init -> make_string;
  edge [color=red];   // so is this
  main -> printf [style=bold,label="100 times"];
  make_string [label="make a\nstring"];
  node [shape=box,style=filled,color=".7 .3 1.0"];
  execute -> compare;
}
  • size 设置图片大小为 4,4(英尺)
  • 节点和边的属性写在方括号里
  • shape=box 设置节点形状为方框
  • 花括号表示一个节点连接多个节点 execute -> { make_string; printf} 等同于 execute -> make_string; execute -> printf;
  • 节点和边的文字可以使用 label 属性来设置

graph-attrs.png

3 图的属性介绍

主要的属性可以参考attrs

3.1 节点形状

节点属性默认设置为 shape=ellipse, width=.75, height=.5 并且使用节点的名字作 为其 label 。节点的形状见 shapes

graph-shapes.png

  • 节点的形状分为两类 polygon-basedrecord-based
  • 除了 recordMrecord 属于 record-based 以外,其它都是 polygon-based
  • polygon-based 一般直接作为形状
  • record-based 可以用于递归定义
digraph graphLabel {
  node [shape=record];
  struct1 [label="<f0> left|<f1> mid\ dle|<f2> right"];
  struct2 [label="<f0> one|<f1> two"];
  struct3 [label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"];
  struct1:f1 -> struct2:f0;
  struct1:f2 -> struct3:here;
}
  • | 用来分隔域
  • <> 里面是 fieldid
  • label 里面的空格和换行符需要转义

graph-labels.png

3.2 标签文字

标签文字默认是

  • 换行需要转义,其中 \n, \l, \r 分别表示换行居中,靠左和靠右。
  • graph 和 cluster subgraph 也有 label,默认在图片正下方, labelloc=t 将 label 移到图的上方, labelloc=b 将图片移到下面。 labeljust=r 文字靠右。
  • 字体默认是 "Times-Roman 14", 可以设置 fontname, fontsizefontcolor 这些属性。例如: fontname=Helvetica
  • 边可以设置两端的 label, headlabeltaillabel ,以及 label 的字体 labelfontname, labelfontsizelabelfontcolor ,另外如果有需求也可以 设置 labelanglelabeldistance
digraph graphTextAlign {
  node [shape=record fontname=Arial];
  a [label="one\ltwo three\lfour five six seven\l"]
  b [label="one\ntwo three\nfour five six seven"]
  c [label="one\rtwo three\rfour five six seven\r"]
  a -> b -> c
}

graph-text-align.png

3.3 HTML 类型标签

digraph graphHtmlLabel {
  node [shape=plaintext]
  struct1 [label=<
           <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
           <TR><TD>left</TD><TD PORT="f1">mid dle</TD><TD PORT="f2">right</TD></TR>
           </TABLE>>];
  struct2 [label=<
           <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
           <TR><TD PORT="f0">one</TD><TD>two</TD></TR>
           </TABLE>>];
  struct3 [label=<
           <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
           <TR>
           <TD ROWSPAN="3">hello<BR/>world</TD>
           <TD COLSPAN="3">b</TD>
           <TD ROWSPAN="3">g</TD>
           <TD ROWSPAN="3">h</TD>
           </TR>
           <TR>
           <TD>c</TD><TD PORT="here">d</TD><TD>e</TD>
           </TR>
           <TR>
           <TD COLSPAN="3">f</TD>
           </TR>
           </TABLE>>];
  struct1:f1 -> struct2:f0;
  struct1:f2 -> struct3:here;
}
  • <TD> 的 PORT 表示单元格的名称

graph-html-labels.png

使用 Port

digraph graphPorts {

  parent [
   shape=plaintext
   label=<
     <table border='1' cellborder='1'>
       <tr><td colspan="3">The foo, the bar and the baz</td></tr>
       <tr><td port='port_one'>First port</td><td port='port_two'>Second port</td><td port='port_three'>Third port</td></tr>
     </table>
  >];

  child_one [
   shape=plaintext
   label=<
     <table border='1' cellborder='0'>
       <tr><td>1</td></tr>
     </table>
  >];

  child_two [
   shape=plaintext
   label=<
     <table border='1' cellborder='0'>
       <tr><td>2</td></tr>
     </table>
  >];

  child_three [
   shape=plaintext
   label=<
     <table border='1' cellborder='0'>
       <tr><td>3</td></tr>
     </table>
  >];

  parent:port_one   -> child_one;
  parent:port_two   -> child_two;
  parent:port_three -> child_three;

}

graph-ports.png

3.4 节点和边的样式

节点和边的颜色使用 color 属性来设置, color 接收以下几种类型的值:

  • X11 的颜色名称,例如: red, yellow, green
  • 0 到 1 之间表示 HSB 的三元组,例如: "0.83, 0.48, 0.85"
  • 十六进制的 RGB 值,例如: "#DA70D6"

通过 fontcolorfontname 设置字体颜色,例如: fontcolor=white; fontname=Helvetica; 。样式 style 也有预定义的值,线条属性包括: solid, dashed, dotted, bold 和 invis;节点属性包括:filled, diagonals 和 rounded。

digraph graphStyle {
  solid -> dashed[style=solid; label=solid];
  dashed -> dotted[style=dashed; label=dashed];
  dotted -> bold[style=dotted; label=dotted];
  bold -> invis[style=bold; label=bold];
  solid -> diagonals[style=invis; label=invis];

  solid[style=solid];
  dashed[style=dashed];
  dotted[style=dotted];
  bold[style=bold];
  invis[style=invis];
  filled[style=filled];

  diagonals[style=diagonals];
  rounded[style=rounded];

  { rank=same solid dashed dotted bold invis filled}
  { rank=same diagonals rounded }
}

graph-styles.png

边有 dir 属性来设置箭头方向,包括:forward, back, both 和 none。还有一些可 以控制箭头的头和尾样式的属性 arrowheadarrowtail 。箭头样式包括: normal, inv, dot, invdot, odot, invodot, empty 和 none 等等。 arrowsize 设 置箭头的长度,例如 arrowsize=2.0 可以将箭头长度扩大两倍。

digraph graphArrows {
  mynode [color=red; style=diagonals];
  b[style="bold,filled"];
  c[shape=box,style="bold,filled",color="#24a222",fillcolor="#ff7f00"fontcolor=white];
  d[shape=Mrecord,style=filled,color="red",fillcolor="#666666"fontcolor=white];
  a -> b [arrowhead=dot];
  a -> c [arrowhead=vee; arrowsize=2];
  a -> d [dir=back, arrowtail=empty];
}

graph-arrows.png

3.5 图的方向,大小和空隙

  • nodesep 控制同样 rank 节点之间的最小的间距,单位是英尺
  • ranksep 控制不同 rank 之间的间距,单位是英尺。例如: ranksep=equally 表 示所有的 rank 都是等距的
  • size 控制图片大小,单位是英尺。例如: size=x,y, 在命令行中使用 -G 选项
  • ratio 控制图片布局,例如: ratio=compress
    • 如果 ratio 没有设置,根据 size 进行绘图
    • 如果 ratio=x ,x 是一个浮点数,对图片大小进行等比缩放
    • 如果 ratio=fill 并且 size=x,y 图片根据 x,y 来进行布局
    • 如果 ratio=compress 并且 size=x,y 根据 x,y 来布局,然后压缩图形
    • 如果 ratio=auto 并且设置 page 属性并且图不能在一页中画满,那么 size 将被忽略
  • 如果 rotate=90 或者 orientation=landscape ,旋转 90 度水平作图
  • 如果 page=x,y ,则输出页的长宽
  • pagedir 控制也的作图方向,例如 pagedir=BL 表示从下往上,从左往右作图
    • B botttom-to-top
    • L left-to-right
    • T top-to-bottom
    • R right-to-left
  • center=true 居中作图

3.6 节点和边的位置

节点和边的分别通过 rankdir 属性控制

  • LR left-to-right
  • TB top-to-bottom
  • BT bottom-to-top
  • RL right-to-left

对于有时间线的图,子图的 rank 可以设置为:

  • same 子图中的所有节点属于同一个级别
  • min 最小级别
  • source 使得子图中的级别相同,并且严格小于其他节点
  • max 最大级别
  • sink
digraph graphRank {
  node [shape=record];
  { rank=same rA sA tA }
  { rank=same uB vB wB }
  rA -> sA;
  sA -> vB;
  t  -> rA;
  uB -> vB;
  wB -> u;
  wB -> tA;
}

graph-rank.png

4 高级特性

4.1 节点端口

每个节点有 8 个端口可以用于连接: "n", "ne", "e", "se", "s", "sw", "w" 和 "nw",其中 HTML 标签可以是 <TD> 中的 PORT 属性来指明端口。语法是使用 node_name:port_name

digraph graphNodePorts {
  a -> b [tailport=w];
  c:sw -> b:e;
}

graph-node-ports.png

4.2 Cluster

Cluster 使用一个在特定矩形区域中的子图

  • Cluster 的子图名称要以 cluster 开头
  • 如果 compound=true, dot 就会允许边将 cluster 和节点相连
digraph graphCluster {
  subgraph cluster_0 {
    style=filled;
    color=lightgrey;
    node [style=filled,color=white];
    a0 -> a1 -> a2 -> a3;
    label = "process #1";
  }

  subgraph cluster_1 {
    node [style=filled];
    b0 -> b1 -> b2 -> b3;
    label = "process #2";
    color=blue
  }
  start -> a0;
  start -> b0;
  a1 -> b3;
  b2 -> a3;
  a3 -> a0;
  a3 -> end;
  b3 -> end;

  start [shape=Mdiamond];
  end [shape=Msquare];
}

graph-cluster.png

4.3 合并边

如果 concentrate=true 则 dot 会自动帮忙合并相同的边

5 杂项

strict graphstrict digraph 不允许建立重复的边,即每两个节点之间最多只 有一个边。

6 举例

6.1 ER 图

graph ER_Diagram {
  rankdir="LR";
  bgcolor="#e8e8e8";
  compound=true;
  splines=false;

  node [fontname="Helvetia"; fontcolor="#ffffff"]

  node [shape=ellipse, style=filled, color="#5588a3"];
  student_number; student_name; joined_on;
  course_name; started_on; finished_on;
  value; passed;

  node [shape=box, style=filled, color="#145374", width=2];
  courses; students;

  node [shape=diamond, color="#00334e"];
  scores;

  edge[color="#000000", penwidth=1.2];
  {course_name; started_on; finished_on} -- courses:w ;
  scores:e -- {value; passed};
  students:e -- {student_number; student_name; joined_on};
  {
    rank=same
    courses -- scores -- students;
  }
}

er-diagram.png

6.2 类图

digraph classDiagram {
  rankdir=TB;
  fontname="Helvetia";
  node[shape="record"; fontname="Helvetia"];
  edge[fontname="Helvetia", dir=back; arrowtail=empty;];

  animal [label="{Animal|+ name : string\l+ age : int\l|+ die() : void\l}"];
  dog [label="{Dog| | +bark() : void\l}"];
  cat [label="{Cat| | +meow() : void\l}"];

  animal -> dog;
  animal -> cat;
}

class-diagram.png

Last Updated 2020-02-22 Sat 20:05. Created by Jinghui Hu at 2019-07-12 Fri 23:58.