谷本 心 in せろ部屋 このページをアンテナに追加 RSSフィード

2009-02-16

[]JGraph Xについて簡単にメモしておく。

かっこいいっていうのも、また罪だなと、そんな風に思う今日この頃です。


さて、JGraph Xの話です。

日本語ドキュメントはおろか、あまり英語ドキュメントもないようなので、

簡単に使い方とかをまとめておきます。


JGraph Xを使うときは、

まず「mxGraph」「mxGraphComponent」「mxGraphModel」の

3クラスについて理解することが必須です。


com.mxgraph.view.mxGraph

JGraph Xのメインクラス。

ノードやエッジの追加は、基本的にこのクラスを経由して行う。


com.mxgraph.swing.mxGraphComponent

extends JScrollPaneなクラス。要するにSwingとJGraphXのブリッジ的なクラス。

new mxGraphComponent(mxGraph)で、Swingコンポーネントを作ることができる。


com.mxgraph.model.mxGraphModel

mxGraphのモデル管理や、トランザクション管理、イベントハンドラの設定などを行う。

ただ、API的には結構「これってmxGraphにあるべきじゃん?」と思うものもあるので

今後使いながら、使用感を見ていく必要あり。


これらのクラスの実際の使い方は、

サンプルのHelloWorldを見ればすぐに分かるので、割愛します。


後は、少しハマりそうな所について書いておきます。


背景を白くする方法

JGraph Xで作ったグラフは背景が灰色なので、以下のコードで白にします。

mxGraphComponent component = new mxGraphComponent(graph);
component.getViewport().setBackground(Color.WHITE);

グラフ全体を縮小する方法

拡大・縮小などもスーパーファミコン並みに簡単にできます。

mxGraphComponent component = new mxGraphComponent(graph);
component.zoomOut();

ノードやエッジのスタイルを設定する方法

昨日イケてないと言っていたスタイルは、mxUtilsを使うと簡単に設定できます。

String style = mxUtils.setStyle(null, mxConstants.STYLE_ENDARROW, mxConstants.ARROW_OPEN);
style = mxUtils.setStyle(style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM);
style = mxUtils.setStyle(style, mxConstants.STYLE_DASHED, "1");

ただイケてないことに、スタイルは文字列として表現されており、

mxUtils.setStyleの中ではString同士で文字列結合を行っているのみです。


何度も呼ばれると無駄なオブジェクトができて仕方ないでしょうから、

デフォルトのスタイル設定は、staticイニシャライザで行えば良いかなと思います。


若干挙動が微妙だなと思う所もあるのですが、JGraph X、なかなかイケてます。

2009-02-15

[]シーケンス図をJGraph X化してみた。

JGraphで描いていたシーケンス図を、JGraph Xで描き直してみた。

何よりもまず、できあがるシーケンスがかっこよくなったし、

ノードを動かした時のパフォーマンスも幾分か向上したみたい。

f:id:cero-t:20090216034806p:image


JGraphを使ってた時に感じていた、「これ、不要だろ」っていうAPI呼び出し

(例えばgraph.getGraphLayoutCache().insertとか)を使わなくて良くなったり、

ノードを作る時にnewを呼ばなくて良くなったりしたので、コードがすっきりした。


ただ、スタイルの扱いがちょっとイケてなくなった(APIがよく分からない)のが残念。

あと、長すぎるノードの文字列を自動的に省略をする方法も良く分からない。

この辺りはただの勉強不足かも知れないので、もうちょっと調べてみるけど。


そういえば、やっぱりGenericsとかは使えず、

むしろノードやエッジをjava.lang.Objectとして扱うことが逆に増えたんだけど

この辺りは「Swingでグラフを描きたい」っていう要望があるような現場は

そもそもJava1.4だろっていう事を考えれば、妥当なのかも。


とりあえず、ソースを貼っとく。

package cerot.blight.sequence;

import java.awt.Color;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cerot.blight.sequence.entity.Connector;
import cerot.blight.sequence.entity.ConnectorType;
import cerot.blight.sequence.entity.EndPoint;
import cerot.blight.sequence.entity.Node;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;

public class SequenceCreator {
	/** 呼び出し線のスタイル */
	private static final String STYLE_CALL = mxConstants.STYLE_ENDARROW + "="
			+ mxConstants.ARROW_OPEN + ";" + mxConstants.STYLE_VERTICAL_ALIGN
			+ "=" + mxConstants.ALIGN_BOTTOM + ";";

	/** 戻り線のスタイル */
	private static final String STYLE_RETURN = mxConstants.STYLE_DASHED + "=1;"
			+ mxConstants.STYLE_ENDARROW + "=" + mxConstants.ARROW_OPEN + ";"
			+ mxConstants.STYLE_VERTICAL_ALIGN + "=" + mxConstants.ALIGN_BOTTOM
			+ ";";

	/** 生存線のスタイル */
	private static final String STYLE_LIFELINE = mxConstants.STYLE_ENDARROW
			+ "=" + mxConstants.NONE;

	/** ノードの幅 */
	private int nodeWidth = 80;

	/** ノードの高さ */
	private int nodeHeight = 20;

	/** ノードの間隔 */
	private int nodeInterval = 10;

	/** 結線の間隔 */
	private int edgeInterval = 25;

	private mxGraph graph;

	/** ノードセルのマップ */
	private Map<Node, Object> nodeMap = new HashMap<Node, Object>();

	/** 結線の数 */
	private int edgeCount;

	/**
	 * コンストラクタ。モデルの変換を行います。
	 * @param connectorList 結線のリスト
	 */
	public SequenceCreator(List<Connector> connectorList) {
		graph = new mxGraph();

		for (Connector connector : connectorList) {
			createConnectorCell(connector);
		}

		// 生存線の作成
		Object parent = this.graph.getDefaultParent();
		for (Object nodeCell : this.nodeMap.values()) {
			Object endCell = createEndPointCell(nodeCell);
			graph.insertEdge(parent, null, null, nodeCell, endCell,
					STYLE_LIFELINE);
		}
	}

	/**
	 * グラフの描画を行います。
	 */
	public mxGraphComponent createGraphComponent() {
		mxGraphComponent component = new mxGraphComponent(this.graph);
		component.getViewport().setBackground(Color.WHITE);
		return component;
	}

	private void createConnectorCell(Connector connector) {
		EndPoint from = connector.getFrom();
		EndPoint to = connector.getTo();

		Object fromNodeCell = findParentNodeCell(from);
		Object fromCell = createEndPointCell(fromNodeCell);

		Object toNodeCell = findParentNodeCell(to);
		Object toCell = createEndPointCell(toNodeCell);

		Object parent = this.graph.getDefaultParent();

		if (connector.getConnectorType() == ConnectorType.METHOD_RETURN) {
			graph.insertEdge(parent, null, connector.getName(), fromCell,
					toCell, STYLE_RETURN);
		} else {
			graph.insertEdge(parent, null, connector.getName(), fromCell,
					toCell, STYLE_CALL);
		}

		this.edgeCount++;
	}

	private Object findParentNodeCell(EndPoint endPoint) {
		Node node = endPoint.getParentNode();
		Object result = findNodeCell(node);

		return result;
	}

	private Object createEndPointCell(Object nodeCell) {
		// X座標 = ノードの中央値
		mxRectangle rectangle = this.graph.getCellBounds(nodeCell);
		double x = rectangle.getCenterX();

		// Y座標 = 上端からの余白 + ノードの高さ + (結線の数 + 1) * 結線の間隔
		int y = this.nodeInterval + this.nodeHeight + (this.edgeCount + 1)
				* edgeInterval;

		Object parent = this.graph.getDefaultParent();
		Object result = this.graph.insertVertex(parent, null, null, x, y, 0, 0);

		return result;
	}

	private Object findNodeCell(Node node) {
		Object result = this.nodeMap.get(node);

		if (result == null) {
			Object parent = this.graph.getDefaultParent();

			// X座標 = 左端からの余白 + ノード数 * (ノード幅 + ノード間隔)
			int x = this.nodeInterval + this.nodeMap.size()
					* (this.nodeWidth + this.nodeInterval);

			// Y座標 = 上端からの余白
			int y = this.nodeInterval;

			result = this.graph.insertVertex(parent, null, node.getName(), x,
					y, nodeWidth, nodeHeight);

			this.nodeMap.put(node, result);
		}

		return result;
	}
}

興味がある人は、以前のエントリで書いたJGraphのソースと比べてみると良いかも。

JGraphでシーケンス図を描いてみた。 - 谷本 心 in せろ部屋


SourceForgeの方にもコミットしといたので、Diffで見たほうが見やすいかも。

http://svn.sourceforge.jp/view/b-light/trunk/B-Light/src/main/java/cerot/blight/sequence/SequenceCreator.java?root=b-light&r1=2&r2=9

2009-02-12

[][]BTraceとJGraphで簡単シーケンス図作成ツール

せっかくの祝日を活かし、シーケンス図作成ツールを作っちゃいました。

論より証拠、スクリーンキャプチャで見ていきましょう。


起動直後の様子。ただのSwingアプリですね。

f:id:cero-t:20090213003036p:image

ちなみにこの画面はNetBeansGUIビルダー(Matisse)で作りました。サクサクです。


ドロップダウンリストを開くと、Javaプロセス一覧が選択できます。

f:id:cero-t:20090213003035p:image

今回はTomcatプロセスを解析しましょうか。


解析対象クラスを正規表現で記述します。tutorialパッケージ以下の全クラスにしましょう。

f:id:cero-t:20090213003034p:image

記述したら、Startを押します。


これでBTraceによるトレースが行われるので、解析対象のアプリケーションをしばらく動かします。

f:id:cero-t:20090213003033p:image

動かし終わったら、Stopを押します。


すると、、、ババーン!! キャー! シーケンス図が描画されます。

f:id:cero-t:20090213003032p:image

このシーケンス図はJGraphを使って作成しました。


例に示したのはSAStrutsのサンプルです。

結構やりとりがあるように見えて、

AOPの呼び出しがゴリゴリと動いてるだけだったりするんですけどね。


このツールは、総ライン数が1KL、実ライン数が600L程度の小ささです。

とりあえず動くというレベルですが、BTraceやJGraphのおかげでサクッと作ることが出来ました。

2009-02-11

[]JGraph XとmxGraphはコアモデルを共有

JGraph Xのサイト (http://www.jgraph.com/jgraphx.html) をちょっと見ていたら

なんかmxGraphへのリンク (http://www.mxgraph.com/) があった。


mxGraphはブラウザでグラフを描くことができるコンポーネント

いくつかのサイトで取り上げられていたから、JGraph Xよりむしろ有名かも。


で、JGraph XとmxGraphはコアモデルのAPIが一緒だから、

JGraph Xで作ったアプリケーションをmxGraphに移植しやすいとのこと。


でも、ここまで力を入れて作ったライブラリコンポーネント)って

なんか有償化されちゃいそうだよね。

2009-02-10

[]JGraphでシーケンス図を描いてみた。

JGraphを使って、簡単なシーケンス図を描いてみました。

まず実行結果から。

f:id:cero-t:20090210233338p:image

見ての通りシーケンス図です。

Returnが点線じゃないとかのツッコミは置いといて。


では、簡単にソースを見ていきましょう。

今回は、BTraceで取ったログからシーケンス図を作成するという想定なので

中間モデルを定義して、そこからJGraphのクラスを作っていきます。


まずは、ノード(要するにクラス)を示すモデル。

public class Node {
	/** ノード名 */
	private String name;

	// setter/getterは省略
}

続いて、メソッドのIN/OUTの端点(コネクタの両端)のモデル

public class EndPoint {
	/** 所属するノード */
	private Node node;

	// setter/getterは省略
}

モデルの最後です。メソッド呼び出しを示す矢印のモデル

public class Connector {
	/** 結線の名称 */
	private String name;

	/** 開始端点 */
	private EndPoint from;

	/** 終了端点 */
	private EndPoint to;

	// setter/getterは省略
}

親から子を参照するのではなく、子から親を参照する形式なのは若干RDBMS中毒のような気もしますね。

まぁJGraph的に親から子を参照する方が良かったら、そう修正すれば良いかなぐらいで、

あんまり深くは考えていません。


続いて、このモデルからシーケンス図を描くクラス。今回のメインソースです。

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphModel;

public class GraphCreator {
	/** ノードの幅 */
	private int nodeWidth = 80;

	/** ノードの高さ */
	private int nodeHeight = 20;

	/** ノードの間隔 */
	private int nodeInterval = 10;

	/** 結線の間隔 */
	private int edgeInterval = 25;

	/** 端点セルのマップ */
	private Map<EndPoint, DefaultGraphCell> endPointMap = new HashMap<EndPoint, DefaultGraphCell>();

	/** ノードセルのマップ */
	private Map<Node, DefaultGraphCell> nodeMap = new HashMap<Node, DefaultGraphCell>();

	/** 全オブジェクトのリスト */
	List<DefaultGraphCell> cellList = new ArrayList<DefaultGraphCell>();

	/** 結線の数 */
	private int edgeCount;

	/**
	 * コンストラクタ。モデルの変換を行います。
	 * @param connectorList 結線のリスト
	 */
	public GraphCreator(List<Connector> connectorList) {
		for (Connector connector : connectorList) {
			createConnectorCell(connector);
		}

		// 活性線の作成
		for (DefaultGraphCell nodeCell : this.nodeMap.values()) {
			DefaultGraphCell endCell = createEndPointCell(nodeCell);
			this.cellList.add(endCell);
			endCell.addPort();

			DefaultEdge edge = new DefaultEdge();
			edge.setSource(nodeCell.getChildAt(0));
			edge.setTarget(endCell.getChildAt(0));
			this.cellList.add(edge);
		}
	}

	/**
	 * グラフの描画を行います。
	 */
	public JGraph createGraph() {
		GraphModel model = new DefaultGraphModel();
		JGraph graph = new JGraph(model);
		graph.setJumpToDefaultPort(true);
		graph.getGraphLayoutCache().insert(this.cellList.toArray());

		return graph;
	}

	private void createConnectorCell(Connector connector) {
		EndPoint from = connector.getFrom();
		DefaultGraphCell fromCell = findEndPointCell(from);

		EndPoint to = connector.getTo();
		DefaultGraphCell toCell = findEndPointCell(to);

		DefaultEdge edge = new DefaultEdge(connector.getName());
		edge.setSource(fromCell.getChildAt(0));
		edge.setTarget(toCell.getChildAt(0));
		GraphConstants.setLineEnd(edge.getAttributes(),
				GraphConstants.ARROW_CLASSIC);
		GraphConstants.setLabelAlongEdge(edge.getAttributes(), true);

		this.cellList.add(edge);
		this.edgeCount++;
	}

	private DefaultGraphCell findEndPointCell(EndPoint endPoint) {
		DefaultGraphCell cell = this.endPointMap.get(endPoint);

		if (cell == null) {
			Node node = endPoint.getNode();
			DefaultGraphCell nodeCell = findNodeCell(node);
			cell = createEndPointCell(nodeCell);

			this.endPointMap.put(endPoint, cell);
			this.cellList.add(cell);
		}

		return cell;
	}

	private DefaultGraphCell createEndPointCell(DefaultGraphCell nodeCell) {
		DefaultGraphCell cell = new DefaultGraphCell();

		// X座標 = ノードの中央値
		Rectangle2D rectangle = (Rectangle2D) nodeCell.getAttributes().get(
				GraphConstants.BOUNDS);
		double x = rectangle.getCenterX();

		// Y座標 = 上端からの余白 + ノードの高さ + (結線の数 + 1) * 結線の間隔
		int y = this.nodeInterval + this.nodeHeight + (this.edgeCount + 1)
				* edgeInterval;

		GraphConstants.setBounds(cell.getAttributes(), new Rectangle2D.Double(
				x, y, 0, 0));
		cell.addPort();

		return cell;
	}

	private DefaultGraphCell findNodeCell(Node node) {
		DefaultGraphCell cell = this.nodeMap.get(node);

		if (cell == null) {
			cell = new DefaultGraphCell(node.getName());

			// X座標 = 左端からの余白 + ノード数 * (ノード幅 + ノード間隔)
			int x = this.nodeInterval + this.nodeMap.size()
					* (this.nodeWidth + this.nodeInterval);

			// Y座標 = 上端からの余白
			int y = this.nodeInterval;

			GraphConstants.setBounds(cell.getAttributes(),
					new Rectangle2D.Double(x, y, nodeWidth, nodeHeight));
			GraphConstants.setBorderColor(cell.getAttributes(), Color.black);

			this.nodeMap.put(node, cell);
			this.cellList.add(cell);
			cell.addPort();
		}

		return cell;
	}
}

JGraphの使い方をあまりよく理解しようとしてないので、親子関係とかは適当です。

とにかく、シーケンス図さえ描ければ良いと思ってゴリゴリ書きました。


さて、最後に、上のGraphCreatorクラスを利用するサンプルクラスです。

import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

import org.jgraph.JGraph;

public class Sample {
	public static void main(String[] args) {
		Node node1 = new Node();
		node1.setName("Class1");

		Node node2 = new Node();
		node2.setName("Class2");

		Node node3 = new Node();
		node3.setName("Class3");

		Connector connector1 = new Connector();
		connector1.setName("method1");

		EndPoint endPoint1From = new EndPoint();
		endPoint1From.setParentNode(node1);

		EndPoint endPoint1To = new EndPoint();
		endPoint1To.setParentNode(node2);

		connector1.setFrom(endPoint1From);
		connector1.setTo(endPoint1To);

		Connector connector2 = new Connector();
		connector2.setName("method2");

		EndPoint endPoint2From = new EndPoint();
		endPoint2From.setParentNode(node2);

		EndPoint endPoint2To = new EndPoint();
		endPoint2To.setParentNode(node3);

		connector2.setFrom(endPoint2From);
		connector2.setTo(endPoint2To);

		Connector connector3 = new Connector();
		connector3.setName("return2");

		EndPoint endPoint3From = new EndPoint();
		endPoint3From.setParentNode(node3);

		EndPoint endPoint3To = new EndPoint();
		endPoint3To.setParentNode(node2);

		connector3.setFrom(endPoint3From);
		connector3.setTo(endPoint3To);

		Connector connector4 = new Connector();
		connector4.setName("return1");

		EndPoint endPoint4From = new EndPoint();
		endPoint4From.setParentNode(node2);

		EndPoint endPoint4To = new EndPoint();
		endPoint4To.setParentNode(node1);

		connector4.setFrom(endPoint4From);
		connector4.setTo(endPoint4To);

		List<Connector> connectorList = new ArrayList<Connector>();
		connectorList.add(connector1);
		connectorList.add(connector2);
		connectorList.add(connector3);
		connectorList.add(connector4);

		GraphCreator graphCreator = new GraphCreator(connectorList);
		JGraph graph = graphCreator.createGraph();

		JFrame frame = new JFrame();
		frame.getContentPane().add(new JScrollPane(graph));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}
}

特にボリュームも大きくなく、難しいところもありません。

これぐらい簡単なソースでシーケンス図が描けたのは、まさにJGraphのおかげですね。

[]なぜJGraphなのか。

JGraphはAPIが今風じゃない(Genericsも使えない)のが若干イケてないのですが、

その辺りは後継である「JGraph X」にて解決するみたいです。

JGraph Xはバージョンが、まだ0.15.1.1と微妙な感じだったので、今回は敬遠しました。


ちなみにシーケンス図のようなグラフを描くためのライブラリと言えば、

EclipseのGEFの方が、きっと有名でしょう。


ただ今回は、Swingで構築するため、というかVisualVMプラグインを目指しているため

GEFではなくJGraphを使いました。さすがに、GEFでVisualVMプラグイン

っていうか実質NetBeansプラグインは作れないですよね。


まぁGEFはGEFで、Eclipseプラグインとなるだけでなく、

2〜3年もしてSWTブラウザベースで動くようになれば、

ブラウザで動くグラフ」を描ける貴重なライブラリになるかも知れませんけどね。


まぁ、そんなのはまだまだ先の話なので

ひとまず、VisualVMプラグインを目指して、JGraphを使い始めました。