具体例で学ぶ!情報可視化のテクニック

第4回 ツリーマップによる木構造の可視化(後編)

この記事を読むのに必要な時間:およそ 3.5 分

ツリーマップの描画

それでは,ツリーマップの描画ロジックを作成していきましょう。

まず始めに,TreeMapRendererインターフェイスを用意します。TreeMapRendererは,指定された長方形領域の内部にツリーマップを描画するrender()メソッドを持ちます。

リスト1 TreeMapRenderer.java

public interface TreeMapRenderer {
    void render(Graphics2D g, Node node, Rectangle2D bounds);
}

そして,このインターフェイスを実装したBinaryTreeMapRendererクラスを作成します。BinaryTreeMapRendererクラスは,次のような方針で描画を行います。

  • ノードが末端なら,領域を確定し,内部を塗りつぶす。
  • ノードが末端でなければ,領域を分割し,子ノードを再帰的に処理する。
  • 子ノードの処理が終了後,親ノードの輪郭を描画する。

上記の方針に対応する実際の部分コードを,順番に見ていきましょう。なお,これらの部分コードを含むBinaryTreeMapRendererクラスの完全なソースコードは,こちらをご覧ください。

末端ノードの塗りつぶし

以下は,ノードが末端の色データである場合の処理です。bounds変数に格納されている現在の長方形領域を,ノードの色を使って塗りつぶしています。

リスト2 BinaryTreeMapRenderer.java(部分)

    if (node instanceof ColorItem) {
        g.setPaint(((ColorItem) node).getColor());
        g.fill(bounds);
    }

ノード領域の分割

以下は,ノードがクラスタを示している場合の処理です。

リスト3 BinaryTreeMapRenderer.java(部分)

    } 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);
    }

始めに,クラスタが持つ2つの子ノードのgetArea()メソッドを呼び出し,面積比率を求めます(A⁠⁠。次に,その面積比率にしたがって,長方形を左右または上下に分割します(B⁠⁠。最後に,それぞれの子ノードを再帰的に処理します(C⁠⁠。

ノードの輪郭の描画

ノードの塗りつぶしと,輪郭の描画の実行順序には注意が必要です。なぜなら,先に描画した輪郭が,その後の塗りつぶしによって上書きされてしまう可能性があるからです。この問題を避けるには,全ての子ノードの処理が終わった後に,輪郭を描画するようにします。

以下が,実際の輪郭の描画コードになります。depth変数で与えられるノードの深さを元にしてストロークを作成し,輪郭線の幅を指定しています。

リスト4 BinaryTreeMapRenderer.java(部分)

    float borderWidth = Math.max(8f - depth, 1f);
    g.setStroke(new BasicStroke(borderWidth));
    g.setPaint(Color.BLACK);
    g.draw(bounds);

著者プロフィール

浜本階生(はまもとかいせい)

1981年生まれ。栃木県出身。東京工業大学情報工学科卒業。技術やアイデアの組み合わせから面白いソフトウェアを生み出したいと日々考えている。現在,ブログの解析および視覚化の試みとして「TopHatenar」「Blogopolis」を開発,運用中。

URLhttp://d.hatena.ne.jp/kaiseh/