「蓄積されたデータおよび随時更新されるデータをグラフとして可視化する」アプリケーション(続き)
センサーデータが更新されるたびにグラフを更新する処理の作成
第2段階の最後のステップは、センサーデータが更新されるたびにグラフを更新する処理の作成です。この処理にはWebSocketを使います。Node-REDフローエディターで既存のフローに対し、画面35で赤く囲った部分を追加・修正してください。本記事のサンプルファイル(codezine_iotdemo_nodered_app2-c.json)からインポート(追加・修正部分のみ)することもできます。
画面35:「センサーデータが更新されるたびにグラフを更新する処理の作成」のための追加・修正部分
ここで行うのは、2つのノードの追加・変更のみです。
① output >「websocket」ノードの追加
「format EX timestamp」のfunctionノードから接続します。websocketノードをダブルクリックして以下を設定します。
-
Type: Listen on
-
Path: /ws/sensor(ペンアイコンをクリックして追加してください)
-
Name: 任意(画面35では「websocket」)
画面36:output >「websocket」ノードの設定
画面37:Path欄の設定。WecSocketリスナーの新規追加
② 「Template」ノード(index.html)の修正
-
Template: 後に示すコードを入力(サンプルファイル「index_app2-c.html」)
Template欄に入力するコード(▼クリックするとプルダウンしてコードが表示されます)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- jQuery -->
<script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
<!-- d3.js -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<title>IoT demo chart</title>
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: royalblue;
stroke-width: 1.5px;
}
.line2 {
fill: none;
stroke: orangered;
stroke-width: 1.5px;
}
.line3 {
fill: none;
stroke: forestgreen;
stroke-width: 1.5px;
}
</style>
<script type="text/javascript">
//グラフ用のデータ配列
var data = [];
//WebSocket接続用 Node-REDで生成した文字列を指定
var wsUrl = 'ws://codezineiotdemo.mybluemix.net/ws/sensor';
var socket;
window.addEventListener('load', function () {
//Cloudant APIのURL
var recordnum = 10;
var dataurl = "./data?num=" + recordnum;
//Cloudantからデータ取得して初期表示
d3.json(dataurl, function(datas){
//console.log("===== d3.json ========");
//console.log(datas);
//JSONで取得する項目はそのまま使えるのでグラフ用配列にセット
data = datas;
//配列の順序を入れ替えてセット→D3.jsがよしなにしてくれるので不要
// for (var i in datas){
// data.unshift(datas[i]);
// }
//console.log("==== data object =====");
//console.log(data);
//グラフ描画処理
drawInitialChart();
//最新データを表示
var dispdata = {
timestamp : formatDateTime(new Date(datas[0].timestamp)),
temp : datas[0].temp,
humidity : datas[0].humidity,
objectTemp : datas[0].objectTemp
};
disp_info(dispdata);
});
});
</script>
</head>
<body>
<div style="margin:30px;">
<h1>IoTセンサーデータ可視化デモ</h1>
<div>
<p>最新データ :
<span id="latestts">timestamp</span>
, <span style="color:royalblue;">気温:<span id="latesttemp">xx</span></span>
, <span style="color:forestgreen;">湿度:<span id="latesthum">xx</span></span>
, <span style="color:orangered;">機械温度:<span id="latestot">xx</span></span>
</p>
</div>
<div id="chartarea"></div>
<p></p>
<div>
<button onclick="wsConnect()">WebSocket接続</button>
<button onclick="wsDisconnect()">WebSocket切断</button>
<span id="con-msg">message</span>
</div>
</div>
<script>
//D3.js 初期処理
//表示サイズを設定
var areasize = {width: 960, height: 500};
var margin = {top: 40, right: 40, bottom: 40, left: 40};
//グラフ表示用の高さと幅
var chartwidth = areasize.width - margin.left - margin.right;
var chartheight = areasize.height - margin.top - margin.bottom;
var formatDateTime = d3.time.format("%Y-%m-%d_%H:%M:%S");
var parseDate = d3.time.format("%Y-%m-%d_%H:%M:%S").parse;
//SVG領域の設定
var svg = d3.select("#chartarea").append("svg")
.attr("width", areasize.width)
.attr("height", areasize.height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.time.scale()
.range([0, chartwidth]);
var y = d3.scale.linear()
.range([chartheight, 0]);
var y2 = d3.scale.linear()
.range([chartheight, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
//.orient("bottom")
//.tickFormat(d3.time.format("%m/%d_%H:%M"));
//温度のY軸(左側)
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//湿度のY軸(右側)
var yAxis2 = d3.svg.axis()
.scale(y2)
.orient("right");
//ライン・気温
var line = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y(d.temp); });
//ライン・機器温度
var line2 = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y(d.objectTemp); });
//ライン・湿度
var line3 = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y2(d.humidity); });
//グラフの初期描画処理 データ取得後にコールされる
function drawInitialChart(){
//console.log("===== drawInitialChart ======");
data.forEach(function(d){
d.timestamp = parseDate(d.timestamp);
d.temp = +d.temp;
d.objectTemp = +d.objectTemp;
d.humidity = +d.humidity;
});
//X軸のドメイン=Timestampの最小値と最大値
x.domain(d3.extent(data, function(d){ return d.timestamp; }));
//Y軸のドメイン=0から、気温Maxと機器温度Maxの大きい方
var maxtemp = d3.max(data, function(d){ return d.temp; });
var maxobjtemp = d3.max(data, function(d){ return d.objectTemp; });
var max_y = maxtemp > maxobjtemp ? maxtemp : maxobjtemp;
y.domain([0, max_y]);
//Y軸(湿度)は0-100固定
y2.domain([0, 100]);
//X軸 描画
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + chartheight + ")")
.call(xAxis);
//Y軸(左) 描画
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "translate(60,-20) rotate(0)")
.attr("y", 6)
.attr("dy", ".7em")
.style("text-anchor", "end")
.text("温度(℃)");
//Y軸(右) 描画
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + chartwidth + " ,0)")
.call(yAxis2)
.append("text")
.attr("transform", "translate(0,-20) rotate(0)")
.attr("y", 6)
.attr("dy", ".7em")
.style("text-anchor", "end")
.text("湿度(%)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("path")
.datum(data)
.attr("class", "line2")
.attr("d", line2);
svg.append("path")
.datum(data)
.attr("class", "line3")
.attr("d", line3);
}
//データ更新時の描画処理
function updateDraw() {
//console.log("===== updateDraw =======");
//console.log(data);
data.forEach(function(d){
d.timestamp = d.timestamp;
d.temp = +d.temp;
d.objectTemp = +d.objectTemp;
d.humidity = +d.humidity;
});
//ドメイン(入力値の範囲)更新
x.domain(d3.extent(data, function(d){ return d.timestamp; }));
var maxtemp = d3.max(data, function(d){ return d.temp; });
var maxobjtemp = d3.max(data, function(d){ return d.objectTemp; });
var max_y = maxtemp > maxobjtemp ? maxtemp : maxobjtemp;
y.domain([0, max_y]);
y2.domain([0, 100]);
//アニメーション宣言
svg = d3.select("#chartarea").transition();
svg.select(".line")
.duration(750)
.attr("d", line(data));
svg.select(".line2")
.duration(750)
.attr("d", line2(data));
svg.select(".line3")
.duration(750)
.attr("d", line3(data));
svg.select(".x.axis")
.duration(750)
.call(xAxis);
svg.select(".y.axis")
.duration(750)
.call(yAxis);
}
//接続・切断時のメッセージを表示
function disp_msg(str) {
var msgarea = $("#con-msg");
msgarea.text(str);
msgarea.stop().fadeIn(0).fadeOut(2000);
}
//最新データ表示
function disp_info(wsdata){
$("#latestts").text(wsdata.timestamp);
$("#latesttemp").text(wsdata.temp);
$("#latesthum").text(wsdata.humidity);
$("#latestot").text(wsdata.objectTemp);
}
//WebSocket接続
function wsConnect() {
socket = new WebSocket(wsUrl);
disp_msg('接続しました。');
socket.onmessage = function(e) {
var wsData = JSON.parse(e.data);
console.log("timestamp:" + wsData.timestamp + " , temp:" + wsData.temp);
//最新データの情報を表示
disp_info(wsData);
//グラフのデータを更新
//グラフ用のデータが新しい順になっているので、配列の先頭に追加する
data.unshift({
timestamp : parseDate(wsData.timestamp),
temp : wsData.temp,
humidity: wsData.humidity,
objectTemp: wsData.objectTemp
});
//配列の末尾を取り除く
data.pop();
//グラフ描画
updateDraw();
};
};
//WebSocket切断
function wsDisconnect() {
socket.close();
disp_msg('切断しました');
};
</script>
</body>
</html>
以下、コードの追加部分の説明です。
47~49行目:WebSocketの接続用文字列とWebSocketオブジェクトを追加しています。接続用文字列は、
"ws://" + アプリケーションのURI + Node-REDのwebsocketノードで設定した文字列
となります(例 ws://codezineiotdemo.mybluemix.net/ws/sensor
)。
101~105行目:WebSocketの接続/切断用のボタンを追加。
231~273行目:データ更新時のD3.jsによるグラフ描画処理。更新されたデータを使って、アニメーション処理で描画しています。
292~318行目:WebSocket接続、受信時の処理。最新1件のデータをグラフ描画用の配列に追加して1件を取り除き、グラフ描画処理を呼び出しています。
ここまで確認・編集できたら動作確認します。Webブラウザで、本アプリケーションのルートURL(例 http://codezineiotdemo.mybluemix.net/)にアクセスします。画面38に示すようなグラフが表示された後に「WebSocket接続」ボタンを押して、しばらく待ってグラフが更新されれば成功です。
画面38:動作確認。グラフがこのように表示されればOK
IoT成功のカギを握る:データ管理・分析クラウドサービスのご紹介
現象をデータ化するセンサーやデバイスに注目が集まりがちなIoTですが、価値を生むのはデータ化したその後の処理です。
「多数のセンサー、デバイスからインターネット経由で送信されてくるJSONデータを効率よく保管する」
「保管した大量のデータから未知のパターンを割り出し、ビジネスを成長させる知見を提供する」
こうしたIoTが価値を生むためのサービス・製品がIBMにあります。ぜひ、下記の資料をご覧ください。(編集部)