<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Linux/UNIX on I Code It</title>
    <link>https://icodeit.org/categories/linux/unix/</link>
    <description>Recent content in Linux/UNIX on I Code It</description>
    <generator>Hugo -- gohugo.io</generator><atom:link href="https://icodeit.org/categories/linux/unix/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>使用graphviz绘图</title>
      <link>https://icodeit.org/2015/11/using-graphviz-drawing/</link>
      <pubDate>Tue, 10 Nov 2015 00:00:00 +0000</pubDate>
      
      <guid>https://icodeit.org/2015/11/using-graphviz-drawing/</guid>
      <description>2015年11月10日更新 在实践中，我又发现了一些graphviz的有趣的特性，比如时序图，rank以及图片节点等。在这里一并更新。
##前言
日常的开发工作中，为代码添加注释是代码可维护性的一个重要方面，但是仅仅提供注释是不够的，特别是当系统功能越来越复杂，涉及到的模块越来越多的时候，仅仅靠代码就很难从宏观的层次去理解。因此我们需要图例的支持，图例不仅仅包含功能之间的交互，也可以包含复杂的数据结构的示意图，数据流向等。
但是，常用的UML建模工具，如Visio等都略显复杂，且体积庞大。对于开发人员，特别是后台开发人员来说，命令行，脚本才是最友好的，而图形界面会很大程度的限制开发效率。相对于鼠标，键盘才是开发人员最好的朋友。
###graphviz简介
本文介绍一个高效而简洁的绘图工具graphviz。graphviz是贝尔实验室开发的一个开源的工具包，它使用一个特定的DSL(领域特定语言): dot作为脚本语言，然后使用布局引擎来解析此脚本，并完成自动布局。graphviz提供丰富的导出格式，如常用的图片格式，SVG，PDF格式等。
graphviz中包含了众多的布局器：
 dot 默认布局方式，主要用于有向图 neato 基于spring-model(又称force-based)算法 twopi 径向布局 circo 圆环布局 fdp 用于无向图  graphviz的设计初衷是对有向图/无向图等进行自动布局，开发人员使用dot脚本定义图形元素，然后选择算法进行布局，最终导出结果。
首先，在dot脚本中定义图的顶点和边，顶点和边都具有各自的属性，比如形状，颜色，填充模式，字体，样式等。然后使用合适的布局算法进行布局。布局算法除了绘制各个顶点和边之外，需要尽可能的将顶点均匀的分布在画布上，并且尽可能的减少边的交叉(如果交叉过多，就很难看清楚顶点之间的关系了)。所以使用graphviz的一般流程为：
 定义一个图，并向图中添加需要的顶点和边 为顶点和边添加样式 使用布局引擎进行绘制  一旦熟悉这种开发模式，就可以快速的将你的想法绘制出来。配合一个良好的编辑器(vim/emacs)等，可以极大的提高开发效率，与常见的GUI应用的所见即所得模式对应，此模式称为所思即所得。比如在我的机器上，使用Sublime Text 编辑dot脚本，然后将F7/Cmd-B映射为调用dot引擎去绘制当前脚本，并打开一个新的窗口来显示运行结果：
对于开发人员而言，经常会用到的图形绘制可能包括：函数调用关系，一个复杂的数据结构，系统的模块组成，抽象语法树等。
###基础知识
graphviz包含3中元素，图，顶点和边。每个元素都可以具有各自的属性，用来定义字体，样式，颜色，形状等。下面是一些简单的示例，可以帮助我们快速的了解graphviz的基本用法。
####第一个graphviz图
比如，要绘制一个有向图，包含4个节点a,b,c,d。其中a指向b，b和c指向d。可以定义下列脚本：
digraph abc{ a; b; c; d; a -&amp;gt; b; b -&amp;gt; d; c -&amp;gt; d; } 使用dot布局方式，绘制出来的效果如下：
默认的顶点中的文字为定义顶点变量的名称，形状为椭圆。边的默认样式为黑色实线箭头，我们可以在脚本中做一下修改，将顶点改为方形，边改为虚线。
####定义顶点和边的样式
在digraph的花括号内，添加顶点和边的新定义：
node [shape=&amp;quot;record&amp;quot;]; edge [style=&amp;quot;dashed&amp;quot;]; 则绘制的效果如下：
####进一步修改顶点和边样式
进一步，我们将顶点a的颜色改为淡绿色，并将c到d的边改为红色，脚本如下：
digraph abc{ node [shape=&amp;quot;record&amp;quot;]; edge [style=&amp;quot;dashed&amp;quot;]; a [style=&amp;quot;filled&amp;quot;, color=&amp;quot;black&amp;quot;, fillcolor=&amp;quot;chartreuse&amp;quot;]; b; c; d; a -&amp;gt; b; b -&amp;gt; d; c -&amp;gt; d [color=&amp;quot;red&amp;quot;]; } 绘制的结果如下：</description>
      <content:encoded><![CDATA[<hr>
<p>2015年11月10日更新
在实践中，我又发现了一些<code>graphviz</code>的有趣的特性，比如<code>时序图</code>，<code>rank</code>以及<code>图片节点</code>等。在这里一并更新。</p>
<p>##前言</p>
<p>日常的开发工作中，为代码添加注释是代码可维护性的一个重要方面，但是仅仅提供注释是不够的，特别是当系统功能越来越复杂，涉及到的模块越来越多的时候，仅仅靠代码就很难从宏观的层次去理解。因此我们需要图例的支持，图例不仅仅包含功能之间的交互，也可以包含复杂的数据结构的示意图，数据流向等。</p>
<p>但是，常用的UML建模工具，如<code>Visio</code>等都略显复杂，且体积庞大。对于开发人员，特别是后台开发人员来说，命令行，脚本才是最友好的，而图形界面会很大程度的限制开发效率。相对于鼠标，键盘才是开发人员最好的朋友。</p>
<p>###graphviz简介</p>
<p>本文介绍一个高效而简洁的绘图工具<code>graphviz</code>。<code>graphviz</code>是贝尔实验室开发的一个开源的工具包，它使用一个特定的<code>DSL</code>(领域特定语言): <code>dot</code>作为脚本语言，然后使用布局引擎来解析此脚本，并完成自动布局。<code>graphviz</code>提供丰富的导出格式，如常用的图片格式，SVG，PDF格式等。</p>
<p><code>graphviz</code>中包含了众多的布局器：</p>
<ul>
<li><code>dot</code> 默认布局方式，主要用于有向图</li>
<li><code>neato</code> 基于spring-model(又称force-based)算法</li>
<li><code>twopi</code> 径向布局</li>
<li><code>circo</code> 圆环布局</li>
<li><code>fdp</code> 用于无向图</li>
</ul>
<p><code>graphviz</code>的设计初衷是对<code>有向图/无向图</code>等进行自动布局，开发人员使用dot脚本定义图形元素，然后选择算法进行布局，最终导出结果。</p>
<p>首先，在dot脚本中定义图的顶点和边，顶点和边都具有各自的属性，比如形状，颜色，填充模式，字体，样式等。然后使用合适的布局算法进行布局。布局算法除了绘制各个顶点和边之外，需要尽可能的将顶点均匀的分布在画布上，并且尽可能的减少边的交叉(如果交叉过多，就很难看清楚顶点之间的关系了)。所以使用<code>graphviz</code>的一般流程为：</p>
<ul>
<li>定义一个图，并向图中添加需要的顶点和边</li>
<li>为顶点和边添加样式</li>
<li>使用布局引擎进行绘制</li>
</ul>
<p>一旦熟悉这种开发模式，就可以快速的将你的想法绘制出来。配合一个良好的编辑器(vim/emacs)等，可以极大的提高开发效率，与常见的GUI应用的所见即所得模式对应，此模式称为所思即所得。比如在我的机器上，使用Sublime Text 编辑<code>dot</code>脚本，然后将<code>F7/Cmd-B</code>映射为调用<code>dot引擎</code>去绘制当前脚本，并打开一个新的窗口来显示运行结果：</p>
<p><img loading="lazy" src="/images/2015/11/workspace-resized.png" type="" alt="workspace"  /></p>
<p>对于开发人员而言，经常会用到的图形绘制可能包括：函数调用关系，一个复杂的数据结构，系统的模块组成，抽象语法树等。</p>
<p>###基础知识</p>
<p>graphviz包含3中元素，<code>图</code>，<code>顶点</code>和<code>边</code>。每个元素都可以具有各自的属性，用来定义字体，样式，颜色，形状等。下面是一些简单的示例，可以帮助我们快速的了解graphviz的基本用法。</p>
<p>####第一个graphviz图</p>
<p>比如，要绘制一个有向图，包含4个节点<code>a,b,c,d</code>。其中<code>a</code>指向<code>b</code>，<code>b</code>和<code>c</code>指向<code>d</code>。可以定义下列脚本：</p>
<pre><code>digraph abc{
	a;
	b;
	c;
	d;
 
	a -&gt; b;
	b -&gt; d;
	c -&gt; d;
}
</code></pre><p>使用<code>dot</code>布局方式，绘制出来的效果如下：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image004.gif" type="" alt="dot-simple"  /></p>
<p>默认的顶点中的文字为定义顶点变量的名称，形状为椭圆。边的默认样式为黑色实线箭头，我们可以在脚本中做一下修改，将顶点改为<code>方形</code>，边改为<code>虚线</code>。</p>
<p>####定义顶点和边的样式</p>
<p>在<code>digraph</code>的花括号内，添加顶点和边的新定义：</p>
<pre><code>node [shape=&quot;record&quot;];
edge [style=&quot;dashed&quot;];
</code></pre><p>则绘制的效果如下：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image006.gif" type="" alt="dot-simple2"  /></p>
<p>####进一步修改顶点和边样式</p>
<p>进一步，我们将顶点<code>a</code>的颜色改为<code>淡绿色</code>，并将<code>c</code>到<code>d</code>的边改为<code>红色</code>，脚本如下：</p>
<pre><code>digraph abc{
	node [shape=&quot;record&quot;];
	edge [style=&quot;dashed&quot;];
	 
	a [style=&quot;filled&quot;, color=&quot;black&quot;, fillcolor=&quot;chartreuse&quot;];
	b;
	c;
	d;
	 
	a -&gt; b;
	b -&gt; d;
	c -&gt; d [color=&quot;red&quot;];
}
</code></pre><p>绘制的结果如下：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image008.gif" type="" alt="dot-simple3"  /></p>
<p>应当注意到，顶点和边都接受属性的定义，形式为在顶点和边的定义之后加上一个由方括号括起来的<code>key-value</code>列表，每个<code>key-value</code>对由逗号隔开。如果图中顶点和边采用统一的风格，则可以在图定义的首部定义<code>node</code>, <code>edge</code>的属性。比如上图中，定义所有的顶点为方框，所有的边为虚线，在具体的顶点和边之后定义的属性将覆盖此全局属性。如特定与<code>a</code>的绿色，<code>c</code>到<code>d</code>的边的红色。</p>
<p>####以图片为节点</p>
<p>除了颜色，节点还可以使用图片。不过需要注意的是，在使用图片作为节点的时候，需要将本来的形状设置为<code>none</code>，并且将<code>label</code>置为空字符串，避免出现文字对图片的干扰。</p>
<pre><code>digraph abc{
	node [shape=&quot;record&quot;];
	edge [style=&quot;dashed&quot;];
	 
	a [style=&quot;filled&quot;, color=&quot;black&quot;, fillcolor=&quot;chartreuse&quot;];
	b;
	c [shape=&quot;none&quot;, image=&quot;logos/browser-icon-chrome-resized.png&quot;, label=&quot;&quot;];
	d;
	 
	a -&gt; b;
	b -&gt; d;
	c -&gt; d [color=&quot;red&quot;];
}
</code></pre><p><img loading="lazy" src="/images/2015/11/image-node.png" type="" alt="image-node"  /></p>
<p>###子图的绘制</p>
<p>graphviz支持子图，即图中的部分节点和边相对对立(软件的模块划分经常如此)。比如，我们可以将顶点c和d归为一个子图：</p>
<pre><code>digraph abc{

	node [shape=&quot;record&quot;];
	edge [style=&quot;dashed&quot;];
	 
	a [style=&quot;filled&quot;, color=&quot;black&quot;, fillcolor=&quot;chartreuse&quot;];
	b;
 
    subgraph cluster_cd{
	    label=&quot;c and d&quot;;
	    bgcolor=&quot;mintcream&quot;;
	    c;
	    d;
    }
 
	a -&gt; b;
	b -&gt; d;
	c -&gt; d [color=&quot;red&quot;];
}
</code></pre><p>将<code>c</code>和<code>d</code>划分到<code>cluster_cd</code>这个子图中，标签为<code>c and d</code>,并添加背景色，以方便与主图区分开，绘制结果如下：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image010.gif" type="" alt="cluster"  /></p>
<p>应该注意的是，子图的名称必须以<code>cluster</code>开头，否则<code>graphviz</code>无法设别。</p>
<p>####数据结构的可视化</p>
<p>实际开发中，经常要用到的是对复杂数据结构的描述，<code>graphviz</code>提供完善的机制来绘制此类图形。</p>
<p>#####一个hash表的数据结构</p>
<p>比如一个hash表的内容，可能具有下列结构：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">st_hash_type</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">compare</span><span class="p">)</span> <span class="p">();</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">hash</span><span class="p">)</span> <span class="p">();</span>
<span class="p">};</span>
 
<span class="k">struct</span> <span class="n">st_table_entry</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">hash</span><span class="p">;</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">key</span><span class="p">;</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">record</span><span class="p">;</span>
    <span class="n">st_table_entry</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="p">};</span>
 
<span class="k">struct</span> <span class="n">st_table</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">st_hash_type</span> <span class="o">*</span><span class="n">type</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">num_bins</span><span class="p">;</span> <span class="cm">/* slot count */</span>
    <span class="kt">int</span> <span class="n">num_entries</span><span class="p">;</span> <span class="cm">/* total number of entries */</span>
    <span class="k">struct</span> <span class="n">st_table_entry</span> <span class="o">**</span><span class="n">bins</span><span class="p">;</span> <span class="cm">/* slot */</span>
<span class="p">};</span>
</code></pre></div><p>#####绘制hash表的数据结构</p>
<p>从代码上看，由于结构体存在引用关系，不够清晰，如果层次较多，则很难以记住各个结构之间的关系，我们可以通过下图来更清楚的展示：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image012.gif" type="" alt="hash-datastruct"  /></p>
<p>脚本如下：</p>
<pre><code>digraph st2{
	fontname = &quot;Verdana&quot;;
	fontsize = 10;
	rankdir=TB;
	 
	node [fontname = &quot;Verdana&quot;, fontsize = 10, color=&quot;skyblue&quot;, shape=&quot;record&quot;];
	 
	edge [fontname = &quot;Verdana&quot;, fontsize = 10, color=&quot;crimson&quot;, style=&quot;solid&quot;];
	 
	st_hash_type [label=&quot;{&lt;head&gt;st_hash_type|(*compare)|(*hash)}&quot;];
	st_table_entry [label=&quot;{&lt;head&gt;st_table_entry|hash|key|record|&lt;next&gt;next}&quot;];
	st_table [label=&quot;{st_table|&lt;type&gt;type|num_bins|num_entries|&lt;bins&gt;bins}&quot;];
	 
	st_table:bins -&gt; st_table_entry:head;
	st_table:type -&gt; st_hash_type:head;
	st_table_entry:next -&gt; st_table_entry:head [style=&quot;dashed&quot;, color=&quot;forestgreen&quot;];
}
</code></pre><p>应该注意到，在顶点的形状为<code>record</code>的时候，<code>label</code>属性的语法比较奇怪，但是使用起来非常灵活。比如，用竖线”|”隔开的串会在绘制出来的节点中展现为一条分隔符。用<code>&lt;&gt;</code>括起来的串称为锚点，当一个节点具有多个锚点的时候，这个特性会非常有用，比如节点<code>st_table</code>的<code>type</code>属性指向<code>st_hash_type</code>，第4个属性指向<code>st_table_entry</code>等，都是通过锚点来实现的。</p>
<p>我们发现，使用默认的<code>dot</code>布局后，绿色的这条边覆盖了数据结构<code>st_table_entry</code>，并不美观，因此可以使用别的布局方式来重新布局，如使用<code>circo</code>算法：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image014.gif" type="" alt="circo"  /></p>
<p>则可以得到更加合理的布局结果。</p>
<p>####hash表的实例</p>
<p>另外，这个hash表的一个实例如下：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image016.gif" type="" alt="hash-instance"  /></p>
<p>脚本如下：</p>
<pre><code>digraph st{
	fontname = &quot;Verdana&quot;;
	fontsize = 10;
	rankdir = LR;
	rotate = 90;
	 
	node [ shape=&quot;record&quot;, width=.1, height=.1];
	node [fontname = &quot;Verdana&quot;, fontsize = 10, color=&quot;skyblue&quot;, shape=&quot;record&quot;];
	 
	edge [fontname = &quot;Verdana&quot;, fontsize = 10, color=&quot;crimson&quot;, style=&quot;solid&quot;];
	node [shape=&quot;plaintext&quot;];
	 
	st_table [label=&lt;
	    &lt;table border=&quot;0&quot; cellborder=&quot;1&quot; cellspacing=&quot;0&quot; align=&quot;left&quot;&gt;
	    &lt;tr&gt;
	    &lt;td&gt;st_table&lt;/td&gt;
	    &lt;/tr&gt;
	    &lt;tr&gt;
	    &lt;td&gt;num_bins=5&lt;/td&gt;
	    &lt;/tr&gt;
	    &lt;tr&gt;
	    &lt;td&gt;num_entries=3&lt;/td&gt;
	    &lt;/tr&gt;
	    &lt;tr&gt;
	    &lt;td port=&quot;bins&quot;&gt;bins&lt;/td&gt;
	    &lt;/tr&gt;
	    &lt;/table&gt;
	&gt;];
	 
	node [shape=&quot;record&quot;];
	num_bins [label=&quot; &lt;b1&gt; | &lt;b2&gt; | &lt;b3&gt; | &lt;b4&gt; | &lt;b5&gt; &quot;, height=2];
	node[ width=2 ];
	 
	entry_1 [label=&quot;{&lt;e&gt;st_table_entry|&lt;next&gt;next}&quot;];
	entry_2 [label=&quot;{&lt;e&gt;st_table_entry|&lt;next&gt;null}&quot;];
	entry_3 [label=&quot;{&lt;e&gt;st_table_entry|&lt;next&gt;null}&quot;];
	 
	st_table:bins -&gt; num_bins:b1;
	num_bins:b1 -&gt; entry_1:e;
	entry_1:next -&gt; entry_2:e;
	num_bins:b3 -&gt; entry_3:e;
}
</code></pre><p>上例中可以看到，节点的<code>label</code>属性支持类似于<code>HTML</code>语言中的TABLE形式的定义，通过行列的数目来定义节点的形状，从而使得节点的组成更加灵活。</p>
<p>####软件模块组成图</p>
<p>Apache httpd 模块关系</p>
<p><img loading="lazy" src="/images/2012/01/clip_image018.gif" type="" alt="httpd"  /></p>
<p>在实际的开发中，随着系统功能的完善，软件整体的结构会越来越复杂，通常开发人员会将软件划分为可理解的多个子模块，各个子模块通过协作，完成各种各样的需求。</p>
<p>下面有个例子，是某软件设计时的一个草稿：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image020.gif" type="" alt="idp"  /></p>
<p>IDP支持层为一个相对独立的子系统，其中包括如数据库管理器，配置信息管理器等模块，另外为了提供更大的灵活性，将很多其他的模块抽取出来作为外部模块，而支持层提供一个模块管理器，来负责加载/卸载这些外部的模块集合。</p>
<p>这些模块间的关系较为复杂，并且有部分模块关系密切，应归类为一个子系统中，上图对应的<code>dot</code>脚本为：</p>
<pre><code>digraph idp_modules{
 
	rankdir = TB;
	fontname = &quot;Microsoft YaHei&quot;;
	fontsize = 12;
	 
	node [ fontname = &quot;Microsoft YaHei&quot;, fontsize = 12, shape = &quot;record&quot; ]; 
	edge [ fontname = &quot;Microsoft YaHei&quot;, fontsize = 12 ];
	 
	    subgraph cluster_sl{
	        label=&quot;IDP支持层&quot;;
	        bgcolor=&quot;mintcream&quot;;
	        node [shape=&quot;Mrecord&quot;, color=&quot;skyblue&quot;, style=&quot;filled&quot;];
	        network_mgr [label=&quot;网络管理器&quot;];
	        log_mgr [label=&quot;日志管理器&quot;];
	        module_mgr [label=&quot;模块管理器&quot;];
	        conf_mgr [label=&quot;配置管理器&quot;];
	        db_mgr [label=&quot;数据库管理器&quot;];
	    };
	 
	    subgraph cluster_md{
	        label=&quot;可插拔模块集&quot;;
	        bgcolor=&quot;lightcyan&quot;;
	        node [color=&quot;chartreuse2&quot;, style=&quot;filled&quot;];
	        mod_dev [label=&quot;开发支持模块&quot;];
	        mod_dm [label=&quot;数据建模模块&quot;];
	        mod_dp [label=&quot;部署发布模块&quot;];
	    };
	 
	mod_dp -&gt; mod_dev [label=&quot;依赖...&quot;];
	mod_dp -&gt; mod_dm [label=&quot;依赖...&quot;];
	mod_dp -&gt; module_mgr [label=&quot;安装...&quot;, color=&quot;yellowgreen&quot;, arrowhead=&quot;none&quot;];
	mod_dev -&gt; mod_dm [label=&quot;依赖...&quot;];
	mod_dev -&gt; module_mgr [label=&quot;安装...&quot;, color=&quot;yellowgreen&quot;, arrowhead=&quot;none&quot;];
	mod_dm -&gt; module_mgr [label=&quot;安装...&quot;, color=&quot;yellowgreen&quot;, arrowhead=&quot;none&quot;];
}
</code></pre><p>####状态图</p>
<p>有限自动机示意图</p>
<p><img loading="lazy" src="/images/2012/01/clip_image022.gif" type="" alt="fsm"  /></p>
<p>上图是一个简易有限自动机，接受<code>a</code>及<code>a</code>结尾的任意长度的串。其脚本定义如下：</p>
<pre><code>digraph automata_0 {
	size = &quot;8.5, 11&quot;;
	fontname = &quot;Microsoft YaHei&quot;;
	fontsize = 10;
	 
	node [shape = circle, fontname = &quot;Microsoft YaHei&quot;, fontsize = 10];
	edge [fontname = &quot;Microsoft YaHei&quot;, fontsize = 10];
	 
	0 [ style = filled, color=lightgrey ];
	2 [ shape = doublecircle ];
	 
	0 -&gt; 2 [ label = &quot;a &quot; ];
	0 -&gt; 1 [ label = &quot;other &quot; ];
	1 -&gt; 2 [ label = &quot;a &quot; ];
	1 -&gt; 1 [ label = &quot;other &quot; ];
	2 -&gt; 2 [ label = &quot;a &quot; ];
	2 -&gt; 1 [ label = &quot;other &quot; ];
	 
	&quot;Machine: a&quot; [ shape = plaintext ];
}
</code></pre><p>形状值为plaintext的表示不用绘制边框，仅展示纯文本内容，这个在绘图中，绘制指示性的文本时很有用，如上图中的<code>Machine: a</code>。</p>
<p>####OSGi中模块的生命周期图</p>
<p>OSGi中，模块具有生命周期，从安装到卸载，可能的状态具有已安装，已就绪，正在启动，已启动，正在停止，已卸载等。如下图所示：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image024.gif" type="" alt="osgi"  /></p>
<p>对应的脚本如下：</p>
<pre><code>digraph module_lc{
	rankdir=TB;
	fontname = &quot;Microsoft YaHei&quot;;
	fontsize = 12;
	 
	node [fontname = &quot;Microsoft YaHei&quot;, fontsize = 12, shape = &quot;Mrecord&quot;, color=&quot;skyblue&quot;, style=&quot;filled&quot;]; 
	edge [fontname = &quot;Microsoft YaHei&quot;, fontsize = 12, color=&quot;darkgreen&quot; ];
	 
	installed [label=&quot;已安装状态&quot;];
	resolved [label=&quot;已就绪状态&quot;];
	uninstalled [label=&quot;已卸载状态&quot;];
	starting [label=&quot;正在启动&quot;];
	active [label=&quot;已激活(运行)状态&quot;];
	stopping [label=&quot;正在停止&quot;];
	start [label=&quot;&quot;, shape=&quot;circle&quot;, width=0.5, fixedsize=true, style=&quot;filled&quot;, color=&quot;black&quot;];
	 
	start -&gt; installed [label=&quot;安装&quot;];
	installed -&gt; uninstalled [label=&quot;卸载&quot;];
	installed -&gt; resolved [label=&quot;准备&quot;];
	installed -&gt; installed [label=&quot;更新&quot;];
	resolved -&gt; installed [label=&quot;更新&quot;];
	resolved -&gt; uninstalled [label=&quot;卸载&quot;];
	resolved -&gt; starting [label=&quot;启动&quot;];
	starting -&gt; active [label=&quot;&quot;];
	active -&gt; stopping [label=&quot;停止&quot;];
	stopping -&gt; resolved [label=&quot;&quot;]; 
}
</code></pre><p>###其他实例</p>
<p>一棵简单的抽象语法树(AST)</p>
<p>表达式 <code>(3+4)*5</code> 在编译时期，会形成一棵语法树，一边在计算时，先计算<code>3+4</code>的值，最后与5相乘。</p>
<p><img loading="lazy" src="/images/2012/01/clip_image026.gif" type="" alt="ast-calc"  /></p>
<p>对应的脚本如下：</p>
<pre><code>digraph ast{
	fontname = &quot;Microsoft YaHei&quot;;
	fontsize = 10;
	 
	node [shape = circle, fontname = &quot;Microsoft YaHei&quot;, fontsize = 10];
	edge [fontname = &quot;Microsoft YaHei&quot;, fontsize = 10];
	node [shape=&quot;plaintext&quot;];
	 
	mul [label=&quot;mul(*)&quot;];
	add [label=&quot;add(+)&quot;];
	 
	add -&gt; 3
	add -&gt; 4;
	mul -&gt; add;
	mul -&gt; 5;
}
</code></pre><p>####简单的UML类图</p>
<p>下面是一简单的UML类图，<code>Dog</code>和<code>Cat</code>都是<code>Animal</code>的子类，<code>Dog</code>和<code>Cat</code>同属一个包，且有可能有联系<code>(0..n)</code>。</p>
<p><img loading="lazy" src="/images/2012/01/clip_image028.gif" type="" alt="uml"  /></p>
<p>脚本：</p>
<pre><code>digraph G{
	 
	fontname = &quot;Courier New&quot;
	fontsize = 10
	 
	node [ fontname = &quot;Courier New&quot;, fontsize = 10, shape = &quot;record&quot; ];
	edge [ fontname = &quot;Courier New&quot;, fontsize = 10 ];
	 
	Animal [ label = &quot;{Animal |+ name : String\l+ age : int\l|+ die() : void\l}&quot; ];
	 
	    subgraph clusterAnimalImpl{
	        bgcolor=&quot;yellow&quot;
	        Dog [ label = &quot;{Dog||+ bark() : void\l}&quot; ];
	        Cat [ label = &quot;{Cat||+ meow() : void\l}&quot; ];
	    };
	 
	edge [ arrowhead = &quot;empty&quot; ];
	 
	Dog-&gt;Animal;
	Cat-&gt;Animal;
	Dog-&gt;Cat [arrowhead=&quot;none&quot;, label=&quot;0..*&quot;];
}
</code></pre><p>####状态图</p>
<p><img loading="lazy" src="/images/2012/01/clip_image030.gif" type="" alt="status-chart"  /></p>
<p>脚本：</p>
<pre><code>digraph finite_state_machine {
	rankdir = LR;
	size = &quot;8,5&quot;
	 
	node [shape = doublecircle]; 
	 
	LR_0 LR_3 LR_4 LR_8;
	 
	node [shape = circle];
	 
	LR_0 -&gt; LR_2 [ label = &quot;SS(B)&quot; ];
	LR_0 -&gt; LR_1 [ label = &quot;SS(S)&quot; ];
	LR_1 -&gt; LR_3 [ label = &quot;S($end)&quot; ];
	LR_2 -&gt; LR_6 [ label = &quot;SS(b)&quot; ];
	LR_2 -&gt; LR_5 [ label = &quot;SS(a)&quot; ];
	LR_2 -&gt; LR_4 [ label = &quot;S(A)&quot; ];
	LR_5 -&gt; LR_7 [ label = &quot;S(b)&quot; ];
	LR_5 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
	LR_6 -&gt; LR_6 [ label = &quot;S(b)&quot; ];
	LR_6 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
	LR_7 -&gt; LR_8 [ label = &quot;S(b)&quot; ];
	LR_7 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
	LR_8 -&gt; LR_6 [ label = &quot;S(b)&quot; ];
	LR_8 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
}
</code></pre><h4 id="时序图">时序图</h4>
<pre><code>digraph G { 
    rankdir=&quot;LR&quot;; 
    node[shape=&quot;point&quot;, width=0, height=0]; 
    edge[arrowhead=&quot;none&quot;, style=&quot;dashed&quot;] 

    { 
        rank=&quot;same&quot;; 
        edge[style=&quot;solided&quot;];
        LC[shape=&quot;plaintext&quot;]; 
        LC -&gt; step00 -&gt; step01 -&gt; step02 -&gt; step03 -&gt; step04 -&gt; step05; 
    } 

    { 
        rank=&quot;same&quot;; 
        edge[style=&quot;solided&quot;];
        Agency[shape=&quot;plaintext&quot;];
        Agency -&gt; step10 -&gt; step11 -&gt; step12 -&gt; step13 -&gt; step14 -&gt; step15; 
    } 

    { 
        rank=&quot;same&quot;; 
        edge[style=&quot;solided&quot;];
        Agent[shape=&quot;plaintext&quot;];
        Agent -&gt; step20 -&gt; step21 -&gt; step22 -&gt; step23 -&gt; step24 -&gt; step25; 
    } 

    step00 -&gt; step10 [label=&quot;sends email new custumer&quot;, arrowhead=&quot;normal&quot;]; 
    step11 -&gt; step01 [label=&quot;declines&quot;, arrowhead=&quot;normal&quot;]; 
    step12 -&gt; step02 [label=&quot;accepts&quot;, arrowhead=&quot;normal&quot;]; 
    step13 -&gt; step23 [label=&quot;forward to&quot;, arrowhead=&quot;normal&quot;]; 
    step24 -&gt; step14; 
    step14 -&gt; step04 [arrowhead=&quot;normal&quot;]; 
} 
</code></pre><p><code>rankdir=&quot;LR&quot;</code>表示，布局从左<code>L</code>到右<code>R</code>。可以看到，在代码中有<code>{}</code>括起来的部分。</p>
<pre><code>{ 
    rank=&quot;same&quot;; 
    edge[style=&quot;solided&quot;];
    Agency[shape=&quot;plaintext&quot;];
    Agency -&gt; step10 -&gt; step11 -&gt; step12 -&gt; step13 -&gt; step14 -&gt; step15; 
}
</code></pre><p>每一个<code>rank=&quot;same&quot;</code>的block中的所有节点都会在同一条线上。我们设置了所有的线为虚线，但是在该block中，将线改为<code>solided</code>。</p>
<p><img loading="lazy" src="/images/2015/11/seq.png" type="" alt="seq"  /></p>
<p>###附录</p>
<p>事实上，从<code>dot</code>的语法及上述的示例中，很容易看出，dot脚本很容易被其他语言生成。比如，使用一些简单的数据库查询就可以生成数据库中的ER图的dot脚本。</p>
<p>如果你追求高效的开发速度，并希望快速的将自己的想法画出来，那么<code>graphviz</code>是一个很不错的选择。</p>
<p>当然，<code>graphviz</code>也有一定的局限，比如绘制时序图(序列图)就很难实现。<code>graphviz</code>的节点出现在画布上的位置事实上是不确定的，依赖于所使用的布局算法，而不是在脚本中出现的位置，这可能使刚开始接触<code>graphviz</code>的开发人员有点不适应。<code>graphviz</code>的强项在于自动布局，当图中的顶点和边的数目变得很多的时候，才能很好的体会这一特性的好处：</p>
<p><img loading="lazy" src="/images/2012/01/clip_image034.gif" type="" alt="complex"  /></p>
<p>比如上图，或者较上图更复杂的图，如果采用手工绘制显然是不可能的，只能通过<code>graphviz</code>提供的自动布局引擎来完成。如果仅用于展示模块间的关系，子模块与子模块间通信的方式，模块的逻辑位置等，<code>graphviz</code>完全可以胜任，但是如果图中对象的物理位置必须是准确的，如节点A必须位于左上角，节点B必须与A相邻等特性，使用<code>graphviz</code>则很难做到。毕竟，它的强项是自动布局，事实上，所有的节点对与布局引擎而言，权重在初始时都是相同的，只是在渲染之后，节点的大小，形状等特性才会影响权重。</p>
<p>本文只是初步介绍了<code>graphviz</code>的简单应用，如图的定义，顶点/边的属性定义，如果运行等，事实上还有很多的属性，如画布的大小，字体的选择，颜色列表等，大家可以通过<code>graphviz</code>的官网来找到更详细的资料。</p>
<p>文中的代码都已经在<a href="https://github.com/abruzzi/graphviz-scripts">Github</a>上。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
