JUNG2(Java Universal Network/Graph Framework)とは

目的

2009年4月に,Javaライブラリである JUNG の 2.0(JUNG2-2.0) がリリースされたようだが,これまでのJUNG1.Xとは何かが違っていたため,日本語のサイトのサンプルコードは動かず,しかも目新しい情報もない,というのがここにメモを残そうと思ったのがきっかけ.

おそらくネットワークの分析目的で,JUNGを使ってJavaでプログラムをするぐらいなら適当なソフトか,最近(2009年現在)では,Rのライブラリが充実してきたこともあり,見捨てられつつあるのかもしれない.また,大規模なものになるとやはりJavaよりはC++かなとなってBoostを使うことになるんだとおもう.

だが,モックアップをつくるのはC++よりも,Javaが楽だと思う.しかし,ちょっとしたグラフ構造でもちゃんと管理してアルゴリズムを組み立てるのは面倒.適当なライブラリを探していたとkろお JUNG2 を使うかとなった.それほど,古いライブラリでもないし,どこかにまとめてあるサイトぐらいあるかとおもったが,予想以上に参考になるサイトが少なかったのでメモとしてこのページを残しておく.なにかの役に立っていれば幸いです.

JUNG1.Xからの違い

このページを作成しようとおもったもう一つの理由として,JUNG2と呼ばれているバージョンが,JUNG 1.X の頃とはだいぶ入れ替わってしまっていて別物といってもいいものとなっていたこと

基本的に,ものとして間違い無くよいものにはなっているが,例えばライブラリの名前空間が変わっているとか,グラフの構成要素としてVertexとかNodeとか定義されていたものが無くなったとか,下位互換性はまったくなく参考にしたサイトになっているサンプルのコンパイルが通らなくって苦労した.

単に,JavaのGenericにそって実装しなおしたともいえるんだけど.

いまのところ分かっている違いとしてはライブラリが複数のJARファイルに分割されたのと,名前空間が見直された?こと,あとはPageRankがDirectedGraphのみだったのが UndirectedGraphも可能になったことあたりだろうか.必要に応じて追加できればと思う.

インストール

下記のサイトからバイナリーを取得する.

取得したファイルを展開すると必要な JAR ファイルが入っているので,それらに CLASSPATH を通す.おそらく Java のオプション -cp とかで設定してもいいが数が多いので環境変数で設定したほうがおそらく楽だと思う.

あとは簡単なサンプルを作成してパスが通ったかどうかを確認.確認するだけなら import だけで十分かな.

import edu.uci.ics.jung.graph.*; import edu.uci.ics.jung.algorithms.layout.*; import edu.uci.ics.jung.visualization.BasicVisualizationServer; public class JungSample { }

グラフの種類

JUNG2で定義されているグラフの種類は以下である.

  • Hypergraph<V,E>
  • JUNG2で基底となるクラスのようす.ノードとエッジから構成されるのは一般的なグラフと変わらないが,自己ループも許容して....いなかった.

    どうもいくつか制限があるっぽい.というか,重みつきグラフをどうあつかうのだろうか?

  • Graph<V,E>
  • ノードとエッジで構成されるのは変わらないが,Hypergraphとのちがいは,エッジの両端が異なるノード同士が連結されていることが必須.

  • DirectedGraph<V,E>
  • UndirectedGraph<V,E>
  • Tree<V,E>
  • そのまま木構造.directed とのことなので方向あり.各ノードとルートノードとの経路が唯一?それなら全てのノード間の経路が唯一になるなぁ.

  • Forest<V,E>
  • 木構造との違いは循環路がない,つまり複数経路はありえる.あと,consists of a collection of rooted とあるので複数のルートノードを設定できるのかな.

  • KPartiteGraph<V,E>
  • 1つ以上のコンポーネントからなるネットワーク.このスーパークラスが Graph と Hypergraph しか無いってことは他は連結グラフではないとだめってこと?

ノードとエッジの定義

JUNG2では,VertexやEdgeが定義から無くなってしまったが,それは逆にIntegerやStringなどの既存のクラスをそのまま,ノードやエッジの定義として利用できるようになっている.ただし,重みやら名前やらなんやかんやと属性情報が欲しくなるのは当り前なので適当に作っておいたほうが無難だとおもう.

とりあえず,現時点で,利用しているクラスを掲載しておく.以下に出てくるVertexやEdgeは,ここでのクラスのことを示す.

-- Vertex.java --

class Vertex {

    private int m_id;
    private Vec m_pos;
    private String m_name;

    private static int m_tid = 0;

    public Vertex() { m_id = m_tid++; m_pos = new Vec(0,0); }
    public Vertex(double x, double y) { 
	this(); 
	m_pos = new Vec(x,y);
    }

    public int id()  { return m_id; }
    public Vec pos() { return m_pos; }
    public double x()   { return pos().x; }
    public double y()   { return pos().y; }
    
    public double distanceTo(Vertex v) {
	return pos().distance(v.pos());
    }

    public String toString() { return "n"+id()+" ("+x()+","+y()+")"; };
    public boolean equals(Vertex v) { return v.id() == id(); }
}
	     
-- Edge.java --

class Edge {

    private double m_capacity;
    private String m_name;
    private int    m_id;
    private double m_weight;

    private static int m_tid = 0;

    public Edge(double weight, double capacity) {
	m_id  = m_tid++;
	m_weight   = weight;
	m_capacity = capacity;
    }
    
    public String toString() {	return "e"+m_id;  }

    public double weight() { return m_weight; }
    public int id() { return m_id; }
}
	     

ネットワークの構造的特徴を計算する

ネットワークの構造的特徴を計算するには"edu.uci.ics.jung.algorithms.scoring.*;"を import すれば,重要なアルゴリズムはほぼ?含まれている.

  • BarycenterScorer
  • BetweennessCentrality
  • ClosenessCentrality
  • DegreeScorer
  • DistanceCentralityScorer
  • EigenvectorCentrality
  • HITS
  • HITS.Scores
  • HITSWithPriors
  • KStepMarkov
  • PageRank
  • PageRankWithPriors
  • VoltageScorer

正直,いくつか知らない指標もあるが,実際触ってみれば分かると思うので,細かい説明は省く.

Degree Centralityを求める

Closeness Centralityを求める

Betweenness Centralityを求める

PageRankを求める

JUNG1.X では,DirectedGraph のみが対象だったが, Hypergraphに拡張?されUndirectedGraphも計算可能となったようだ.計算的には対して変わらないと思うのだがなぜだろう?

おもなプロセスとしては,PageRankのオブジェクトを作成して,evaluate() すれば計算は終了.おそらく,注意するのはPageRankのオブジェクトを作成するときにあたえるランダムな遷移確率,pageらの論文では 0.15 と設定されている.


  Graph graph = new SparseMultigraph();
  PageRank rank = new PageRank(graph,0.15);
  rank.evaluate();
	       

HITSを計算する

HITSもPageRankと似ていて,WebPageの重要度を量るための指標だが,記憶違いでなければ2部グラフ向きの計算手法だったように思う.

基本的な部分はPageRankと変わらない感じみたい.ただし,それぞれに出力される値が違うが.指標の特性からも重要なのは相対的な順序とその差にあるのだから,まぁ構わない.

重みつきグラフの扱い

どうも JUNG2 にアップしたときに,Vertex, Edge クラスが無くなったからなのか,重みつきグラフの扱いがよくわからないことになっている.公式サイトになるToutrialのp.5の3.2節をよむと,グラフとはべつにTransformerというクラスでedgeと weightとのリンクを作成しないとだめっぽい.うーん.

というわけで?まずはエッジクラスの定義をおこなう.

Transformerの利用

Transformerクラスを利用するにはcommons collections を importする必要がある.


  import org.apache.commons.collections15.Transformer;
	       

続いて,turtorialにあるように Transformer クラスのインスタンスを定義するのだが,turtrial どおりではコンパイルが通らなかったので,以下のように変更する必要がある.


  Transformer wtTransformer =
     new Transformer() {
     public Number transform(MyLink link) {
       return link.weight;
    }
  };
 DijkstraShortestPath alg =
    new DijkstraShortestPath(g, wtTransformer);
	       

変更点は,Doubleとなっているところを,Numberに変更して,new DijkstraShortestPath に <MyNode,MyLink>を追加する.

なお,これら修正点に関しては,副田さんの助言から得られたものである.

グラフの描画関連

ノードの位置を固定する

StaticLayoutを使用する

setLocation(vertex object, x, y)で指定するが,x が横軸, yが縦軸となっている様子.


 graph2 = new UndirectedSparseMultigraph();
 for(int i = 0; i < 20; i++) {  // (600,400)の描画エリアにランダムに配置
  // 計算上intだが, Vertex では double で格納
    int x = (int)(Math.random() * 560.0) + 15;
    int y = (int)(Math.random() * 360.0) + 10; 
    Vertex v = new Vertex(x,y);
    vertices.add(v.id(), v);
    graph2.addVertex(vertices.get(v.id()));
 }
 StaticLayout layout2 = new StaticLayout(graph2);
  // レイアウト上にsetLocationでノードの座標を設定
 for(Vertex i : graph2.getVertices()) {
    layout2.setLocation(i, i.x(), i.y());
 }
	       

ノードのサイズを変更する

JUNGを使って普通に描画してみると,下のようになる.

これでは,ノードの描画サイズが少し大きすぎるので,小さくしたり,ノードごとに大きさを変えたいということがあると思う.

そのときに,どうしたらよいかとライブラリを探してみたが,どうも一筋縄ではいかない感じ.

いろいろ調べてみた結果,visualization で RenderContext() をいじればよさそうなのをサンプルをみて分かった.そこにあるメソッドは

  • void setVertexDrawPaintTransformer(Transformer<V,Paint> vertexDrawPaintTransformer)
  • void setVertexFillPaintTransformer(Transformer<V,Paint> vertexFillPaintTransformer)
  • void setVertexFontTransformer(Transformer vertexFontTransformer)
  • void setVertexIconTransformer(Transformer vertexIconTransformer)
  • void setVertexIncludePredicate(Predicate,V>> vertexIncludePredicate)
  • void setVertexLabelRenderer(VertexLabelRenderer vertexLabelRenderer)
  • void setVertexLabelTransformer(Transformer vertexStringer)
  • void setVertexShapeTransformer(Transformer vertexShapeTransformer)
  • void setVertexStrokeTransformer(Transformer vertexStrokeTransformer)

このなかでも,setVertexShapeTransformer を使うとどうも描画の形自体を変更できるっぽい.ということで.


  public class MyVertexShapeTransformer implements Transformer {
    public Shape transform(V v) {
      return new Rectangle(-5,-5,10,10);
    }
  } 
	       

という感じで,Rectangleを返す Transformer を定義.


  BasicVisualizationServer vv 
    = new BasicVisualizationServer(layout);
  vv.getRenderContext().setVertexShapeTransformer(new MyVertexShapeTransformer());
	       

つづいて,VisualizationServer で放り投げてみると

でも,これでは,ノードが四角として描画されるけど,円の時にはどうしたものか.残念ながらShapeにはCircleというのがみあたらない.でも,よく見たら Arc2D というのがあるのでこれを使ってみる.


  public class MyVertexShapeTransformer implements Transformer {
    public Shape transform(V v) {
   return new Arc2D.Double(-5, -5, 10, 10, 0, 360, Arc2D.OPEN);
    }
  } 
	       
sample graph draw

これで,ノードの条件などで形,大きさを変更できる.上の図では,見た目がわからないけど...ただし,Shapeの(0,0)が,ノードの原点(edgeが交叉する点)なので,それだけ注意が必要.

Arc2D.Double(原点X,原点Y,横,縦,円弧の始点,円弧の終点, 半径を描画するか?)となっている.この円の描画は画面ピクセルサイズに固定なので,Windowサイズに応じて変えるならそれなりに操作が必要.

ラベルの張り付け

せっかくグラフを描画するならラベルも張り付けたいよねということで,ラベルの張り付け.方法は基本的にはこれまでと同じく Transformer を使用して実装する.

かと思っていたらちょっと違った.

いや,基本的にそれでいいのだが,ライブラリには ToStringLabeller というクラスが用意されていてそれでもよいみたい.


  import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
	       

まず,これを import する.そして,


  StaticLayout layout = new StaticLayout(graph);
  BasicVisualizationServer vv 
     = new BasicVisualizationServer(layout);
  vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
	       

という形で,宣言するとラベルが張り付けられる.でも,このとき張られるラベルはなに?となるとおもうが,それは,対象とするクラスの toString() で定義された出力となる.言うまでもなく,元のクラス(ここではVertex)には String toString() を定義する必要がある.

ところで,出力の位置ってなんとかならんものか?

リンク描画の変更

Jung-TECHSCORE-では,renderer を利用した変更方法がサンプルとしてのせられているが,RendererContext を利用した変更方法を載せておく.


  import edu.uci.ics.jung.visualization.decorators.EdgeShape;
	       

説明は不要だと思うが,上のパッケージを import する.続いて,setEdgeShapeTransformer を使用してエッジの描画タイプを指定する.


  VisualizationImageServer vv = new VisualizationImageServer(layout, panelsize());
  vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line());
	       

その他には

  • EdgeShape.BentLine: 真ん中で折れた線
  • EdgeShape.Box: 不明
  • EdgeShape.CubicCurve: S字カーブ
  • EdgeShape.Line: ノード間を直線で描画
  • EdgeShape.Loop: よくわからない
  • EdgeShape.Orthogonal: 直角?
  • EdgeShape.QuadCurve: 円弧
  • EdgeShape.SimpleLoop: 不明
  • EdgeShape.Wedge: コンパイルが通らない

それぞれを利用して描画したときには,下記のようなグラフが描画される.ただし,Wedgeはコンパイルが通らなかったため不明.

graph draw sample
BentLine Draw
graph draw sample
Box, Loop, SimpleLoop draw
graph draw sample
CubicCurve Draw
graph draw sample
Line Draw
graph draw sample
Orthogonal Draw
graph draw sample
QuadCurve Draw

リンクの色の指定

色を変更する際の方法もいろいろあるようだが,基本的には,setEdgeDrawPaintTransformer(Transformer) を使って実装するみたい.ということで,sample のソースコードとかをみると,LazyMapとかを使っているが,他と同じような書き方で問題無く動作する.


  BasicVisualizationServer vv 
    = new BasicVisualizationServer(layout);
  Transformer epTransformer = new Transformer(){
    public Paint transform(Edge edge) {
	return Color.LIGHT_GRAY; // リンクの色を全てライトグレイに指定
    }
  };
  vv.getRenderContext().setEdgeDrawPaintTransformer(epTransformer);
	       

これで.デフォルトでは黒だったリンクの色を,ライトグレイに変更できる.

GradientEdgePaintTransformerを使ってみる.

JUNGのライブラリを覗いてみると,GradientEdgePaintTransformer というクラスがある.名前からして関係ありそうなのでとりあえず使ってみる.

javadoc には,コンストラクタで,Colorをふたつ,VisualViewerを属性値として指定するだけでいいみたい.ということで,下記のように記述してみる.


  vv.getRenderContext().setEdgeDrawPaintTransformer(new GradientEdgePaintTransformer(Color.black, 
		  Color.LIGHT_GRAY,new VisualizationViewer(layout)));		     
		 

なぜ,VisualizationViewerが必要なのかわからないが,使用していた描画パーツはServerだったので使用できなかったこともあり,改めて new してみた.描画された結果は,2つめの色を中心として,両端が一つめの色となる.

多少見にくいが,上の黒で描画されたものと比較してみればなんとなく分かるのではないだろうか.

graph draw sample
simple black Draw
sample graph draw
gradient edge paint draw

蛇足だが,色指定には半透明色(例えば new Color(0,0,0,0) など)も指定可能の様子.

グラフのイメージの取り出し

VisualizationViewerでは,パネル全体を張りつけてしまう.そのため,グラフ自体を背景としてすることはできない.そんなときには,VisualizationImageServerを使うとイメージだけを取り出す.

基本的な部分は,VisualizationViewerと変わらない.


  Graph g; // 他で作成したグラフを用意
  StaticLayout layout = new StaticLayout(g);
 layout.setSize(panelsize());
 for(Vertex v: g.getVertices()) { // static layout なので座標を指示
     layout.setLocation(v, drawX(v.x()), drawY(v.y()));
 }
 VisualizationImageServer vv = 
      new VisualizationImageServer(layout, panelsize());
 // ラベルとノード形状の指定
  vv.getRenderContext().setVertexLabelTransformer(new MyVertexLabelTransformer());
 vv.getRenderContext().setVertexShapeTransformer(new MyVertexShapeTransformer());
  vv.setBackground(Color.gray); // 背景色の指定.半透明色も可能
  // buf は背景用スクリーンバッファ,
 // (0,0)からpanelseizeの領域までのイメージを切り出して張り付け
  buf.image(vv.getImage(new Point(0,0), panelsize()),0,0,this);
	       

参考サイト