2007/11/07(水)16:35
Python から Graphviz を使う( pydot を日本語で出力)
グラフ図を書きたくなったので、pydot がどの程度使えるか調べてみる。
こんな図とか、こんな図とか Python のプログラムから描けたらいいなと。この図は Graphviz - Graph Visualization Software で描かれたもので、この Python のラッパーとなるのが pydot。pydot のオリジナルの作者のサイト を見ると pydot - Python interface to Graphviz's Dot language(Google code) をメインにするよということなので、そちらを中心に見る。でもドキュメントは pydot (version 0.9.10): Graphviz's dot language Python interface でオリジナルサイトにあったりする。現状、まだ、オリジナルサイトの方が情報が多い。
必要なもの
Graphviz - Graph Visualization Software
pyparsing(dotファイルのロードに使う)
pydot - Python interface to Graphviz's Dot language(Google code)
Windows の場合、標準の Python ではなく、Python Enthought Edition とかインストールしておくと、Graphviz や pyparsing、pydot が最初からインストールされているので楽だったりする。インストールされていない環境の場合は個別にインストールする。残念ながら pydot は easy_install に対応していない。ん、pypi も pydot の URL がまだ dkbza.org(オリジナルのサイト) で Google Code に移行していない。pydot のダウンロードはここから。
# easy_install pydot
Searching for pydot
Reading http://cheeseshop.python.org/pypi/pydot/
Reading http://dkbza.org/pydot.html
Reading http://cheeseshop.python.org/pypi/pydot/0.9.10
No local packages or download links found for pydot
error: Could not find suitable distribution
for Requirement.parse('pydot')
なお、注意点としては、Graphviz のバイナリ (fdp, twopi, neato, dot, circo) があるディレクトリをパスに追加しておくこと。そうしないと使えない。Enthought の場合は、\Python24\Enthought\Graphviz\bin が PATH に追加されているはずなので特に問題なし。もし、使えない場合は PATH をチェック。
実際に使ってみる。とりあえず、まずは動くかどうかを
http://dkbza.org/pydot.htmlと同じようにしてテスト。
import pydot
edges=[(1,2), (1,3), (1,4), (3,4)]
g=pydot.graph_from_edges(edges)
g.write_jpeg('graph_from_edges_dot.jpg', prog='dot')
無事に表示される。では日本語を入れるとどうなるだろうかとやってみる。数字ではなく、文字列を入れるとアルファベットは表示されるが、日本語は文字化けしてしまう。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import pydot
edges=[('root', u'日本語'), ('root', 'Latin'),
('root' , 'English'), ('Latin', 'English')]
g=pydot.graph_from_edges(edges)
g.write_jpeg('graph_from_edges_dot.jpg', prog='dot')
さて、文字化けする原因はどこにあるか。とりあえず Graphviz が日本語が通るか見てみる。Graphviz 簡単な使い方 を見ると、
Windows版のdottyで日本語を表示することはできない(ようだ)が、dotから出力する画像ファイルで日本語を出力することは可能。バージョンはGraphviz2.6。以下そのやりかた。
が書かれていた。一つはdotファイルをUTF-8で書いて、フォントの指定をすること。もう一つは、lefty を改造して Shift_JIS に対応したパッチを当てたものを使う方法が書かれている。UTF-8 でいいので、前者を試すことにする。pydot.py の末尾 class Dot の def create を見てみると、
self.write(tmp_name)
stdin, stdout, stderr = os.popen3(self.progs[prog]+' \
-T'+format+' '+tmp_name, 'b')
と、テンポラリファイルに dot ファイルを書き出しておいて、popen3 で Graphviz のプログラムを呼び出してそのファイルを処理させているようなので、書き出すところでフォントの指定を入れてやればよいかな、と思ったら、各クラスをよく見たら、fontpath とか fontname の attribute を持たせられるようになっているので、そこで指定してやればよいか。で、次のようにしてみる。
g=pydot.graph_from_edges(edges)
g.fontname="arialuni.ttf"
g.fontsize=10
ところが、これでは文字化けのまま。原因を探るために、試しに、dot ファイルをまず手で書いてみる。
graph "g" {
node [fontname="arialuni.ttf", fontsize=10]
"root";
"日本語";
"root" -- "日本語";
"ラテン語";
"root" -- "ラテン語";
"英語";
"root" -- "英語";
"ラテン語" -- "英語";
}
test.dot ファイルに上記を入れておいて実行してみる。
dot -Kneato -Tjpg test.dot -o pydotteset01.jpg
これだと、ちゃんと日本語が表示される。pydot から出力されているファイルを見ると、次のようになっている。node の属性がベタに書き出されていて、ちゃんとリストになっていないからなのかな。
graph G {
fontsize=10;
fontname="arialuni.ttf";
"root";
"日本語";
"root" -- "日本語";
"Latin";
"root" -- "Latin";
"English";
"root" -- "English";
"Latin" -- "English";
}
試しに手書きで、node [fontsize=10, fontname="arialuni.ttf"] と書き換えてやると、日本語が表示できるようになった。ということで node を作って、そこに属性を指定することにした。が、g.add_node でそのノードを追加すると一番最後に追加されてダメなので、pydot.py の class Graph の def add_node の中を書き換えてみるとちゃんと日本語が表示されるようになった。、
- # self.sorted_graph_elements.append(graph_node)
+ if graph_node.name == 'node':
+ self.sorted_graph_elements.insert(0, graph_node)
+ else:
+ self.sorted_graph_elements.append(graph_node)
なんだか強引なやり方だが、とりあえず pydot で日本語が出せた。適当なことをやっているので副作用があるかもしれない。というか、ドキュメントちゃんと読んだら、もっとちゃんとした使い方があるのかもしれないが、面倒なのでとりあえず。あとで Google で探してみるかな。まあ、とりあえず日本語が使えることは確認できたのでよしとする。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import pydot
edges=[(u'私', u'食べる'),
(u'私', u'遊ぶ'),
(u'私' , u'寝る'),
(u'寝る', u'ベッド'),
(u'寝る', u'道端'),
(u'食べる', u'魚'),
(u'食べる', u'米'),
(u'食べる', u'肉'),
(u'遊ぶ', 'Python'),
('Python', 'pydot'),
('pydot', 'pyparse'),
('pydot', 'Graphviz'),
]
n = pydot.Node('node')
n.fontname = "arialuni.ttf"
n.fontsize = 9
n.fontcolor = "blue"
g=pydot.graph_from_edges(edges)
g.add_node(n)
g.write_jpeg('graph_from_edges_dot.jpg', prog='dot')