我想使用以下示例数据源制作一个带有交互式网络图的 Web 应用程序:
data = {'Source Node': ['a', 'a', 'b', 'b', 'c'],
'Destination Node': ['b', 'c', 'c', 'd', 'd'],
'Link': ['likes', 'likes', 'likes', 'likes', 'dislikes']
}
所需的网络图具有以下标准:
-
节点应标有其名称。 (完毕)
-
根据“链接”是“喜欢”(绿色)还是“不喜欢”(红色),边缘应具有不同的颜色。 (通过 Networkx 边缘属性完成)
-
用户应该可以拖动节点。 (完成 - 首先尝试 Bokeh,然后切换到 Dash Cytoscape 以使其成为可能)
-
用户应该能够选择清单中的元素,以便仅查看网络图中的这些元素。 (以不太理想的方式完成,请参阅最后的注释以了解更多详细信息)
-
通过清单添加或删除元素时,现有节点应保持在固定位置,但默认情况并非如此,因为每次回调都会创建一个新的网络图。
到目前为止,我制作了一个满足标准 1 到 4 的草稿应用程序。但是,在添加和删除节点时,我没有设法将节点保持在固定位置。在展示我迄今为止拥有的代码后,我将提供更多详细信息。
下面是满足条件 1 到 4 的代码。
# Python v 3.9.13
# Importing libraries
import pandas as pd # v 1.4.4
import numpy as np # v 1.21.5
import networkx as nx # v 2.8.8
import dash_cytoscape as cyto # v 0.2.0
from dash import Dash, html, dcc, Input, Output # v 2.7.0
# Data source
data = {'Source Node': ['a', 'a', 'b', 'b', 'c'],
'Destination Node': ['b', 'c', 'c', 'd', 'd'],
'Link': ['likes', 'likes', 'likes', 'likes', 'dislikes']
}
# Creating a DataFrame using Pandas
df = pd.DataFrame(data)
# Defining lists for dcc.Checklist
list_all_ids = ['a', 'b', 'c', 'd']
default_list_selected_ids = ['a', 'c']
# Creating Dash app
app = Dash(__name__)
app.layout = html.Div(
[
dcc.Checklist(list_all_ids, default_list_selected_ids, id='checklist-elements'),
cyto.Cytoscape(
id='cytoscape_layout',
elements=[],
style={"width": "100%", "height": "400px"},
layout={"name": "preset"},
stylesheet=[
{
'selector': 'node',
'style': {
'content': 'data(label)'
}
},
{
'selector': 'edge',
'style': {
'line-color': 'data(edge_color)'
}
}
]
)
]
)
@app.callback(
Output('cytoscape_layout', 'elements'),
Input('checklist-elements', 'value')
)
def update_elements(selected_ids):
# Making a new df
df_temp = df[df['Source Node'].isin(selected_ids)]
df_selected = df_temp[df_temp['Destination Node'].isin(selected_ids)]
# MAKING A NETWORK GRAPH WITH NETWORKX
# Creating a network graph from the DataFrame
G = nx.from_pandas_edgelist(df_selected, 'Source Node', 'Destination Node', edge_attr='Link')
# Setting edge colors (green or red based on whether the nodes "like" or "dislike" each other)
list_colors = ['red' if a['Link']=='dislikes' else 'green' for u, v, a in G.edges(data=True)]
dict_colors = {k:v for (k,v) in zip(G.edges(), list_colors)}
nx.set_edge_attributes(G, dict_colors, 'edge_color')
pos = nx.spring_layout(G, seed = 100)
# CONVERTING NETWORKX GRAPH TO CYTOSCAPE FORMAT - Big thanks to Robert Alexander and canbax
# https://stackoverflow.com/questions/71428375/dash-cytoscape-from-python-networks-graph-not-honouring-the-nodes-coordinates
# Initial conversion
cy = nx.cytoscape_data(G)
# Replacing dictionary key 'value' with 'label' in the nodes list of cy
for n in cy["elements"]["nodes"]:
for k, v in n.items():
v["label"] = v.pop("value")
# Adding positions of nodes from pos
SCALING_FACTOR = 100
for n, p in zip(cy["elements"]["nodes"], pos.values()):
n["position"] = {"x": int(p[0] * SCALING_FACTOR), "y": int(p[1] * SCALING_FACTOR)}
elements=cy["elements"]["nodes"] + cy["elements"]["edges"]
return elements
if __name__ == "__main__":
app.run_server()
正如您所看到的,如果您运行代码并使用清单,每次单击复选框时,节点都会获得一个新位置。这是有道理的,因为每次都会创建一个新的网络图。但是,我希望当您选择或取消选择其他节点时,节点坚持到一个位置。为了实现这一目标,我尝试了两种完全不同的方法,但均不成功:
第一种方法:
- 而不是使用
pos = nx.spring_layout(G, seed = 100)
,我创建了一个预定义位置的字典(np.array()
对于位置,因为它是在中找到的类型pos = nx.spring_layout(G, seed = 100)
(以下)。我在定义应用程序之前放置了此代码。
dict_fixed_pos = {'a': np.array([-0.5, -0.5]),
'b': np.array([-0.5, 0.5]),
'c': np.array([0.5, 0.5]),
'd': np.array([0.5, -0.5])}
for n, p in zip(cy["elements"]["nodes"], pos.values()):
n["position"] = {"x": int(p[0] * SCALING_FACTOR), "y": int(p[1] * SCALING_FACTOR)}
with:
for n in cy["elements"]["nodes"]:
if n['data']['id'] in selected_ids:
n['position'] = dict_fixed_pos[n['data']['id']]
有了这个,我希望每次创建网络图时,每个给定节点都能从dict_fixed_pos
。但相反,所有节点都重叠在同一位置。看起来 Cytoscape 无法识别指定的位置,但我不知道如何解决这个问题。任何想法?
第二种方法:
- Instead of a callback that adds and removes elements, I tried to see if I could create a callback that changes the style of the nodes. So, the nodes would be invisible by default, and the selected nodes would be set as visible.
As a test to see if this would be possible, I made the following changes in the callback function:
- 我删除了重新定义数据框的部分,以便使用原始的 df 。
- 循环内部:
for n, p in zip(cy["elements"]["nodes"], pos.values()):
n["position"] = {"x": int(p[0] * SCALING_FACTOR), "y": int(p[1] * SCALING_FACTOR)}
我添加了以下代码以添加密钥'selected'
到每个节点。的价值'selected'
将会'yes'
or 'no'
根据是否对应'id'
被选择于dcc.Checklist
:
if n['data']['id'] in selected_ids:
n['selected'] = "yes"
else:
n['selected'] = "no"
我在样式表中添加了以下内容cyto.Cytoscape()
(只是更改背景颜色而不是可见性,因为我还没有弄清楚如何更改可见性(刚刚看到这是可能的):
{
'selector': '[selected = "yes"]',
'style': {
'background-color': '#FF4136'
}
然而,这些变化导致all节点成为blue(代替selected节点成为red).
那么,您认为这两种方法中的一种在进行一些更改后是否可行?或者你有什么完全不同的建议吗?预先感谢您抽出时间。
请注意第四个标准,能够根据清单中的选择添加或删除节点:最初,我想在应用程序启动时创建一次 df、网络图和元素列表;然后,在回调函数内,根据清单值,将从元素列表中选择要显示的元素。因为这不起作用,所以我决定在回调函数中从头开始重新创建网络图。然而,这远非理想,因此如果有人知道如何仅更新回调函数中的元素列表,我们也将非常感谢您的帮助。