Japanese Only

Step 3: 自動レイアウト機能の利用

  1. 自動レイアウトの実装方法
  2. 既存のレイアウト機能の使い方
  3. 用意されているレイアウト機能
目次ページへ

自動レイアウトの実装方法

自動レイアウト機能の実装

簡単な自動レイアウト機能を実装してみましょう。最も簡単なグラフのレイアウトはノードをランダムな位置に配置するものです。そのプログラムは下のようになります。

RandomLayout1b.scala

  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]型に変換しています。

明示的に型変換を行わず、次の例のように暗黙の型変換を使用することもできます。

RandomLayout.scala

  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]クラスのインスタンスを代入しています。

Sample3b.scala

  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: }

このプログラムを実行するとすると下のようなウィンドウが現われます。ただし、ノードの配置はランダムなので、実行の度に配置は変ります。

Execution result of Sample3b.scala

継続的にレイアウトを変更する

RandomLayout[V,E]クラスを下のように改造すると、レイアウトを変更し続けます。ここでは別のファイルにするためにクラス名をRandomLayout2.scalaに変更していますが、Sample3bクラスで使う場合には、ファイル名(クラス名)を修正する必要はありません。

修正箇所はInteractiveContextトレイトのミックスインです。InteractiveContextトレイトは、done()メソッドとstep()メソッドを必要とするので、それら二つのメソッドも実装しました。

RandomLayout2.scala

  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を下のように改造します。

Sample3d.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ほどのばらつきはないので、グラフが立方体の構造を持っていることは分るでしょう。

Execution result of Sample3c.scala

用意されているレイアウト機能

その他の自動レイアウト機能

FRLayout以外にも、以下のようなアルゴリズムが用意されています。

目次ページへ