【问题标题】:Slider based on Networkx node attribute value with Bokeh基于具有 Bokeh 的 Networkx 节点属性值的滑块
【发布时间】:2019-08-24 20:23:08
【问题描述】:

我正在尝试开发一个滑块,该滑块将根据节点属性的值限制网络图中可见节点的数量。下面的 Pandas DataFrame (df) 表示节点,以及节点的关联属性(计数信息)。

source   target   source_count   target_count
A        C        15             10
A        D        15             20
A        E        15             30
B        F        25             10
B        G        25             20
B        H        25             30

我已使用以下代码为节点及其关联属性生成网络图。

import pandas as pd
from bokeh.layouts import column, widgetbox,layout, 
from bokeh.plotting import figure, show, output_file, 
from bokeh.models import HoverTool, value,PanTool, LabelSet, Legend, ColumnDataSource,Circle,Plot, Range1d, MultiLineBoxSelectTool,ResetTool,LassoSelectTool,Slider
from bokeh.models.callbacks import CustomJS
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes


df = pd.DataFrame({
            "source":["A", "A", "A", "B", "B","B"],
            "target":["C", "D", "E", "F", "G","H"],
            "source_count":["15", "15", "15", "25","25","25"]
            "target_count":["10", "20", "30", "10","20","30"]
})

net_graph = from_pandas_edgelist(df, 'source', 'target')

#assign attributes
for index, row in df.iterrows():
net_graph.nodes[row['source']]['yearly_count'] = row['source_count']
net_graph.nodes[row['target']]['yearly_count'] = row['target_count']


graph_plot= Plot(plot_width=800, plot_height=600,
            x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))

node_hover_tool = HoverTool(tooltips=[("Name", "@index"),("Yearly Count", "@yearly_count")])

graph_plot.add_tools(node_hover_tool)
graph_setup = from_networkx(net_graph, nx.spring_layout, scale=1, center=(0, 0))

graph_setup.node_renderer.glyph = Circle(size=20,fill_color = 'blue') 
graph_setup.edge_renderer.glyph = MultiLine(line_color="red", line_alpha=0.8, line_width=1)

graph_plot.renderers.append(graph_setup)

output_file("test_1.html")
show(graph_plot)

我尝试使用的滑块将使用yearly_count 属性来限制显示的节点数。我知道 Bokeh 允许嵌入 JavaScript 回调,但是,我还没有看到与 NetworkX 集成的用例。

任何人都可以提供的任何帮助将不胜感激。

【问题讨论】:

    标签: bokeh networkx


    【解决方案1】:

    较新版本的 Bokeh 对 JavaScript (see release log) 使用严格模式,这意味着 Tony 接受的答案中的 code 不适用于 Bokeh 版本 2.0.0 及更高版本。代码只需要一些小的显式变量声明即可在较新的 Bokeh 版本中运行:

    code = ''' 
        var new_start = start.slice();
        var new_end = end.slice();
        var new_index = end.slice();
    
        new_start = new_start.splice(0, cb_obj.value)
        new_end = new_end.splice(0, cb_obj.value)
        new_index = ['A'].concat(new_end)
    
        var new_data_edge = {'start': new_start, 'end': new_end};
        var new_data_nodes = {'index': new_index};
        graph_setup.edge_renderer.data_source.data = new_data_edge; 
        graph_setup.node_renderer.data_source.data = new_data_nodes;
    '''
    

    【讨论】:

      【解决方案2】:

      如果您可以使用 bokeh serve 运行您的应用程序,那么我会尝试:

      from bokeh.models import Slider
      
      graph_plot= Plot()
      graph_setup.node_renderer.glyph = Circle()
      graph_setup.edge_renderer.glyph = MultiLine()
      
      def callback(attr, old, new):
          //filter your data here to show less nodes and edges based 
          graph_setup.node_renderer.data_source.data = data
          graph_setup.edge_renderer.data_source.data = data
      
      slider = Slider()
      slider.on_change('value', callback)
      

      如果您想运行 Bokeh 独立应用,请将滑块回调替换为:

      code = """
          //filter your data here to show less nodes and edges
          graph_setup.node_renderer.data_source.data = data;
          graph_setup.edge_renderer.data_source.data = data; """
      
      callback = CustomJS(args = dict(graph_setup = graph_setup, data = data), code = code)
      slider = Slider()
      slider.js_on_change('value', callback)
      

      请看下面完整的 JS 回调示例:

      import networkx as nx
      from bokeh.io import show, output_file
      from bokeh.models import Plot, Range1d, MultiLine, Circle, TapTool, OpenURL, HoverTool, CustomJS, Slider, Column
      from bokeh.models.graphs import from_networkx, EdgesAndLinkedNodes
      from bokeh.palettes import Spectral4
      from dask.dataframe.core import DataFrame
      import pandas as pd
      
      data = {'source': ['A', 'A', 'A', 'A', 'A', 'A'], 'target': ['C', 'D', 'E', 'F', 'G', 'H'], 'source_count': [15, 15, 15, 25, 25, 25], 'target_count': [10, 20, 30, 10, 20, 30]}
      df = pd.DataFrame(data)
      net_graph = nx.from_pandas_edgelist(df, 'source', 'target')
      
      for index, row in df.iterrows():
          net_graph.nodes[row['source']]['yearly_count'] = row['source_count']
          net_graph.nodes[row['target']]['yearly_count'] = row['target_count']
      
      graph_plot = Plot(plot_width = 800, plot_height = 600, x_range = Range1d(-1.1, 1.1), y_range = Range1d(-1.1, 1.1))
      
      node_hover_tool = HoverTool(tooltips = [("Name", "@index"), ("Yearly Count", "@yearly_count")])
      graph_plot.add_tools(node_hover_tool)
      
      graph_setup = from_networkx(net_graph, nx.spring_layout, scale = 1, center = (0, 0))
      graph_setup.node_renderer.glyph = Circle(size = 20, fill_color = 'blue')
      graph_setup.edge_renderer.glyph = MultiLine(line_color = "red", line_alpha = 0.8, line_width = 1)
      
      graph_plot.renderers.append(graph_setup)
      
      code = """ 
          var new_start = start.slice();
          var new_end = end.slice();
          new_index = end.slice();
      
          new_start = new_start.splice(0, cb_obj.value)
          new_end = new_end.splice(0, cb_obj.value)
          new_index = ['A'].concat(new_end)
      
          new_data_edge = {'start': new_start, 'end': new_end};
          new_data_nodes = {'index': new_index};
          graph_setup.edge_renderer.data_source.data = new_data_edge; 
          graph_setup.node_renderer.data_source.data = new_data_nodes; 
      """
      callback = CustomJS(args = dict(graph_setup = graph_setup,
                                      start = df['source'].values,
                                      end = df['target'].values), code = code)
      slider = Slider(title = 'Slider', start = 1, end = 6, value = 6)
      slider.js_on_change('value', callback)
      
      layout = Column(graph_plot, slider)
      show(layout)
      

      结果:

      【讨论】:

      • 非常感谢您的时间和回答。我在渲染散景图上的滑块时遇到了困难。当我尝试在布局中调用 slider 时,我收到以下错误 TypeError: Object of type 'DataFrame' is not JSON serializable
      • 我明白了。然后你需要在创建数据框之前将数据传递给回调。
      • 在创建数据框之前,您是否将数据传递给filtered_data
      • 您好,托尼,感谢您更新的答案,它运行良好。最后一个问题,有没有办法用 code 部分调用 df 列。我的完整数据框有超过 100 个 sources 和“目标”节点。再次感谢。
      • 只需将df 拆分为多个参数,例如:target = df['target'].valuessource_count = df['source_count'].values 等...并将它们传递给您的回调
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-14
      • 2020-11-07
      • 2021-09-27
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多