我正在创建一个应用程序,它能够以简单的方式连接文本标签,就像它可以在Matplotlib中完成:
import matplotlib.pyplot as plt
import networkx as nx
G = nx.DiGraph()
G.add_edges_from([(0,1)])
G.add_nodes_from([0, 1])
pos = {0:(0.1, 0.9), 1: (0.9, 0.5)}
fig, ax = plt.subplots()
annotations = {0: ax.annotate('Python', xy=pos[0], xycoords='data',
ha="center", va="center", bbox=dict(facecolor = "blue")),
1:ax.annotate('Programming', xy=pos[1], xycoords='data',
ha="center", va="center", bbox=dict(facecolor = "red"))}
annotations[1].draggable()
# if you don't have networkx installed, replace G.edges with [(0,1)]
for A, B in G.edges:
ax.annotate("", xy=pos[B], xycoords='data', xytext=pos[A], textcoords='data',
arrowprops=dict(arrowstyle="->", color="0.5", # shrinkA=85, shrinkB=85,
patchA=annotations[A],
patchB=annotations[B],
connectionstyle='arc3'))
plt.axis('off')
plt.show()
我目前正在寻找
plotly
解决方案,因为它支持 Jupyter Notebook 上的动态 HTML,并且允许在没有 JavaScript 的情况下运行它。此外,我想实现这些目标:
对我来说最重要的是连接矩形文本标签的最小工作脚本。
matplotlib
还支持处理事件。我还想讨论 plotly
是否支持使所有这三种交互都可以进行编码。
经过长时间研究代码,我可以说大多数事情都可以运行,但有一些小缺点。
首先,我发现可以实现所有想要的功能:
import dash
import dash_cytoscape as cyto
from dash import html, dcc
from dash.dependencies import Input, Output
#demo for adding urls: https://stackoverflow.com/a/69700675/3044825
cyto.load_extra_layouts() #dagre layout
P1 = {'data': {'id': 'p1', 'label': 'Use Bulb'}, 'grabbable': True, 'classes': 'process'}
P2 = {'data': {'id': 'p2', 'label': 'Prod. Bulb'}, 'grabbable': True, 'classes': 'process'}
P3 = {'data': {'id': 'p3', 'label': 'Prod. Elec', 'parent': 'm1'}, 'grabbable': True, 'classes': 'process'}
P4 = {'data': {'id': 'p4', 'label': 'Very long line for testing'}, 'grabbable': True, 'classes': 'process'}
P5 = {'data': {'id': 'p5', 'label': 'Prod. Glass'}, 'grabbable': True, 'classes': 'process'}
P6 = {'data': {'id': 'p6', 'label': 'Prod. Copper'}, 'grabbable': True, 'classes': 'process'}
P7 = {'data': {'id': 'p7', 'label': 'Prod. Fuel', 'parent': 'm1'}, 'grabbable': True, 'classes': 'process'}
E1 = {'data': {'id': 'e1', 'source': 'p7', 'target': 'p3', 'label': 'Fuel'}}
E2 = {'data': {'id': 'e2', 'source': 'p3', 'target': 'p6', 'label': 'Elec.'}}
E3 = {'data': {'id': 'e3', 'source': 'p3', 'target': 'p2', 'label': 'Elec.'}}
E4 = {'data': {'id': 'e4', 'source': 'p3', 'target': 'p5', 'label': 'Elec.'}}
E5 = {'data': {'id': 'e5', 'source': 'p3', 'target': 'p1', 'label': 'Elec.'}}
E6 = {'data': {'id': 'e6', 'source': 'p6', 'target': 'p2', 'label': 'Copper'}}
E7 = {'data': {'id': 'e7', 'source': 'p5', 'target': 'p2', 'label': 'Glass'}}
E8 = {'data': {'id': 'e8', 'source': 'p2', 'target': 'p1', 'label': 'Bulb'}}
E9 = {'data': {'id': 'e9', 'source': 'p4', 'target': 'p1', 'label': 'Waste Treatment'}}
nodes = [P1, P2, P3, P4, P5, P6, P7]
edges = [E1, E2, E3, E4, E5, E6, E7, E8, E9]
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Location(id="location"),
cyto.Cytoscape(
id='cytoscape',
layout={'name': 'dagre', 'spacingFactor': 1.15},
style={'width': '100%', 'height': '900px'},
#stylesheet=stylesheet,
elements=nodes+edges,
autounselectify=True
)])
if __name__ == '__main__':
app.run_server(debug=False, port=8869) #no need for choosing a specific port if it's not in use
您需要定义
stylesheet
参数并在 app.layout
定义中取消注释:
stylesheet = [
# Group selectors
{'selector': 'node', 'style': {'content': 'data(label)', 'font-size': 8}},
{'selector': 'edge',
'style': {'content': 'data(label)',
'curve-style': 'unbundled-bezier',
'width': 1,
'line-color': 'lightblue',
'target-arrow-color': 'lightblue',
'target-arrow-shape': 'triangle',
'text-margin-x': 0,
'font-size': 8}},
# Class selectors
{'selector': '.process',
'style': {'shape': 'round-rectangle',
'background-color': 'white',
'border-color': 'black',
'border-width': 1,
'text-valign': 'center',
'height': 40,
'width': 75}}]
基于这个答案,只需在服务器运行之前添加回调:
@app.callback(
Output("location", "href"),
Input("cytoscape", "tapNodeData"),
prevent_initial_call=True,
)
def navigate_to_url(node_data):
return f"https://en.wikipedia.org/wiki/{node_data['label']}"
要将应用程序嵌入 Jupyter Notebook,请使用 JupyterDash。这很简单,您唯一需要的就是使用不同类型的应用程序:
from jupyter_dash import JupyterDash
...
if __name__ == '__main__':
app.run_server(mode='inline')
nbviewer
如果您在 GitHub 中上传应用程序,它不会显示交互式应用程序,但您可以在
nbviewer
中加载 GitHub 链接。有两个不好的方面:
https://...
的链接 nbviewer
替换为 http://...
,否则它不会加载。我不满意,但这是我能找到的唯一解决方法。Dash Plotly 中没有直接的方法来渲染 Mathjax,直到一个月前发布 Dash v2.3.0。我在这些应用程序中使用的 Dash Cytoscape 仍然不支持它。我希望这个问题能够在不久的将来得到解决。
我可以找到任何方法来做到这一点,正如您在带有
'Very long line for testing'
的节点示例中看到的那样。目前,如果文本标签很长,更好的设计是使用圆形节点,上面有文本。
我很高兴能够找到大多数问题的解决方案,并且我仍然愿意讨论对 LaTeX/Mathjax、
nbviewer
以及标签中更好的文本包围的支持。
我认为你能用
plotly-python
做的最好的事情就是连接两个静态矩形文本标签。然而,在 Plotly 中进行任何修改任何对象的交互操作大部分都需要回调,这意味着您需要使用 plotly-dash
。
由于您使用的是 Jupyter Notebook,因此您可以使用 Jupyter-Dash。如果您对这样的解决方案感兴趣,我很乐意更新我的答案并提供一个示例。