Python 如何处理PyViz/datashader网络示例中的节点单击?

Python 如何处理PyViz/datashader网络示例中的节点单击?,python,graph,callback,datashader,pyviz,Python,Graph,Callback,Datashader,Pyviz,我查看了and(它也使用datashader)。我想在节点选择上获得对python代码的回调(在节点id上调用函数,即fun(node\u id))。如何在PyViz/datashader中实现这一点?我已经做出了很大的努力,用datashader或PyViz实现了这一点,但是,从你如何表达这个问题来看,在networks graph节点上获得回调似乎比具体技术更重要 这是一种基于maccdc2012\u edges网络图的node\u id回调解决方案,使用与指定技术非常类似的NetworkX

我查看了and(它也使用datashader)。我想在节点选择上获得对python代码的回调(在节点id上调用函数,即
fun(node\u id)
)。如何在PyViz/datashader中实现这一点?

我已经做出了很大的努力,用datashader或PyViz实现了这一点,但是,从你如何表达这个问题来看,在networks graph节点上获得回调似乎比具体技术更重要

这是一种基于
maccdc2012\u edges
网络图的
node\u id
回调解决方案,使用与指定技术非常类似的NetworkX

声明的
my\u callback(node\u id)
callback,符合您的要求,在单击特定节点时触发

我已经限制在前10行,以便它可以清楚地看到,并添加了滚动缩放为您的方便

import pandas as pd

import networkx as nx
import matplotlib.pyplot as plt
import graphistry
from pylab import *

class AnnoteFinder:  # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
    """
    callback for matplotlib to visit a node (display an annotation) when points are clicked on.  The
    point which is closest to the click and within xtol and ytol is identified.
    """
    def __init__(self, xdata, ydata, annotes, callback = None, threshold=None, axis=None, xtol=None, ytol=None):
        self.data = list(zip(xdata, ydata, annotes))
        if xtol is None: xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
        if ytol is None: ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
        self.xtol = xtol
        self.ytol = ytol
        if axis is None: axis = gca()
        self.axis= axis
        self.drawnAnnotations = {}
        self.links = []
        self.callback = callback
        self.threshold = threshold if threshold else 1.0e-3

    def __call__(self, event):
        if event.inaxes:
            clickX = event.xdata
            clickY = event.ydata
            if self.axis is None or self.axis==event.inaxes:
                annotes = []
                smallest_x_dist = float('inf')
                smallest_y_dist = float('inf')
                for x,y,a in self.data:
                    if abs(clickX-x)<=smallest_x_dist and abs(clickY-y)<=smallest_y_dist :
                        dx, dy = x - clickX, y - clickY
                        annotes.append((dx*dx+dy*dy,x,y, a) )
                        smallest_x_dist=abs(clickX-x)
                        smallest_y_dist=abs(clickY-y)
                if annotes:
                    annotes.sort() # to select the nearest node
                    distance, x, y, annote = annotes[0]
                    print(distance)
                    if distance < self.threshold:
                        if self.callback:
                            self.callback(annote)

# https://notebooks.azure.com/seanreed1111/projects/PYVIZ1/html/data/maccdc2012_edges.parq
df = pd.read_parquet('maccdc2012_edges.parq').head(10)

def my_callback(node_id):
    print(f'Clicked {node_id}')

# Build your graph
G = nx.from_pandas_edgelist(df, 'source', 'target')
pos = nx.spring_layout(G,k=0.1, iterations=20)  # the layout gives us the nodes position x,y,annotes=[],[],[] for key in pos:
x, y, annotes = [], [], []
for key in pos:
    d = pos[key]
    annotes.append(key)
    x.append(d[0])
    y.append(d[1])

fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)

nx.draw(G, pos, font_size=6, node_color='skyblue', edge_color='#BB0000', width=0.5, node_size=200, with_labels=True)


af = AnnoteFinder(x, y, annotes, my_callback)
connect('button_press_event', af)

class ZoomPan:
    def __init__(self):
        self.press = None
        self.cur_xlim = None
        self.cur_ylim = None
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.xpress = None
        self.ypress = None


    def zoom_factory(self, ax, base_scale = 2.):
        def zoom(event):
            cur_xlim = ax.get_xlim()
            cur_ylim = ax.get_ylim()

            xdata = event.xdata # get event x location
            ydata = event.ydata # get event y location

            if event.button == 'down':
                # deal with zoom in
                scale_factor = 1 / base_scale
            elif event.button == 'up':
                # deal with zoom out
                scale_factor = base_scale
            else:
                # deal with something that should never happen
                scale_factor = 1
                print(event.button)

            new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
            new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor

            relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
            rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])

            ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
            ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
            ax.figure.canvas.draw()

        fig = ax.get_figure() # get the figure of interest
        fig.canvas.mpl_connect('scroll_event', zoom)

        return zoom

    def pan_factory(self, ax):
        def onPress(event):
            if event.inaxes != ax: return
            self.cur_xlim = ax.get_xlim()
            self.cur_ylim = ax.get_ylim()
            self.press = self.x0, self.y0, event.xdata, event.ydata
            self.x0, self.y0, self.xpress, self.ypress = self.press

        def onRelease(event):
            self.press = None
            ax.figure.canvas.draw()

        def onMotion(event):
            if self.press is None: return
            if event.inaxes != ax: return
            dx = event.xdata - self.xpress
            dy = event.ydata - self.ypress
            self.cur_xlim -= dx
            self.cur_ylim -= dy
            ax.set_xlim(self.cur_xlim)
            ax.set_ylim(self.cur_ylim)

            ax.figure.canvas.draw()

        fig = ax.get_figure() # get the figure of interest

        # attach the call back
        fig.canvas.mpl_connect('button_press_event',onPress)
        fig.canvas.mpl_connect('button_release_event',onRelease)
        fig.canvas.mpl_connect('motion_notify_event',onMotion)

        #return the function
        return onMotion

scale = 1.1
zp = ZoomPan()
figZoom = zp.zoom_factory(ax, base_scale = scale)
figPan = zp.pan_factory(ax)

show()
将熊猫作为pd导入
将networkx导入为nx
将matplotlib.pyplot作为plt导入
进口葡萄
从派拉布进口*
类注释查找器:#感谢http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
"""
单击点时matplotlib访问节点(显示注释)的回调
确定了xtol和ytol内最靠近咔哒声的点。
"""
定义初始化(self、扩展数据、ydata、注释、回调=None、阈值=None、axis=None、xtol=None、ytol=None):
self.data=list(zip(扩展数据、扩展数据、注释))
如果xtol为None:xtol=((最大(扩展数据)-min(扩展数据))/float(len(扩展数据))/2
如果ytol为None:ytol=((最大(ydata)-min(ydata))/float(len(ydata))/2
self.xtol=xtol
伊托尔
如果轴为无:轴=gca()
自定轴=自定轴
self.drawnAnnotations={}
self.links=[]
self.callback=回调
self.threshold=阈值为1.0e-3时的阈值
定义调用(自身、事件):
如果event.inaxes:
单击X=event.xdata
clickY=event.ydata
如果self.axis为None或self.axis==event.inaxes:
注释=[]
最小距离=浮点('inf')
最小距离=浮动('inf')
对于self.data中的x,y,a:
如果abs(clickX-x)参见,其中显示了如何将可单击的抽取节点子集覆盖到更大的datashader渲染网络图上。如果这对你有效,请将其作为你问题的答案发布!还要注意,Datashader是HoloViz.org的一部分,而PyViz涵盖了Python中的所有viz工具。