概要

  • D3.js で Force layout を動かしてみる。
    • 理解用に簡単なサンプルを作る。
  • その他、オプションを試してみる。
    • ラベル表示と矢印を追加。

情報

D3.js って…?

  • 日本語サイト
  • D3 = Data Driven Document
    • データに基づいてドキュメント (要は DOM) を操作するための Javascriptライブラリ。
  • svg を使った華麗なグラフのデモが目立つが、DOM操作のライブラリとしても優れている (と、使ってみて思った)。

Force layout

  • force = 『力』とか『エネルギー』とか。
    • スターウォーズのアレ??
  • 要素同士が影響し合っている状態を node (円) と link (線) で表している。
  • 説明よりもサンプル見た方が早い。
    • Force-Directed Graph

作ったサンプル

  • jsdo.it 上に置きました。


  • 作り方とかは以下、詳細に。

簡単なサンプル作ってみる

データ

  • 『AはBを好き』というデータを適当に作ってみた。
No. 名前 性別 好き (No.)
1 陽菜 (6)
2 大翔 結菜 (8)
3 陽向 葵 (9)
4 陽太 結菜 (8)
5 悠真 結愛 (10)
6 陽菜 陽向 (3)
7 悠真 (5)
8 結菜 大翔 (2)
9 悠真 (5)
10 結愛 蓮 (1)

  • 名前は2014年の名前ランキング上位から取得。他意は無いです。
  • 好き合っているのは乱数で設定。両想い分だけ手作業。

データを json 化

  • Javascript で扱うにあたり、JSON形式が読みやすい。
  • Force layout のサンプルに従い、手作業でJSON作成。
  • data.json
{
    "nodes":[
        {"name": "蓮", "group": 1},
        {"name": "大翔", "group": 1},
        {"name": "陽向", "group": 1},
        {"name": "陽太", "group": 1},
        {"name": "悠真", "group": 1},
        {"name": "陽菜", "group": 2},
        {"name": "凛", "group": 2},
        {"name": "結菜", "group": 2},
        {"name": "葵", "group": 2},
        {"name": "結愛", "group": 2}
    ],
    "links":[
        {"source": 0, "target": 5},
        {"source": 1, "target": 7},
        {"source": 2, "target": 8},
        {"source": 3, "target": 7},
        {"source": 4, "target": 9},
        {"source": 5, "target": 2},
        {"source": 6, "target": 4},
        {"source": 7, "target": 1},
        {"source": 8, "target": 4},
        {"source": 9, "target": 0}
    ]
}
  • メモ
    • group は後で色分けに使用。性別で振った。
    • links の source、target は nodes の並び順 (0 indexed)。

HTML、CSS、Javascript 準備

  • 構成
  index.html
  - js
      - main.js
  - css
      - style.css
  - libs
      - d3.v3.min.js
  - data
      - data.json

  • index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8"/>
        <title>d3.js sample</title>
        
        <!-- styles -->
        <link rel="stylesheet" href="css/style.css"/>
    </head>
    <body>
        <div class="svg_area" id="svg_area"></div>
        
        <!-- libs -->
        <script type="text/javascript" src="libs/d3.v3.min.js"></script>
        <!-- scripts -->
        <script type="text/javascript" src="js/main.js"></script>
    </body>
</html>

  • style.css
/*** Field ***/
.svg_area {
    position: relative;
    width: 500px;
    height: 500px;
    border: dashed 5px #000;
}

/*** Force layout ***/
.node {
    stroke: #fff;
    stroke-width: 1.5px;
}

.link {
    stroke: #999;
    stroke-opacity: .6;
}
  • メモ
    • 表示エリアのサイズと、force layout の設定。

  • main.js
/**
 * Initialize SVG display
 */
function init(svg_id, width, height, data_path)
{
    // 色は既定のがある。
    var color = d3.scale.category20();

    var force = d3.layout.force()
                  .charge(-500)      // node同士の力の基準?
                  .linkDistance(100)  // node同士の距離の基準
                  .size([width, height]);

    var svg = d3.select(# + svg_id).append(svg)
                .attr(width, width)
                .attr(height, height);

    d3.json(data_path, function(error, graph) {

        // JSON read error 時
        if (graph == null) {
            alert(error);
            return;
        }

        force.nodes(graph.nodes)
             .links(graph.links);

        // Tick start
        force.start();

        var link = svg.selectAll(.link)
                      .data(graph.links).enter()
                      .append(line)
                      .attr(class, link)
                      .style(stroke-width, 1);

        var node = svg.selectAll(.node)
                      .data(graph.nodes).enter()
                      .append(circle)
                      .attr(class, node)
                      .attr(r, 5)
                      .style(fill, function(d) { return color(d.group); })
                      .call(force.drag);

        force.on(tick, function() {
            link.attr(x1, function(d) { return d.source.x; })
                .attr(y1, function(d) { return d.source.y; })
                .attr(x2, function(d) { return d.target.x; })
                .attr(y2, function(d) { return d.target.y; });

            node.attr(cx, function(d) { return d.x; })
                .attr(cy, function(d) { return d.y; });
        });
    });
}

/**
 * Main
 */
var svg_id    = svg_area;
var element   = document.getElementById(svg_id);
var data_path = data/data.json;

init(svg_id, element.clientWidth, element.clientHeight, data_path);
  • メモ
    • force layout は「d3.layout.force()」にデータ与えれば自動生成してくれる。便利!!
      • tick 動かして Update 処理とか、JSON の形式とか、色々制約はあるけど…。

オプション表示

  • ラベル、矢印を追加してみる。
  • 参照
    • COPPELIA「An A to Z of extra features for the D3 force layout」
  • 以降、コードは主要部分のみ。詳しくは jsdo.it に上げたもの参照のこと。

ラベル付与

  • css.style に text のスタイル追加
.node text {
    font: 16px helvetica;
}

  • main.js (変更主要部分)
    d3.json(data_path, function(error, graph) {

        // 略

        var node = svg.selectAll(.node)
                      .data(graph.nodes).enter()
                      .append(g)
                      .attr(class, node)
                      .call(force.drag);

        var circle = node.append(circle)
                         .attr(r, 5)
                         .style(fill, function(d) { return color(d.group); });

        var text = node.append(text)
                       .attr(dx, 10)
                       .attr(dy, .35em)
                       .text(function(d) { return d.name; })
                       .style(stroke, gray);

        force.on(tick, function() {
            // 略 (link 位置更新)

            circle.attr(cx, function(d) { return d.x; })
                  .attr(cy, function(d) { return d.y; });

            text.attr(x, function(d) { return d.x; })
                .attr(y, function(d) { return d.y; });
        });
    });
  • メモ
    • 元は .node 直下に circle 作っていたが、変更。
      • .node 直下に g 作って、node 属性複数個。
      • その下に circle と ’text 付加
    • tick 時処理も circle と text 両方更新

矢印追加

  • source から target に向かう矢印表示
  • svg の defs に矢印表示を定義し、それを各 link に当てる形の様だ。

  • main.js (主要部分)
function _setArrow(svg)
{
    svg.append(defs).selectAll(marker)
       .data([arrow]).enter()
       .append(marker)
       .attr(id, function(d) { return d; })
       .attr(viewBox, 0 -5 10 10)
       .attr(refX, 25)
       .attr(refY, 0)
       .attr(markerWidth, 10)
       .attr(markerHeight, 10)
       .attr(orient, auto)
       .append(path)
       .attr(d, M0,-5L10,0L0,5 L10,0 L0, -5)
       .style(stroke, #4679BD)
       .style(opacity, 0.6);
};

function init(svg_id, width, height, data_path)
{
    // 略

    var svg = d3.select(# + svg_id).append(svg)
                .attr(width, width)
                .attr(height, height);

    _setArrow(svg);

    d3.json(data_path, function(error, graph) {

        // 略

        var link = svg.selectAll(.link)
                      .data(graph.links).enter()
                      .append(line)
                      .attr(class, link)
                      .style(stroke-width, 1)
                      .style(marker-end, url(#arrow));

        // 略
    });
}

参照

  • D3.js 日本語サイト
  • Force-Directed Graph
  • COPPELIA「An A to Z of extra features for the D3 force layout」