D3.jsを使ってみる



## 概要

Force Layoutを使いたいがためだけに使い方を覚える。
D3とはData Driven Docuement。
データを視覚化するライブラリ。

- [公式サイト](http://d3js.org/)
- [APIリファレンス](https://github.com/mbostock/d3/wiki/API-Reference)


## サンプル

### Hello D3.js

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <p>Hello 1</p>
    <p>Hello 2</p>
    <p>Hello 3</p>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        d3.select('body').selectAll('p').text('hello from d3!');
    </script>
</body>
</html>
```

### 要素の取得、テキスト/スタイルの変更

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <p>Hello 1</p>
    <p>Hello 2</p>
    <p>Hello 3</p>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var p = d3.select('body').selectAll('p')
        p.text('hello from d3!');
        p.style('font-size', '28px');

        p.text('hello from d3!')
            .style('font-size', '28px')
            .style('font-weight', 'bold');

        p.text('hello from d3!')
            .style({
                'font-size': '28px',
                'font-weight': 'bold'
            });

        // alert(p.style('font-size'));

        p.style('font-size', function() {
            return Math.floor(Math.random() * 32) + 'px';
        });
    </script>
</body>
</html>
```

### 要素の削除

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <p>Hello 1</p>
    <p>Hello 2</p>
    <p>Hello 3</p>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var obj = d3.select('body').append('p').text('hello 4');
        obj.remove();
    </script>
</body>
</html>
```

### dataを使ってみる

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <p>Hello 1</p>
    <p>Hello 2</p>
    <p>Hello 3</p>
    <span></span>
    <span></span>
    <span></span>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36];
        var p = d3.select('body').selectAll('p');
        p.data(dataset).text(function(d) {
            return d;
        });
        p.data(dataset).text(function(d, index) {
            return index + "番目は" + d;
        });

        var dataset2 = [{name: 'aki', age: 1},{name: 'mii', age: 2},{name: 'akiton', age: 3}];
        var span = d3.select('body').selectAll('span');
        span.data(dataset2).text(function(d, index) {
            return d.name + '(' + d.age + ')';
        });
    </script>
</body>
</html>
```

### update, enter, exit

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <p>Hello 1</p>
    <p>Hello 2</p>
    <p>Hello 3</p>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48];
        var p = d3.select('body').selectAll('p');

        var update = p.data(dataset);
        var enter = update.enter();
        var exit = update.exit();

        update.text(function(d, index) {
            return "update: " + d;
        });
        enter.append('p').text(function(d, index) {
            return "enter: " + d;
        });
        exit.style('color', 'red').remove();
    </script>
</body>
</html>
```

### データから図形を描画する

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48, 33];
        var w = 640;
        var h = 480;

        var svg = d3.select('body').append('svg').attr({
            width: w,
            height: h
        });

        svg.selectAll('circle')
            .data(dataset)
            .enter()
            .append('circle')
            .attr({
                cx: function(d, i) { return 50 + (i * 100); },
                cy: h / 2,
                r: function(d) { return d; },
                fill: 'red'
            });
    </script>
</body>
</html>
```

### 図形描画時にアニメーションさせてみる

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48, 33];
        var w = 640;
        var h = 480;

        var svg = d3.select('body').append('svg').attr({
            width: w,
            height: h
        });

        svg.selectAll('circle')
            .data(dataset)
            .enter()
            .append('circle')
            .attr({
                cx: function(d, i) { return 50 + (i * 100); },
                cy: h / 2,
                r: 0,
                fill: 'red'
            })
            .transition()
            .delay(function(d, i) {
                return 300 * i;
            })
            .duration(1000)
            .ease('bounce')
            .attr({
                r: function(d) { return d; }
            });
    </script>
</body>
</html>
```

### eachを使ってアニメーションを制御してみる

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48, 33];
        var w = 640;
        var h = 480;

        var svg = d3.select('body').append('svg').attr({
            width: w,
            height: h
        });

        svg.selectAll('circle')
            .data(dataset)
            .enter()
            .append('circle')
            .transition()
            .delay(function(d, i) {
                return 300 * i;
            })
            .duration(1000)
            .ease('bounce')
            .each('start', function() {
                d3.select(this).attr({
                    fill: 'green',
                    r: 0,
                    cy: h
                });
            })
            .attr({
                cx: function(d, i) { return 50 + (i * 100); },
                cy: h / 2,
                r: function(d) { return d; },
                fill: 'red'
            })
            .each('end', function() {
                d3.select(this)
                    .transition()
                    .duration(800)
                    .attr({
                        fill: 'pink',
                        cy: 0,
                        r: 0
                    });
            });
    </script>
</body>
</html>
```

### イベントを取得する

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48, 33];
        var w = 640;
        var h = 480;

        var svg = d3.select('body').append('svg').attr({
            width: w,
            height: h
        });

        svg.selectAll('circle')
            .data(dataset)
            .enter()
            .append('circle')
            .on('mouseover', function(d) {
                d3.select(this).attr('fill', 'orange');
            })
            .on('mouseout', function(d) {
                d3.select(this).attr('fill', 'red');
            })
            .on('mousedown', function(d) {
                var rs = d3.select(this).attr('r');
                alert(d + ':' + rs);
            })
            .attr({
                cx: function(d, i) { return 50 + (i * 100); },
                cy: h / 2,
                r: function(d) { return d; },
                fill: 'red'
            })
    </script>
</body>
</html>
```

### 棒グラフを書く

```bash
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Test</title>
    <style>
        .axis path, .axis line {
            fill: none;
            stroke: black;
        }
        .axis text {
            font-size: 11px;
        }
    </style>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var dataset = [12, 24, 36, 48, 33];
        var w = 640;
        var h = 480;
        var padding = 20;

        var xScale = d3.scale.linear()
            .domain([0, d3.max(dataset)])
            .range([padding, w - padding])
            .nice();

        var svg = d3.select('body').append('svg').attr({
            width: w,
            height: h
        });

        var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient('bottom');

        svg.append('g')
            .attr({
                class: 'axis',
                transform: 'translate(0, 180)'
            })
            .call(xAxis);

        svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr({
                x: padding,
                y: function(d, i) { return i * 25; },
                width: function(d) { return xScale(d) - padding; },
                height: 20,
                fill: 'red'
            });
    </script>
</body>
</html>
```

### 後からノード追加して動かしたり

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3.js Force Layout</title>
</head>
<body>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
        var width = 640;
        var height = 480;

        var svg = d3.select('body').append('svg').attr({
            width: width,
            height: height
        });

        var data = {
            nodes: [
                { name: 'aki' },
                { name: 'mii' },
                { name: 'akiton' }
            ],
            links: [
                { source: 0, target: 2 },
                { source: 1, target: 2 }
            ]
        };

        var force = d3.layout.force()
            .nodes(data.nodes)
            .links(data.links)
            .size([width, height])
            .gravity(0.1)
            .charge([-100])
            .friction(0.95)
            .linkDistance([100])
            .linkStrength(1);

        // レイアウト計算開始
        force.start();
        for (var i = 0; i < 10000; i++) {
            force.tick();
        }
        force.stop();

        // lineを生成
        var line = svg.selectAll('line')
            .data(data.links)
            .enter()
            .append('line')
            .attr({
                'stroke': 'black',
                'x1': function(d, i) { return d.source.x; },
                'y1': function(d, i) { return d.source.y; },
                'x2': function(d, i) { return d.target.x; },
                'y2': function(d, i) { return d.target.y; }
            })
            .style({
            });

        var circle = svg.selectAll('circle')
            .data(data.nodes)
            .enter()
            .append('circle')
            .attr({
                'r': 8,
                'fill': 'black',
                'cx': function(d, i) { return d.x; },
                'cy': function(d, i) { return d.y; }
            })
            .style({
            });
            // .call(force.drag);

        var moveTarget = null;
        circle
            .on('mousedown', function(d, i) {
                console.log(d.name + '(' + d.x + ',' + d.y + ')');
                moveTarget = d;
            })
            .on('mouseup', function(d, i) {
                moveTarget = null;
            });

        svg.on('mousemove', function(e) {
            if (moveTarget != null) {
                var mousePos = d3.mouse(this);
                console.log('moveTarget = ' + moveTarget.name + ' (' + mousePos[0] + ',' + mousePos[1] + ')');
                moveTarget.x = mousePos[0];
                moveTarget.y = mousePos[1];

                d3.selectAll('line')
                    .attr({
                        'x1': function(d) { return d.source.x; },
                        'y1': function(d) { return d.source.y; },
                        'x2': function(d) { return d.target.x; },
                        'y2': function(d) { return d.target.y; }
                    });
                d3.selectAll('circle')
                    .attr({
                        'cx': function(d) { return d.x; },
                        'cy': function(d) { return d.y; }
                    });
            }
        });

        svg.on('mousedown', function() {
            if (moveTarget != null) return;
            var newCircle = svg.append('circle');
            var mousePos = d3.mouse(this);
            var newData = {
                name: 'super',
                age: 10,
                x: mousePos[0],
                y: mousePos[1]
            };
            data.nodes.push(newData);
            console.log('add circle: ' + newData.x + ',' + newData.y);
            newCircle
                .datum(newData)
                .attr({
                    'cx': function(d, i) { console.log(d);return d.x; },
                    'cy': function(d, i) { return d.y; },
                    'r': 10,
                    'fill': 'red'
                })
                .on('mousedown', function(d) {
                    moveTarget = d;
                })
                .on('mouseup', function(d) {
                    moveTarget = null;
                });
        });

        function randomMove() {
            for (var i = 0; i < data.nodes.length; i++) {
                var d = data.nodes[i];
                d.x += Math.random() * 2 <= 1 ? Math.random()*10 : -Math.random()*10;
                d.y += Math.random() * 2 <= 1 ? Math.random()*10 : -Math.random()*10;

                d3.selectAll('line')
                    .attr({
                        'x1': function(d) { return d.source.x; },
                        'y1': function(d) { return d.source.y; },
                        'x2': function(d) { return d.target.x; },
                        'y2': function(d) { return d.target.y; }
                    });
                d3.selectAll('circle')
                    .attr({
                        'cx': function(d) { return d.x; },
                        'cy': function(d) { return d.y; }
                    });
            }
        }
    </script>
    <input type="button" value="randomMove" onClick="randomMove()"/>
</body>
</html>
```

## 参考