🐙

話題のHTMXを使って気軽にリアルタイムグラフを作りたい

2024/03/07に公開

はじめに

アイディオットDX開発部の中川です。
リアルタイムなデータ解析周りを実装している際に、
少し前から話題になっていたHTMXが気になりレンタルサーバーでも動作するような
簡単なデモを作成してみようと思いました。

作ったもの

このようなグラフですが本来WebSocketを使わないと難しいものを
ポーリングに近い形でSSEと組み合わせて動作させています。
グラフはおなじみPlotlyです。

htmx_graph

HTMX記述

こちらのHTMX記述は非常に単純ですが、
レンタルサーバーにおいて通常のようにSSE(Server Sent Events)は動作しませんでした。
そのためそこをポーリングの要領で一定間隔にアクセスすることで、
SSE処理を記載したPHPを動作させるという、ある種荒技になります。

<div id="time" hx-get="events.php" hx-trigger="every 1s" style="display:none"></div>

hx-triggerにてevery 1sと記載すると1秒ごとにhx-getにて指定したevents.phpにアクセスします。
本来はその返却値をhx-swap="innerHtml"などで<div>の中で表示するのですが。
今回はグラフ表示が目的のためdisplay:noneで非表示にしています。

これだけの記述でajaxと同じ処理ができてしまうのが、
やはりhtmxの良いところですね!

SSE送信

SSEの処理は本来メインループを記載して
サーバー上にてWebSocketのように常に動作させるのが通常なのですが、
レンタルサーバーでも動く構成ということでアクセスするたびに返却する
普通のスクリプトとして記載しています。

events.php

<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");

$time = date('r');
$data = rand(1, 100); // デモ用のランダムデータ

echo "data: {$data}\n\n";
ob_flush();
flush();

SSE受信

SSEの発火トリガーは先に記載したHTMX記述で行っていました。
受け取りはjsにてEventSourceで受け取っています。

// SSEを購読
const source = new EventSource('events.php');
source.onmessage = function(event) {
    // ここに受け取った時の処理を記載
};

ソース全文

最後にソースの全文を載せておきます。

events.php

<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");

$time = date('r');
$data = rand(1, 100); // デモ用のランダムデータ

echo "data: {$data}\n\n";
ob_flush();
flush();

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>htmx every example</title>
        <script src="https://unpkg.com/htmx.org"></script>
    </head>
    <body>

    <div id="time" hx-get="events.php" hx-trigger="every 1s" style="display:none"></div>

    <div id="chart"></div>

    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    <script>
        // データポイントの最大数を定義
        const maxDataPoints = 20;

        // Plotlyグラフの初期設定
        const trace = {
            y: [],
            type: 'scatter'
        };
        const layout = {
            title: 'Real-time Data with Limited Points'
        };
        Plotly.newPlot('chart', [trace], layout);

        // SSEを購読
        const source = new EventSource('events.php');
        source.onmessage = function(event) {
            console.log("done!");

            const newData = JSON.parse(event.data);

            // データが最大数を超えたら、古いデータを削除
            if(trace.y.length >= maxDataPoints) {
                trace.y.shift(); // 配列の先頭を削除
            }

            // 新しいデータを追加
            Plotly.extendTraces('chart', { y: [[newData]] }, [0]);

            // グラフが更新された後に、データポイントの最大数を超えていれば修正
            if(trace.y[0].length > maxDataPoints) {
                const update = {
                    y: [trace.y[0].slice(-maxDataPoints)] // 最後のmaxDataPoints個のデータのみを保持
                };
                Plotly.restyle('chart', update, [0]);
            }
        };
    </script>
    </body>
</html>

最後に

こちらかなり簡単なデモでしたが、
うまく使うことで双方向の通信も可能となります。
HTMXはまだまだ設計によってできることが多そうなので
引き続き実験してみたいと思います。

あとがき

AI・データ利活用をリードし、世界にインパクトを与えるプロダクトを開発しませんか?

アイディオットでは、今後の事業拡大及びプロダクト開発を担っていただけるエンジニアチームの強化を行っております。
さらに会社の成長を加速させるため、フロントエンドエンジニア、バックエンドエンジニア、インフラエンジニアのメンバーを募集しております!
日本を代表する企業様へ自社プロダクトを活用した、新規事業コンサルティング、開発にご興味のある方はお気軽にご連絡ください。

【リクルートページ】
https://aidiot.jp/recruit/
【募集ポジション一覧】
https://open.talentio.com/r/1/c/aidiot/homes/3925
【採用についてのお問合せ先】
株式会社アイディオット 採用担当:大島

Discussion