LoginSignup
6
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-31

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>

6
9
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
6
9