スポンサーリンク

WSH・JScriptから Graphviz を利用するためのクラス (関係グラフを描画して、複雑な構造のデータを可視化する方法)



JScriptからGraphvizを利用して、関係グラフを手早く描画する。


下記のような使い方ができる。

var dr = new DotRecorder( "my_graph" );

// ノードを登録
dr.node( "1a", "口" );
dr.node( "2a", "日" );
dr.node( "2b", "回" );
dr.node( "3a", "目" );
dr.node( "3b", "品" );
dr.node( "4a", "田" );

// 関係を登録
dr.rel( "1a", "2a" );
dr.rel( "2a", "3a" );
dr.rel( "3a", "4a" );
dr.rel( "1a", "2b" );
dr.rel( "2b", "3b" );
dr.rel( "3b", "4a" );

// グラフ作成
dr.dot( "hoge.dot" );
dr.png( "hoge.png" );

すると、hoge.pngというファイル名で、右上のようなグラフ画像が出力・保存される。


このDotRecorderというオブジェクトのソースコードは下記の通り。


※こちらからダウンロードできます。
http://www.name-of-this-site.org/codi...

/*
	Graphvizのdotを作成するクラス

	説明:
		・ノードのラベルに日本語が使えます。

	注意:
		・事前にGraphvizをインストールし、binにPATHを通しておくこと。
			Graphviz  http://www.graphviz.org/
		・このjsファイルは、ダブルクリックやbat呼び出しによって実行すること。
			(それ以外の起動方法では、カレントディレクトリが変わってしまう場合があります)

	更新:
		09/01/23 : 初版公開
		09/01/28 : 同ランク登録と,エッジのラベル登録を追加

*/

var DotRecorder = function( graph_name ){
	// 初期化
	this.graph_name    = graph_name;
	this.arr_node      = new Array();
	this.arr_rel       = new Array();
	this.arr_same_rank = new Array();
};
DotRecorder.prototype = {

	// グラフ名
	graph_name : null,

	// ファイル名
	dot_path : null,

	// 「ノードのIDと名前の対応」の配列
	arr_node : null,
	
	// 「ノード同士の関係」の配列
	arr_rel : null,
	
	// 「等ランクなノード」の配列
	arr_same_rank : null,

	// ノードを登録します
	node : function( t_id, t_name ){
		this.arr_node.push(
			{
				node_id   : t_id,
				node_name : t_name
			}
		);
	},
	
	// 関係を登録します
	rel : function( t_from, t_to ){
		var t_label = ( arguments.length > 2 )
			? arguments[2]
			: ""
		;
		this.arr_rel.push(
			{
				from_id : t_from,
				to_id   : t_to,
				label   : t_label
			}
		);
	},
	
	// 同ランクに登録します。引数個数は可変長。
	same_rank : function(){
		var temp_arr = new Array();
		for( var i = 0, len = arguments.length; i < len; i ++ )
		{
			// 引数のIDをpush
			temp_arr.push( arguments[i] );
		}
		// 配列をpush
		this.arr_same_rank.push( temp_arr );
	},
	
	// dotファイルを出力します
	dot : function( file_path ){
		// 新規ファイル作成
			// utf-8で書き込み
			// http://passing.breeze.cc/mt/archives/2008/05/jscript-utf8.html
		var adTypeText = 2;
		var adWriteChar = 0;
		var adWriteLine = 1;
		var adSaveCreateOverWrite = 2;

		var stw = new ActiveXObject("ADODB.Stream");
		stw.Type = adTypeText;
		stw.charset = "utf-8";
		stw.Open();
		
		// グラフ作成開始
		var sp = "  ";
		stw.WriteText(
			"digraph " 
			+ this.graph_name 
			+ "{\r\n"
			+ sp
			+ "node [fontname=\"MS GOTHIC\"];\r\n"
			+ "edge [fontname=\"MS GOTHIC\"];"
			,
			adWriteLine
		);

		// ノード登録
		for( var i = 0, len = this.arr_node.length; i < len; i ++ )
		{
			var node_id   = this.arr_node[i].node_id;
			var node_name = this.arr_node[i].node_name;
			stw.WriteText(
				sp 
				+ "\"" 
				+ node_id 
				+ "\" [label=\"" 
				+ node_name 
				+ "\"];"
				,
				adWriteLine
			);
		}
		
		// ランク登録
		for( var i = 0, len = this.arr_same_rank.length; i < len; i ++ )
		{
			stw.WriteText( 
				sp 
				+ "{rank=same;"
				,
				adWriteChar 
			);
			var arr_temp = this.arr_same_rank[ i ];
			for( var j = 0, len2 = arr_temp.length; j < len2; j ++ )
			{
				stw.WriteText( 
					"\""
					+ arr_temp[j]
					+ "\""
					+ ((j == len2 - 1)
					? ""
					: " "
					)
					, 
					adWriteChar
				);
			}
			stw.WriteText( "};", adWriteLine );
		}
		
		// 関係登録
		for( var i = 0, len = this.arr_rel.length; i < len; i ++ )
		{
			var from_id = this.arr_rel[i].from_id;
			var to_id   = this.arr_rel[i].to_id;
			var label   = this.arr_rel[i].label;
			stw.WriteText(
				sp 
				+ "\"" 
				+ from_id 
				+ "\" -> \"" 
				+ to_id 
				+ "\""
				+ " [label=\""
				+ label
				+ "\"]"
				+ ";"
				,
				adWriteLine
			);
		}

		// グラフ作成終了
		stw.WriteText( "}", adWriteLine );
		stw.SaveToFile( file_path, adSaveCreateOverWrite );
		stw.Close();
		
		// 無事にファイルが作れたのでファイルパスを登録
		this.dot_path = file_path;
	},
	
	// dotからpngを作ります
	png : function( png_path ){
		// dotは作成済みか
		if( this.dot_path.length > 0 )
		{
			var cmd = "dot -Tpng -o "
				+ png_path
				+ " "
				+ this.dot_path
			;
			//WScript.Echo( cmd );
			var ws = WScript.CreateObject("WScript.Shell");
			ws.Run( cmd );
			//dot -Tpng -o hoge.png hoge.dot
		}
		else
		{
			WScript.Echo("dotファイルが未作成です。");
		}
	}
};


// クラスのテスト

/*

var dr = new DotRecorder( "my_graph" );

// ノードを登録
dr.node( "1a", "口" );
dr.node( "2a", "日" );
dr.node( "2b", "回" );
dr.node( "3a", "目" );
dr.node( "3b", "品" );
dr.node( "4a", "田" );

// 関係を登録
dr.rel( "1a", "2a" );
dr.rel( "2a", "3a" );
dr.rel( "3a", "4a" );
dr.rel( "1a", "2b" );
dr.rel( "2b", "3b" );
dr.rel( "3b", "4a" );

// グラフ作成
dr.dot( "hoge.dot" );
dr.png( "hoge.png" );

*/

コード中に併記したように、Graphvizのインストールを済ませ、binフォルダをPATHに登録しておくこと。(dotを呼び出すので)

実行の際には、上記を 適当な名前.js で保存してダブルクリック。


中間生成物として、hoge.dotが出力される。

digraph my_graph{
  node [fontname="MS GOTHIC"];
  "a" [label="口"];
  "b" [label="日"];
  "c" [label="品"];
  "d" [label="田"];
  "a" -> "b";
  "b" -> "c";
  "c" -> "d";
  "d" -> "a";
}

複雑な構造のデータを視覚化する際に役立つかもしれない。


バッチ処理と言うと,「大量の物を一括して何かする」という操作である事が多い。


その操作の最中に,上記のクラスの処理をちょっとだけ混ぜてみれば,バッチ処理結果のレポートが簡単に作れる。という具合だ。

補足

日本語で有向グラフを描画する方法については,下記を参照。

http://cassa.at.webry.info/200711/art...

  • .dotをUTF8で書く
  • 日本語フォントを指定する

このためにはUTF8でファイル入出力を行なう必要があり,下記を参考にさせて頂いた。

jscriptで utf8なファイル出力
http://passing.breeze.cc/mt/archives/...