撰寫佈局外掛程式
若要建立名為 xxx
的新佈局外掛程式,您首先需要提供兩個函式:xxx_layout
和 xxx_cleanup
。這些函式的語意如下所述。
佈局
void xxx_layout(Agraph_t * g)
初始化圖形。
-
如果演算法會使用通用的邊緣路由程式碼,它應該呼叫
setEdgeType (g, ...);
。 -
針對每個節點,呼叫
common_init_node
和gv_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 個可用的函式
spline_edges1 (Agraph_t*, int edgeType)
假設節點座標儲存在ND_coord_i
中,且GD_bb
已設定。針對每個邊緣,此函式會建構適當的資料並將其儲存在ED_spl
中。spline_edges0 (Agraph_t*)
假設節點座標儲存在ND_pos
中,且GD_bb
已設定。如果已設定 ratio 屬性,此函式會使用此屬性,將ND_pos
中的值複製到ND_coord_i
(從英寸轉換為點);並使用setEdgeType()
指定的邊緣類型呼叫 spline_edges1。spline_edges (Agraph_t*)
假設節點座標儲存在ND_pos
中。此函式會計算 g 的邊界框並將其儲存在GD_bb
中,然後呼叫spline_edges0()
。
如果演算法僅適用於連通元件,程式碼可以使用 pack 函式庫來取得元件、個別佈局它們,並根據使用者規格將它們組合在一起。下面給出了一個典型的架構。您可以查看 twopi
、circo
、neato
或 fdp
的程式碼,以取得更詳細的範例。
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_node
和 gv_cleanup_edge
來結束。這會清理 splines 標籤、ND_pos
、形狀並將 A*info_t
清零,因此這些必須最後發生,但如果需要,它們可能是明確的 xxx_cleanup_node
和 xxx_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_XXX
至 layout_type
。
以上允許新的佈局在 neato
外掛程式之上進行搭便車,但需要重建外掛程式。一般來說,使用者可以 (而且可能應該) 完全獨立地建置佈局外掛程式。
若要執行此動作,在撰寫 xxx_layout
和 xxx_cleanup
之後,必須執行下列動作
-
新增類型和資料結構
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 };
-
將所有這些合併到一個動態函式庫中,其名稱包含字串
gvplugin_
,並將該函式庫安裝在與其他 Graphviz 外掛程式相同的目錄中。例如,在 Linux 系統上,點佈局外掛程式位於函式庫libgvplugin_dot_layout.so
中。 -
執行
dot -c
以重新產生設定檔。
注意事項
- 可以將額外的佈局新增為
gvlayout_xxx_types
中的額外行。 - 顯然,大多數的名稱和字串可以是任意的。一個限制是
gvplugin_library_t
類型的外部識別碼必須以_LTX_library
結尾。此外,gvlayout_xxx_types
的每個項目中的字串xxx
是用來識別佈局演算法的名稱,因此需要與任何其他佈局名稱不同。 - 佈局演算法的功能目前僅限於位元的旗標,而唯一支援的旗標是
LAYOUT_USES_RANKDIR
,它啟用rankdir
屬性的佈局。
需要對靜態了解佈局演算法的任何應用程式進行變更。
Automake 設定
如果您想要將程式碼整合到 Graphviz 軟體並使用其建置系統,請遵循下列指示。您當然可以使用您自己的建置軟體來建置和安裝您的外掛程式。
- 將您的軟體放置於
lib/xxxgen
中,並將上述的鉤子加入gvlayout_neato_layout.c
。 - 在
lib/xxxgen
中,提供一個Makefile.am
(基於一個簡單的範例,例如lib/fdpgen/Makefile.am
)。 - 在
lib/Makefile.am
中,將xxxgen
加入SUBDIRS
。 - 在
configure.ac
中,將lib/xxxgen/Makefile
加入AC_CONFIG_FILES
。 - 在
lib/plugin/neato_layout/Makefile.am
中,將$(top_builddir)/lib/xxxgen/libxxxgen_C.la
插入libgvplugin_neato_layout_C_la_LIBADD
。 - 請記得執行
autogen.sh
,因為單獨執行configure
可能會猜測錯誤。
這也假設您的系統上安裝了各種 automake 工具的良好版本。