You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Streamlit、NetworkX、Plotly实现带交互功能的图管理UI?

Streamlit + Plotly + NetworkX 交互式图管理UI实现方案

环境准备

先安装依赖包:

pip install streamlit networkx plotly

核心思路

用NetworkX维护图的拓扑结构(节点、边),Plotly渲染可交互的图可视化,Streamlit搭建UI控制面板并通过st.session_state持久化图数据和交互状态(比如选中的节点、节点位置)。

基础操作实现(增删节点/边)

通过Streamlit的输入组件和按钮,结合NetworkX的API完成基础编辑:

  • 添加节点:输入节点ID,点击按钮后调用G.add_node()
  • 删除节点:输入节点ID,点击按钮后调用G.remove_node()(先判断节点是否存在)
  • 创建边:输入两个节点ID,点击按钮后调用G.add_edge()(判断两个节点是否都存在)

核心交互功能实现

1. 拖拽节点调整位置

Plotly散点图支持拖拽交互,启用draggable=True后,监听拖拽事件并将更新后的节点位置同步到st.session_state.pos字典,确保下次渲染时使用最新位置。

2. 点击两个节点创建边

通过st.session_state记录第一次点击的节点ID,第二次点击时自动创建边:

  • 监听Plotly的单击事件,获取点击的节点数据
  • 无选中节点时,标记当前节点为选中状态;已有选中节点时,创建边并清空选中状态

3. 点击节点执行删除操作

利用Plotly的双击事件触发删除:

  • 监听双击事件,获取目标节点ID
  • 调用G.remove_node()移除节点,同时删除对应位置数据并刷新页面

完整代码示例

import streamlit as st
import networkx as nx
import plotly.graph_objects as go

# 初始化会话状态
if 'G' not in st.session_state:
    st.session_state.G = nx.Graph()
    st.session_state.G.add_nodes_from(['A', 'B', 'C'])
    st.session_state.pos = nx.spring_layout(st.session_state.G)
if 'selected_node' not in st.session_state:
    st.session_state.selected_node = None

# 页面布局:左侧控制面板,右侧可视化
col1, col2 = st.columns([1, 3])

with col1:
    st.subheader("图管理面板")
    
    # 添加节点
    node_id = st.text_input("输入节点ID")
    if st.button("添加节点"):
        if node_id and node_id not in st.session_state.G.nodes:
            st.session_state.G.add_node(node_id)
            # 给新节点分配基础位置
            base_pos = st.session_state.pos[list(st.session_state.G.nodes)[0]]
            st.session_state.pos[node_id] = (base_pos[0] + 0.1, base_pos[1] + 0.1)
    
    # 删除节点
    del_node_id = st.text_input("输入要删除的节点ID")
    if st.button("删除节点"):
        if del_node_id in st.session_state.G.nodes:
            st.session_state.G.remove_node(del_node_id)
            del st.session_state.pos[del_node_id]
    
    # 创建边
    edge_from = st.text_input("边的起点")
    edge_to = st.text_input("边的终点")
    if st.button("创建边"):
        if edge_from in st.session_state.G.nodes and edge_to in st.session_state.G.nodes:
            st.session_state.G.add_edge(edge_from, edge_to)

with col2:
    st.subheader("交互式图可视化")
    
    # 构建Plotly图
    fig = go.Figure()
    
    # 添加边
    for edge in st.session_state.G.edges:
        x0, y0 = st.session_state.pos[edge[0]]
        x1, y1 = st.session_state.pos[edge[1]]
        fig.add_trace(go.Scatter(
            x=[x0, x1], y=[y0, y1],
            mode='lines',
            line=dict(width=2, color='#888'),
            hoverinfo='none'
        ))
    
    # 添加节点
    node_x = [st.session_state.pos[node][0] for node in st.session_state.G.nodes]
    node_y = [st.session_state.pos[node][1] for node in st.session_state.G.nodes]
    node_text = list(st.session_state.G.nodes)
    
    fig.add_trace(go.Scatter(
        x=node_x, y=node_y,
        mode='markers+text',
        text=node_text,
        textposition='top center',
        marker=dict(
            size=20,
            color='#1f77b4',
            line=dict(width=2, color='#fff')
        ),
        hoverinfo='text',
        draggable=True  # 启用节点拖拽
    ))
    
    # 布局设置
    fig.update_layout(
        showlegend=False,
        hovermode='closest',
        margin=dict(b=20, l=5, r=5, t=40),
        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        dragmode='select'
    )
    
    # 渲染图并监听交互事件
    plotly_event = st.plotly_chart(fig, on_select='rerun', key='graph_plot')
    
    # 处理点击事件:创建边或删除节点
    if plotly_event and plotly_event['selection']['points']:
        clicked_node = plotly_event['selection']['points'][0]['text']
        
        # 双击删除节点
        if plotly_event['event_type'] == 'plotly_doubleclick':
            if clicked_node in st.session_state.G.nodes:
                st.session_state.G.remove_node(clicked_node)
                del st.session_state.pos[clicked_node]
                st.rerun()
        
        # 单击选择节点创建边
        elif plotly_event['event_type'] == 'plotly_click':
            if st.session_state.selected_node is None:
                st.session_state.selected_node = clicked_node
                st.success(f"已选中节点:{clicked_node},请点击第二个节点创建边")
            else:
                if st.session_state.selected_node != clicked_node:
                    st.session_state.G.add_edge(st.session_state.selected_node, clicked_node)
                    st.success(f"已创建边:{st.session_state.selected_node} -> {clicked_node}")
                st.session_state.selected_node = None
                st.rerun()
    
    # 处理拖拽事件:更新节点位置
    if plotly_event and plotly_event['relayout']:
        for key, val in plotly_event['relayout'].items():
            if key.startswith('xaxis.range') or key.startswith('yaxis.range'):
                continue
            if 'marker' in key:
                idx = int(key.split('[')[1].split(']')[0])
                node = node_text[idx]
                if 'x' in key:
                    st.session_state.pos[node] = (val, st.session_state.pos[node][1])
                elif 'y' in key:
                    st.session_state.pos[node] = (st.session_state.pos[node][0], val)
        st.rerun()

关键注意事项

  • 会话状态管理:必须用st.session_state保存图对象、节点位置和选中状态,否则Streamlit每次重运行都会重置数据
  • 交互事件处理:Plotly的事件需要通过st.plotly_chart的on_select='rerun'触发页面重运行,才能更新状态
  • 节点位置持久化:拖拽后要及时更新pos字典,确保下次渲染用最新位置
  • 边界判断:所有操作前要检查节点是否存在,避免NetworkX抛出异常

内容的提问来源于stack exchange,提问作者evan54

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 16:27:36