書いたのは2018年です。ずっと限定共有にしていましたが公開にして供養しています。
経緯
生産のC/T(サイクル・タイム)を知るために、とある加工の機械にマイコンを繋げ、1つ生産するときに出るカウンタの信号を拾い、wi-fi経由で自作の社内サーバに送り、csvファイルにその信号の出た年月日時分秒と累積の生産数を記録するようにしました
それをどう可視化するか?ということでいろいろ試行錯誤したのでメモ
今回はとりあえず指定したcsvをグラフ化するだけ
データ
./csvディレクトリの下に年月日をファイル名として日毎に蓄積されるようにcgiを作成
C/Tは機械トラブル等が発生すると長くなってしまい可視化に影響を及ぼすので.inoプログラム側で100秒を上限として書き込むように設定されています
20190301.csv(※実際はヘッダ無しです)
date | time | num | c/t |
---|---|---|---|
2019/3/1 | 8:13:08 | 4514 | 100 |
2019/3/1 | 8:13:47 | 4515 | 38.568001 |
2019/3/1 | 8:14:28 | 4516 | 41.140999 |
2019/3/1 | 8:14:55 | 4517 | 27.17 |
2019/3/1 | 8:16:24 | 4518 | 87.883003 |
... | ... | ... | ... |
使用ライブラリ
「Javascript グラフ」で調べ、コード例が多く見つかったChart.jsを使用することに
公式にもたくさんサンプルが置いてあってgood ES6じゃないのが気になってしまうけど
https://www.chartjs.org
横軸が時刻になるのでmoment.jsが含まれているChart.bundle.min.js、バージョンは2.7.3を使用
構成
/test
├ index.html … グラフの表示
├ /js
│├ graph.js … グラフの設定
│└ Chart.bundle.min.js
└ /csv
├ ...
├ 20190301.csv
├ 20190302.csv
└ ...
コード
index.html
サイズのことは今は考えずにとりあえずページの大きさのグラフを描いてみます
jsファイルはcanvasよりも後ろに記述します(chart.jsはbody直後でも大丈夫)
このcanvasにグラフが描画されます
<html>
<head>
<title>test</title>
</head>
<body>
<div>
<canvas id="graph"></canvas>
</div>
<script src="./js/Chart.bundle.min.js"></script>
<script src="./js/graph.js"></script>
</body>
</html>
graph.js
2019年3月1日のデータ(20190301.csv)をグラフ化してみます
const label = [], num = [], ct = [];
const timeUnix = (time1, time2) => {
const n_time = new Date(time1 + " " + time2);
const time = n_time.getTime();
return time;
}
const req = new XMLHttpRequest();
req.open("GET", "./csv/20190301.csv", true);
req.send(null);
req.onload = () => {
if (req.status != 404) {
const line = req.responseText.split("\n");
const data = [];
for (let i = 0; i < line.length - 1; ++i) {
const cells = line[i].split(",");
data.push(cells);
}
for (let row in data) {
label.push(timeUnix(data[row][0], data[row][1]));
num.push(data[row][2]);
ct.push(data[row][3]);
}
}
}
Chart.defaults.global.defaultFontSize = 16;
Chart.defaults.global.tooltips.mode = 'index';
Chart.defaults.global.tooltips.position = 'nearest';
Chart.defaults.global.tooltips.intersect = false;
Chart.defaults.global.responsive = true;
Chart.defaults.global.legend.labels.boxWidth = 10;
Chart.defaults.global.elements.point = {
radius: 0,
pointHitRadius: 10,
};
Chart.defaults.global.elements.line = {
tension: 0,
borderWidth: 2,
fill: false,
borderDash: [],
};
const conf = {
type: 'line',
data: {
labels: label,
datasets: [{
label: '実績数', data: num, yAxisID: 'y-1',
borderColor: 'red', backgroundColor: 'red'
},{
label: 'C/T', data: ct, yAxisID: 'y-2',
borderColor: 'blue', backgroundColor: 'blue'
},],
},
options: {
title: {
display: true,
text: 'test',
fontSize: 30
},
scales: {
yAxes: [{
id: 'y-1',
position: 'right',
gridLines: {
drawOnChartArea: false
},
},{
id: 'y-2',
position: 'left',
scaleLabel: {
display: true,
labelString: 'sec'
},
ticks: {
min: 0,
max: 100,
stepSize: 20
},
},],
xAxes: [{
type: 'time',
time: {
displayFormat: 'h:mm'
},
},],
},
},
};
const chart = new Chart(document.getElementById('graph').getContext('2d'), conf);
表示
ちょっと説明
/* グラフにプロットするための配列を用意
ここにcsvのデータを分けてぶっこむ */
const label = [], num = [], ct = [];
/* 単純に年月日と時刻を結合してプロットしても
グラフにはただの文字列として認識されてしまうので
「時間」型に変換する必要がある、のでUnix時間に変換する関数を作成 */
const timeUnix = (time1, time2) => {
const n_time = new Date(time1 + " " + time2);
const time = n_time.getTime();
return time;
}
/* csvの読み込み */
const req = new XMLHttpRequest();
req.open("GET", "./csv/20190301.csv", true);
// 読み込んだcsvファイルはひとかたまりのテキストになる
req.send(null);
/* ファイルが存在しているときの動作 */
req.onload = () => {
if (req.status != 404) {
// テキストを改行で分割し配列へ
const line = req.responseText.split("\n");
// 更にそれを,で分割し配列へ
const data = [];
for (let i = 0; i < line.length - 1; ++i) {
const cells = line[i].split(",");
data.push(cells);
}
// 多次元配列のそれぞれの列を用意した配列にぶちこむ
for (let row in data) {
// 年月日と時刻はUnixに直しつつ入れる
label.push(timeUnix(data[row][0], data[row][1]));
num.push(data[row][2]);
ct.push(data[row][3]);
}
}
}
/* Chartのデフォルト設定
複数のグラフに同じ設定を使いたいとき便利
設定についての詳細は別で書きたい */
// フォントサイズ
Chart.defaults.global.defaultFontSize = 16;
// ツールチップ関係
Chart.defaults.global.tooltips.mode = 'index';
Chart.defaults.global.tooltips.position = 'nearest';
Chart.defaults.global.tooltips.intersect = false;
// レスポンシブにする
Chart.defaults.global.responsive = true;
// 凡例の■の大きさ
Chart.defaults.global.legend.labels.boxWidth = 10;
// プロット関係の設定
Chart.defaults.global.elements.point = {
// 点の大きさ
radius: 0,
// マウスホバーを認識する範囲
pointHitRadius: 10,
};
// 線関係の設定
Chart.defaults.global.elements.line = {
// 0だと直線、数値を増やすとぐにゃぐにゃになる
tension: 0,
// 線の太さ
borderWidth: 2,
// 線より下の領域を塗りつぶすか
fill: false,
// おまじない(書かないとエラー出る)
borderDash: [],
};
/* 個別の設定 */
const conf = {
// グラフのタイプ
type: 'line',
// プロットデータとその設定
data: {
// x軸方向にプロットするデータの配列
labels: label,
datasets: [{
label: '実績数', data: num, yAxisID: 'y-1',
borderColor: 'red', backgroundColor: 'red'
},{
label: 'C/T', data: ct, yAxisID: 'y-2',
borderColor: 'blue', backgroundColor: 'blue'
},],
},
options: {
title: {
display: true,
text: 'test',
fontSize: 30
},
scales: {
yAxes: [{
id: 'y-1',
position: 'right',
gridLines: {
drawOnChartArea: false
},
},{
id: 'y-2',
position: 'left',
scaleLabel: {
display: true,
labelString: 'sec'
},
ticks: {
min: 0,
max: 100,
stepSize: 20
},
},],
xAxes: [{
type: 'time',
time: {
displayFormat: 'h:mm'
},
},],
},
},
};
const chart = new Chart(document.getElementById('graph').getContext('2d'), conf);
途中で力尽きてる…