IronPythonとGLEEライブラリを使って、GraphVizのdotもどきなツールを作ろう
はてブしていたURLを眺め返していたら、以下のブックマークを見つけました。
- Nauman Leghari's Blog : Fun with IronPython & GLEE
http://weblogs.asp.net/nleghari/archive/2007/04/02/fun-with-ironpython-glee.aspx
ふむふむ、IronPythonを使って、Microsoft Researchが提供している GLEE って言う グラフ作成ライブラリを使ってみよう。というBlogのようです。
このBlogを書いた方は、どうもpydot(GraphVizをPythonから呼び出せる)ライブラリを使ってダイアグラムを描画したかったらしいのですが、pydotがIronPythonから呼べないので、GLEEを使ってみたよ。って事みたいです。
で、暫く眺めていて、
- そういや、前に簡単なコールグラフを作成したいなぁと思ってたっけなぁ
- でも、Win版のGraphVizって、日本語扱おうとすると面倒だったよなぁ(今は直っているかも)
- GLEEって、結構簡単にグラフが描けるんだなぁ
- 一々API呼び出すより、GraphVizのdotコマンド風に仕立て上げた方が、自分的需要高そう
- ちょっと作ってみようかしらん
と、思いまして、適当ツールを仕立て上げてみました。
名前は、「GraphVizもどきなGLEEを使ったツール」って事で、gra_glee.py(グラグリー←グリズリー風に読んであげて下さい)って命名しました。
gra_glee が出来る事
簡単に言うと以下の機能を持ってます。
- dot書式風のテキストファイルを読み込んで、グラフを作成します
- 作成したグラフは、プレビューできます(下図参照)
- 他に、直接 画像ファイルに保存も出来ます(バッチコマンド用に)
- dot書式風のテキストファイルは、shift-jis形式をサポートしており、日本語大丈夫ぶぅ。です
例えば、以下のファイルを、
digraph "g" { "Python ファミリー" -> "Python 2.4" -> "Python 2.5"; "Python ファミリー" -> "IronPython 1.0" -> "IronPython 1.1" -> "IronPython 2.0"; "Python 2.4" -> "IronPython 1.0"; "Python 2.5" -> "IronPython 2.0"; "Python ファミリー" [shape=box]; }
以下のように呼び出すと、
$ ipy gra_glee.py -i dot1.txt
以下のプレビューワが起動します。
GLEE1 posted by (C)wacky
以下のように呼び出すと、直接画像ファイルにも落とせます。
$ ipy gra_glee.py -i dot1.txt -o hoge.png -t png
動かす為に必要なもの
動かすには、以下を用意します。
- IronPython 1.1
- Python 2.4 or 2.5(欲しいのは、ライブラリ部)
- GLEEライブラリ
GLEEライブラリは、以下のURLから、Downloadん所から持ってきて下さい。
- GLEE: Graph Layout Engine
http://research.microsoft.com/~levnach/GLEEWebPage.htm
GLEEライブラリをインストールすると、"C:\Program Files\Microsoft Research\GLEE\bin"に、以下のアセンブリモジュールがあるので、gra_glee.pyを動かす場所にコピーして下さい。
次に、以下のコードを、"gra_glee.py"としてファイルに保存してください。
#!/usr/bin/env python # coding: UTF-8 import sys sys.path.append(r"c:\python24\lib") from optparse import OptionParser import shlex import clr clr.AddReferenceByPartialName("System.Drawing") clr.AddReferenceByPartialName("System.Windows.Forms") from System.Windows.Forms import * from System.Drawing import * from System.Drawing.Imaging import * clr.AddReferenceByPartialName("Microsoft.GLEE") clr.AddReferenceByPartialName("Microsoft.GLEE.Drawing") clr.AddReferenceByPartialName("Microsoft.GLEE.GraphViewerGDI") import System import Microsoft """ 名前: GraphvizとGLEEを合わせて、gra_glee.py 機能: 要するに、Graphvizのdotユーティリティの真似をするユーティリティです。 メモ: 描画には、GLEEライブラリを使用しています。 dotファイルの構文解析(もどき)には、shlexを使っています。 本来なら、文法解析しながら行うべき(BNF例もあるしね)なんですけど、テキトーです。 なお、読み込む形式は、あくまでdot構文もどきです。 参考にしたもの: Nauman Leghari's Blog : Fun with IronPython & GLEE http://weblogs.asp.net/nleghari/archive/2007/04/02/fun-with-ironpython-glee.aspx GLEE: Graph Layout Engine http://research.microsoft.com/~levnach/GLEEWebPage.htm dot ユーザーズガイド日本語訳 http://www.cbrc.jp/~tominaga/translations/graphviz/dotguide.pdf The shlex module ::: www.effbot.org http://effbot.org/librarybook/shlex.htm """ # color名からカラーパターンを求める連想配列 name2color = { 'red': Microsoft.Glee.Drawing.Color.Red, 'green': Microsoft.Glee.Drawing.Color.Green, 'blue': Microsoft.Glee.Drawing.Color.Blue, 'black': Microsoft.Glee.Drawing.Color.Black, } # shape名からボックス形状を求める連想配列 name2shape = { 'box': Microsoft.Glee.Drawing.Shape.Box, 'triangle': Microsoft.Glee.Drawing.Shape.Triangle, 'circle': Microsoft.Glee.Drawing.Shape.Circle, } # format名から保存形式を求める連想配列 name2format = { 'gif': System.Drawing.Imaging.ImageFormat.Gif, 'png': System.Drawing.Imaging.ImageFormat.Png, 'jpg': System.Drawing.Imaging.ImageFormat.Jpeg, 'bmp': System.Drawing.Imaging.ImageFormat.Bmp } def graph_attr(graph, name, attr_name, val): """アトリビュートの処理""" o = graph.FindNode(name) if attr_name == 'color': o.Attr.Fillcolor = name2color[val] elif attr_name == 'shape': o.Attr.Shape = name2shape[val] elif attr_name == 'weight': o.Attr.LineWidth = int(val) elif attr_name == 'label': o.Attr.Label = val def graph_stmt(graph, yylex): """描画処理本体 """ name = attr_name = "" attr_mode = False while 1: token = yylex.get_token().strip('"') if token == "}": break # 終了チェック if not token: break # 異常終了チェック print repr(token) if token == ";": # ステートメントの終わり name = "" elif token == "-": # "->"を見つけた assert yylex.get_token().strip('"') == '>' token = yylex.get_token().strip('"') graph.AddEdge(name, token) print name, token elif token == '[': # '[' アトリビュート設定の開始 assert attr_mode == False attr_mode = True elif token == ']': # ']' アトリビュート設定の終了 assert attr_mode == True attr_mode = False elif token == '=': # '=' 1つのアトリビュート設定 assert attr_mode == True assert attr_name != "" token = yylex.get_token().strip('"') graph_attr(graph, name, attr_name, token) attr_name = "" # ',' 1つのアトリビュートの区切り elif token == ',': assert attr_mode == True assert attr_name != "" attr_name = "" if attr_mode == False: name = token else: attr_name = token assert token == "}", "'}'で終わってないですよ?" def make_graph(graph, yylex): """構文解析しつつ、グラフを作成する - graph Microsoft.Glee.Drawing.Graphが実体 - yylex shlexが実体 """ print dir(graph) # キーワードは、'digraph'かな? assert yylex.get_token() == "digraph", "最初のワードはdigraphを期待しています" # 次は、id名 graph.Label = yylex.get_token().strip('"') print "id名:", graph.Label # 次は、'{' assert yylex.get_token() == "{" # 処理本体('}'を見つけると返ってくる) graph_stmt(graph, yylex) def preview(in_file): """プレビューウィンドウを開いて確認できる""" # フォームの作成 f = Form() view = Microsoft.Glee.GraphViewerGdi.GViewer() graph = Microsoft.Glee.Drawing.Graph("graph") sys.setdefaultencoding("cp932") lex = shlex.shlex(file(in_file, "r")) make_graph(graph, lex) view.Graph = graph; f.SuspendLayout() view.Dock = DockStyle.Fill f.Controls.Add(view) f.ResumeLayout() f.ShowDialog() # ダイアログを開く def direct_save(in_file, out_file, out_file_type): """直接画像ファイルに保存してしまう""" graph = Microsoft.Glee.Drawing.Graph("graph") sys.setdefaultencoding("cp932") lex = shlex.shlex(file(in_file, "r")) make_graph(graph, lex) renderer = Microsoft.Glee.GraphViewerGdi.GraphRenderer(graph) renderer.CalculateLayout() bmp = Bitmap(graph.Width, graph.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb) renderer.Render(bmp) bmp.Save(out_file, name2format[out_file_type]) def main(): # コマンド解析 usage = "GLEEを使ったユーティリティ(Graphvizのdotコマンド風味)\n\nusage: %prog [options]" parser = OptionParser(usage) parser.add_option("-i", "--in", dest="in_file", help="input dot style file(need sjis encode)", metavar="FILENAME", action="store", type="string") parser.add_option("-o", "--out", dest="out_file", help="output graphic file", metavar="FILENAME", action="store", type="string") parser.add_option("-t", "--type", dest="save_type", help="output file format", action="store", type="string", default="png") (options, args) = parser.parse_args() print options, args if options.out_file == None: # チェック用のウィンドウを開く preview(options.in_file) else: # 直接画像ファイルに落とす direct_save(options.in_file, options.out_file, options.save_type) if __name__=="__main__": main()
これで準備完了です。
使い方
コマンドラインヘルプは、"-h"をコマンドラインオプションに渡せばOKです。
以下のようなメッセージが出ます。
$ ipy gra_glee.py -h usage: GLEEを使ったユーティリティ(Graphvizのdotコマンド風味) usage: gra_glee.py [options] options: -h, --help show this help message and exit -i FILENAME, --in=FILENAME input dot style file(need sjis encode) -o FILENAME, --out=FILENAME output graphic file -t SAVE_TYPE, --type=SAVE_TYPE output file format
入力ファイルに指定できるのは、あくまで GraphVizのdotファイル風味です。
コードを見れば、どこまで対応できるのか一目瞭然ですので、そこいら辺は割愛させてもらいます。
幾つかサンプルを示します。
digraph "g" { "Python ファミリー" -> "Python 2.4" -> "Python 2.5"; "Python ファミリー" -> "IronPython 1.0" -> "IronPython 1.1" -> "IronPython 2.0"; "Python 2.4" -> "IronPython 1.0"; "Python 2.5" -> "IronPython 2.0"; "Python ファミリー" [shape=box]; }
digraph "g" { "A" -> "B" ; "B" -> "C" [weight=2,label="ふが"] ; "C" -> "A" [color=red,shape=box] ; "A" -> "ほげ" -> "E"; }