diff --git a/docs/en/10-third-party/03-visual/02-perspective.md b/docs/en/10-third-party/03-visual/02-perspective.md
index abb2baae23..056abed8f6 100644
--- a/docs/en/10-third-party/03-visual/02-perspective.md
+++ b/docs/en/10-third-party/03-visual/02-perspective.md
@@ -19,7 +19,7 @@ Perform the following installation operations in the Linux system:
- Python version 3.10 or higher has been installed (if not installed, please refer to [Python Installation](https://docs.python.org/)).
- Download or clone the [perspective-connect-demo](https://github.com/taosdata/perspective-connect-demo) project. After entering the root directory of the project, run the "install.sh" script to download and install the TDengine client library and related dependencies locally.
-## Data Analysis
+## Visualize data
**Step 1**, Run the "run.sh" script in the root directory of the [perspective-connect-demo](https://github.com/taosdata/perspective-connect-demo) project to start the Perspective service. This service will retrieve data from the TDengine database every 300 milliseconds and transmit the data in a streaming form to the web-based `Perspective Viewer`.
@@ -33,6 +33,8 @@ sh run.sh
python -m http.server 8081
```
+The effect presented after accessing the web page through the browser is shown in the following figure:
+

## Instructions for use
@@ -56,40 +58,9 @@ The `perspective_server.py` script in the root directory of the [perspective-con
3. Create a Perspective table (the table structure needs to match the type of the table in the TDengine database).
4. Call the `Tornado.PeriodicCallback` function to start a scheduled task, thereby achieving the update of the data in the Perspective table. The sample code is as follows:
- ```python
- def perspective_thread(perspective_server: perspective.Server, tdengine_conn: taosws.Connection):
- """
- Create a new Perspective table and update it with new data every 50ms
- """
- # create a new Perspective table
- client = perspective_server.new_local_client()
- schema = {
- "timestamp": datetime,
- "location": str,
- "groupid": int,
- "current": float,
- "voltage": int,
- "phase": float,
- }
- # define the table schema
- table = client.table(
- schema,
- limit=1000, # maximum number of rows in the table
- name=PERSPECTIVE_TABLE_NAME, # table name. Use this with perspective-viewer on the client side
- )
- logger.info("Created new Perspective table")
-
- # update with new data
- def updater():
- data = read_tdengine(tdengine_conn)
- table.update(data)
- logger.debug(f"Updated Perspective table: {len(data)} rows")
-
- logger.info(f"Starting tornado ioloop update loop every {PERSPECTIVE_REFRESH_RATE} milliseconds")
- # start the periodic callback to update the table data
- callback = tornado.ioloop.PeriodicCallback(callback=updater, callback_time=PERSPECTIVE_REFRESH_RATE)
- callback.start()
- ```
+```python
+{{#include docs/examples/perspective/perspective_server.py:perspective_server}}
+```
### HTML Page Configuration
@@ -100,77 +71,7 @@ The `prsp-viewer.html` file in the root directory of the [perspective-connect-de
- Import the Perspective library, connect to the Perspective server via a WebSocket, and load the `meters_values` table to display dynamic data.
```html
-
-
-
-
-
-
-
-
+{{#include docs/examples/perspective/prsp-viewer.html:perspective_viewer}}
```
## Reference Materials
diff --git a/docs/examples/perspective/perspective_server.py b/docs/examples/perspective/perspective_server.py
new file mode 100644
index 0000000000..919828379e
--- /dev/null
+++ b/docs/examples/perspective/perspective_server.py
@@ -0,0 +1,207 @@
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
+# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
+# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
+# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
+# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
+# ┃ Copyright (c) 2017, the Perspective Authors. ┃
+# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
+# ┃ This file is part of the Perspective library, distributed under the terms ┃
+# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+import logging
+import tornado.websocket
+import tornado.web
+import tornado.ioloop
+from datetime import date, datetime
+import perspective
+import perspective.handlers.tornado
+import json
+import taosws
+
+
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+logger = logging.getLogger('main')
+
+
+# =============================================================================
+# TDengine connection parameters
+# =============================================================================
+TAOS_HOST = "localhost" # TDengine server host
+TAOS_PORT = 6041 # TDengine server port
+TAOS_USER = "root" # TDengine username
+TAOS_PASSWORD = "taosdata" # TDengine password
+
+TAOS_DATABASE = "power" # TDengine database name
+TAOS_TABLENAME = "meters" # TDengine table name
+
+# =============================================================================
+# Perspective server parameters
+# =============================================================================
+PERSPECTIVE_TABLE_NAME = "meters_values" # name of the Perspective table
+PERSPECTIVE_REFRESH_RATE = 250 # refresh rate in milliseconds
+
+
+class CustomJSONEncoder(json.JSONEncoder):
+ """
+ Custom JSON encoder that serializes datetime and date objects
+ """
+ def default(self, obj):
+ if isinstance(obj, datetime):
+ return obj.isoformat()
+ elif isinstance(obj, date):
+ return obj.isoformat()
+ return super().default(obj)
+
+
+json.JSONEncoder.default = CustomJSONEncoder().default
+
+
+def convert_ts(ts) -> datetime:
+ """
+ Convert a timestamp string to a datetime object
+ """
+ for fmt in ('%Y-%m-%d %H:%M:%S.%f %z', '%Y-%m-%d %H:%M:%S %z'):
+ try:
+ return datetime.strptime(ts, fmt)
+ except ValueError:
+ continue
+ raise ValueError(f"Time data '{ts}' does not match any format")
+
+
+def create_tdengine_connection(
+ host: str = TAOS_HOST,
+ port: int = TAOS_PORT,
+ user: str = TAOS_USER,
+ password: str = TAOS_PASSWORD,
+ ) -> taosws.Connection:
+ try:
+ # connect to the tdengine server
+ conn = taosws.connect(
+ user=user,
+ password=password,
+ host=host,
+ port=port,
+ )
+ # switch to the right database
+ conn.execute(f"USE {TAOS_DATABASE}")
+ # connection successful
+ logger.info(f"Connected to tdengine successfully: {host}:{port}")
+ return conn
+ except Exception as err:
+ logger.error(f"Failed to connect to tdengine: {host}:{port} -- ErrMessage: {err}")
+ raise err
+
+
+def read_tdengine(
+ conn: taosws.Connection,
+ ) -> list[dict]:
+ try:
+ # query the database
+ sql = f"""
+ SELECT `ts`, location, groupid, current, voltage, phase
+ FROM {TAOS_TABLENAME}
+ WHERE `ts` >= NOW() - 12h
+ ORDER BY `ts` DESC
+ LIMIT 1000
+ """
+ logger.debug(f"Executing query: {sql}")
+ res = conn.query(sql)
+ data = [
+ {
+ "timestamp": convert_ts(row[0]),
+ "location": row[1],
+ "groupid": row[2],
+ "current": row[3],
+ "voltage": row[4],
+ "phase": row[5],
+ }
+ for row in res
+ ]
+ logger.info(f"select result: {data}")
+ return data
+ except Exception as err:
+ logger.error(f"Failed to query tdengine: {err}")
+ raise err
+
+
+// ANCHOR: perspective_server
+def perspective_thread(perspective_server: perspective.Server, tdengine_conn: taosws.Connection):
+ """
+ Create a new Perspective table and update it with new data every 50ms
+ """
+ # create a new Perspective table
+ client = perspective_server.new_local_client()
+ schema = {
+ "timestamp": datetime,
+ "location": str,
+ "groupid": int,
+ "current": float,
+ "voltage": int,
+ "phase": float,
+ }
+ # define the table schema
+ table = client.table(
+ schema,
+ limit=1000, # maximum number of rows in the table
+ name=PERSPECTIVE_TABLE_NAME, # table name. Use this with perspective-viewer on the client side
+ )
+ logger.info("Created new Perspective table")
+
+ # update with new data
+ def updater():
+ data = read_tdengine(tdengine_conn)
+ table.update(data)
+ logger.debug(f"Updated Perspective table: {len(data)} rows")
+
+ logger.info(f"Starting tornado ioloop update loop every {PERSPECTIVE_REFRESH_RATE} milliseconds")
+ # start the periodic callback to update the table data
+ callback = tornado.ioloop.PeriodicCallback(callback=updater, callback_time=PERSPECTIVE_REFRESH_RATE)
+ callback.start()
+
+// ANCHOR_END: perspective_server
+
+def make_app(perspective_server):
+ """
+ Create a new Tornado application with a websocket handler that
+ serves a Perspective table. PerspectiveTornadoHandler handles
+ the websocket connection and streams the Perspective table changes
+ to the client.
+ """
+ return tornado.web.Application([
+ (
+ r"/websocket", # websocket endpoint. Use this URL to configure the websocket client OR Prospective Server adapter
+ perspective.handlers.tornado.PerspectiveTornadoHandler, # PerspectiveTornadoHandler handles perspective table updates <-> websocket client
+ {"perspective_server": perspective_server}, # pass the perspective server to the handler
+ ),
+ ])
+
+
+if __name__ == "__main__":
+ logger.info("TDEngine <-> Perspective Demo")
+
+ # create a new Perspective server
+ logger.info("Creating new Perspective server")
+ perspective_server = perspective.Server()
+ # create the tdengine connection
+ logger.info("Creating new TDEngine connection")
+ tdengine_conn = create_tdengine_connection()
+
+ # setup and start the Tornado app
+ logger.info("Creating Tornado server")
+ app = make_app(perspective_server)
+ app.listen(8085, address='0.0.0.0')
+ logger.info("Listening on http://localhost:8080")
+
+ try:
+ # start the io loop
+ logger.info("Starting ioloop to update Perspective table data via tornado websocket...")
+ loop = tornado.ioloop.IOLoop.current()
+ loop.call_later(0, perspective_thread, perspective_server, tdengine_conn)
+ loop.start()
+ except KeyboardInterrupt:
+ logger.warning("Keyboard interrupt detected. Shutting down tornado server...")
+ loop.stop()
+ loop.close()
+ logging.info("Shut down")
diff --git a/docs/examples/perspective/prsp-viewer.html b/docs/examples/perspective/prsp-viewer.html
new file mode 100644
index 0000000000..e6b1a6e734
--- /dev/null
+++ b/docs/examples/perspective/prsp-viewer.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+ Perspective Viewer Dashboard
+
+
+
+
+
+
+
+
+
+
+
+// ANCHOR: perspective_viewer
+
+
+
+
+
+
+
+
+// ANCHOR_END: perspective_viewer
+
+
+
\ No newline at end of file
diff --git a/docs/zh/10-third-party/03-visual/02-perspective.mdx b/docs/zh/10-third-party/03-visual/02-perspective.mdx
index 8e92e7209b..1db8474570 100644
--- a/docs/zh/10-third-party/03-visual/02-perspective.mdx
+++ b/docs/zh/10-third-party/03-visual/02-perspective.mdx
@@ -22,7 +22,7 @@ Perspective 是一款开源且强大的数据可视化库,由 [Prospective.co]
- Python 3.10 及以上版本已安装(如未安装,可参考 [Python 安装](https://docs.python.org/)。
- 下载或克隆 [perspective-connect-demo](https://github.com/taosdata/perspective-connect-demo) 项目,进入项目根目录后运行 “install.sh” 脚本,以便在本地下载并安装 TDengine 客户端库以及相关的依赖项。
-## 数据分析
+## 可视化数据
**第 1 步**,运行 [perspective-connect-demo](https://github.com/taosdata/perspective-connect-demo) 项目根目录中的 “run.sh” 脚本,以此启动 Perspective 服务。该服务会每隔 300 毫秒从 TDengine 数据库中获取一次数据,并将数据以流的形式传输至基于 Web 的 `Perspective Viewer` 。
@@ -36,6 +36,8 @@ sh run.sh
python -m http.server 8081
```
+通过浏览器访问该 Web 页面后所呈现出的效果如下图所示:
+

## 使用说明
@@ -59,40 +61,9 @@ Python 连接器详细写入说明可参见 [Python 参数绑定](../../../refer
1. 创建一个 Perspective 表(表结构需要与 TDengine 数据库中表的类型保持匹配)。
1. 调用 `Tornado.PeriodicCallback` 函数来启动定时任务,进而实现对 Perspective 表数据的更新,示例代码如下:
- ```python
- def perspective_thread(perspective_server: perspective.Server, tdengine_conn: taosws.Connection):
- """
- Create a new Perspective table and update it with new data every 50ms
- """
- # create a new Perspective table
- client = perspective_server.new_local_client()
- schema = {
- "timestamp": datetime,
- "location": str,
- "groupid": int,
- "current": float,
- "voltage": int,
- "phase": float,
- }
- # define the table schema
- table = client.table(
- schema,
- limit=1000, # maximum number of rows in the table
- name=PERSPECTIVE_TABLE_NAME, # table name. Use this with perspective-viewer on the client side
- )
- logger.info("Created new Perspective table")
-
- # update with new data
- def updater():
- data = read_tdengine(tdengine_conn)
- table.update(data)
- logger.debug(f"Updated Perspective table: {len(data)} rows")
-
- logger.info(f"Starting tornado ioloop update loop every {PERSPECTIVE_REFRESH_RATE} milliseconds")
- # start the periodic callback to update the table data
- callback = tornado.ioloop.PeriodicCallback(callback=updater, callback_time=PERSPECTIVE_REFRESH_RATE)
- callback.start()
- ```
+```python
+{{#include docs/examples/perspective/perspective_server.py:perspective_server}}
+```
### HTML 页面配置
@@ -103,80 +74,7 @@ Python 连接器详细写入说明可参见 [Python 参数绑定](../../../refer
- 引入 Perspective 库,通过 WebSocket 连接到 Perspective 服务器,加载 meters_values 表来展示动态数据。
```html
-
-
-
-
-