前回に引き続き、
ソースコードのダウンロード
今回作成するプログラムのソースコードは、
Java2Dによるグラフィックス
Javaには、
- 基本図形の描画
- テキストの描画
- パスの作成
- 交差判定
- 座標系の拡大・
縮小・ 回転 - アンチエイリアス
- グラデーション
- 半透明描画
- 画像ファイルの入出力
視覚的に美しい情報可視化を行うにあたっては、
色集合のツリーマップ
今回は、
- 第2回のアルゴリズムにしたがって、
色データ集合のクラスタリングを実行し、 クラスタツリーを作成する。 - クラスタリングの距離関数には、
第3回のWard法を使用する。 - 第3回のアルゴリズムにしたがって、
ツリーマップの領域分割を行う。 - ツリーマップ領域の面積比率は、
ランダムに決定する。 - ツリーマップの各領域は、
ツリーノードに対応する色で塗りつぶす。 - ツリーマップの各領域は、
ツリーノードの深さに応じた太さで輪郭を描画する。ルートノードを最も太く、 末端ノードに近づくほど細く描画する。
この結果出力されるイメージは、

ツリーマップの描画
それでは、
まず始めに、
public interface TreeMapRenderer {
void render(Graphics2D g, Node node, Rectangle2D bounds);
}
そして、
- ノードが末端なら、
領域を確定し、 内部を塗りつぶす。 - ノードが末端でなければ、
領域を分割し、 子ノードを再帰的に処理する。 - 子ノードの処理が終了後、
親ノードの輪郭を描画する。
上記の方針に対応する実際の部分コードを、
末端ノードの塗りつぶし
以下は、
if (node instanceof ColorItem) {
g.setPaint(((ColorItem) node).getColor());
g.fill(bounds);
}
ノード領域の分割
以下は、
} else if (node instanceof Cluster) {
Cluster cluster = (Cluster) node;
// 子ノードchild1とchild2を取得
Node child1 = cluster.getLeft();
Node child2 = cluster.getRight();
// 子ノードの面積比を計算 (A)
double area1 = child1.getArea();
double area2 = child2.getArea();
double ratio1 = area1 / (area1 + area2);
double ratio2 = area2 / (area1 + area2);
double x = bounds.getX();
double y = bounds.getY();
double w = bounds.getWidth();
double h = bounds.getHeight();
Rectangle2D rect1;
Rectangle2D rect2;
// 領域分割を実行 (B)
if (w > h) {
// boundsが横長の場合、左右に分割
rect1 = new Rectangle2D.Double(x, y, ratio1 * w, h);
rect2 = new Rectangle2D.Double(x + ratio1 * w, y, ratio2 * w, h);
} else {
// boundsが縦長の場合、上下に分割
rect1 = new Rectangle2D.Double(x, y, w, ratio1 * h);
rect2 = new Rectangle2D.Double(x, y + ratio1 * h, w, ratio2 * h);
}
// 子ノードを再帰的に処理 (C)
doRender(g, child1, rect1, depth + 1);
doRender(g, child2, rect2, depth + 1);
}
始めに、
ノードの輪郭の描画
ノードの塗りつぶしと、
以下が、
float borderWidth = Math.max(8f - depth, 1f);
g.setStroke(new BasicStroke(borderWidth));
g.setPaint(Color.BLACK);
g.draw(bounds);
デモプログラムの実行
それではいよいよ、
以下が、
Demoクラスでは、
public class Demo {
// 出力ファイル名
private static final String OUTPUT_FILE_NAME = "C:/visualization/treemap.png";
public static void main(String[] args) {
new Demo().run();
}
public void run() {
List<Item> input = new ArrayList<Item>();
// ランラムな色データを100個作成
Random rand = new Random();
for (int i = 0; i < 100; i++) {
Color color = new Color(rand.nextFloat(), rand.nextFloat(), rand
.nextFloat());
double area = 0.2 + 0.8 * rand.nextDouble();
input.add(new ColorItem(color, area));
}
// Ward法に基づく階層的クラスタリングを準備
DistanceEvaluator evaluator = new WardDistanceEvaluator();
ClusterBuilder builder = new ClusterBuilder(evaluator);
// クラスタリングを実行
Node result = builder.build(input);
// クラスタリング結果を表示
output(result);
}
private void output(Node node) {
// 400x400ピクセルの画像を作成
BufferedImage image = new BufferedImage(400, 400,
BufferedImage.TYPE_INT_RGB);
// グラフィックオブジェクトを作成
Graphics2D g = image.createGraphics();
// 背景を白で塗りつぶす
g.setPaint(Color.WHITE);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
// ツリーマップの描画を実行
TreeMapRenderer renderer = new BinaryTreeMapRenderer();
Rectangle2D bounds = new Rectangle2D.Double(20, 20, 360, 360);
renderer.render(g, node, bounds);
// 画像をPNGファイルに保存
try {
ImageIO.write(image, "png", new File(OUTPUT_FILE_NAME));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Demoクラスを実行すると、

入力データはランダムに生成しているため、
また、
- 入力データの数をさらに増やすと、
ツリーマップはどうなるか? - 階層的クラスタリングの距離関数をWard法から最短距離法に戻すと、
ツリーマップはどうなるか?
まとめと次回予告
今回は、
さて、
次回からは、