如何用HTMX更新Plotly图表数据并保留当前视图状态(缩放/平移等)
实现方案(FastAPI + HTMX + Plotly.js 增量更新图表)
一、核心思路
放弃每次发送完整图表HTML的方式,改为以下模式:
- 客户端仅初始化一次Plotly.js图表框架
- 后端通过WebSocket仅推送更新后的数据(从Plotly Python导出JSON格式)
- 客户端用Plotly.js的增量更新API修改图表,保留当前视图状态(缩放、平移等)
此方案完全可行,Plotly.js原生支持增量数据更新,结合FastAPI WebSocket和HTMX可实现需求。
二、具体实现步骤
1. 客户端:初始化图表并监听WebSocket更新
在HTML页面中引入Plotly.js,创建图表容器,初始化WebSocket并处理数据更新:
<!-- 引入Plotly.js --> <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script> <!-- 图表容器 --> <div id="chart-container"></div> <script> // 初始化WebSocket连接 const ws = new WebSocket(`ws://${window.location.host}/ws/chart-update`); // 加载初始数据并渲染图表 fetch('/api/initial-chart-data') .then(res => res.json()) .then(initialData => { Plotly.newPlot('chart-container', initialData.data, initialData.layout); }); // 监听WebSocket消息,增量更新图表 ws.onmessage = function(event) { const updateData = JSON.parse(event.data); // 仅更新数据,保留视图状态 Plotly.restyle('chart-container', updateData.data); // 若需更新布局,改用Plotly.update(): // Plotly.update('chart-container', updateData.data, updateData.layout); }; </script>
2. 后端FastAPI:提供初始数据与WebSocket推送
(1)初始数据接口
用Plotly Python生成初始图表的 data 和 layout,序列化为JSON返回:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect import plotly.graph_objects as go from plotly.utils import PlotlyJSONEncoder import json import asyncio app = FastAPI() # 返回初始图表数据 @app.get("/api/initial-chart-data") async def get_initial_chart_data(): fig = go.Figure(data=[go.Scatter(x=[1,2,3], y=[4,5,6])]) fig.update_layout(title="实时更新图表") # 序列化Plotly对象为JSON格式 return { "data": json.loads(json.dumps(fig.data, cls=PlotlyJSONEncoder)), "layout": json.loads(json.dumps(fig.layout, cls=PlotlyJSONEncoder)) }
(2)WebSocket连接与数据推送
管理客户端连接,在数据变更时推送增量数据:
# WebSocket连接管理器 class ConnectionManager: def __init__(self): self.active_connections: list[WebSocket] = [] async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) async def send_update(self, data: dict): json_data = json.dumps(data, cls=PlotlyJSONEncoder) for connection in self.active_connections: await connection.send_text(json_data) manager = ConnectionManager() # WebSocket端点 @app.websocket("/ws/chart-update") async def websocket_endpoint(websocket: WebSocket): await manager.connect(websocket) try: while True: # 模拟数据变更(实际可替换为数据库监听、定时任务等触发逻辑) await asyncio.sleep(5) # 生成更新后的数据 new_data = [go.Scatter(x=[1,2,3], y=[4+1,5+1,6+1])] # 构造推送 payload(仅含数据部分) update_payload = { "data": json.loads(json.dumps(new_data, cls=PlotlyJSONEncoder)) } await manager.send_update(update_payload) except WebSocketDisconnect: manager.disconnect(websocket)
3. 关键细节
- 视图状态保留:
Plotly.restyle()仅更新图表数据,不会重置缩放、平移等用户操作状态;若需调整布局,Plotly.update()同样支持保留视图状态。 - 数据序列化:
PlotlyJSONEncoder可自动将Python端的Plotly对象转换为Plotly.js兼容的JSON格式,无需手动处理数据结构。 - HTMX WebSocket扩展:若偏好HTMX管理WebSocket,可替换原生WebSocket代码为HTMX的
hx-ws属性,回调逻辑保持一致:<div hx-ws="connect:/ws/chart-update"></div>
内容的提问来源于stack exchange,提问作者Nicola Gigante
相关产品推荐
相关产品推荐

