问题
I am looking for a way to pass a color, as assinged in NetworkX's node construction, to a Bokeh plot.
There are some great ways to implement color in the Bokeh plot after it is generated, such as this, but this solution requires that I apply the transformation to the entire data set based on an attribute.
I wanted to do something even simpler and assign a color and size based on what I assign those to be in NetworkX. I normally plot node set 1 as red, then node set 2 as blue in NetworkX, then connect them through their mutual edges. While the colors and size of the nodes are not passed to matplotlib, it IS passed to gephi when I save the file as graphml, so these data are somewhere..
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure,show
from bokeh.models.graphs import from_networkx #I haven't been able to use this!
from bokeh.io import output_notebook
from bokeh.models import HoverTool, ColumnDataSource
from bokeh.resources import CDN
from bokeh.embed import file_html
Dataset1 = ['A','B','C','D','E','F']
Dataset2 = ['ONE','TWO','THREE','FOUR','FIVE','TWENTY_EIGHT']
Edgelist = [('A','ONE'),('B','ONE'),('E','ONE'),('A','TWO'),('A','THREE'),('A','FOUR'),('C','THREE'),('D','FIVE'),('D',"TWENTY_EIGHT")]
G = nx.Graph()
G.add_nodes_from(Dataset1,color= 'green')
G.add_nodes_from(Dataset2,color='blue')
G.add_edges_from(Edgelist,weight=0.8)
layout = nx.draw_spring(G, with_labels=True)
nx.write_graphml(G,"TEST.graphML")
network = nx.read_graphml("TEST.graphML")
#start Bokeh code
layout = nx.spring_layout(network,k=1.1/sqrt(network.number_of_nodes()),iterations=100) #pass the NX file to a spring layout
nodes, nodes_coordinates = zip(*sorted(layout.items()))
nodes_xs, nodes_ys = list(zip(*nodes_coordinates))
nodes_source = ColumnDataSource(dict(x=nodes_xs, y=nodes_ys,name=nodes)) #Can this pass the color?
hover = HoverTool(tooltips=[('name', '@name')]) #would like to know how to add in more values here manually
plot = figure(plot_width=800, plot_height=400,tools=['tap', hover, 'box_zoom', 'reset'])
r_circles = plot.circle('x', 'y', source=nodes_source, size=10, color='orange', level = 'overlay')#this function sets the color of the nodes, but how to set based on the name of the node?
def get_edges_specs(_network, _layout):
d = dict(xs=[], ys=[], alphas=[])
weights = [d['weight'] for u, v, d in _network.edges(data=True)]
max_weight = max(weights)
calc_alpha = lambda h: 0.1 + 0.6 * (h / max_weight)
# example: { ..., ('user47', 'da_bjoerni', {'weight': 3}), ... }
for u, v, data in _network.edges(data=True):
d['xs'].append([_layout[u][0], _layout[v][0]])
d['ys'].append([_layout[u][1], _layout[v][1]])
d['alphas'].append(calc_alpha(data['weight']))
return d
lines_source = ColumnDataSource(get_edges_specs(network, layout))
r_lines = plot.multi_line('xs', 'ys', line_width=1.5,
alpha='alphas', color='navy',
source=lines_source)#This function sets the color of the edges
show(plot)
When opened in gephi, color is retained:
I can't quite understand how to fetch these values using bokeh's from_networkx function. It seems that this doesn't pass the attributes over as expected. What is actually being passed and how would I pass color?
Is there a better way to just assign more attributes through the ColumnDataSource that is constructed? I'm thinking something akin to passing it to a dataframe, adding a color column, then re-generating the ColumnDataSource, so I can retrieve the colors with '@node_color' for each node value.
I have lists of each of these datasets, so would it be possible to filter somehow such as:
if node_id in list1: node_color = "red" node_size = 10 if node_id in list2: node_color = "blue" node_size = 20
I'm very new to bokeh, and although it seems like these should be easy tasks, I'm completely lost in the documentation. Is it perhaps better to just generate a network using purely bokeh?
回答1:
GOT IT
The color attribute I was seeing everyone set through the various attributes (like degree of centrality, etc...) was done through a color map instance, but that is NOT required for setting sets of nodes as different colors or sizes.
The key here is the way the ColumnDataSource (CDS) was constructed. By using the line:
nodes_source = ColumnDataSource(dict(x=nodes_xs, y=nodes_ys,name=nodes)
There is no assignment of color or size as an attribute. Worse, I wasn't able to see what the CDS actually looked like. (Which I now know you can view as a pandas DF by calling the CDS.to_df()) So, I experimented and found that I can add a column by:
node_color=pd.DataFrame.from_dict({k:v for k,v in network.nodes(data=True)},orient='index').color.tolist()
color = tuple(node_color)
nodes_source = ColumnDataSource(dict(x=nodes_xs, y=nodes_ys,name=nodes, _color_= color)
This assigned the attribute that I retrieved from networkX as a value for each node as a function of its ID, passed it to a tuple, then placed it in the CDS which will be retrieved by asking the Bokeh construction to retrieve data from the column who's name is passed as a** STRING**:
plot = figure(plot_width=800, plot_height=400,tools=['tap', hover, 'box_zoom', 'reset'])
r_circles = plot.circle('x', 'y', source=nodes_source, size=10,fill_color="_color_", level = 'overlay')
To answer all 3 questions:
from_networkx retrieves all the characteristics of the nodes, [at least] if retrieved through the from_graphml() function.
The easy way to assign a columndatavalue is to pass it a tuple, as demonstrated above with a color attribute. However, this is where you can add things into the CDS that you may retrieve from the hover tool. For me, this will be very useful.
Construct a pandas DF with all the attributes you want, then use the CDS.from_df() function to pass it to a CDS for bokeh to analyze.
回答2:
Maybe the answers under this question can help you: https://stackoverflow.com/a/54475870/8123623
Create a dict where the nodes are the keys and the colors the values.
colors = [...]
colors = dict(zip(network.nodes, colors))
nx.set_node_attributes(network, {k:v for k,v in colors.items()},'colors' )
graph.node_renderer.glyph = Circle(size=5, fill_color='colors')
This is how you can use from_network()
graph = from_networkx(G, nx.dot, scale=1, center=(0,0))
来源:https://stackoverflow.com/questions/52673098/how-to-pass-node-attributes-from-networkx-to-bokeh