Ползунок на основе значения атрибута узла Networkx с Bokeh - PullRequest
1 голос
/ 03 апреля 2019

Я пытаюсь разработать ползунок, который будет ограничивать количество узлов, видимых на сетевом графике, на основе значения атрибута узла.Приведенный ниже 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.

Любая помощь, которую кто-либо может оказать, будет принята с благодарностью.

1 Ответ

2 голосов
/ 03 апреля 2019

Если вы можете запустить ваше приложение с 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)

Результат:

enter image description here

...