如何在Plotly中添加交互式文本标签之间的箭头?

发布于 2025-01-23 21:59:48 字数 1898 浏览 3 评论 0 原文

我正在创建一个能够以简单方式连接文本标签的应用,例如中完成通道“ rel =” nofollow noreferrer”>:

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笔记本上的动态HTML,并允许没有JavaScript运行它。此外,我想实现这些目标:

  1. 功能,以准确连接矩形文本标签,这些标签是矩形的,如我的图表交互中所示的
  2. ,1:使与鼠标
  3. 互动的标签可拖动标签2:redraw箭头在拖动交互时连接文本标签
  4. 3:redirect 3:redirect在文本标签之后,对我的一些URL进行了双击,

对我来说最重要的是矩形文本标签连接的最小工作脚本。 matplotlib 还支持“ nofollow noreferrer”>处理事件。我还可能想讨论 plotly 是否支持使所有这三个交互成为代码。

I'm creating an app which is able to connect text labels in a simple manner like it could be done in 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()

enter image description here

I'm looking for plotly solutions at the moment since it has a support for dynamic HTML on Jupyter Notebook and allows to run it with no JavaScript. Moreover, I want to achieve these things:

  1. Functionality for accurate connection of rectangular text labels that are rectangular like it is shown in my diagram
  2. Interaction 1: make labels draggable with mouse
  3. Interaction 2: redraw arrows that connects text labels while dragging
  4. Interaction 3: redirect to some url after text label is double clicked

The most important for me is minimal working script of connection of rectangular text labels. matplotlib also supports handling events. I might also like to discuss if plotly has a support for making all these three interactions possible to code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

初心未许 2025-01-30 21:59:48

经过长时间的研究代码,我可以说大多数事情都涉及一些小缺点。

首先,我发现可以实现所有想要的功能:

  • 支持矩形节点
  • 可以拖动
  • 边缘的更新(与Matplotlib注释不同)
  • 超链接需要一些自定义,这并不是

互动图的硬结构:

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笔记本中的应用程序,使用 jupyterdash 。很简单,唯一需要的是使用其他类型的应用程序:

from jupyter_dash import JupyterDash
...
if __name__ == '__main__':
    app.run_server(mode='inline')

支持github和 nbviewer

如果您将应用程序上传到GitHub中,它将不会显示交互式应用程序,但是可以在 。有两个不好的方面:

  • 除非您更换链接 https:// ... nbviewer http:// ... 。我不满意,但这是我唯一能找到的解决方法。
  • 它不会在一个笔记本中加载多个应用程序。

支持乳胶

,直到发行 nofollow noreferrer“> dash) v2.3.0 一个月前。我在这些应用中一直使用的Dash Cytoscape中仍然不支持它。我希望这个问题将在不久的将来得到解决

文字封闭在适合其形状的矩形标签中。

我可以找到任何方法可以做到这一点,正如您在带有“非常长的线”测试'的节点的示例中看到的那样。目前,如果文本标签的长度很长,则更好的设计是使用上面文本的圆形节点。


我很高兴能够找到大多数问题的解决方案,但我仍然开放讨论有关乳胶/Mathjax, nbviewer 以及标签中更好的文本外壳的支持。

After long time spent studying codes I could say that majority of things are working with some minor drawbacks.

To start with, I found it possible to implement all the functionality wanted:

  • Rectangular nodes are supported
  • They can be dragged
  • Update of edges is working (unlike in Matplotlib annotations)
  • Hyperlinks require some customisation which is not hard

Construction of interactive graph:

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

enter image description here

Customising node, edge and label style

You'll need to define stylesheet parameter and uncomment it in app.layout definition:

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}}]

enter image description here

Navigation to url after node click

Based on this answer, just add a callback before server run:

@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']}"

Support for Jupyter Notebook

For embedding app inside Jupyter Notebook, use JupyterDash. It's simple, the only think you need is to use a different kind of app:

from jupyter_dash import JupyterDash
...
if __name__ == '__main__':
    app.run_server(mode='inline')

Support for GitHub and nbviewer

If you upload your app in GitHub it won't display interactive apps but you could load your GitHub link in nbviewer. There are two bad sides:

  • It won't load unless you replace your link https://... of nbviewer with http://.... I'm not satisfied but this is the only workaround I could find.
  • It won't load multiple apps in one notebook.

Support for Latex

There was no straight way in Dash Plotly to render Mathjax until release of Dash v2.3.0 one month ago. It's still not supported in Dash Cytoscape which I've been using in these apps. I hope this issue is going to be resolved in near future.

Text enclosing in rectangular labels that fits it's shape.

I could find any way to do it yet as you could see in an example of node with 'Very long line for testing'. At the moment, if text labels occurs to be long, a better design is to use circular nodes with text above.


I'm glad I was able to find solutions to majority of my questions and I'm still open to discuss about support for LaTeX/Mathjax, nbviewer and better text enclosion in labels.

还在原地等你 2025-01-30 21:59:48

我认为最好使用 Plotly-Python 连接两个静态矩形文本标签。但是,在绘图中执行任何交互式的操作,以修改任何对象都需要在大多数情况下需要回调,这意味着您需要使用 plotly-dash

由于您在Jupyter笔记本电脑中工作,因此可以使用 jupyter-dash 。如果您对这样的解决方案感兴趣,我很乐意更新我的答案并提供示例。

I think the best you can do with plotly-python is connect two static rectangular text labels. However, doing anything interactive in Plotly that modifies any of the objects is going to requires callbacks for the most part, meaning that you will need to use plotly-dash.

Since you're working in a Jupyter Notebook, you could use Jupyter-Dash. If you would be interested in such a solution, I'd be happy to update my answer and provide an example.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文