I just created enough plots and added them to the layout, in the js callback I just set visible the selected one and set invisible the others. For bigger data, it’s not a good solution. How to deal with it? (i used nx.DiGraph)
plots count is 437, and the data length is 4650
from bokeh.io import output_notebook
output_notebook()
import pandas as pd
import networkx as nx
from bokeh.io import output_notebook, show, output_file
from bokeh.layouts import column, row
from bokeh.models import (Plot, Range1d, Circle, HoverTool, TapTool, BoxSelectTool, ColumnDataSource,
LabelSet, CustomJS, Select, Legend, LegendItem, MultiLine, NodesAndLinkedEdges, StaticLayoutProvider)
from bokeh.plotting import figure, from_networkx
from bokeh.palettes import Blues4
import math
data = pd.read_csv('data.csv')
data['id'] = data['id'].astype(float).astype(str)
data['parentID'] = data['parentID'].astype(str)
data['firstName'] = data['firstName'].astype(str)
data['lastName'] = data['lastName'].astype(str)
data = data.sort_values(by = 'credit', ascending = False).reset_index(drop=True)
dd = set(data['id'])
dd.add('nan')
len(dd)
nones = data['parentID'][(data['parentID'].isin(dd) == False)].unique()
list_of_nans = []
for i in nones:
l = [i, 0, '', 0, '', '', 0.0, 0]
list_of_nans.append(l)
ll = pd.DataFrame(list_of_nans, columns = data.columns)
data = pd.concat([ll, data], ignore_index=False)
second_followers_filtered = data[(data['secondLevelfollowersCount'] > 0)].sort_values(by = 'firstName').reset_index(drop=True)
names_second = second_followers_filtered['firstName'] + ' ' + second_followers_filtered['lastName']
second_people = pd.DataFrame({'id': second_followers_filtered['id'], 'name': names_second})
colors = ["#d7191c", "#fdae61", "#a6d96a", "#1a9641"]
def set_coords(DG, parent_id, angle, radius, i):
parent_coord = DG.nodes[parent_id]['pos']
x = parent_coord[0] + radius * math.cos(angle * i)
y = parent_coord[1] + radius * math.sin(angle * i)
return (x, y)
def set_second_level_coords(DG, parent_id, num_followers, angle):
parent_coord = DG.nodes[parent_id]['pos']
parent_angle = math.atan2(parent_coord[1], parent_coord[0])
sector_angle = angle * math.pi / 180
angle_increment = sector_angle / num_followers if num_followers > 1 else 0
if num_followers % 2 != 0:
angle_start = (parent_angle - sector_angle / 2) + (sector_angle / 2)
else:
angle_start = (parent_angle - sector_angle / 2) + (sector_angle / 2) + (angle_increment / 2)
coords = []
angle = 0
for i in range(num_followers):
angle = angle_start - angle_increment * (num_followers // 2 - i) if num_followers > 1 else angle_start
x = parent_coord[0] + 0.45 * math.cos(angle)
y = parent_coord[1] + 0.45 * math.sin(angle)
coords.append((x, y))
return coords
return coords
def di_graph(central_node):
DG = nx.DiGraph()
parent_id = data['parentID'][data['id'] == central_node].values[0]
parent_name = data['firstName'][data['id'] == parent_id].values[0] if data['parentID'][data['id'] == central_node].values[0] != 'nan' else "None"
firstF = data['followersCount'][data['id'] == central_node].values[0]
secondF = data['secondLevelfollowersCount'][data['id'] == central_node].values[0]
DG.add_node(central_node, label=data['firstName'][data['id'] == central_node].values[0],
ardx=data['credit'][data['id'] == central_node].values[0],
parent_info = parent_name, first = firstF, second = secondF, pos=(0, 0))
first_level = data[data['parentID'] == central_node]
alpha = 2 * math.pi / len(first_level) if len(first_level) > 1 else 0
for i, row in enumerate(first_level.iterrows()):
node_id = row[1]['id']
coord = set_coords(DG, central_node, alpha, 0.5, i)
parent_name = data['firstName'][data['id'] == row[1]['parentID']].values[0]
firstF = row[1]['followersCount']
secondF = row[1]['secondLevelfollowersCount']
DG.add_node(node_id, label=row[1]['firstName'], ardx=row[1]['credit'],
parent_info = parent_name, first = firstF, second = secondF, pos=coord)
DG.add_edge(central_node, node_id)
DG.add_edge(node_id, central_node)
for parent_id in first_level['id']:
second_level = data[data['parentID'] == parent_id]
coords = set_second_level_coords(DG, parent_id, len(second_level), 720/len(first_level))
for i, row in enumerate(second_level.iterrows()):
node_id = row[1]['id']
coord = coords[i]
parent_name = data['firstName'][data['id'] == row[1]['parentID']].values[0]
firstF = row[1]['followersCount']
secondF = row[1]['secondLevelfollowersCount']
DG.add_node(node_id, label=row[1]['firstName'], ardx=row[1]['credit'],
parent_info = parent_name, first = firstF, second = secondF, pos=coord)
DG.add_edge(parent_id, node_id)
DG.add_edge(node_id, parent_id)
node_colors = []
for node in DG.nodes():
if node in data['id'][data['credit'] < 500].values:
node_colors.append(colors[0])
elif node in data['id'][(data['credit'] >= 500) & (data['currentArdx'] < 1000)].values:
node_colors.append(colors[1])
elif node in data['id'][(data['credit'] >= 1000) & (data['currentArdx'] < 2500)].values:
node_colors.append(colors[2])
else:
node_colors.append(colors[3])
node_sizes = []
node_line_colors = []
for node in DG.nodes():
if node == central_node:
node_sizes.append(55)
node_line_colors.append('black')
elif node in first_level['id'].values:
node_sizes.append(35)
node_line_colors.append('#555555')
else:
node_sizes.append(20)
node_line_colors.append('#a9a9a9')
labels = [DG.nodes[node]['label'] if (node == central_node or node in first_level['id'].values) else '' for node in DG.nodes()]
return DG, node_colors, node_sizes, node_line_colors, labels
def create_network_plot(data, central_node):
DG, node_colors, node_sizes, node_line_colors, labels = di_graph(central_node)
node_mapping = {node: i for i, node in enumerate(DG.nodes())}
DG_int = nx.relabel_nodes(DG, node_mapping)
pos = nx.get_node_attributes(DG_int, 'pos')
network_graph = from_networkx(DG_int, layout_function=nx.spring_layout)
network_graph.layout_provider = StaticLayoutProvider(graph_layout=pos)
network_graph.node_renderer.data_source.data['colors'] = node_colors
network_graph.node_renderer.data_source.data['sizes'] = node_sizes
network_graph.node_renderer.data_source.data['line_colors'] = node_line_colors
network_graph.node_renderer.glyph = Circle(size='sizes', fill_color='colors', line_color='line_colors', line_width = 2)
network_graph.edge_renderer.glyph = MultiLine(line_color="grey", line_alpha=0.8, line_width=3)
network_graph.node_renderer.data_source.data['firstName'] = [DG.nodes[node]['label'] for node in DG.nodes()]
network_graph.node_renderer.data_source.data['credit'] = [DG.nodes[node]['ardx'] for node in DG.nodes()]
network_graph.node_renderer.hover_glyph = Circle(size=0.8, fill_color="white", line_width=3, line_color='black')
network_graph.node_renderer.selection_glyph = Circle(size=0.8, fill_color="white", line_width=3, line_color='black')
network_graph.edge_renderer.glyph = MultiLine(line_alpha=0.5, line_width=1)
network_graph.edge_renderer.selection_glyph = MultiLine(line_color="black", line_width=3)
network_graph.edge_renderer.hover_glyph = MultiLine(line_color="black", line_width=3)
network_graph.selection_policy = NodesAndLinkedEdges()
network_graph.inspection_policy = NodesAndLinkedEdges()
plot = figure(title="Network of Relations", x_range=Range1d(-1.5, 1.5), y_range=Range1d(-1.5, 1.5),
tools="pan,wheel_zoom,save,reset", active_scroll='wheel_zoom', width=800, height=600)
plot.title.text_font_size = "16px"
hover_tool = HoverTool(tooltips=[("First name", "@firstName"), ("Current Ardx", "@credit"),
("Parent name", "@parent_info"), ("1st level followers", "@first"), ("2nd level followers", "@second")])
plot.add_tools(hover_tool, TapTool(), BoxSelectTool())
plot.renderers.append(network_graph)
x, y = zip(*network_graph.layout_provider.graph_layout.values())
source = ColumnDataSource({'x': x, 'y': y, 'firstName': labels})
labels = LabelSet(x='x', y='y', text='firstName', source=source, text_align='center', text_baseline='middle',
text_font_size='10pt', text_color='black')
plot.add_layout(labels)
items = [
LegendItem(label=f"Ardx < 500", renderers=[plot.circle(x=100, y=100, size=15, fill_color=colors[0])]),
LegendItem(label=f"500 =< Ardx < 1000", renderers=[plot.circle(x=100, y=100, size=15, fill_color=colors[1])]),
LegendItem(label=f"1000 =< Ardx < 2500", renderers=[plot.circle(x=100, y=100, size=15, fill_color=colors[2])]),
LegendItem(label=f"2500 =< Ardx", renderers=[plot.circle(x=100, y=100, size=15, fill_color=colors[3])])
]
legends = Legend(items=items, )
plot.add_layout(legends)
plot.legend.title = "Current Ardx"
plot.legend.title_text_font_style = "bold"
plot.legend.title_text_font_size = "16px"
plot.legend.label_text_font_size = "12px"
plot.axis.visible = False
return plot
def selection(tit, datafr, plts):
initial_value = datafr.name.iloc[0] if not datafr.empty else ''
select = Select(title= tit, value=initial_value,
options=datafr.name.tolist(), width=300)
return select
code_ = """
var selected = select.value;
var main_p_ids = ids;
var main_p_names = names;
for (var key in plots) {
plots[key].visible = false;
}
for (var i = 0; i < main_p_names.length; i++) {
if (main_p_names[i] == selected) {
plots[main_p_ids[i]].visible = true;
break;
}
}
"""
nodes_of_second = second_people['id'].tolist()
def children_plots(chs):
plots = {}
for node in chs:
plots[node] = create_network_plot(data, node)
layout = column(*[plot for plot in plots.values()])
for plot in plots.values():
plot.visible = False
if chs:
plots[chs[0]].visible = True
names_second = data['firstName'][data['id'].isin(chs)] + ' ' + data['lastName'][data['id'].isin(chs)]
people = pd.DataFrame({'id': chs, 'name': names_second})
sel = selection('Select person with 1st level followers', people, plots)
callback = CustomJS(args=dict(plots=plots, select=sel, ids = people['id'].tolist(),
names = people['name'].tolist()), code=code_)
sel.js_on_change('value', callback)
first_layout = column([sel, layout], margin=(0, 0, 0, 10))
return first_layout
plots_of_second = {}
childrens = []
layouts_of_children = {}
lays = {}
for node in nodes_of_second:
plots_of_second[node] = create_network_plot(data, node)
childrens = data['id'][data['parentID'] == node].tolist()
layouts_of_children[node] = children_plots(childrens)
lays[node] = row([plots_of_second[node], layouts_of_children[node]])
sel2 = selection('Select person with 2nd level followers', second_people, plots_of_second)
callback2 = CustomJS(args=dict(plots=plots_of_second, select=sel2, ids = second_people['id'][:10].tolist(), names = second_people['name'][:10].tolist()), code=code_)
sel2.js_on_change('value', callback2)
layout = column(*[plot for plot in lays.values()])
for plot in lays.values():
plot.visible = False
if nodes_of_second:
lays[nodes_of_second[0]].visible = True
second_layout = column([sel2, layout] , margin=(0, 0, 0, 20))
show(second_layout)