search
LoginSignup
9
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

d3.js Advent Calendar 2016 Day 24

posted at

updated at

d3.jsを用いたIoTセンサーのリアルタイムチャート作成

d3.jsは、データドリブンで色々かっこいいグラフなどが書けるJavascriptのライブラリとして有名です。 このライブラリを使って、IoTセンサーのリアルタイムチャートを作成してみます。素人なので、コードには問題があるかもしれませんが、その際はご指摘いただけると幸いです><

IoTデバイスとの接続/データの取得

デバイスを使っているかは具体的には説明しません。各々のデバイス毎にしかるべき設定を行ってください。 私が使用しているデバイスは、GETリクエストを送ってやるとセンサーの値を返してくれます。 具体的に書くと以下のようになります。

 const url = "hogehoge";
 var xhr = new XMLHttpRequest();
 xhr.addEventListener('error', function() {
      alert("Not establish connection");
      // エラー処理
       });

xhr.addEventListener('loadend', function() {
   if (xhr.status === 200 && xhr.readyState === 4) {
      const res = parseFloat(xhr.responseText);
      const now = new Date();
      dataset.push({
         time: now,
         val: res
         });
    else {
       console.error(xhr.status + ' ' + xhr.statusText);
    }
});

xhr.open("GET", url + '/brightness', false);
// ここではセンサーのうちの照度センサーを使ったとします
xhr.send(null);
// 本来は非同期で送った方がいいのですが、簡単のために同期でリクエストを送ってあげます
xhr.send(null);

d3.jsの使用

d3.jsを使いましょう。ここでは、サンプルコードの量が多い、d3.jsのversion3を使用します。
CDNで用いるには次のように加えるだけで大丈夫です。

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

まずはグラフの描画の元となるものの宣言です。

var margin = {
            top: 30,
            right: 50,
            bottom: 30,
            left: 50
        };
var width = 600 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var xScale = d3.time.scale()
            .range([0, width]);
//x軸は時間のスケールになるように設定します

var yScale = d3.scale.linear()
            .range([height, 0]);


var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom")
            .tickFormat(d3.time.format('%M:%S'));

var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left");

// 線の定義
var line = d3.svg.line()
            .x(function(d) {
                return xScale(d.time);
            })
            .y(function(d) {
                return yScale(d.val);
            })
            .interpolate("cardinal");
            // 点の間の補間をcardinalに設定

// svgの定義
var svg = d3.selectAll("#d3graph").append("svg")
// このd3のselectAllメソッドにより、基となるHTMLの <div id="d3graph"> </div>なる要素を引っ張ってきます
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

これで大体の描画は完了しました。そのあとはデータが入ってくるたびに描画するプログラムを付け足せば完了です。

 function update() {
            if (dataset.length > 20) {
                dataset.shift();
                // データ数が20を超えたら入っているデータを消します        
            }
            svg.selectAll("path").remove();
            // xy軸削除
            svg.selectAll("g").remove();
       // 線の削除

            xScale.domain(d3.extent(dataset, function(d) {
                return d.time;
            }));
            yScale.domain(d3.extent(dataset, function(d) {
                 return d.val;
             })).nice();
            //xy軸それぞれのドメインをdatasetのmin, maxの範囲に収めます

            svg.append("g")
                .attr("class", "y axis")
                .call(yAxis)
                .append("text")
                .attr("y", -10)
                .attr("x", 10)
                .style("text-anchor", "end")
                .text("");
            // y軸の再描画

            svg.append("g")
                .attr("class", "x axis")
                .call(xAxis)
                .attr("transform", "translate(0," + height + ")")
            // x軸の再描画

            // path要素をsvgに表示し、折れ線グラフを設定
            svg.append("path")
                .datum(dataset)
           // lineとdatasetのbind
                .attr("fill", "none")
                .attr("stroke", "steelblue")
                .attr("stroke-linejoin", "round")
                .attr("stroke-linecap", "round")
                .attr("stroke-width", 1.5)
                .attr("d", line);
        };

このupdate()を定期的に繰り返してあげれば描画完了です。
念のため、貼り付けるだけで使えるHTMLをサンプルとして貼っておきます。


<!DOCTYPE html>
<html lang="en">
<head>
    <style type="text/css">
        .axis text {
            font: 10px sans-serif;
        }
        .axis path {
            opacity: 0.1;
            stroke: #000;
            stroke-width: 0.1;
            shape-rendering: crispEdges;
        }
        .axis line {
            stroke: #000;
            stroke-width: 1;
            shape-rendering: crispEdges;
        }
        .line {
            fill: none;
            stroke: DarkGreen;
            stroke-width: 1.5px;
        }
    </style>
</head>

<body>
        <div id="d3graph"></div>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script>
        window.addEventListener("loaded", update, false);

        var dataset = [];

        function generateData() {
            const now = new Date();
            const data = {
                time: now,
                val: Math.random() * 10
            };
            dataset.push(data)
        }

        var margin = {
            top: 30,
            right: 50,
            bottom: 30,
            left: 50
        };
        var width = 600 - margin.left - margin.right;
        var height = 500 - margin.top - margin.bottom;

        var xScale = d3.time.scale()
            .range([0, width]);

        var yScale = d3.scale.linear()
            .range([height, 0])
            .domain([0, 10]);

        var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom")
            .ticks(10)
            .tickFormat(d3.time.format('%M:%S'));

        var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left");

        var line = d3.svg.line()
            .x(function(d) {
                return xScale(d.time);
            })
            .y(function(d) {
                return yScale(d.val);
            })
            .interpolate("cardinal");

        var svg = d3.selectAll("#d3graph").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        function update() {
            if (dataset.length > width / 20) {
                dataset.shift();
            }
            svg.selectAll("path").remove();
            svg.selectAll("g").remove();

            xScale.domain(d3.extent(dataset, function(d) {
                return d.time;
            }));
             yScale.domain(d3.extent(dataset, function(d) {
                 return d.val;
             })).nice();

            svg.append("g")
                .attr("class", "y axis")
                .call(yAxis)
                .append("text")
                .attr("y", -10)
                .attr("x", 10)
                .style("text-anchor", "end")
                .text("");

            svg.append("g")
                .attr("class", "x axis")
                .call(xAxis)
                .attr("transform", "translate(0," + height + ")")


            svg.append("path")
                .datum(dataset)
                .attr("fill", "none")
                .attr("stroke", "steelblue")
                .attr("stroke-linejoin", "round")
                .attr("stroke-linecap", "round")
                .attr("stroke-width", 1.5)
                .attr("d", line);
        };

        setInterval(generateData, 500);
        setInterval(update, 500);
    </script>
</body>
</html>

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
What you can do with signing up
9
Help us understand the problem. What are the problem?