LoginSignup
5
5

More than 5 years have passed since last update.

D3.js (v4) で散布図を作成

Last updated at Posted at 2017-05-21

はじめに

D3.jsを勉強し始めたのですが、まだまだv4に対応した日本語の書籍や情報などは少ないようです(2017年5月現在)。
シンプルな散布図を作ろうとしたのですが、ちょうどいいサンプルコードが見つからなかったので自分が作ったものを載せてみます。

目標

  • 散布図(Scatter Plot)を作成する。
  • データの追加と削除を行う。
  • 軸をデータに合わせて自動調整する。
  • プロットと軸の状態はtransitionで遷移させる。

初期状態

img1.png

データを追加した状態

img2.png

サンプルコード

デモページ
htmlファイルをローカルに置いても動作します。

以下の問題がありますが、コードをシンプルにするために対策はしていません。
- データの追加時にも、削除のtransitionの待ち時間が発生します。
- 描画中にデータの追加・削除を行うと、プロットが正常に描画されません。

scatter-plot.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Scatter Plot</title>

    <style>
    svg {
        border: solid 1px gray;
    }
    </style>

    <script src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>
    <!-- グラフを描画するsvg -->
    <svg></svg>

    <div>
        <button onclick="addData()">追加</button>
        <button onclick="deleteData()">削除</button>
    </div>

    <script>
    // ==================== メインルーチン ==================== //
    var data = [
        { x: 1, y: 10 },
        { x: 2, y: 20 },
        { x: 3, y: 30 },
        { x: 4, y: 40 },
        { x: 5, y: 50 }

    ];

    var svgWidth = 600; // svg要素の幅
    var svgHeight = 400; // svg要素の高さ
    var margin = {top: 20, right: 20, bottom: 20, left: 40}; // マージン
    var width = svgWidth - margin.left - margin.right; // グラフエリアの幅
    var height = svgHeight - margin.top - margin.bottom; // グラフエリアの高さ
    var t1, t2, t3; // transition用変数(t1:プロット削除, t2:スケール遷移, t3:プロット追加)
    var scale = {}; // スケール用変数

    // ----- SVG要素の設定 ----- //
    var svg = d3.select("svg")
        .attr('width', svgWidth)
        .attr('height', svgHeight);

    // ----- グラフエリアの設定 ----- //
    var g = svg.append('g')
        .attr('transform', translate(margin.left, margin.top));

    // ----- 初期データの表示 ----- //
    setScale();
    initializeAxis();
    initializePlot();

    // ==================== 関数定義 ==================== //

    // ----- プロットの初期設定 ----- //
    function initializePlot() {
        var plot = g.selectAll('circle')
            .data(data)
            .enter()
            .append('circle')
            .attr('cx', function(d) { return scale.x(d.x) })
            .attr('cy', function(d) { return scale.y(d.y) })
            .attr('r', 5);
    }

    // ----- プロットのアップデート ----- //
    function updatePlot() {
        // データのバインド
        var plot = g.selectAll('circle')
            .data(data);

        // 削除されたデータのプロットを削除
        plot.exit()
            .transition(t1)
            .attr('r', 0)
            .remove();

        // 既存のデータのプロットはスケールの変更に合わせて位置を変更
        plot.transition(t2)
            .attr('cx', function(d) { return scale.x(d.x) })
            .attr('cy', function(d) { return scale.y(d.y) });

        // 新規データのプロットを追加
        plot.enter()
            .append('circle')
            .attr('cx', function(d) { return scale.x(d.x) })
            .attr('cy', function(d) { return scale.y(d.y) })
            .attr('r', 0)
            .transition(t3)
            .attr('r', 5);
    }

    // ----- スケールの設定 ----- //
    function setScale() {
        // X軸
        var xRangeMin = 0;
        var xRangeMax = d3.max(data, function(d) { return d.x; }) + 1;

        scale.x = d3.scaleLinear()
            .domain([xRangeMin, xRangeMax])
            .range([0, width]);

        // Y軸
        var yRangeMin = 0;
        var yRangeMax = d3.max(data, function(d) { return d.y; }) + 10;

        scale.y = d3.scaleLinear()
            .domain([yRangeMin, yRangeMax])
            .range([height, 0]);
    }

    // ----- 軸の初期設定 ----- //
    function initializeAxis() {
        // X軸
        svg.append('g')
            .attr('class', "x_axis")
            .attr('transform', translate(margin.left, height + margin.top))
            .call(d3.axisBottom(scale.x));

        // Y軸
        svg.append('g')
            .attr('class', "y_axis")
            .attr('transform', translate(margin.left, margin.top))
            .call(d3.axisLeft(scale.y));
    }

    // ----- 軸の再設定 ----- //
    function resetAxis() {
        // X軸
        svg.select(".x_axis")
            .transition(t2)
            .call(d3.axisBottom(scale.x));

        // Y軸
        svg.select(".y_axis")
            .transition(t2)
            .call(d3.axisLeft(scale.y));
    }

    // ----- transitionの設定(delayで調整) ----- //
    function setTransition() {
        t1 = d3.transition()
            .duration(500);

        t2 = d3.transition()
            .duration(1000)
            .delay(500);

        t3 = d3.transition()
            .duration(500)
            .delay(1000);
    }
/*
    // ----- transitionの設定(チェーンで調整) ----- //
    function setTransition() {
        t1 = d3.transition()
            .duration(500);

        t2 = t1.transition()
            .duration(1000);

        t3 = t2.transition()
            .duration(500);
    }
*/
    // ----- translate用の文字列を作成 ----- //
    function translate(x, y) {
        return 'translate(' + x + ',' + y + ')';
    }

    // ----- データの追加 ----- //
    function addData() {
        if (data.length >= 10) {
            return;
        }

        data.push({
            x: data.length + 1,
            y: (data.length + 1) * 10
        });

        setTransition();
        setScale();
        resetAxis();
        updatePlot();
    }

    // ----- データの削除 ----- //
    function deleteData() {
        if (data.length == 0) {
            return;
        }

        data.pop();

        setTransition();
        setScale();
        resetAxis();
        updatePlot();
    }
    </script>
</body>

</html>

参考

D3 Tips and Tricks v4.x
GitHub - d3/d3-transition:Animated transitions for D3 selections.
General Update Pattern, III
Chained Transitions

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5