Windows環境にて、Graphvizを使用して状態遷移図や人物相関図等の有向/無向グラフを描く、基本的な方法を説明します。前半はコマンド、後半はC++からの呼び出しになります。
開発環境 | Visual Studio 2022 |
---|---|
言語 | C++ |
ライブラリ | Graphviz 5.0.0 |
動作確認 | Windows 11 Windows 10 Windows 7 |

Graphvizのインストール
https://graphviz.org/からWindows用のインストーラーをダウンロードして、実行します。ダウンロードの際、32bit版と64bit版の選択は、C++から呼び出す場合のソリューションに合わせます。インストールの途中にて、環境変数PATHにGraphvizのbinフォルダーのパスを追加するかどうか聞かれますので、追加を選択します。
dotコマンドの使用方法
インストール完了後、コマンドプロンプトにて動作確認します。以下の例では、Graphvizのバージョンを表示します。
> dot -V
dotコマンドのヘルプを見る場合は、以下の通りです。
> dot -?
冒頭の有向グラフを作成する、dotファイルの例です。文字コードは、UTF-8(BOM無し)にて保存してください。ちなみに、Microsoft Officeをインストールしている環境では、拡張子dotのファイルは「Microsoft Word 97-2003 テンプレート」と表示されますが、全く関係ありません。
digraph {
node [fontname=Meiryo]
edge [fontname=Meiryo]
// node
n0 [label=ラー]
n1 [label=オシリス]
n2 [label=セト shape=doublecircle]
n3 [label=ネフティス]
n4 [label=ホルス shape=doublecircle]
n5 [label=ハトホル]
n6 [label=ベック]
n7 [label=ザヤ]
n8 [label=トト]
// edge
n0 -> n1 [label=親子]
n0 -> n2 [label=親子]
n1 -> n4 [label=親子]
n2 -> n1 [label=倒す]
n2 -> n3 [label=倒す]
n2 -> n3 [label=夫婦 arrowhead=none]
n2 -> n8 [label=倒す]
n4 -> n2 [label=復讐]
n4 -> n5 [label=夫婦 arrowhead=none]
n5 -> n4 [label=手伝う]
n6 -> n4 [label=手伝う]
n6 -> n7 [label=カップル arrowhead=none]
n7 -> n4 [label=信仰]
n8 -> n4 [label=手伝う]
}
上記のファイルをtest.dot、出力フォーマットをPNGとして、test.pngに出力する場合、コマンドは以下の通りです。
> dot -Tpng test.dot -o test.png
pngを、bmp、gif、jpg、pdf、svg、tifに変更すると、それぞれのフォーマットにて出力します。その際、-Tオプションのフォーマット名は小文字にて指定します。
(解説)
- digraphは有向グラフ、graphは無向グラフ
- fontnameは英語名にて指定する。デフォルトのTimes-Romanは、日本語表示不可
(例)メイリオはMeiryo。同様に、MS Gothic、MS PGothic、MS Mincho、MS PMincho、Yu Gothic、Yu Mincho、等 - fontname、labelに空白やハイフンが含まれる場合、ダブルクォートで括る
(例)node [fontname="MS Gothic"]
- //はコメント文
- nodeにはIDを付与することができる(上記の例ではn0~n8)。以下の制限があるが、ダブルクォートで括ると制限は無い
- 使用できる文字は英数字、日本語、アンダースコア。数字のみは可だが、他の文字と混在させる場合は、数字で始まるのは不可
- 数字のみの場合、マイナスと小数点としてハイフンとピリオドの使用可
- nodeにlabelを設定しない場合、IDが表示される
- shape=doublecircleは2重丸
- edgeは、digraphの場合「
->
」、graphの場合「--
」(ハイフン2個)で指定する。「<-
」は不可 - nodeのIDやlabelを定義せず、edgeに直接書くことも可
(例)ラー -> オシリス [label=親子]
- arrowhead=noneは、矢印の三角無し
- 属性の間にカンマ、行末にセミコロンを付けてもよい
(例)n2 [label=セト, shape=doublecircle];
他にも、色をつけたり、nodeやedgeの形状を変更したり、いろいろな表現が可能です。
C++から呼び出す
準備
Visual Studio 2022の場合、プロジェクトのプロパティに以下の設定を追加します。インストール先のフォルダーがデフォルトと異なる場合は、そちらに合わせてください。
[C/C++]-[全般]-[追加のインクルードディレクトリ] |
---|
C:\Program Files\Graphviz\include |
[リンカー]-[全般]-[追加のライブラリディレクトリ] |
C:\Program Files\Graphviz\lib |
[リンカー]-[入力]-[追加の依存ファイル] |
cgraph.lib;gvc.lib |
サンプルソース
Graphvizのライブラリ関数は、C言語にて実装されています。ライブラリ側にてextern "C"
しているので、そのままC++から呼び出しが可能です。
以下のソースは、dotコマンドの説明と同じ有向グラフを作成します。ただし、処理の内容としては、nodeのIDを自動採番している点のみ異なります。
#include <unordered_map>
// Graphviz
#define GVDLL
#include <graphviz/gvc.h>
const std::vector<std::string> nodes
{
u8"ラー",
u8"オシリス",
u8"セト",
u8"ネフティス",
u8"ホルス",
u8"ハトホル",
u8"ベック",
u8"ザヤ",
u8"トト"
};
const std::vector<std::tuple<int, int, std::string>> edges
{
{0, 1, u8"親子"},
{0, 2, u8"親子"},
{1, 4, u8"親子"},
{2, 1, u8"倒す"},
{2, 3, u8"倒す"},
{2, 3, u8"夫婦"},
{2, 8, u8"倒す"},
{4, 2, u8"復讐"},
{4, 5, u8"夫婦"},
{5, 4, u8"手伝う"},
{6, 4, u8"手伝う"},
{6, 7, u8"カップル"},
{7, 4, u8"信仰"},
{8, 4, u8"手伝う"}
};
// グラフ作成
// 2番目の引数
// 有向グラフはAgdirected、無向グラフはAgundirected
Agraph_t *g = agopen(nullptr, Agdirected, nullptr);
// フォント指定
agattr(g, AGNODE, const_cast<char *>("fontname"), "Meiryo");
agattr(g, AGEDGE, const_cast<char *>("fontname"), "Meiryo");
// node
std::unordered_map<std::string, Agnode_t *> node;
for (const std::string &n : nodes)
{
// 2番目の引数はID
// 既存の場合、そのAgnode_tを返す
// IDを自動採番する場合はnullptr
// 3番目の引数
// IDが存在しない場合
// trueはAgnode_tを新規作成
// falseはnullptrを返す
node[n] = agnode(g, nullptr, true);
// ラベル
// 設定しない場合、IDを表示
agsafeset(node[n], const_cast<char *>("label"), n.c_str(), "");
}
// nodeの形状の、デフォルト以外を指定
agsafeset(node[nodes[2]], const_cast<char *>("shape"), "doublecircle", "");
agsafeset(node[nodes[4]], const_cast<char *>("shape"), "doublecircle", "");
// edge
for (const std::tuple<int, int, std::string> &e : edges)
{
// 2番目の引数 -> 3番目の引数
// 各nodeのAgnode_t
// 4番目の引数はedgeを識別するキー
// nullptrは毎回異なるキーとして扱う
// 2,3,4番目の引数の組み合わせが既存の場合、同じedge
// 5番目の引数
// 組み合わせが存在しない場合
// trueはedgeを新規作成
// falseはnullptrを返す
Agedge_t *edge = agedge(g, node[nodes[std::get<0>(e)]], node[nodes[std::get<1>(e)]], nullptr, true);
// ラベル
agsafeset(edge, const_cast<char *>("label"), std::get<2>(e).c_str(), "");
// edgeの形状の、デフォルト以外を指定
if (std::get<2>(e) == u8"夫婦" || std::get<2>(e) == u8"カップル")
{
agsafeset(edge, const_cast<char *>("arrowhead"), "none", "");
}
}
// dotフォーマットにレイアウト
GVC_t *gvc = gvContext();
gvLayout(gvc, g, "dot");
// デバッグのため、内部にて作成されたdotをファイル出力
//gvRenderFilename(gvc, g, "canon", "test.dot");
// PNGにてファイル出力
// bmp、gif、jpg、pdf、svg、tifに変更すると、それぞれのフォーマットにて出力
gvRenderFilename(gvc, g, "png", "test.png");
// 解放
gvFreeLayout(gvc, g);
agclose(g);
gvFreeContext(gvc);
実際に開発する際には、何らかの処理結果から状態遷移図を作成するような、動的なラベルになるでしょう。Graphvizのライブラリ関数に渡すテキストの文字コードはUTF-8です。ただし、gvRenderFilename関数の4番目の引数に日本語を含むパスを渡す場合は、Shift_JISです。UTF-16からUTF-8やShift_JISに変換する方法は、「【C++/Win32】UTF-16とUTF-8を相互に変換する」を参照してください。
リファレンス
Documentation
https://graphviz.org/documentation/
GitLab
https://gitlab.com/graphviz/graphviz
ライブラリ関数の仕様がよくわからない場合は、libの下のソースを読みましょう。
※冒頭の人物相関図は、映画「キング・オブ・エジプト」(2016)を観て作成しました。