#bokeh #networkx
#боке #networkx
Вопрос:
У меня есть слайдер, который будет ограничивать количество узлов, отображаемых на графике. Однако, когда используется слайдер, всплывающие подсказки для проверки атрибутов узла перестают функционировать и ???
вместо этого отображаются.
Кроме того, при использовании слайдера раскраска узлов работает некорректно. При проверке консоли в Chrome отображается следующая ошибка: Uncaught Error: attempted to retrieve property array for nonexistent field 'node_colors'
. Я полагаю, что это связано с несоответствием длины массива, переданного в node_renderer.glyph
код. Текущая раскраска окрашивает все source
значения в зеленый цвет, а target
значения — в синий.
Полный код для описанного решения можно увидеть ниже:
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', '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]}
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']
node_colors = []
for node in net_graph:
if node in df["source"].values:
node_colors.append("green")
else: node_colors.append("maroon")
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.data_source.data['node_colors'] = node_colors
graph_setup.node_renderer.glyph = Circle(size = 20, fill_color = 'node_colors')
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','B'].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 = 0, end = 6, value = 6)
slider.js_on_change('value', callback)
layout = Column(graph_plot, slider)
show(layout)
Данные, включенные в пример кода, представляют собой фрагмент общего фрейма данных.
Любая помощь, которую кто-либо может предоставить, будет высоко оценена.
Ответ №1:
Пожалуйста, замените свой код обратного вызова на этот:
code = """
var new_start = start.slice();
var new_end = end.slice();
var new_index = ndata['index'].slice();
var new_node_colors = ndata['node_colors'].slice();
var new_yearly_count = ndata['yearly_count'].slice();
new_start = new_start.splice(0, cb_obj.value)
new_end = new_end.splice(0, cb_obj.value)
new_data_edge = {'start': new_start, 'end': new_end};
new_data_nodes = {};
new_data_nodes['index'] = new_index.splice(0, cb_obj.value);
new_data_nodes['node_colors'] = new_node_colors.splice(0, cb_obj.value);
new_data_nodes['yearly_count'] = new_yearly_count.splice(0, cb_obj.value);
graph_setup.edge_renderer.data_source.data = new_data_edge;
graph_setup.node_renderer.data_source.data = new_data_nodes;
"""
И добавьте это в свой код Python:
import copy
backup_node_data = copy.deepcopy(graph_setup.node_renderer.data_source.data)
callback = CustomJS(args = dict(graph_setup = graph_setup,
start = df['source'].values,
end = df['target'].values,
ndata = backup_node_data),
code = code)
Или замените весь ваш код на этот:
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
import copy
data = {'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]}
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']
node_colors = []
for node in net_graph:
if node in df["source"].values:
node_colors.append("green")
else:
node_colors.append("maroon")
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")], show_arrow = False)
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.data_source.data['node_colors'] = node_colors
graph_setup.node_renderer.glyph = Circle(size = 20, fill_color = 'node_colors')
graph_setup.edge_renderer.glyph = MultiLine(line_color = "red", line_alpha = 0.8, line_width = 1)
graph_plot.renderers.append(graph_setup)
backup_node_data = copy.deepcopy(graph_setup.node_renderer.data_source.data)
code = """
var new_start = start.slice();
var new_end = end.slice();
var new_index = ndata['index'].slice();
var new_node_colors = ndata['node_colors'].slice();
var new_yearly_count = ndata['yearly_count'].slice();
new_start = new_start.splice(0, cb_obj.value)
new_end = new_end.splice(0, cb_obj.value)
new_data_edge = {'start': new_start, 'end': new_end};
new_data_nodes = {};
new_data_nodes['index'] = new_index.splice(0, cb_obj.value);
new_data_nodes['node_colors'] = new_node_colors.splice(0, cb_obj.value);
new_data_nodes['yearly_count'] = new_yearly_count.splice(0, cb_obj.value);
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,
ndata = backup_node_data),
code = code)
slider = Slider(title = 'Slider', start = 0, end = 8, value = 8)
slider.js_on_change('value', callback)
layout = Column(graph_plot, slider)
show(layout)
Результат:
Комментарии:
1. Это гениально, Тони, большое спасибо! Знаете ли вы, как заставить ползунок удалять только
target
узлы? Т.Е. Все исходные узлы будут видны, когда ползунок установлен в0
положение. Еще раз спасибо!2. Это сложнее. Вам нужно будет разделить
source
наA
иB
. Я изучу это и вернусь к этому позже или завтра.3. Отлично, Тони, большое спасибо за всю вашу помощь.
4. Извините, я забыл вас. Я выполнил часть скрытия / отображения узлов, но я не делал ребер, поэтому всегда остается два ребра. Вам все еще нужен этот код?
Ответ №2:
Другая версия, которая всегда оставляет центральные узлы:
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
import copy
data = {'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]}
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']
node_colors = []
for node in net_graph:
if node in df["source"].values:
node_colors.append("green")
else:
node_colors.append("maroon")
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.data_source.data['node_colors'] = node_colors
graph_setup.node_renderer.glyph = Circle(size = 20, fill_color = 'node_colors')
graph_setup.edge_renderer.glyph = MultiLine(line_color = "red", line_alpha = 0.8, line_width = 1)
graph_plot.renderers.append(graph_setup)
a_index = graph_setup.node_renderer.data_source.data['index'].index("A")
b_index = graph_setup.node_renderer.data_source.data['index'].index("B")
if a_index != 0:
index_item = graph_setup.node_renderer.data_source.data[field][a_index]
new_data = graph_setup.node_renderer.data_source.data[field][0:a_index] graph_setup.node_renderer.data_source.data[field][a_index 1:]
new_data.insert(0, index_item)
graph_setup.node_renderer.data_source.data[field] = new_data
if b_index != 1:
for field in graph_setup.node_renderer.data_source.data:
index_item = graph_setup.node_renderer.data_source.data[field][b_index]
new_data = graph_setup.node_renderer.data_source.data[field][0:b_index] graph_setup.node_renderer.data_source.data[field][b_index 1:]
new_data.insert(1, index_item)
graph_setup.node_renderer.data_source.data[field] = new_data
backup_node_data = copy.deepcopy(graph_setup.node_renderer.data_source.data)
backup_edge_data = copy.deepcopy(graph_setup.edge_renderer.data_source.data)
code = """
var new_start = start.slice();
var new_end = end.slice();
var new_index = ndata['index'].slice();
var new_node_colors = ndata['node_colors'].slice();
var new_yearly_count = ndata['yearly_count'].slice();
new_start = new_start.splice(0, cb_obj.value)
new_end = new_end.splice(0, cb_obj.value)
new_data_edge = {'start': new_start, 'end': new_end};
new_data_nodes = {};
new_data_nodes['index'] = new_index.splice(0, cb_obj.value);
new_data_nodes['node_colors'] = new_node_colors.splice(0, cb_obj.value);
new_data_nodes['yearly_count'] = new_yearly_count.splice(0, cb_obj.value);
console.log(new_data_edge)
graph_setup.edge_renderer.data_source.data = new_data_edge;
graph_setup.node_renderer.data_source.data = new_data_nodes;
graph_setup.edge_renderer.data_source.change.emit();
graph_setup.node_renderer.data_source.change.emit();
"""
callback = CustomJS(args = dict(graph_setup = graph_setup,
start = df['source'].values,
end = df['target'].values,
ndata = backup_node_data,
edata = backup_edge_data),
code = code)
slider = Slider(title = 'Slider', start = 2, end = 8, value = 8)
slider.js_on_change('value', callback)
layout = Column(graph_plot, slider)
show(layout)