簡単な自動レイアウト機能を実装してみましょう。最も簡単なグラフのレイアウトはノードをランダムな位置に配置するものです。そのプログラムは下のようになります。
1: import scala.collection.JavaConversions.JCollectionWrapper 2: import java.awt.Dimension 3: import edu.uci.ics.jung.algorithms.layout.AbstractLayout 4: import edu.uci.ics.jung.graph.Graph 5: class RandomLayout1b[V,E]( 6: g: Graph[V,E] 7: ) extends AbstractLayout[V,E](g) { 8: def this(g: Graph[V,E], d: Dimension) = { 9: this(g) 10: setSize(d) 11: } 12: override def initialize(): Unit = layoutNodes() 13: override def reset(): Unit = {} 14: private def layoutNodes(): Unit = { 15: val clearance: Int = 20 16: val d: Dimension = getSize 17: val width: Int = d.width - clearance * 2 18: val height: Int = d.height - clearance * 2 19: val nodeSet = new JCollectionWrapper[V](getGraph.getVertices) 20: nodeSet.foreach((v: V) => { 21: if (!isLocked(v)) { 22: val x: Double = math.random * width + clearance 23: val y: Double = math.random * height + clearance 24: transform(v).setLocation(x, y) 25: } 26: }) 27: } 28: }
自動レイアウト機能を実装するには、Layout[V,E]トレイトをミックスインします。Layout[V,E]トレイトを部分的に拡張したAbstractLayout[V,E]クラスがあるので、それを拡張すると簡単です。AbstractLayout[V,E]クラスを拡張する場合には、initialize()メソッドとreset()メソッドを実装します。initialize()メソッドの本体はlayoutNodes()メソッドで、描画領域のサイズを獲得し、その範囲内に納まるようにノードの位置を決めています。reset()メソッドも実装していますが、本体は空です(*1)。
【注*1】 reset()メソッドはその名の通り何かをリセットするために使われるのだと思いますが、APIの記述がありません。
各ノードの座標を決めているのはlayoutNode()メソッドですが、ここではすべてのノードの座標をランダムに決めています。すべてのノードに対して座標を設定するために20行目でforeachを使用しています。ただし、getGraph.getVerticesはjava.util.Collection[V]型のオブジェクトなので、このままではforeachが使用できません。そこで、ここでは19行目でforeachが使用できるJCollectionWrapper[V]型に変換しています。
明示的に型変換を行わず、次の例のように暗黙の型変換を使用することもできます。
1: import scala.collection.JavaConversions.asIterable (中略) 19: getGraph.getVertices.foreach((v: V) => { 20: if (!isLocked(v)) { 21: val x: Double = math.random * width + clearance 22: val y: Double = math.random * height + clearance 23: transform(v).setLocation(x, y) 24: } 25: })
ここでは1行目で暗黙の型変換のためのメソッドscala.collection.JavaConversions.asIterableをインポートしています。これによりCollection[V]型は自動的にforeach()メソッドが使用できるscala.collection.Iterable[V]型に変換されます。このように暗黙の型変換を利用することで、getGraph.getVerticesに対して直接foreach()メソッドを使用することができます。
実装した自動レイアウト機能を利用するには下のようにします。このプログラムはSample3.scalaと基本的な構造は同じです。ただし、グラフを作成する部分だけをcreateGraphメソッドに切り出しています。変数layoutにStaticLayout[MyNode,MyEdge]クラスのインスタンスを代入していたところを、上で定義したRandomLayout[MyNode,MyEdge]クラスのインスタンスを代入しています。
1: import swing._ 2: import java.awt.Dimension 3: import edu.uci.ics.jung.algorithms.layout.Layout 4: import edu.uci.ics.jung.graph.{Graph,UndirectedSparseGraph} 5: import edu.uci.ics.jung.visualization.BasicVisualizationServer 6: object Sample3b extends SimpleSwingApplication { 7: def top = new MainFrame { 8: title = "Graph View: Random Layout" 9: val graph: Graph[MyNode,MyEdge] = createGraph 10: val viewArea = new Dimension(300, 300) 11: val layout: Layout[MyNode,MyEdge] = new RandomLayout[MyNode,MyEdge](graph, viewArea) 12: val panel = new BasicVisualizationServer[MyNode,MyEdge](layout, viewArea) 13: contents = Component.wrap(panel) 14: } 15: def createGraph: Graph[MyNode,MyEdge] = { 16: val g: Graph[MyNode,MyEdge] = new UndirectedSparseGraph[MyNode,MyEdge] 17: val n1 = new MyNode("n1") 18: val n2 = new MyNode("n2") 19: val n3 = new MyNode("n3") 20: val n4 = new MyNode("n4") 21: val n5 = new MyNode("n5") 22: val n6 = new MyNode("n6") 23: val n7 = new MyNode("n7") 24: val n8 = new MyNode("n8") 25: g.addEdge(new MyEdge("e1"), n1, n2) 26: g.addEdge(new MyEdge("e2"), n2, n3) 27: g.addEdge(new MyEdge("e3"), n3, n4) 28: g.addEdge(new MyEdge("e4"), n4, n1) 29: g.addEdge(new MyEdge("e5"), n5, n6) 30: g.addEdge(new MyEdge("e6"), n6, n7) 31: g.addEdge(new MyEdge("e7"), n7, n8) 32: g.addEdge(new MyEdge("e8"), n8, n5) 33: g.addEdge(new MyEdge("e9"), n1, n5) 34: g.addEdge(new MyEdge("e10"), n2, n6) 35: g.addEdge(new MyEdge("e11"), n3, n7) 36: g.addEdge(new MyEdge("e12"), n4, n8) 37: g 38: } 39: }
このプログラムを実行するとすると下のようなウィンドウが現われます。ただし、ノードの配置はランダムなので、実行の度に配置は変ります。
RandomLayout[V,E]クラスを下のように改造すると、レイアウトを変更し続けます。ここでは別のファイルにするためにクラス名をRandomLayout2.scalaに変更していますが、Sample3bクラスで使う場合には、ファイル名(クラス名)を修正する必要はありません。
修正箇所はInteractiveContextトレイトのミックスインです。InteractiveContextトレイトは、done()メソッドとstep()メソッドを必要とするので、それら二つのメソッドも実装しました。
4: import edu.uci.ics.jung.algorithms.util.IterativeContext (中略) 6: class RandomLayout2[V,E]( 7: g: Graph[V,E] 8: ) extends AbstractLayout[V,E](g) with IterativeContext { (中略) 13: override def initialize(): Unit = layoutNodes() 14: override def reset(): Unit = {} 15: override def step(): Unit = layoutNodes() 16: override def done: Boolean = false 17: private def layoutNodes(): Unit = { (中略) 29: } 30: }
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.scalaを下のように改造します。
1: import swing._ 2: import java.awt.Dimension 3: import edu.uci.ics.jung.algorithms.layout.{FRLayout,Layout} 4: import edu.uci.ics.jung.graph.{Graph,UndirectedSparseGraph} 5: import edu.uci.ics.jung.visualization.BasicVisualizationServer 6: object Sample3c extends SimpleSwingApplication { 7: def top = new MainFrame { 8: title = "Graph View: F&R Layout" 9: val graph: Graph[MyNode,MyEdge] = createGraph 10: val viewArea = new Dimension(300, 300) 11: val layout: Layout[MyNode,MyEdge] = new FRLayout[MyNode,MyEdge](graph, viewArea) 12: val panel = new BasicVisualizationServer[MyNode,MyEdge](layout, viewArea) 13: contents = Component.wrap(panel) 14: } 15: def createGraph: Graph[MyNode,MyEdge] = { (中略) 38: } 39: }
このプログラムを実行すると下のようなウィンドウが現われます。ただし、FRLayoutにもランダムな要素があるため、必ずしもこれと同じ配置にはなりません。それでも、RandomLayoutほどのばらつきはないので、グラフが立方体の構造を持っていることは分るでしょう。
FRLayout以外にも、以下のようなアルゴリズムが用意されています。