タイトルのまんまです。Node.jsを使います。
Node.jsでsubscribeして取得したデータをwebsocket通信でリアルタイムにChart.jsに反映させます。
まずは、Node.jsのプログラム部。3000番ポートのみを開放しています。
POT と COFFEE の2つのチャンネルをsubscribeします。
もしデータが来れば、websocket にemitします。(websocketに送ります)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var http = require('http'); var socketio = require('socket.io'); var fs = require('fs'); var sys = require('sys'); var client = require('redis').createClient(6379, 'localhost'); var io = socketio.listen(server); client.subscribe('POT'); client.subscribe('COFFEE'); client.on("message", function(channel,message){ console.log(channel + " : " + message); io.sockets.emit('server_to_client', {type:channel, datetime:message}); }); var server = http.createServer(function(req, res) { res.writeHead(200, {'Content-Type' : 'text/html'}); res.end(fs.readFileSync(__dirname + '/TimeCheckerGraph.html', 'utf-8')); }).listen(3000); |
次に、クライアントが開くHTMLファイルです。
websocketの通信にエラーが出ると再送はしない設計です。
Chartは2つありますが、
- 日ごとの水入れ替え回数を1ヶ月分表示
- これまでの水入れ替え回数を1時間単位で並べて表示
の2つです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>TimeChecker Graph</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script> <script src="/socket.io/socket.io.js"></script> </head> <body> <div class="container"> <h1>TimeChecker Graph</h1> <br> <h4>表示期間:<span id="year"></span>年<span id="month"></span>月</h4> <br> <button id="before" type="button" class="btn btn-primary">前の月へ</button> <button id="next" type="button" class="btn btn-primary">次の月へ</button> <div id="chatLogs"></div> <br><br> <div class="col-md-9"> <canvas id="monthly"></canvas> <br><br> <h4>統計(全期間)</h4> <br> <canvas id="allhour"></canvas> <br><br><br> </div> </div> </body> <script type="text/javascript"> const phpURL = "%%DBのログデータをすべて取得するPHPファイルのURL%%"; var socket = io.connect(); var param; //送られてくるパラメタ var get_param = location.search; //送るパラメタ var monthlyChart; var allhoursChart; //ボタンアクション関係 document.getElementById("before").onclick = function() { var _date = new Date(param.year, param.month, 1); _date.setMonth(_date.getMonth() - 1); var _year = _date.getFullYear(); var _month = ("0"+_date.getMonth()).slice(-2); get_param = "?date=" + _year + "-" + _month; updateData(); } document.getElementById("next").onclick = function() { var _date = new Date(param.year, param.month, 1); _date.setMonth(_date.getMonth() + 1); var _year = _date.getFullYear(); var _month = ("0"+_date.getMonth()).slice(-2); get_param = "?date=" + _year + "-" + _month; updateData(); } //Websocket関連 socket.on('connect_error', (error) =>{ socket.close(); }); socket.on("server_to_client", function(data){ console.log(data); var time = new Date(data.datetime); day = time.getDay(); if (data.type == 'POT') { param.mpValues[day]++; } if (data.type == 'COFFEE') { param.mcValues[day]++; } updateChart(); }); //読み込み時 window.onload = function() { updateData(true); } //チャート関連(最初だけCreateChartが走る) function updateData(create = false) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { if (xmlhttp.status == 200) { param = JSON.parse(xmlhttp.response); var year = document.getElementById("year"); year.innerText = param.year; var month = document.getElementById("month"); month.innerText = param.month; if (create == true) { createChart(); } updateChart(); } else { alert("status = " + xmlhttp.status); } } } xmlhttp.open("GET", phpURL + get_param); xmlhttp.send(); } function createChart() { var ctx = document.getElementById("monthly").getContext('2d'); var cty = document.getElementById("allhour").getContext('2d'); monthlyChart = new Chart(ctx, { type: 'bar', data: { labels: Object.keys(param.mLabels).map(function(key){return param.mLabels[key]}), datasets: [{ label: 'ポットの水', data: Object.keys(param.mpValues).map(function(key){return param.mpValues[key]}), backgroundColor: 'rgba(255, 99, 132, 0.5)', borderColor: 'rgba(255,99,132,1)', borderWidth: 1 },{ label: 'コーヒーメーカーの水', data: Object.keys(param.mcValues).map(function(key){return param.mcValues[key]}), backgroundColor: 'rgba(54, 162, 235, 0.5)', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1 } ] }, options: { scales: { xAxes: [{ scaleLabel:"日付", }], yAxes: [{ scaleLabel:"回数", ticks: { beginAtZero:true, stepSize:1 } }] } } }); allhoursChart = new Chart(cty, { type: 'line', data: { labels: Object.keys(param.dLabels).map(function(key){return param.dLabels[key]}), datasets: [{ label: 'これまでの水換え記録', data: Object.keys(param.dValues).map(function(key){return param.dValues[key]}), backgroundColor: 'rgba(75, 192, 192, 0.2)', borderColor: 'rgba(75, 192,192,1)', borderWidth: 1 }, ] }, options: { scales: { yAxes: [{ ticks: { beginAtZero:true, stepSize:1 } }] } } }); } function updateChart(){ monthlyChart.data.labels = Object.keys(param.mLabels).map(function(key){return param.mLabels[key]}); monthlyChart.data.datasets[0].data = Object.keys(param.mpValues).map(function(key){return param.mpValues[key]}); monthlyChart.data.datasets[1].data = Object.keys(param.mcValues).map(function(key){return param.mcValues[key]}); monthlyChart.update(); allhoursChart.data.datasets[0].data = Object.keys(param.dValues).map(function(key){return param.dValues[key]}), allhoursChart.update(); } </script> </html> |
上記のHTMLファイル内のjavascript変数 phpURL の先に置くphpファイルが必要になります。
前々記事で作成したテーブルを使用して動作確認を行います。(前々記事)
もし、header(“Access-Control-Allow-Origin: *”) の一文がなければ、javascript内から送られてきたリクエストを受けることが出来ませんので忘れないようにしましょう。
個人的にjavascriptよりPHPの方が得意なので、処理は極力PHPファイル内で行っています。
Chart.jsで必要なラベル配列やデータ配列をJSONにどんどん詰め込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<?php #ini_set('display_errors', 1); #error_reporting(E_ALL); header("Access-Control-Allow-Origin: *"); require_once('%%mysql_connect が存在するファイルのパス%%'); $pdo = connectDB(); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); date_default_timezone_set('Asia/Tokyo'); $date = $_GET['date']; if (strlen($date) == 7) { list($year, $month) = explode('-', $date); if ($month == '00') { $year--; $month = '12'; }elseif ($month == '13') { $year++; $month='01'; } $day = '01'; }elseif (strlen($date) == 10) { list($year, $month, $day) = explode('-', $date); } if (!checkdate($month, $day, $year)) { $year = date('Y'); $month = date('m'); $day = date('d'); } $lastDay = date("t", strtotime($year.'-'.$month.'-'.$day)); for($i = 1; $i <= $lastDay; $i++) { $mLabels[$i] = intval($month)."/".$i; $mpValues[$i] = 0; $mcValues[$i] = 0; } for($i = 0; $i < 24; $i++) { $dLabels[$i] = $i."時"; $dValues[$i] = 0; } $data; try{ $stmt=$pdo->prepare("SELECT DATE_FORMAT(date, '%Y-%m-%d') as time, COUNT(*) as count FROM TimeCheckerLog WHERE type = 'POT' AND date BETWEEN :month AND :nextMonth GROUP BY DATE_FORMAT(date, '%Y-%m-%d')"); $thisMonth = $year.'-'.$month.'-01'; $dtnext = new DateTime($thisMonth); $nextMonth = $dtnext->modify('+1 months')->format('Y-m-d'); $stmt->bindParam(":month", $thisMonth , PDO::PARAM_STR); $stmt->bindParam(":nextMonth", $nextMonth, PDO::PARAM_STR); $stmt->execute(); foreach($stmt as $value) { list($_, $_, $d) = explode('-', $value['time']); $mpValues[intval($d)] = $value['count']; } $stmt2=$pdo->prepare("SELECT DATE_FORMAT(date, '%Y-%m-%d') as time, COUNT(*) as count FROM TimeCheckerLog WHERE type = 'COFFEE' AND date BETWEEN :month AND :nextMonth GROUP BY DATE_FORMAT(date, '%Y-%m-%d')"); $stmt2->bindParam(":month", $thisMonth , PDO::PARAM_STR); $stmt2->bindParam(":nextMonth", $nextMonth, PDO::PARAM_STR); $stmt2->execute(); foreach($stmt2 as $value) { list($_, $_ , $d) = explode('-', $value['time']); $mcValues[intval($d)] = $value['count']; } } catch (Exception $e) { $data['ERROR'] = $e; } $stmt3=$pdo->prepare("SELECT DATE_FORMAT(date, '%H') as time, COUNT(*) as count FROM TimeCheckerLog GROUP BY DATE_FORMAT(date, '%H')"); $stmt3->bindParam(":month", $thisMonth , PDO::PARAM_STR); $stmt3->bindParam(":nextMonth", $nextMonth, PDO::PARAM_STR); $stmt3->execute(); foreach($stmt3 as $value) { $key = $value['time']; $dValues[intval($key)] = $value['count']; } $pdo = null; $data['mLabels'] = $mLabels; $data['mpValues'] = $mpValues; $data['mcValues'] = $mcValues; $data['year'] = $year; $data['month'] = $month; $data['day'] = $day; $data['date'] = $date; $data['dLabels'] = $dLabels; $data['dValues'] = $dValues; echo json_encode($data); ?> |
これらをサーバに置いて、
1 |
$node app.js |
として、HTMLファイルにアクセスすれば、
こんな感じに出るはず。
前々回の記事のpythonアプリからpublishすれば、ここのグラフが動的に変わります。