Building a live chart with Deno, WebSockets, Chart.js and Materialize

2025-06-08

使用 Deno、WebSockets、Chart.js 和 Materialize 构建实时图表

介绍

[!警告]此演示包含不受支持的Materialize (0.26.x)
版本的示例

这是一个由 Deno、Web Sockets、Chart.js 和Materialize提供支持的实时图表的独立示例

Deno是一个简单且安全的 JavaScript 和 TypeScript 运行时,它使用 V8 引擎。与 Materialize 一样,Deno 也是用 Rust 编写的。

在此演示中,我们将构建一个简单的实时仪表板应用程序,用于显示来自 Deno Web Socket 服务器的实时数据。然后,Deno 将连接到 Materialize 并运行我们的实时物化视图TAIL以获取最新数据,并使用 Chart.js 将其显示在实时图表中。

概述

以下是该项目的简要概述:

  • 持续生成用户得分事件的模拟服务。
  • Redpanda 实例将用户得分事件存储在主题中。
  • 物化实例连接到 Redpanda 实例并从实时物化视图中的主题中提取数据,我们只需使用 SQL 即可实时查询。
  • Deno 后端服务连接到 Materialize 并 TAIL 实时物化视图以获取最新数据并将其显示在实时图表中。
  • 通过 Web 套接字连接到 Deno 应用程序并使用 Chart.js 在实时图表中显示数据的前端服务。

以下是该项目的图表:

Materialize + Deno + Chart.js + Web Sockets

先决条件

要运行此演示,您需要安装以下内容。

运行演示

首先,克隆存储库:



git clone git clone https://github.com/bobbyiliev/materialize-tutorials.git
cd materialize-tutorials
git checkout lts


Enter fullscreen mode Exit fullscreen mode

然后就可以访问目录了:



cd mz-deno-live-dashboard


Enter fullscreen mode Exit fullscreen mode

这样您就可以构建图像:



docker-compose build


Enter fullscreen mode Exit fullscreen mode

最后,您可以运行所有容器:



docker-compose up -d


Enter fullscreen mode Exit fullscreen mode

启动容器并生成演示数据可能需要几分钟。

之后,您可以http://localhost在浏览器中访问以查看演示:

Deno websockets 和 chart.js

接下来,让我们回顾一下 Materialize 设置和 Deno 后端设置。

实现设置

Deno 服务将在启动时执行以下 DDL 语句,因此我们不必手动运行它们:

  • 创建 Kafka 源:在 Materialize 中创建源并不会真正启动数据提取。您可以将非物化源视为 Materialize 连接源所需的元数据,但不处理任何数据:


CREATE SOURCE score
FROM KAFKA BROKER 'redpanda:9092' TOPIC 'score_topic'
FORMAT BYTES;


Enter fullscreen mode Exit fullscreen mode
  • 创建一个非物化视图,本质上只为我们提供它们所包含的语句的别名SELECT


CREATE VIEW score_view AS
    SELECT
        *
    FROM (
        SELECT
            (data->>'user_id')::int AS user_id,
            (data->>'score')::int AS score,
            (data->>'created_at')::double AS created_at
        FROM (
            SELECT CAST(data AS jsonb) AS data
            FROM (
                SELECT convert_from(data, 'utf8') AS data
                FROM score
            )
        )
    );


Enter fullscreen mode Exit fullscreen mode
  • 创建物化视图:


CREATE MATERIALIZED VIEW score_view_mz AS
    SELECT
        (SUM(score))::int AS user_score,
        user_id
    FROM score_view GROUP BY user_id;


Enter fullscreen mode Exit fullscreen mode

要检查视图和源是否已创建,请启动 Materialize CLI:



docker-compose run mzcli


Enter fullscreen mode Exit fullscreen mode

这只是一个预先安装了 postgres-client 的 docker 容器的快捷方式,如果您已经安装了,psql您可以运行psql -U materialize -h localhost -p 6875 materialize

然后检查观点和来源:



SHOW VIEWS;
-- Output:
-- +-----------------+
-- | score_view      |
-- | score_view_mz   |
-- +-----------------+

SHOW sources;
-- Output:
-- +-----------------+
-- | score           |
-- +-----------------+


Enter fullscreen mode Exit fullscreen mode

使用TAIL

接下来,为了实时查看结果,我们可以使用TAIL



COPY ( TAIL score_view_mz ) TO STDOUT;


Enter fullscreen mode Exit fullscreen mode

您将看到实时生成的新用户分数流。

我们还可以启动TAIL没有快照的查询,这意味着运行查询后您只会看到最新的记录:



COPY ( TAIL score_view_mz WITH (SNAPSHOT = false) ) TO STDOUT;


Enter fullscreen mode Exit fullscreen mode

这就是我们将在 Deno 应用程序中使用的功能,用于获取最高用户分数并将其显示在实时图表中。

有关该TAIL函数如何工作的更多信息,请参阅Materialize 文档

德诺

现在我们已经准备好了 Materialize,让我们回顾一下 Deno 设置。

我们将使用两个 Deno 模块:

  • 连接到 Materialize 的 Postgres 模块。
  • Web Sockets 模块用于创建与我们的前端服务的 Web Socket 连接。

backend您可以在目录中找到代码



import { WebSocketClient, WebSocketServer } from "https://deno.land/x/websocket@v0.1.4/mod.ts";
import { Client } from "https://deno.land/x/postgres/mod.ts";

// Specify your Materialize connection details
const client = new Client({
  user: "materialize",
  database: "materialize",
  hostname: "materialized",
  port: 6875,
});

await client.connect();
console.log("Connected to Postgres");

// Start a transaction
await client.queryObject('BEGIN');
// Declare a cursor without a snapshot
await client.queryObject(`DECLARE c CURSOR FOR TAIL score_view_mz WITH (SNAPSHOT = false)`);

const wss = new WebSocketServer(8080);

wss.on("connection", async function (ws: WebSocketClient) {
  console.log("Client connected");
  setInterval(async () => {
    const result = await client.queryObject<{ mz_timestamp: string; mz_diff: number, user_id: number, user_score: number}>(`FETCH ALL c`);
    for (const row of result.rows) {
      let message = { user_id: row.user_id, user_score: row.user_score };
      broadcastEvent(message);
    }
  } , 1000);

});

// Broadcast a message to all clients
const broadcastEvent = (message: any) => {
  wss.clients.forEach((ws: WebSocketClient) => {
    ws.send(JSON.stringify(message));
  });
}


Enter fullscreen mode Exit fullscreen mode

代码概要:

  • 由于 Materialize 与 Postgres 连接兼容,我们首先Clienthttps://deno.land/x/postgres/mod.ts模块中导入一个类。我们将使用该类连接到 Materialize 实例。
  • 接下来,我们创建一个新Client实例并将 Materialize 的凭证传递给它。
  • 然后我们调用connect()客户端实例上的方法来连接到Materialize。
  • 接下来,我们调用queryObject()客户端实例上的方法来启动一个事务,同时调用queryObject()客户端实例上的方法来声明一个没有快照的游标。
  • 最后,我们创建一个新WebSocketServer实例并将要监听的端口传递给它。
  • 然后我们在实例connection上定义一个事件处理程序WebSocketServer,当客户端连接时调用该事件处理程序。
  • 然后,我们设置一个间隔来从 Materialize 获取最新数据并将其广播给所有客户端。

前端设置

对于前端,我们不会使用任何 JavaScript 框架,而只使用Chart.js 库

借助 Web 套接字连接,我们现在可以从 Materialize 接收最新数据并将其显示在实时图表中。



<!DOCTYPE html>
<html lang="en">
    <head>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    </head>
    <body>
        <div class="w-full mt-10">
            <canvas id="myChart"></canvas>
        </div>
    <script>
      const ctx = document.getElementById("myChart");
      const myChart = new Chart(ctx, {
        type: "bar",
        data: {
          labels: [ "Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6" ],
          datasets: [
            {
              label: "# of points",
              data: [0, 0, 0, 0, 0, 0],
              backgroundColor: [
                "rgba(255, 99, 132, 0.2)",
                "rgba(54, 162, 235, 0.2)",
                "rgba(255, 206, 86, 0.2)",
                "rgba(75, 192, 192, 0.2)",
                "rgba(153, 102, 255, 0.2)",
                "rgba(255, 159, 64, 0.2)",
              ],
              borderColor: [
                "rgba(255, 99, 132, 1)",
                "rgba(54, 162, 235, 1)",
                "rgba(255, 206, 86, 1)",
                "rgba(75, 192, 192, 1)",
                "rgba(153, 102, 255, 1)",
                "rgba(255, 159, 64, 1)",
              ],
              borderWidth: 1,
            },
          ],
        },
        options: {
          scales: {
            y: {
              beginAtZero: true,
            },
          },
        },
      });

      webSocket = new WebSocket("ws://127.0.0.1:8080");
      webSocket.onmessage = function (message) {
        const data = message.data;
        const dataObj = JSON.parse(data);
        const dataArray = Object.values(dataObj);
        console.log(dataArray);
        index = dataArray[0] - 1;
        myChart.data.datasets[0].data[index] = dataArray[1];
        myChart.update();
      };
    </script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

代码概要:

  • 我们首先使用库定义新图表Chart.jsnew Chart()并传递不同的配置选项。
  • 然后我们创建一个新WebSocket实例,并将 Web Socket 服务器的 URL 传递给它webSocket = new WebSocket("ws://backend:8080");
  • 最后,我们在实例onmessage上定义一个事件处理程序WebSocket,当收到消息并更新图表时调用该事件处理程序。

frontend您可以在目录中找到代码

结论

您可以让 Deno 应用程序保持运行,以便它订阅 Materialize 实例并实时更新图表。

下一步,您可以查看基于相同用户评论模拟数据的 Materialize + dbt + Redpanda 演示:

Materialize + dbt + Redpanda 演示

有用的资源:

社区

如果您有任何问题或意见,请加入Materialize Slack 社区

鏂囩珷鏉ユ簮锛�https://dev.to/bobbyiliev/building-a-live-chart-with-deno-websockets-chartjs-and-materialize-3cd7
PREV
使用 Materialize 解耦微服务架构
NEXT
9 个值得贡献的开源项目 - Hacktoberfest 2024 LaraSail Content Deleight