Adding interactive legend to Bokeh networkx graph to mute communities

I’ve been utilizing Bokeh to visualize the community partition of a network of text documents. I’ve been able to successfully visualize the network, color the nodes based on community membership and add various attributes to the hover-window for each node. However, I would like to add an interactive legend that would allow the user to choose which community to show, and which communities to mute.

Here is my current code:

## Visualization function

def visualize_network(GraphObject, meta_df, day, save_map=False):
    
    #Create a network graph object with spring layout
# https://networkx.github.io/documentation/networkx-1.9/reference/generated/networkx.drawing.layout.spring_layout.html
    network_graph = from_networkx(GraphObject, 
                                  nx.spring_layout, 
                                  scale=10, center=(0, 0))
    ## create the colors, add meta label:
    cpalette = [item for sublist in Category20.values() for item in sublist]

    for i,r in meta_df.iterrows():
        if i == 0:
            network_graph.node_renderer.data_source.data.setdefault('COLORS', []).append((cpalette[r.Community]))
            network_graph.node_renderer.data_source.data.setdefault('title', []).append(r.title)
            network_graph.node_renderer.data_source.data.setdefault('Entities', []).append((r.doc_))
            network_graph.node_renderer.data_source.data.setdefault('Community', []).append((r.Community))
            network_graph.node_renderer.data_source.data.setdefault('Source', []).append((r.source))
            network_graph.node_renderer.data_source.data.setdefault('Date', []).append((r.publication_date.strftime('%d/%m/%Y')))
            
        else:
            network_graph.node_renderer.data_source.data['COLORS'].append((cpalette[int(r.Community)]))
            network_graph.node_renderer.data_source.data['title'].append(r.title)
            network_graph.node_renderer.data_source.data['Entities'].append((r.doc_))
            network_graph.node_renderer.data_source.data['Community'].append((r.Community))
            network_graph.node_renderer.data_source.data['Source'].append((r.source))
            network_graph.node_renderer.data_source.data['Date'].append((r.publication_date.strftime('%d/%m/%Y')))
            
    #Establish which categories will appear when hovering over each node
    HOVER_TOOLTIPS = [("Community","@Community"),("Title", "@title"), ("entities","@Entities"),
                      ('Source','@Source'),('Date','@Date')]
    #Create a plot — set dimensions, toolbar, and title
    plot = figure(tooltips = HOVER_TOOLTIPS,
              tools="pan,wheel_zoom,save,reset", active_scroll='wheel_zoom',
            x_range=Range1d(-10.1, 10.1), y_range=Range1d(-10.1, 10.1), 
                  title=f"Coverage of {EVENT} - Day {day}",
                 width=1000, height=800)
    
        #Set node size and color
    network_graph.node_renderer.glyph = Circle(size=15, fill_color='COLORS')

    #Set edge opacity and width
    network_graph.edge_renderer.glyph = MultiLine(line_alpha=0.5, line_width=1)

    #Add network graph to the plot
    plot.renderers.append(network_graph)

    if save_map is True:
        save(plot, filename=f"{EVENT}_Day{day}.html")
    else:
        show_map(plot)
    
    return

I have tried simply adding a legend via the built-in legend function:

#Set node size and color - with label added:
network_graph.node_renderer.glyph = Circle(size=15, fill_color='COLORS', legend_label='Community')

and then adding the legend to the plot:

plot.legend.location = "top_left"
plot.legend.click_policy="mute"

I have seen some posts regarding a manual creation of a Legend (such as Legend in network graph) but have been unable to implement it using the community meta information.

Any ideas?

Thanks

There’s not really enough details in your code snippet to offer concrete guidance directly. I will say that each separately hideable (or mutable) entry in a legend has to be its own LegendItem. So you should be workable as long as the “partitions” are each all plotted with entirely separate graph renderers. Then you could put the node and edge renderers for graph render in separate LegendItem that can be clicked to mute or hide. I don’t think this is workable if you are only using a single graph renderer and e.g. color mapping the nodes.

Thanks for the reply! I’m still not understanding exactly how to go about this. I’m also not positive that I am completely fluent in the structure of the Bokeh objects. That being said:

Essentially, I have a single graph renderer “network_graph” that takes the data from a networkX “GraphObject” and creates node and edge objects (“network_graph.node_renderer” and “network_graph.edge_renderer” respectively). Each node is a document, and each edge is a pairwise cosine similarity to another text document.

I have manually added columns to the graph renderer based on a dataframe with the meta information for each document (i.e. each node), screenshotted here:

I have added the columns as additional data to the “network_graph.node_renderer.data_source.data” , which then appears when the cursor hovers upon each node.

What I would like, is for one of the colums (“Community”) to appear in a Legend, that I could then allow the user to click on and mute specific communities of nodes based on their categories.

How do I add a “LegendItem” specifically for this one column of the data?

As I said above, I don’t think there is any good, obvious way to have an interactive legend with all the data being rendered by a single graph renderer. You would need to split up the data and have a separate graph renderer for each community, so that there can be a separate LegendItem for each community. As long as the nodes and edges of each community are disjoint from other communities, this seems like it ought to be possible. Otherwise, Bokeh may not be the best tool for your needs.

1 Like

FWIW, I’ve experimented using d3’s hierarchy (GitHub - d3/d3-hierarchy: 2D layout algorithms for visualizing hierarchical data.) in conjunction with these network graphs in similar situations, to build something along these lines (Collapsible Tree / D3 / Observable ) but also to perform aggregate operations on each node (and display results in other linked plots etc all within the bokeh framework). I gotta say it gets complicated fast and this might not be the best approach, but in theory you could assemble a hierarchy object and use the parent-children relationships etc to hide/show certain nodes and edges, all based on user behaviour (like them clicking on a node or a widget separate from the figure, or even a legend item perhaps).

1 Like

ok, thanks! will continue trying to work on this… thanks for the tips

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.