-->

How to add permanent name labels (not interactive

2020-07-23 04:05发布

问题:

I am trying to add a permanent label on nodes for a networkx graph using spring_layout and bokeh library. I would like for this labels to be re-positioned as the graph scales or refreshed like what string layout does, re-positioning the nodes as the graph scales or refreshed.

I tried to create the graph, and layout, then got pos from the string_layout. However, as I call pos=nx.spring_layout(G), it will generated a set of positions for the nodes in graph G, which I can get coordinates of to put into the LabelSet. However, I have to call graph = from_networkx(G, spring_layout, scale=2, center=(0,0)) to draw the network graph. This will create a new set of position for the node. Therefore, the positions of the nodes and the labels will not be the same.

How to fix this issues?

回答1:

Thanks for asking this question. Working through it, I've realized that it is currently more work than it should be. I'd very strongly encourage you to open a GitHub issue so that we can discuss what improvements can best make this kind of thing easier for users.

Here is a complete example:

import networkx as nx

from bokeh.io import output_file, show
from bokeh.models import CustomJSTransform, LabelSet
from bokeh.models.graphs import from_networkx

from bokeh.plotting import figure

G=nx.karate_club_graph()

p = figure(x_range=(-3,3), y_range=(-3,3))
p.grid.grid_line_color = None

r = from_networkx(G, nx.spring_layout, scale=3, center=(0,0))
r.node_renderer.glyph.size=15
r.edge_renderer.glyph.line_alpha=0.2

p.renderers.append(r)

So far this is all fairly normal Bokeh graph layout code. Here is the additional part you need to add permanent labels for each node:

from bokeh.transform import transform    

# add the labels to the node renderer data source
source = r.node_renderer.data_source
source.data['names'] = [str(x*10) for x in source.data['index']]

# create a transform that can extract the actual x,y positions
code = """
    var result = new Float64Array(xs.length)
    for (var i = 0; i < xs.length; i++) {
        result[i] = provider.graph_layout[xs[i]][%s]
    }
    return result
"""
xcoord = CustomJSTransform(v_func=code % "0", args=dict(provider=r.layout_provider))
ycoord = CustomJSTransform(v_func=code % "1", args=dict(provider=r.layout_provider))

# Use the transforms to supply coords to a LabelSet 
labels = LabelSet(x=transform('index', xcoord),
                  y=transform('index', ycoord),
                  text='names', text_font_size="12px",
                  x_offset=5, y_offset=5,
                  source=source, render_mode='canvas')

p.add_layout(labels)

show(p)

Basically, since Bokeh (potentially) computes layouts in the browser, the actual node locations are only available via the "layout provider" which is currently a bit tedious to access. As I said, please open a GitHub issue to suggest making this better for users. There are probably some very quick and easy things we can do to make this much simpler for users.

The code above results in:



回答2:

Fixed node positions

From the networkx.spring_layout() documentation: you can add a list of nodes with a fixed position as a parameter.

import networkx as nx
import matplotlib.pyplot as plt

g = nx.Graph()
g.add_edges_from([(0,1),(1,2),(0,2),(1,3)])

pos = nx.spring_layout(g)
nx.draw(g,pos)
plt.show()

Then you can plot the nodes at a fixed position:

pos = nx.spring_layout(g, pos=pos, fixed=[0,1,2,3])
nx.draw(g,pos)
plt.show()



回答3:

similar solution as @bigreddot.

    #Libraries for this solution
    from bokeh.plotting import figure ColumnDataSource
    from bokeh.models import LabelSet

    #Remove randomness
    import numpy as np
    np.random.seed(1337)

    #Load positions
    pos = nx.spring_layout(G)

    #Dict to df
    labels_df = pd.DataFrame.from_dict(pos).T

    #Reset index + column names
    labels_df = labels_df.reset_index()
    labels_df.columns = ["names", "x", "y"]

    graph_renderer = from_networkx(G, pos, center=(0,0))
    .
    .
    .
    plot.renderers.append(graph_renderer)

    #Set labels
    labels = LabelSet(x='x', y='y', text='names', source=ColumnDataSource(labels_df))

    #Add labels
    plot.add_layout(labels)