如何在Python Shiny中提取、保存及访问pyvis.network的操作变更?
解决Python Shiny + Pyvis 捕获网络变更并保存的方案
核心思路
Pyvis的manipulation功能基于vis.js实现,要捕获前端的节点/边增删操作,需在生成的HTML中添加自定义JavaScript,把变更数据传递回Shiny后端,同步更新后端的Pyvis网络对象后,再实现保存功能。
步骤1:自定义Pyvis HTML模板
Pyvis允许自定义输出模板,我们需要在模板中添加监听vis.js事件的代码,通过Shiny的Shiny.setInputValue将变更数据发送给后端。
创建模板文件custom_template.html,内容如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Network</title> <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> <style type="text/css"> #mynetwork { width: 100%; height: 600px; border: 1px solid lightgray; } </style> </head> <body> <div id="mynetwork"></div> <script type="text/javascript"> // 加载网络数据 var nodes = new vis.DataSet({{ nodes }}); var edges = new vis.DataSet({{ edges }}); var container = document.getElementById("mynetwork"); var data = { nodes: nodes, edges: edges }; var options = {{ options }}; var network = new vis.Network(container, data, options); // 监听节点添加事件 network.on("addNode", function(params) { Shiny.setInputValue("network_change", { type: "add_node", node: params.nodes[0] }, {priority: "event"}); }); // 监听节点删除事件 network.on("deleteNode", function(params) { Shiny.setInputValue("network_change", { type: "delete_node", node_ids: params.nodes }, {priority: "event"}); }); // 监听边添加事件 network.on("addEdge", function(params) { Shiny.setInputValue("network_change", { type: "add_edge", edge: params.edges[0] }, {priority: "event"}); }); // 监听边删除事件 network.on("deleteEdge", function(params) { Shiny.setInputValue("network_change", { type: "delete_edge", edge_ids: params.edges }, {priority: "event"}); }); </script> </body> </html>
步骤2:Shiny应用实现
在Shiny应用中,使用自定义模板生成Pyvis网络,接收前端传来的变更并更新网络对象,同时添加保存按钮。
from shiny import App, ui, render, reactive from pyvis.network import Network import json # 初始化Pyvis网络 def init_network(): net = Network(height="600px", width="100%", directed=False) net.enable_physics(True) # 启用manipulation功能 net.set_options(""" { "manipulation": { "enabled": true, "addNode": true, "addEdge": true, "deleteNode": true, "deleteEdge": true, "editNode": true, "editEdge": true } } """) # 添加初始节点示例 net.add_node(1, label="Node 1") net.add_node(2, label="Node 2") net.add_edge(1, 2) return net app_ui = ui.page_fluid( ui.h1("Pyvis Network Editor"), ui.output_ui("network"), ui.download_button("save_network", "Save Final Network"), ) def server(input, output, session): # 用reactive变量存储网络对象,方便更新 net = reactive.Value(init_network()) @output @render.ui def network(): # 使用自定义模板生成HTML current_net = net.get() # 将节点、边、选项转为JSON格式,适配模板 nodes_json = json.dumps([n for n in current_net.nodes]) edges_json = json.dumps([e for e in current_net.edges]) options_json = json.dumps(current_net.options) with open("custom_template.html", "r") as f: template = f.read() # 替换模板中的占位符 html_content = template.replace("{{ nodes }}", nodes_json)\ .replace("{{ edges }}", edges_json)\ .replace("{{ options }}", options_json) return ui.HTML(html_content) # 监听前端传来的网络变更 @reactive.Effect @reactive.event(input.network_change) def update_network(): change = input.network_change() current_net = net.get() if change["type"] == "add_node": node = change["node"] # Pyvis添加节点,处理id和label current_net.add_node(node["id"], label=node.get("label", f"Node {node['id']}")) elif change["type"] == "delete_node": for node_id in change["node_ids"]: current_net.remove_node(node_id) elif change["type"] == "add_edge": edge = change["edge"] current_net.add_edge(edge["from"], edge["to"], id=edge["id"]) elif change["type"] == "delete_edge": for edge_id in change["edge_ids"]: # 通过id匹配并删除边 for e in current_net.edges: if e["id"] == edge_id: current_net.remove_edge(e["from"], e["to"]) break # 更新reactive变量,触发网络重新渲染 net.set(current_net) # 保存网络为HTML文件 @session.download() def save_network(): current_net = net.get() output_path = "final_network.html" current_net.write_html(output_path) return output_path app = App(app_ui, server)
关键说明
- 借助vis.js原生事件(
addNode、deleteNode等)捕获操作,通过Shiny.setInputValue将数据同步到后端。 - 使用Shiny的
reactive.Value存储网络对象,确保变更后实时重新渲染网络。 - 自定义模板是核心,默认Pyvis生成的HTML没有与Shiny通信的逻辑,需要手动添加交互回调。
内容的提问来源于stack exchange,提问作者ferpi
相关产品推荐
相关产品推荐

