モーションセンサの可視化に使えるリアルタイムチャートをDjango ChannelsとWebGL-Plotでつくってみた

# はじめに
「[リアルタイムチャートをDjango ChannelsとEpoch.jsでつくってみた](https://qiita.com/yakiimo121/items/b008398dfc36e19125f2)」の続編です。前回はDjango ChannelsとEpoch.jsを用いて、モーションセンサのリアルタイム可視化に挑戦しました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Django ChannelsとEpoch.jsでセンサデータをリアルタイムに表示するチャート作ってみました。Django Channelsを使用することで、HTTPと同じくらい簡単にWebsocketを実装出来ました。Epoch.jsも学習コストが低く使いやすかったです。 <a href="https://t.co/oflpVucoVk">pic.twitter.com/oflpVucoVk</a></p>&mdash; yakiimo121 (@yakiimo121) <a href="https://twitter.com/yakiimo121/status/1406586719856594949?ref_src=twsrc%5Etfw">June 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


しかし、約100Hzで流れてくる加速度データをEpoch.jsでは捌ききれず、微妙な結果に終わりました。
> Epoch.jsでも100Hzのモーションセンサデータをリアルタイムで描画するのは困難でした。遅延が発生したり、それにともないキューからデータがこぼれたりします。さすがにWebベースの限界なのでしょうか？それとも他のライブラリを使えば解決できるのでしょうか？？（「[リアルタイムチャートをDjango ChannelsとEpoch.jsでつくってみた/まとめ](https://qiita.com/yakiimo121/items/b008398dfc36e19125f2#%E3%81%BE%E3%81%A8%E3%82%81)」より）

そこで、今回はEpoch.jsの代わりにWebGL-Plotという可視化ライブラリを用いて、リベンジしようと思います。

完成版はこちらに上げておきます。

https://github.com/yakiimo121/realtime_chart_project

# WebGL-Plotとは
ネイティブWebGLベースの高性能リアルタイム2Dプロットライブラリです。

https://webgl-plot.vercel.app/

https://github.com/danchitnis/webgl-plot

**Performance (パフォーマンス)**
> Fast, simple and efficient plotting library with no dependencies based on native WebGL. High refresh rate and ideal for low resource devices

（和訳）ネイティブのWebGLをベースにした、依存性のない高速でシンプルかつ効率的なプロッティングライブラリです。高リフレッシュレートで低リソースデバイスに最適。

高リフレッシュレートである点が今回のポイントです。

**Versatile (多用途)**
> Flexible to use as vanilla Javascript or in combination with variety of popular frameworks and configurations.

（和訳）Javascript単体での使用はもちろん、様々なフレームワークや構成との組み合わせにも柔軟に対応します。

Epoch.jsのときと同様にvanilla Javascriptで使います。

**Limitations (制限)**
> It cannot change the line width due to the OpenGL implementation of a line. The OpenGL specification only guarantees a minimum of a single pixel line width. There are other solutions to increase the line width however they substantially increase the size of the data vector and take a hit on the performance. Top performance (refresh rate, memory, etc) is the top priority for this library.

（和訳）OpenGLの線の実装により、線幅を変更することはできません。OpenGLの仕様では、最低でも1ピクセルの線幅しか保証されていません。線幅を広げる方法は他にもありますが、データベクタのサイズが大きくなり、性能に打撃を与えます。このライブラリでは、最高のパフォーマンス（リフレッシュレート、メモリなど）が最優先されます。

リフレッシュレートやメモリなどに性能を振っているため、線幅の調整などで融通の利かない部分があります。

# 実装
[前回のもの](https://qiita.com/yakiimo121/items/b008398dfc36e19125f2)をベースにしています。

はじめに、`<script>`タグで`webglplot.umd.min.js`を読み込み、`<canvas>`タグでWebGL APIを使うためのcanvasを作成します。

```html
<script src="https://cdn.jsdelivr.net/gh/danchitnis/webgl-plot@master/dist/webglplot.umd.min.js"></script>
<div>
  <canvas class="canvas" id="my_canvas"></canvas>
</div>
```

続いて、cssでブラウザ全体とcanvasのスタイルを調整します。

```css
body {
    background-color: rgb(255, 255, 255);
    color: rgb(0, 0, 0);
    font-family: Arial, Helvetica, sans-serif;
}
.canvas {
    width: 100%;
    height: 70vh;
}
```

最後にjavascriptの実装を行います。大きく分けて二つの機能があります。

- WebSocketでDjangoからデータを受け取る機能
- WebGL-Plotでグラフを描画する機能

WebSocketの実装は前回のEpoch.jsのときと同じです。WebGL-Plotは[公式のドキュメント](https://webgl-plot.vercel.app/docs)を参考に実装しました。


```js
const dataSocket = new WebSocket('ws://'+window.location.host+'/ws/');

var x_value = 0;
var y_value = 0;
var z_value = 0;
var i = 0;

dataSocket.onmessage = function(e) {
    const current = JSON.parse(e.data);
    x_value = current[0]["y"];
    y_value = current[1]["y"];
    z_value = current[2]["y"];
};

const canvas = document.getElementById("my_canvas");

const devicePixelRatio = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;

const numX = canvas.width;
const color_r = new WebglPlotBundle.ColorRGBA(255, 0, 0, 1,);
const color_b = new WebglPlotBundle.ColorRGBA(0, 0, 255, 1,);
const color_g = new WebglPlotBundle.ColorRGBA(0, 255, 0, 1,);

const line1 = new WebglPlotBundle.WebglLine(color_r, numX);
const line2 = new WebglPlotBundle.WebglLine(color_b, numX);
const line3 = new WebglPlotBundle.WebglLine(color_g, numX);
const wglp = new WebglPlotBundle.WebglPlot(canvas);

line1.lineSpaceX(-1, 2 / numX)
line2.lineSpaceX(-1, 2 / numX)
line3.lineSpaceX(-1, 2 / numX)
line1.scaleY = 0.5;
line2.scaleY = 0.5;
line3.scaleY = 0.5;
wglp.addLine(line1);
wglp.addLine(line2);
wglp.addLine(line3);

function newFrame() {
    update();
    wglp.update();
    requestAnimationFrame(newFrame);
}
requestAnimationFrame(newFrame);

function update() {
    if (i > line1.numPoints){
        line1.shiftAdd([x_value]);
        line2.shiftAdd([y_value]);
        line3.shiftAdd([z_value]);
    }else{
        line1.setY(i, x_value);
        line2.setY(i, y_value);
        line3.setY(i, z_value);
    }
    i+=1;
}

dataSocket.onclose = function(e) {
    console.error('Chat socket closed unexpectedly');
};
```

# 実行
前回と同様にスマートフォンで観測した3軸加速度データをリアルタイムで可視化するデモを行います。

初めに[HASC Logger](http://hasc.jp/tools/hasclogger.html)をスマートフォンにインストールします。
HASC Loggerから`[ホストのIP]:4001`へ３軸加速度をUDPで送信します。設定の方法は「[角速度から回転行列を求める - 実装編](https://qiita.com/yakiimo121/items/1433044a2ca975f0eebe)」を参考にしてください。

リアルタイムチャートアプリを起動します。

```shell
python manage.py migrate
python manage.py runserver
```

ブラウザから`http://[ホストのIP]:8000/realtimechart/webglplot`へアクセスします。（Epoch.js版は`http://[ホストのIP]:8000/realtimechart/epochjs`からアクセスできます。）

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Django ChannelsとWebGL-Plotでセンサデータをリアルタイムで表示するチャートを作ってみました。100Hzで流れてくるモーションセンサでも遅延なく描画できてます。<a href="https://t.co/iJy4YsIPQf">https://t.co/iJy4YsIPQf</a> <a href="https://t.co/SYnJCS9j7W">pic.twitter.com/SYnJCS9j7W</a></p>&mdash; yakiimo121 (@yakiimo121) <a href="https://twitter.com/yakiimo121/status/1478625442710306820?ref_src=twsrc%5Etfw">January 5, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ
今回はDjango channelsとWebGL-Plotを用いてセンサデータをリアルタイムで表示するチャートを作ってみました。WebGL-Plotを用いることで、[Epoch.jsを用いた可視化システム](https://qiita.com/yakiimo121/items/b008398dfc36e19125f2)の課題であったリフレッシュレートを改善しました。結果、約100Hzで流れてくる加速度のリアルタイム可視化に成功しました。

しかし、WebGL-Plotには目盛、凡例、軸ラベル、グリッドなどを表示させる機能がありませんでした（自分調べ）。こう言った点の使い勝手の良さはEpoch.jsの方が上だと思います。

# 参考
- [リアルタイムチャートをDjango ChannelsとEpoch.jsでつくってみた](https://qiita.com/yakiimo121/items/b008398dfc36e19125f2)
- [webgl-plot](https://webgl-plot.vercel.app/docs)