簡単な自動レイアウト機能を実装してみましょう。最も簡単なグラフのレイアウトはノードをランダムな位置に配置するものです。そのプログラムは下のようになります。
1: import java.awt.Dimension; 2: import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 3: import edu.uci.ics.jung.graph.Graph; 4: public class RandomLayout<V,E> extends AbstractLayout<V,E> { 5: public RandomLayout(Graph<V,E> g) { 6: super(g); 7: } 8: public RandomLayout(Graph<V,E> g, Dimension d) { 9: super(g); 10: setSize(d); 11: } 12: @Override 13: public void initialize() { 14: layoutNodes(); 15: } 16: @Override 17: public void reset() { 18: } 19: private void layoutNodes() { 20: int clearance = 20; 21: Dimension d = getSize(); 22: int width = d.width - clearance * 2; 23: int height = d.height - clearance * 2; 24: for (V v : getGraph().getVertices()) { 25: if (isLocked(v)) continue; 26: double x = Math.random() * width + clearance; 27: double y = Math.random() * height + clearance; 28: transform(v).setLocation(x, y); 29: } 30: } 31: }
自動レイアウト機能を実装するには、Layout<V,E>インタフェースを実装する必要があります。Layout<V,E>インタフェースを部分的に実装したAbstractLayout<V,E>クラスがあるので、それを継承すると便利です。AbstractLayout<V,E>クラスを継承する場合には、initialize()メソッドとreset()メソッドを実装します。initialize()メソッドの本体はlayoutNodes()メソッドで、描画領域のサイズを獲得し、その範囲内に納まるようにノードの位置をランダムに決めているだけです。reset()メソッドも実装していますが、本体は空です(*1)。
(*1) reset()メソッドはその名の通り何かをリセットするために使われるのだと思いますが、APIの記述がありません。
実装した自動レイアウト機能を利用するには下のようにします。このプログラムはSample3.javaと基本的な構造は同じです。ただし、グラフを作成する部分をcreateGraph()メソッドに切り出し、変数layoutにStaticLayout<MyNode,MyEdge>クラスのインスタンスを代入していたところを、上で定義したRandomLayout<MyNode,MyEdge>クラスのインスタンスを代入しています。
1: import java.awt.Dimension; 2: import javax.swing.JFrame; 3: import edu.uci.ics.jung.algorithms.layout.Layout; 4: import edu.uci.ics.jung.graph.Graph; 5: import edu.uci.ics.jung.graph.UndirectedSparseGraph; 6: import edu.uci.ics.jung.visualization.BasicVisualizationServer; 7: public class Sample3b { 8: public static void main(String[] args) { 9: Dimension viewArea = new Dimension(300, 300); 10: Graph<MyNode,MyEdge> graph = createGraph(); 11: Layout<MyNode,MyEdge> layout = new RandomLayout<MyNode,MyEdge>(graph, viewArea); 12: BasicVisualizationServer<MyNode,MyEdge> panel = new BasicVisualizationServer<MyNode,MyEdge>(layout, viewArea); 13: JFrame frame = new JFrame("Graph View: Random Layout"); 14: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 15: frame.getContentPane().add(panel); 16: frame.pack(); 17: frame.setVisible(true); 18: } 19: private static Graph<MyNode,MyEdge> createGraph() { 20: Graph<MyNode,MyEdge> g = new UndirectedSparseGraph<MyNode,MyEdge>(); 21: MyNode n1 = new MyNode("n1"); 22: MyNode n2 = new MyNode("n2"); 23: MyNode n3 = new MyNode("n3"); 24: MyNode n4 = new MyNode("n4"); 25: MyNode n5 = new MyNode("n5"); 26: MyNode n6 = new MyNode("n6"); 27: MyNode n7 = new MyNode("n7"); 28: MyNode n8 = new MyNode("n8"); 29: g.addEdge(new MyEdge("e1"), n1, n2); 30: g.addEdge(new MyEdge("e2"), n2, n3); 31: g.addEdge(new MyEdge("e3"), n3, n4); 32: g.addEdge(new MyEdge("e4"), n4, n1); 33: g.addEdge(new MyEdge("e5"), n5, n6); 34: g.addEdge(new MyEdge("e6"), n6, n7); 35: g.addEdge(new MyEdge("e7"), n7, n8); 36: g.addEdge(new MyEdge("e8"), n8, n5); 37: g.addEdge(new MyEdge("e9"), n1, n5); 38: g.addEdge(new MyEdge("e10"), n2, n6); 39: g.addEdge(new MyEdge("e11"), n3, n7); 40: g.addEdge(new MyEdge("e12"), n4, n8); 41: return g; 42: } 43: }
このプログラムを実行するとすると下のようなウィンドウが現われます。ただし、ノードの配置はランダムなので、実行の度に変ります。
RandomLayout<V,E>クラスを下のように改造すると、レイアウトを変更し続けます。ここでは別のファイルにするためにクラス名をRandomLayout2.javaに変更していますが、Sample3bクラスで使う場合には、ファイル名(クラス名)を修正する必要はありません。
修正箇所はInteractiveContextインタフェースの実装です。InteractiveContextインタフェースは、done()メソッドとstep()メソッドを必要とするので、それら二つのメソッドを実装する必要があります。
1: import java.awt.Dimension; 2: import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 3: import edu.uci.ics.jung.algorithms.util.IterativeContext; 4: import edu.uci.ics.jung.graph.Graph; 5: public class RandomLayout2<V,E> extends AbstractLayout<V,E> implements IterativeContext { 6: public RandomLayout2(Graph<V,E> g) { 7: super(g); 8: } (中略) 20: @Override 21: public void step() { 22: layoutNodes(); 23: } 24: @Override 25: public boolean done() { 26: return false; 27: } 28: protected void layoutNodes() { (中略) 39: } 40: }
done()メソッドは自動レイアウトが終了したかどうかを示すメソッドです。上の例ではいつもfalseを返すので、いつまでもレイアウトは終了しないということになります。InteractiveContextインタフェースを実装したLayoutオブジェクトの場合、一定時間毎にstep()メソッドが呼び出されます。上の例では、一定時間毎にlayoutNodes()メソッドが呼び出されることで、レイアウトをランダムに変更し続けます。
JUNG2.0にはすでにいくつかの自動レイアウト機能が用意されています。自分で自動レイアウト機能を実装しなくても、これらを利用することで簡単に自動レイアウトを行うことができます。たとえばFRLayout<V,E>クラス(edu.uci.ics.jung.algorithms.layout.FRLayout<V,E>クラス)を利用すれば、古典的なグラフ描画アルゴリズムであるFruchterman and Reingoldの力指向アルゴリズムを利用できます。FRLayout<V,E>クラスを使用するにはSample3b.javaを下のように改造します。
3: import edu.uci.ics.jung.algorithms.layout.FRLayout; (中略) 9: public static void main(String[] args) { 10: Dimension viewArea = new Dimension(300, 300); 11: Graph<MyNode,MyEdge> graph = createGraph(); 12: Layout<MyNode,MyEdge> layout = new FRLayout<MyNode,MyEdge>(graph, viewArea); (中略) 19: } 20: private static Graph<MyNode,MyEdge> createGraph() { (中略) 43: } 44: }
このプログラムを実行すると下のようなウィンドウが現われます。ただし、FRLayoutにもランダムな要素があるため、必ずしもこれと同じ配置にならないかも知れません。それでも、RandomLayoutほどのばらつきはないので、グラフが立方体の構造を持っていることは分るでしょう。
FRLayout以外にも、以下のようなアルゴリズムが用意されています。