markdown 繪圖利器之granphviz

可酷可樂發表於2020-08-22

概述

Markdown 是一個輕量級的標記語言,語法簡單、易於上手,深受程式設計師、博主等人群的鐘愛。Markdown 工具鏈也非常豐富,如graphviz軟體,就是可以嵌入markdown文字,進行思維導圖、流程圖、系統框圖繪製的利器,讓你心無旁騖,全心投入到系統架構、軟體、演算法的設計中。

在markdown使用 graphviz 語法,推薦使用 vnote 軟體

graphviz 是 AT&T的bell實驗室開源的指令碼自動化繪圖軟體,其主要用於繪製關係圖,自動排版,有效提升工作效率。同時,其也是自動化繪圖工具plantuml、很多資料視覺化工具的基礎。類似於python中的matplotlib。

graphviz 的基本結構如下所示,其主要包含三部分:

  • 1) layout 自動化佈局工具:dot,neato等,本文重點講解dot的使用。
  • 2) script指令碼:主要包含 graph,node,edge三類實體,以及attributes屬性;
  • 3)APIs:若需要在其它語言中呼叫,graphviz提供了 C,java,python,php等語言的API。

graphviz的dot的基本使用也非常簡單,流程如下:

graphviz示例,也是上述基礎流程的dot指令碼程式碼如下:

digraph base_flow {
    // 步驟1: 定義digraph的屬性
    label = <<B>graphviz使用流程</B>>;
    
   // 步驟2: 定義node、edge的屬性
    node[shape=box];

    // 步驟3: 新增node、edge
    graph_attr -> node_edge_attr -> node_edge_added -> custom_attr;

    // 步驟4: 定義特定node,edge的屬性
    graph_attr[label="1. 定義digraph的屬性"];
    node_edge_attr[label="2. 定義node、edge的屬性"];
    node_edge_added[label="3. 新增node、edge"];
    custom_attr[label="4. 定義特定node,edge的屬性"];
}

graphviz 指令碼語法結構

Graphviz 語法非常簡單,主要由程式碼塊、語句、識別符號、註釋等幾部分組成:

  • 程式碼塊: 位於{}語句中的語句即為程式碼塊;
  • 語句:以;結尾,主要分為程式碼塊,節點,連線,屬性四種語句型別;
  • 實體物件識別符號:除了特殊字元外的所有字元都可以用於識別符號,如數字,中英文字串等;
  • 註釋//表示單行註釋,/*...*/表示多行註釋。

graphviz 圖實體主要分三類:

  • graph: 1)digraph {...} 定義有向圖;2)graph {...}定義無向圖;
  • subgraph:subgraph {...}定義子圖;
  • cluster subgraph: subgraph cluster_xxx {...}定義的程式碼塊

子圖的型別(有向圖還是無向圖)與父圖相同,子圖的名稱以cluster開頭才被當成聚集子圖渲染。示例如下:

digraph graph_name{
    bgcolor="transparent";//背景透明
    
    subgraph cluster_subgraph_name{//聚集子圖
        node[shape=box];
        cluster_A -> cluster_B;
    }
    
    subgraph subgraph_name{//子圖
        node[shape=none];
        sub_A -> sub_B;
    }
    
    {//匿名子圖
        node[shape=octagon];
        nest_A -> nest_B;
    }
    
    global_A -> global_B;
    
    cluster_B -> global_B;
    sub_B -> global_B;
    nest_B -> global_B;
}

方向,尺寸,間距

兩個重要的屬性決定了圖的尺寸,分別為 nodesep, ranksep。

  • nodesep : 同一個 rank 中的相鄰節點的最小距離,單位為英寸(=2.54 cm)。直線的不同端點屬於不同的 rank;
  • ranksep : 相鄰 rank 之間的距離;
  • rankdir: rank的指向,如 LR (left to right) or RL,或者 TB (top to bottom) or BT;
digraph G {
    nodesep = 2;
    ranksep = 1;
    rankdir = LR;
    a -> b;
    c;
    b -> d;
}

節點

graphviz的節點的定義方式如下:

 節點識別符號[節點屬性]

所有的節點屬性見官網的節點屬性介紹,下面,介紹常用的節點屬性。

當想讓節點的形狀完全由類HTML的標籤設定時,一般設定屬性 width=0, height=0, margin=0

shape 屬性

graphviz 主要有三種型別的形狀(shape)型別:多邊形(polygon), 記錄(record)形狀, 使用者定義(user-defined)形狀。基於記錄的形狀在很大程度上已經被類似HTML的標籤所取代。也就是說,可以考慮使用shape=none、margin=0和類HTML標籤,而不是使用shape=record。

多邊形

所有的多邊形的屬性值與形狀如下,其中 rect/rectanglebox 是同義詞,noneplaintext 是同義詞。

當想讓節點的形狀完全由類HTML的標籤設定時,一般設定屬性 width 0, height=0, margin=0

![](https://raw.githubusercontent.com/melon-li/markdown_pics/master/1598067382_20200816110541022_17972.png =556x)

record-based 的形狀

基於 record的形狀,是指節點的屬性為 record 或者 Mrecord 的節點,其節點的表現形式由 label 屬性決定。 record 與 Mrecord 的區別在於 Mrecord 的外圍是圓角。

label 屬性的語法結構如下:

  • 不同的欄位使用|隔開;
  • 欄位的 portname 使用 <...>尖括號括起來;
  • {...}中的內容,在水平和垂直佈局之間翻轉,取決於 graph 的 rankdir 屬性:1)若 graph[rankdir=TB],則整體圖片垂直佈局,{...}中的內容垂直佈局。2)graph[rankdir=LR],則整體圖片水平佈局,{...}中的內容水平佈局;
digraph structs {
    node[shape=record];
    graph[rankdir=TB];
    
    struct1[label="<f0> left|<f1> mid&#92; dle|<f2> right"];
    struct2[label="<f0> one|<f1> two"];
    struct3[label="hello&#92;nworld |{ b |{c|<here> d|e}| f}| g | h"];
    struct1:f1 -> struct2:f0;
    struct1:f2 -> struct3:here;
}

修改 graph 屬性為 LR,則整體水平佈局。

digraph structs {
    node[shape=record];
    graph[rankdir=LR];
    
    struct1[label="<f0> left|<f1> mid&#92; dle|<f2> right"];
    struct2[label="<f0> one|<f1> two"];
    struct3[label="hello&#92;nworld |{ b |{c|<here> d|e}| f}| g | h"];
    struct1:f1 -> struct2:f0;
    struct1:f2 -> struct3:here;
}

使用者定製

使用者定製圖形有幾種方式:

  • 通過類HTML的 label 使用 IMG 屬性載入使用者定製的影像;
digraph structs {
    node [shape=plaintext];

    struct1 [label=<<TABLE>
    <TR><TD><IMG SRC="eqn.png"/></TD></TR>
    <TR><TD>caption</TD></TR>
    </TABLE>>];
}
  • 如果使用的是 SVG (-Tsvg),或者 postScript (-Tps, -Tps2) 或者 光柵格式 (-Tgif, -Tpng, 或者-Tjpg),可以通過指定影像檔名載入圖片,例如:
graph pic_test {
    your_pic[shape=none, label="", imagepath="D:\\cloud_sync\\vnote_book\\效率工具", image="test.png"];
}

關於 image 的路徑,參考環境變數的設定

label 屬性

基本用法

label 屬性的基本用法是設定節點的文字顯示,若節點沒有顯示設定label屬性,則文字顯示節點的識別符號。

graph lebel_demo {
    node1;
    node2[label="文字顯示"];
}

HTML用法

label 屬性HTML用法是將節點轉換為一個類似於HTML的實體,實體的具體呈現完全由HTML指令碼語言控制,其注意事項如下:

  • 節點的 shape 屬性設定為 record 或 none,建議 none ;
  • 節點的 寬、高、邊緣屬性設定為0:width=0, height=0, margin=0
  • 節點的 label 屬性字串通過尖括號<...>包含HTML語法字串;

示例:使用HTML實現一個表格節點實體(注意:html不區分大小寫)。

digraph html_label_Example
{
    label = "html_like_label_example";
    
    bgcolor="transparent";//背景透明
    
    html_ex_node[shape=record, margin=0, label=<
        <table border="0" cellborder="1" cellspacing="0" cellpadding="4">
            <tr>
                <td rowspan="2">test</td>
                <td>a</td>                
                <td rowspan="2">HTML-Like<br/>label</td>
            </tr>
            <tr>
                <td>b</td>
            </tr>
        </table>
    >];
}

graphviz不支援所有的html-4的語法,目前只支援如下的html-like語法:


label	:	text
|	fonttable
text	:	textitem
|	text textitem
textitem	:	string
|	<BR/>
|	<FONT> text </FONT>
|	<I> text </I>
|	<B> text </B>
|	<U> text </U>
|	<O> text </O>
|	<SUB> text </SUB>
|	<SUP> text </SUP>
|	<S> text </S>
fonttable	:	table
|	<FONT> table </FONT>
|	<I> table </I>
|	<B> table </B>
|	<U> table </U>
|	<O> table </O>
table	:	<TABLE> rows </TABLE>
rows	:	row
|	rows row
|	rows <HR/> row
row	:	<TR> cells </TR>
cells	:	cell
|	cells cell
|	cells <VR/> cell
cell	:	<TD> label </TD>
|	<TD> <IMG/> </TD>

更多的html-like語法見 graphviz官網中對record節點的介紹

style 屬性

style 屬性用於修改節點的外觀,當前,支援8種型別的 style:filled, invisible, diagonals, rounded. dashed, dotted, solid, bold

  • filled : 此值指示應填充節點的內部。使用的顏色是 fillcolor 定義的,若 fillcolor 屬性未定義,則使用 color 屬性的顏色。對於未填充的節點,節點內部對當前圖形或簇背景色的任何顏色都是透明的。請注意,點形狀始終是填充的。
digraph G {
  rankdir=LR
  node [shape=box, color=blue]
  node1 [style=filled] 
  node2 [style=filled, fillcolor=red] 
  node0 -> node1 -> node2
}

  • invisible : 不可見。設定此樣式會導致節點根本不顯示。請注意,節點仍用於佈局圖形。
  • diagonals: 斜線 。“斜線”樣式會導致在節點多邊形的頂點附近繪製小斜線。
digraph G {
  rankdir=LR
  node [shape=box, color=blue]
  
  node0 [style=diagonals] 
}

  • rounded :圓形的,使節點的邊變得圓滑,可以作用在 record 形狀上。
digraph R {
  rankdir=LR
  node [style=rounded]
  node1 [shape=box]
  node2 [fillcolor=yellow, style="rounded,filled", shape=diamond]
  node3 [shape=record, label="{ a | b | c }"]

  node1 -> node2 -> node3
}

  • dashed : 使節點的邊變為虛線;

  • dotted : 使節點的邊變為點線;

  • solid : 使節點的邊變為直線,預設屬性;

  • bold : 使節點的邊線加粗。

port 屬性

節點的 port 屬性是指節點連線另一個節點的線條端點位置,埠的位置有8種,分別為節點的東、南、西、北、東南、東北、西南、西北,屬性的值分別為e, s, w, n, se, ne, sw, nw

有兩種型別的 port 屬性:

  • 一種使指定源節點的端點位置,使用 tailport 屬性,如下指令碼指定 a節點的端點位置為東:
digraph G {
    a -> b [tailport = e];
}

  • 一種指定目的節點的端點位置,使用:pos語法,如下指令碼指定b節點的端點位置為西:
digraph G {
    a -> b:w;
}

也可以通過上述語法指定 record 形狀的域欄位(如f1)的端點位置:

digraph G {
    a -> b:f1:w;
}

參考

附錄

Graphviz基本組成結構dot程式碼

digraph gv_basic_structure{
    label=<<B>Graphviz基本組成結構</B>>;
    
    node[shape=box];
    
    graphviz[label="Graphviz"];
    
    subgraph{
        layout[label="Layouts"];
        script[label="Script Files"];
        api[label="APIs"]
        rank=same;
    }
    
    graphviz -> layout;
    graphviz -> script;
    graphviz -> api;
    
    
    script ->
    subgraph{
        element[label="Elements"];
        attribute[label="Attributes"];
        rank=same;
    }
    
    layout ->
    subgraph{
        layout_dot[label="dot", color="red"];
        layout_neato[label="neato"];
        layout_etc[label="......"];
    }
    
    element ->
    subgraph{
        ele_graph[label="Graph"];
        ele_node[label="Node"];
        ele_edge[label="Edge"];
    }
}

相關文章