What you are describing is not really consistent with my understanding or experience, so what would really be helpful is a complete code example (i.e. that could actually be run to test) showing what you tried.

I’ve tried the example from https://bokeh.pydata.org/en/latest/docs/user_guide/graph.html#interaction-policies and indeed, I can have both tooltips.

So I’ve simplified my code in order to obtain an example that shows the problem. My goal is to have a subclass of `networkx.DiGraph `

with a draw method, I’ve added some geometric computation to have the edges starting/ending on the border of the nodes.

import networkx as nx

import pandas as pd

import numpy as np

import bokeh.io as bkio

import bokeh.plotting as bkp

import bokeh.models as bkm

import bokeh.palettes as bkpal

class Graph (nx.DiGraph) :

def **init** (self) :

# builds a dummy graph

super().**init**()

for name, labels in [(“a”, {1, 2}), (“b”, {2, 3}), (“c”, {3, 4}),

(“d”, {4, 5}), (“e”, {6, 7})] :

self.add_node(name, labels=labels)

for src, tgt in [“ab”, “ac”, “bc”, “bd”, “be”, “ce”, “ed”] :

self.add_edge(src, tgt,

labels=self.node[src][“labels”] | self.node[tgt][“labels”])

def draw (self, engine=“dot”, nodes_size=.02, arrow_size=.01, palette=None) :

# 1. convert graph to dataframes

# 1.1. init nodes dataframe with layout

nodes = pd.DataFrame.from_dict(nx.nx_pydot.pydot_layout(self, prog=engine),

orient=“index”, columns=[“x”, “y”])

# 1.2. normalize layout in [0;1]

for coord in [“x”, “y”] :

nodes[coord] -= nodes[coord].min()

nodes[coord] /= nodes[coord].max()

# 1.3. concert labels to str

nodes[“labels”] = [", ".join(sorted(str(l) for l in self.node[node][“labels”]))

for node in nodes.index]

# 1.4. create edges dataframe

idx = nodes.to_dict(orient=“index”)

edges = pd.DataFrame(((src, idx[src][“x”], idx[src][“y”],

tgt, idx[tgt][“x”], idx[tgt][“y”],

“, “.join(sorted(str(l) for l in label)))

for src, tgt, label in self.edges(data=“labels”)),

columns=[“start”, “x_start”, “y_start”,

“end”, “x_end”, “y_end”,

“labels”])

# 1.5. compute edges slopes

edges[“slope”] = np.arctan2(edges[“y_end”]-edges[“y_start”],

edges[“x_end”]-edges[“x_start”])

# 1.6. compute edges start/end points wrt to nodes size

edges[“x_start”] += np.cos(edges[“slope”]) * nodes_size

edges[“y_start”] += np.sin(edges[“slope”]) * nodes_size

edges[“x_end”] -= np.cos(edges[“slope”]) * nodes_size

edges[“y_end”] -= np.sin(edges[“slope”]) * nodes_size

edges[“size”] = np.full(len(edges), arrow_size)

# 2. draw graph with bokeh

# 2.1. get palette

pal = getattr(bkpal, palette or “Spectral4”, bkpal.Spectral4)

# 2.2. create plot

plot = bkp.figure(title=“Graph”,

x_range=(-2*nodes_size, 1 + 2*nodes_size),

y_range=(-2*nodes_size, 1 + 2*nodes_size),

x_axis_location=None, y_axis_location=None,

outline_line_dash=“dotted”)

plot.grid.visible = False

# 2.3. create graph renderer

graph = bkm.GraphRenderer()

# 2.4. create and add nodes CDS

nodes_dict = {k: nodes[k] for k in nodes.columns}

nodes_dict[“index”] = nodes.index

nodes_data = bkm.ColumnDataSource(data=nodes_dict)

graph.node_renderer.data_source.data = nodes_data.data

# 2.5. create and add edges CDS

edges_dict = {k: edges[k] for k in edges.columns}

edges_dict[“xs”] = [(edges[“x_start”][e], edges[“x_end”][e])

for e in edges.index]

edges_dict[“ys”] = [(edges[“y_start”][e], edges[“y_end”][e])

for e in edges.index]

edges_data = bkm.ColumnDataSource(data=edges_dict)

graph.edge_renderer.data_source.data = edges_data.data

# 2.6. create layout as expected by GraphRenderer

layout = {n : (nodes[“x”][n], nodes[“y”][n]) for n in nodes.index}

graph.layout_provider = bkm.StaticLayoutProvider(graph_layout=layout)

# 2.7. add arrows

arrows = bkm.Arrow(end=bkm.NormalHead(size=arrow_size*1000),

x_start=“x_start”,

y_start=“y_start”,

x_end=“x_end”,

y_end=“y_end”,

source=edges_data)

plot.add_layout(arrows)

# 2.8. set glyphs for nodes and edges

graph.node_renderer.glyph = bkm.Circle(radius=nodes_size,

fill_color=pal[0],

fill_alpha=.5)

graph.node_renderer.selection_glyph = bkm.Circle(radius=nodes_size,

fill_color=pal[2],

fill_alpha=.5)

graph.node_renderer.hover_glyph = bkm.Circle(radius=nodes_size,

fill_color=pal[1],

fill_alpha=.5)

graph.edge_renderer.glyph = bkm.MultiLine(line_color=”#CCCCCC”,

line_alpha=0.8, line_width=5)

graph.edge_renderer.selection_glyph = bkm.MultiLine(line_color=pal[2],

line_width=5)

graph.edge_renderer.hover_glyph = bkm.MultiLine(line_color=pal[1],

line_width=5)

# 2.9. set policies and finish it

graph.selection_policy = bkm.NodesAndLinkedEdges()

graph.inspection_policy = bkm.NodesAndLinkedEdges()

plot.renderers.append(graph)

plot.add_tools(bkm.TapTool(),

bkm.LassoSelectTool(),

bkm.BoxSelectTool(),

bkm.HoverTool(tooltips=[(“node”, “@index”),

(“labels”, “@labels”)]))

bkio.output_file(“interactive_graphs.html”)

bkio.show(plot)

return graph, plot

Graph().draw()

``

Ideally, I’d like to have `graph.inspection_policy = bkm.```Edges`

AndLinkedNodes()`` `and distinct tooltips for nodes and edges (index&labels for nodes, just labels for edges).

But when I set this policy, I just get edges tooltips, and if I set the policy as in the code above, I just get nodes tooltips.

I don’t see what makes a difference difference between my code and adding tooltips to the example from https://bokeh.pydata.org/en/latest/docs/user_guide/graph.html#interaction-policies, which works.

Finally, I’ve noticed that on tootips that are displayed at the right of a node, there are two arrows pointing to the node :

Thank you for your help!

Franck