Connect behaviors between networkx and table

Hi everyone, I built a networkx graph which shows the odds ratio between medications and diseases. Before I finalize the graph, I’m trying to connect it to an interactive DataTable which highlight the properties of “clicked” nodes and connected nodes, edges. However, I am keep getting error message " TypeError: Object of type DataFrame is not JSON serializable"

Here is my complete code for the bokeh networkx:

### Choose a title!
output_notebook()
title = 'Medication Network Graph'

### Establish which categories will appear when hovering over each node
node_hover_tool = [('Node','@index'),('Class','@class')]

### Create a plot — set dimensions, toolbar, and title
plot = figure(tooltips = node_hover_tool, title=title,
              tools="pan,wheel_zoom,box_zoom,reset", toolbar_location="above",plot_width=1350,plot_height=2000) #x_range=(-2000, 1700), y_range=(-2000, 4000),xwheel_pan, ywheel_pan,
plot.add_tools(HoverTool(tooltips=node_hover_tool), TapTool(), BoxSelectTool())

### Add nodes and edges (1178 nodes - medications + diseases / 778 edges - odds ratio between medications and diseases)
G=nx.Graph()
G.add_nodes_from(node_list) # format: [(node, {size,color,pos,class}),(node2, {size,color,pos,class}),...]
G.add_edges_from(edge_list) # format: [(node,node2,{width,color,color_clicked}),(node4,node7,{width,color,color_clicked}),...]

edge_weight = [i['weight'] for i in dict(G.edges).values()]
edge_color = [i['color'] for i in dict(G.edges).values()]

fixed_nodes = node_dict.keys()
fixed_positions = nx.get_node_attributes(G,'pos')
node_sizes = nx.get_node_attributes(G,'size')
node_colors = nx.get_node_attributes(G,'color')

pos = nx.spring_layout(G,pos=fixed_positions)#,pos=fixed_positions, fixed = fixed_nodes
edge_width = nx.get_edge_attributes(G,'weight')
edge_color = nx.get_edge_attributes(G,'color')
edge_color_click=nx.get_edge_attributes(G,'color_clicked')


nx.set_node_attributes(G, node_colors, 'node_color')
nx.set_node_attributes(G, node_sizes, 'node_size')
nx.set_edge_attributes(G, edge_color, "edge_color")
nx.set_edge_attributes(G, edge_color_click, "edge_color_click")

network_graph = from_networkx(G, nx.spring_layout,pos=fixed_positions, fixed = fixed_nodes,scale=10,center=(0,0))

### Set node_renderer/edge_renderer to specify the properties after click the node #########
network_graph.node_renderer.glyph = Circle(size='node_size', fill_color='node_color')
network_graph.node_renderer.selection_glyph = Circle(size='node_size', fill_color='node_color')
network_graph.node_renderer.hover_glyph = Circle(size='node_size', fill_color='node_color')

network_graph.edge_renderer.data_source.data["line_color"] = [G.get_edge_data(a,b)['color'] for a, b in G.edges()]
network_graph.edge_renderer.glyph = MultiLine(line_color="edge_color", line_alpha=0.2)
network_graph.edge_renderer.selection_glyph = MultiLine(line_color= "edge_color_click",  line_alpha=1)
network_graph.edge_renderer.hover_glyph = MultiLine(line_color="edge_color_click", line_alpha=1)
network_graph.edge_renderer.data_source.data["line_width"] = [G.get_edge_data(a,b)['weight'] for a, b in G.edges()]
network_graph.edge_renderer.glyph.line_width = {'field': 'line_width'}
network_graph.edge_renderer.selection_glyph.line_width = {'field': 'line_width'}
network_graph.edge_renderer.hover_glyph.line_width = {'field': 'line_width'}

network_graph.selection_policy = NodesAndLinkedEdges()


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





### Making interactive tables connected to the networkx 
### `data`: data with all edge information. nodes information is merged to include "End Node Class"
Edges_DF = pd.DataFrame.from_dict(G.edges,orient='index').reset_index().rename(columns={'level_0':'start','level_1':'end'})
Nodes_DF = pd.DataFrame.from_dict(G.nodes,orient='index').reset_index()[['index','class']]
data = pd.merge(Edges_DF,Nodes_DF,left_on='end',right_on='index',how='left').drop(columns=['index'],axis=1)

### Making Data Table object in Bokeh using `data`
source = ColumnDataSource(data)

columns = [
        TableColumn(field="start", title="Start"),
        TableColumn(field="end", title="End"),
        TableColumn(field="class", title="End Node Class"),
        TableColumn(field="odds_ratio", title="Odds Ratio"),
        TableColumn(field="p-val", title="p-value"),
    ]

columns_2 = [
        TableColumn(field="start", title="Clicked"),
        TableColumn(field="end", title="Connected"),
        TableColumn(field="class", title="Connected Node Class"),
        TableColumn(field="odds_ratio", title="Odds Ratio"),
        TableColumn(field="p-val", title="p-value"),
    ]

### Making two tables based on the `source` data and columns selected
#### Desired Output: data_table will be displayed without change. `data_table_2` will be changed based on the selected node in the networkx (e.g., if Acetaminophen is selected, then all the rows having Acetaminophen in `Start` column will be selected and displayed.)
data_table = DataTable(source=source, columns=columns) #, width=600, height=280
data_table_2 = DataTable(source=source, columns=columns_2) 


def selected_points(attr,old,new): ### Return the selected points ###
    selected_idx = network_graph.node_renderer.data_source.selected.indices #does not work
    print(selected_idx)

network_graph.node_renderer.data_source.selected.js_on_change('indices', CustomJS(args=dict(graph=network_graph.node_renderer.data_source,data=data), code="""
        const inds = cb_obj.indices;
        const d1 = data_table_2.data;
        d2 = []
        for (let i = 0; i < inds.length; i++) {
            d2.push(d1[inds[i]])
        }
        data_table_2.change.emit();
    """)
)


############ UPDATE END #############



plot.xgrid.visible = False
plot.ygrid.visible = False
plot.axis.visible = False
plot.outline_line_color = None


show(Column(plot,Row(data_table,data_table_2)))

save(plot, filename="May10_2022_Interactive_Plot.html")

And here is the example of my plot before I incorporated the CustomJS part.

Any recommendations will be much appreciated!

Double check the dtypes your are trying to send to/from the server. The “not-serializable” error leads me to believe its some type of nested structure.