撰寫佈局外掛程式

如何撰寫自訂佈局引擎。

若要建立名為 xxx 的新佈局外掛程式,您首先需要提供兩個函式:xxx_layoutxxx_cleanup。這些函式的語意如下所述。

佈局

void xxx_layout(Agraph_t * g)

初始化圖形。

  • 如果演算法會使用通用的邊緣路由程式碼,它應該呼叫 setEdgeType (g, ...);

  • 針對每個節點,呼叫 common_init_nodegv_nodesize

  • 如果演算法會使用 spline_edges() 來路由邊緣,節點座標需要儲存在 ND_pos 中,因此應該在此處配置。此項和上述兩個呼叫,全部由呼叫 neato_init_node() 處理。

  • 針對每個邊緣,呼叫 common_init_edge

  • 演算法應該配置它需要的其他任何資料結構。這可能會涉及 A*info_t 欄位中的欄位。此外,這些欄位的每一個都包含一個 void* alg; 子欄位,演算法可以使用它來儲存額外的資料。一旦我們移至 cgraph,這一切都會被演算法專屬的記錄取代。

  • 佈局圖形。完成後,每個節點應該將其座標儲存在 ND_coord_i(n) 中的點中,每個邊緣應該在其佈局中描述在 ED_spl(e) 中。(注意:從 2.21 版開始,ND_coord_i 已被 ND_coord 取代,現在是浮點座標。)

若要新增邊緣,有 3 個可用的函式

  1. spline_edges1 (Agraph_t*, int edgeType) 假設節點座標儲存在 ND_coord_i 中,且 GD_bb 已設定。針對每個邊緣,此函式會建構適當的資料並將其儲存在 ED_spl 中。
  2. spline_edges0 (Agraph_t*) 假設節點座標儲存在 ND_pos 中,且 GD_bb 已設定。如果已設定 ratio 屬性,此函式會使用此屬性,將 ND_pos 中的值複製到 ND_coord_i (從英寸轉換為點);並使用 setEdgeType() 指定的邊緣類型呼叫 spline_edges1。
  3. spline_edges (Agraph_t*) 假設節點座標儲存在 ND_pos 中。此函式會計算 g 的邊界框並將其儲存在 GD_bb 中,然後呼叫 spline_edges0()

如果演算法僅適用於連通元件,程式碼可以使用 pack 函式庫來取得元件、個別佈局它們,並根據使用者規格將它們組合在一起。下面給出了一個典型的架構。您可以查看 twopicirconeatofdp 的程式碼,以取得更詳細的範例。

Agraph_t **ccs;
Agraph_t *sg;
Agnode_t *c = NULL;
int ncc;
int i;

ccs = ccomps(g, &ncc, 0);
if (ncc == 1) {
    /* layout nodes of g */
    adjustNodes(g);  /* if you need to remove overlaps */
    spline_edges(g); /* generic edge routing code */

} else {
    pack_info pinfo;
    pack_mode pmode = getPackMode(g, l_node);

    for (i = 0; i < ncc; i++) {
        sg = ccs[i];
        /* layout sg */
        adjustNodes(sg);  /* if you need to remove overlaps */
    }
    spline_edges(g);  /* generic edge routing */

    /* initialize packing info, e.g. */
    pinfo.margin = getPack(g, CL_OFFSET, CL_OFFSET);
    pinfo.doSplines = 1;
    pinfo.mode = pmode;
    pinfo.fixed = 0;
    packSubgraphs(ncc, ccs, g, &pinfo);
}
for (i = 0; i < ncc; i++) {
    agdelete(g, ccs[i]);
}

free(ccs);

如果您依賴僅在根圖形中設定的屬性,請小心佈局子圖形。使用連通元件時,可以在封裝之前 (如上) 或在封裝元件之後 (請參閱 circo) 新增邊緣。

最好檢查圖形具有 0 或 1 個節點,或沒有邊緣的簡單情況。

xxx_layout 的結尾,呼叫

dotneato_postprocess(g);

以下範本在大多數情況下都適用,忽略處理已斷開連線圖形和移除節點重疊的問題

static void
xxx_init_node(node_t * n)
{
  neato_init_node(n);
  /* add algorithm-specific data, if desired */
}

static void
xxx_init_edge(edge_t * e)
{
  common_init_edge(e);
  /* add algorithm-specific data, if desired */
}

static void
xxx_init_node_edge(graph_t * g)
{
  node_t *n;
  edge_t *e;

  for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
      xxx_init_node(n);
  }
  for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
      for (e = agfstout(g, n); e; e = agnxtout(g, e)){          
          xxx_init_edge(e);
      }
  }
}

void
xxx_layout (Agraph_t* g)
{
  xxx_init_node_edge(g);
  /* Set ND_pos(n) for each node n */
  spline_edges(g);
  dotneato_postprocess(g);
}  

清理

void xxx_cleanup(Agraph_t * g)

釋放在佈局中配置的任何資源。

使用對每個節點和邊緣呼叫 gv_cleanup_nodegv_cleanup_edge 來結束。這會清理 splines 標籤、ND_pos、形狀並將 A*info_t 清零,因此這些必須最後發生,但如果需要,它們可能是明確的 xxx_cleanup_nodexxx_cleanup_edge 的一部分。

最後,您應該執行

if (g != g->root) memset(&(g->u), 0, sizeof(Agraphinfo_t));

這對於再次佈局圖形是必要的,因為佈局程式碼假設此結構是乾淨的。

libgvc 會對根圖形進行最後的清理,釋放任何繪圖、釋放其標籤並將根圖形的 Agraphinfo_t 清零。

以下範本在大多數情況下都適用

static void xxx_cleanup_graph(Agraph_t * g)
{
  /* Free any algorithm-specific data attached to the graph */
  if (g != g->root) memset(&(g->u), 0, sizeof(Agraphinfo_t));
}

static void xxx_cleanup_edge (Agedge_t* e)
{
  /* Free any algorithm-specific data attached to the edge */
  gv_cleanup_edge(e);
}

static void xxx_cleanup_node (Agnode_t* n)
{
  /* Free any algorithm-specific data attached to the node */
  gv_cleanup_node(e);
}

void xxx_cleanup(Agraph_t * g)
{
  Agnode_t *n;
  Agedge_t *e;

  for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
      for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
          xxx_cleanup_edge(e);
      }
      xxx_cleanup_node(n);
  }
  xxx_cleanup_graph(g);
}   

大多數佈局使用類似於 neato 的輔助常式,因此可以在 plugin/neato_layout 中新增進入點。

新增至 gvlayout_neato_layout.c

gvlayout_engine_t xxxgen_engine = {
    xxx_layout,
    xxx_cleanup,
};

以及行

{LAYOUT_XXX, "xxx", 0, &xxxgen_engine, &neatogen_features},

gvlayout_neato_types 和該檔案中的新 emum LAYOUT_XXXlayout_type

以上允許新的佈局在 neato 外掛程式之上進行搭便車,但需要重建外掛程式。一般來說,使用者可以 (而且可能應該) 完全獨立地建置佈局外掛程式。

若要執行此動作,在撰寫 xxx_layoutxxx_cleanup 之後,必須執行下列動作

  1. 新增類型和資料結構

    typedef enum { LAYOUT_XXX } layout_type;
    
    static gvlayout_features_t xxxgen_features = {
        0
    };
    gvlayout_engine_t xxxgen_engine = {
        xxx_layout,
        xxx_cleanup,
    };
    static gvplugin_installed_t gvlayout_xxx_types[] = {
        {LAYOUT_XXX, "xxx", 0, &xxxgen_engine, &xxxgen_features},
        {0, NULL, 0, NULL, NULL}
    };
    static gvplugin_api_t apis[] = {
        {API_layout, &gvlayout_xxx_types},
        {(api_t)0, 0},
    };
    gvplugin_library_t gvplugin_xxx_layout_LTX_library = { "xxx_layout", apis };
    
  2. 將所有這些合併到一個動態函式庫中,其名稱包含字串 gvplugin_,並將該函式庫安裝在與其他 Graphviz 外掛程式相同的目錄中。例如,在 Linux 系統上,點佈局外掛程式位於函式庫 libgvplugin_dot_layout.so 中。

  3. 執行 dot -c 以重新產生設定檔。

注意事項

  • 可以將額外的佈局新增為 gvlayout_xxx_types 中的額外行。
  • 顯然,大多數的名稱和字串可以是任意的。一個限制是 gvplugin_library_t 類型的外部識別碼必須以 _LTX_library 結尾。此外,gvlayout_xxx_types 的每個項目中的字串 xxx 是用來識別佈局演算法的名稱,因此需要與任何其他佈局名稱不同。
  • 佈局演算法的功能目前僅限於位元的旗標,而唯一支援的旗標是 LAYOUT_USES_RANKDIR,它啟用 rankdir 屬性的佈局。

需要對靜態了解佈局演算法的任何應用程式進行變更。

Automake 設定

如果您想要將程式碼整合到 Graphviz 軟體並使用其建置系統,請遵循下列指示。您當然可以使用您自己的建置軟體來建置和安裝您的外掛程式。

  1. 將您的軟體放置於 lib/xxxgen 中,並將上述的鉤子加入 gvlayout_neato_layout.c
  2. lib/xxxgen 中,提供一個 Makefile.am(基於一個簡單的範例,例如 lib/fdpgen/Makefile.am)。
  3. lib/Makefile.am 中,將 xxxgen 加入 SUBDIRS
  4. configure.ac 中,將 lib/xxxgen/Makefile 加入 AC_CONFIG_FILES
  5. lib/plugin/neato_layout/Makefile.am 中,將 $(top_builddir)/lib/xxxgen/libxxxgen_C.la 插入 libgvplugin_neato_layout_C_la_LIBADD
  6. 請記得執行 autogen.sh,因為單獨執行 configure 可能會猜測錯誤。

這也假設您的系統上安裝了各種 automake 工具的良好版本。

上次修改時間:2022 年 12 月 6 日:修正錯字 (de77e3d)