【JavaScript 2020】 MQTT で受信したデータを Smoothie Charts（smoothie.js）以外でリアルタイムにグラフ化：  Chart.js とプラグインを利用

今まで JavaScript で数値データをリアルタイムにグラフ化する際、「[Smoothie Charts（smoothie.js）](http://smoothiecharts.org/)」を使うことがあったのですが、他のライブラリも試してみようと思い行った内容です。また、描画対象となるデータの入力について、MQTT で受信して受け取る形にしてみました。

今回の内容は JavaScriptネタなので、後付けで [JavaScript のアドベントカレンダー](https://qiita.com/advent-calendar/2020/javascript2)の 22日目に登録をしてみたりもしました。

作ったものが動いた時の様子は以下のとおりです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Chart.js とプラグインを使ったデータの可視化のその後。<br><br>サンプルに手を加えて、MQTT を介して受け取ったデータを表示させる形に変更。MQTT関連の処理を加えたり、グラフへのデータ追加を Pull型 から Push型に変えたりしたのが差分です（見た目的な差分は少ないですがw）。<br><br> <a href="https://twitter.com/hashtag/%E5%A4%A7%E6%99%A6%E6%97%A5%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#大晦日ハッカソン</a> <a href="https://t.co/5cf53W4pTs">pic.twitter.com/5cf53W4pTs</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1344552407112376322?ref_src=twsrc%5Etfw">December 31, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## Smoothie Charts（smoothie.js）と他のグラフライブラリ
冒頭にも名前を出していた smoothie.js は、6〜7年前くらいに初心者向けの電子工作ハンズオンを実施する際、「Arduino につないだセンサから取得した値を Webページ上に表示する」ということを行おうとして使ったのが最初でした。

今年も、例えば以下の記事を書くときなどに使いました。ちなみに、以下の記事では、Web Bluetooth API でリアルタイムに受信した値を、グラフのデータの入力として用いています。

●【toio 2020】 #toio のシェイク検出の情報を Web Bluetooth API で受け取り Smoothie Charts（smoothie.js）でリアルタイムにグラフ化 - Qiita
　https://qiita.com/youtoy/items/82cf0e90e11bf68c0507

[公式ページのトップ](http://smoothiecharts.org/)でも、リアルタイムに動くグラフのサンプルを見ることができます。

### smoothie.js 以外のライブラリの調査

あらためて、smoothie.js 以外のライブラリの情報を検索していて、以下の記事などを見かけました。

- [Chart.js でリアルタイムストリーミングデータをグラフ化 - Qiita](https://qiita.com/narutaro/items/320b35a712bb3f058a75)
    - 【上記の記事内で引用されていたもの】[A simple guide: How to select a chart library to use? | by Jae Sung Park | Medium](https://medium.com/@alberto.park/a-simple-guide-how-to-select-a-chart-library-to-use-6f17878248f0)
- [JSのグラフライブラリを今選ぶならHighchartsで決まり（2020年版） - Qiita](https://qiita.com/grgrjnjn/items/fa39778657493008c3b6)

最終的には、上記の 1つ目の記事で、[Chart.js](https://www.chartjs.org/) と [「chartjs-plugin-streaming」というプラグイン](https://nagix.github.io/chartjs-plugin-streaming/ja/)を用いた話が出てきていて、これを試してみることにしました。

実際に描画した例として、このプラグイン（chartjs-plugin-streaming）の公式ページで、以下のような色付けされたグラフの事例を見ることができます。
![公式のグラフ描画（色付き）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3f997a6a-904a-9d03-8b96-6051a2c8fe47.jpeg)

## Chart.js とプラグイン（chartjs-plugin-streaming）
[Chart.js](https://www.chartjs.org/) は、8種類のグラフを Canvas上に描画することが可能な、オープンソースのグラフライブラリです。
また、今回使ってみる[プラグイン（chartjs-plugin-streaming）](https://nagix.github.io/chartjs-plugin-streaming/ja/)は、Chart.js でリアルタイムなグラフ描画を行うために利用できるプラグインです。以下のようにフレームワークと組み合わせた利用も想定された仕組みのようです。

![プラグインのトップ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9aec6342-7196-c1f8-b08f-ad44371c8a86.jpeg)

詳細な仕様は[GitHubに用意されたページ](https://github.com/nagix/chartjs-plugin-streaming)の説明を見ていただくのが良いです。

### プラグイン「chartjs-plugin-streaming」のサンプルを試す
プラグインである chartjs-plugin-streaming を使う場合、公式ページに記載されているとおり、自身のライブラリを含めて以下を読み込む必要があります。

```JavaScript
<script type="text/javascript" src="moment.js"></script>
<script type="text/javascript" src="Chart.js"></script>
<script type="text/javascript" src="chartjs-plugin-streaming.js"></script>
```
今回は、これらのライブラリを CDN から読み込むことにしてみます。

- [cdnjs の Moment.js のページ](https://cdnjs.com/libraries/moment.js)より
    - `<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>`
- [Chart.js公式の「Getting Started」](https://www.chartjs.org/docs/latest/getting-started/)より
    - `<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>`
- [jsDelivr の chartjs-plugin-streaming のページより](https://www.jsdelivr.com/package/npm/chartjs-plugin-streaming)
    - `<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.8.0/dist/chartjs-plugin-streaming.min.js"></script>`

この 3つの中に「[Moment.js](https://github.com/moment/moment)」が含まれていますが、こちらはちょこちょこ名前を見かけたり、以下の記事等で見かけて気になっていた「[Day.js](https://github.com/iamkun/dayjs/blob/dev/docs/ja/README-ja.md)」に置きかえられないかなどとも思ったりしたのですが、ライブラリに手を入れる必要がありそうなので機会があれば試してみようと思います。

- [JSの日付ライブラリは、どれを使えばいいのかまとめ](https://zenn.dev/imaginelab/articles/68de83f4948fe2)
- [JavaScript dayjsはMoment.jsの代替になるか? | nansystem](https://nansystem.com/dayjs-alternative-to-momentjs/)
- [JavaScript での時刻操作に Moment.js ではなく Day.js を利用し続けている理由 | potato4d D(iary)](https://d.potato4d.me/entry/20200921-migrate-to-dayjs/)
- [cdnjs の Days.jsページ](https://cdnjs.com/libraries/moment.js)より：CDN での読み込み
    - `<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>`

#### 元になるソースコード
ソースコードの元になるものは、公式ページトップの内容（公式の手順5「遅延を追加する」までの内容）を使いました。
具体的には以下のとおりです。

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>リアルタイムなグラフ描画</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.8.0/dist/chartjs-plugin-streaming.min.js"></script>
  </head>
  <body>
    <canvas id="myChart"></canvas>
    <script>
      var ctx = document.getElementById("myChart").getContext("2d");

      var chart = new Chart(ctx, {
        type: "line",
        data: {
          datasets: [
            {
              data: [],
            },
            {
              data: [],
            },
          ],
        },
        options: {
          scales: {
            xAxes: [
              {
                type: "realtime",
                realtime: {
                  delay: 2000,
                  onRefresh: function (chart) {
                    chart.data.datasets.forEach(function (dataset) {
                      dataset.data.push({
                        x: Date.now(),
                        y: Math.random(),
                      });
                    });
                  },
                },
              },
            ],
          },
        },
      });
    </script>
  </body>
</html>
```

この内容で、以下のようなグラフがリアルタイムに描画されます。

![リアルタイムなグラフ描画1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c44a1324-de43-9eea-eb96-e13c86f8429a.gif)

### プラグイン「chartjs-plugin-streaming」と MQTT を組み合わせる

#### MQTT を利用するための準備
上記のサンプルで描画しているデータは、グラフを表示している Webページ内で生成された乱数です。これを外部のセンサー等の別デバイスから受け取った値に置きかえられるように、MQTT 経由でのデータ受信を行ってみます。

送信元には何らかのデバイスを用いてもよかったのですが、特別なデバイスの準備なしに簡単に試せるよう、グラフ描画を行うページとは別の Webページを作成し、それらのページ間を MQTT でつなぐ形にしてみます（今回、MQTT での送受信を行う処理には、ライブラリ「[MQTT.js](https://github.com/mqttjs)」を用います）。また、MQTT によるデータ送受信を行う仲介役の MQTTブローカーには [shiftr.io](https://www.shiftr.io/) の[クラウド版](https://www.shiftr.io/cloud)を用いることにします。

ここでは、MQTT についてや MQTTブローカー・shiftr.io についての説明は省略します。Qiita や Qiita 以外の記事でこれらを扱った記事は複数ありますので、気になる方はそれらのキーワードで記事を検索してみてください。

#### MQTT のブローカーの準備
MQTTブローカーは [shiftr.io](https://www.shiftr.io/) の[クラウド版](https://www.shiftr.io/cloud)を利用します。

MQTT による通信を行えるよう設定などを行うと、以下の情報が取得できるはずです。これらの情報を準備してから次にお進みください。

- username
- token secret

![shiftr.ioのトークンなど.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/435de59f-0d72-e918-e444-6692acdcfb9f.jpeg)

#### データの送信側の準備
今回のデータ送信側は、上で書いたとおり「数値データを乱数生成し、MQTT で生成した値を送信する Webページ」をグラフを表示させる Webページとは別に準備します。

乱数生成と、[MQTT.js](https://github.com/mqttjs) を用いたデータ送信を行うプログラムのソースは以下となります。以下のソースコードに関して、URL を指定する部分について、プロトコルは「mqtt」ではなく「wss」を用いる点に注意してください。

また URL を指定する部分では、以下の 2つの情報を記載する部分（合計 3箇所）がありますが、これらはご自身で準備された shiftr.io の設定の値をご利用ください。

- 【username】
- 【token secret】

また、MQTT のトピックを指定する部分で「【任意の文字列】」と書いてある部分は、任意の文字列をご利用ください（※ 送信側と受信側で一致させる必要があります）。

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>MQTTでデータを送る</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mqtt/4.2.6/mqtt.min.js"></script>
  </head>
  <body>
    <button onclick="start()">スタート</button>
    <script>
      const MQTTURL = "wss://【username】:【token secret】@【username】.cloud.shiftr.io";
      const TOPIC = "【任意の文字列】";

      let isStarted = false;
      function start() {
        if (!isStarted) {
          isStarted = true;
          let client = mqtt.connect(MQTTURL, {
            clientId: "Browser-" + Math.floor(Math.random() * 100), // ID Example: "Browser-23"
          });

          client.on("connect", function () {
            client.subscribe("presence", function (err) {
              if (!err) {
                console.log("接続");
                setInterval(pubMQTT, 1000);
              } else {
                console.log("接続エラー");
              }
            });
          });

          function pubMQTT() {
            const message = Math.random().toString();
            console.log("pub message: " + message);
            client.publish(TOPIC, message);
          }
        }
      }
    </script>
  </body>
</html>
```

上記のファイルをブラウザで開き、ページ内のボタンを押すと、1秒間隔で MQTT を用いたデータ送信が行われます。

#### データの受信側と実際に表示されたグラフ
MQTT でデータを送信する側は準備ができたので、ここでは受信側を準備します。

ソースコードは以下となります。

最初にグラフ描画を試したソースコードと比べると、グラフ用のデータを追加する部分が今回のものは「Push型」、最初に試したものは「Pull型」で、実装が違っていますのでご注意ください。なお、それら「Pull型/Push型」の詳細は[公式のGitHub の「Data Feed Models」](https://github.com/nagix/chartjs-plugin-streaming#data-feed-models) をご参照ください。

また URL を指定する部分・トピックを指定する部分は、送信側と同じように値を設定してください。。

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>リアルタイムなグラフ描画</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.8.0/dist/chartjs-plugin-streaming.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mqtt/4.2.6/mqtt.min.js"></script>
  </head>
  <body>
    <canvas id="myChart"></canvas>
    <script>
      const MQTTURL = "wss://【username】:【token secret】@【username】.cloud.shiftr.io";
      const TOPIC = "【任意の文字列】";

      let client = mqtt.connect(MQTTURL, {
        clientId: "Browser-" + Math.floor(Math.random() * 100), // ID Example: "Browser-23"
      });

      client.on("connect", function () {
        client.subscribe("presence", function (err) {
          if (!err) {
            console.log("接続");
            client.subscribe(TOPIC);
          } else {
            console.log("接続エラー");
          }
        });
      });

      const ctx = document.getElementById("myChart").getContext("2d");

      let chart = new Chart(ctx, {
        type: "line",
        data: {
          datasets: [
            {
              data: [],
            },
            {
              data: [],
            },
          ],
        },
        options: {
          scales: {
            xAxes: [
              {
                type: "realtime",
                realtime: {
                  delay: 2000,
                },
              },
            ],
          },
        },
      });

      client.on("message", function (topic, message) {
        console.log(message.toString());

        chart.data.datasets[0].data.push({
          x: Date.now(),
          y: message.toString(),
        });

        chart.update({
          preservation: true,
        });
      });
    </script>
  </body>
</html>
```

MQTT を介して送られたデータを描画した場合のグラフ表示は、以下となりました。

![リアルタイムなグラフ描画2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/feb4bbfd-e2a6-b411-3f78-2620122e95e7.gif)

## まとめ
今回、MQTT を使って送信された数値データを、Chart.js とプラグインを利用してリアルタイムにグラフ表示させてみました。

今回の内容について、グラフ表示では色付け等のカスタマイズは行ってなく、データの送信側についてはダミーデータを用いていました。これらの部分は、グラフ表示のカスタマイズを行ってみたり、データの送信元を M5Stack などのセンサー内蔵/センサーの外付けが可能な通信機能を持つデバイスにしてみるなどといった事を行ってみるのも良さそうです。
またの機会に、そういった方向のこともやってみようと思います。