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>