Hover-over tooltips on network edges

Hi there,

Is it possible to hover over edges in a bokeh-visualized network and display tooltip of associated ColumnDataSource?

For example, I have the following code:

node_source = ColumnDataSource(data=dict(index=node_index,

text=node_text,

tag=node_tag))

edge_source = ColumnDataSource(data=dict(

start=edges_start,

end=edges_end,

edge_label=edge_labels

))

graph_renderer = GraphRenderer()

graph_renderer.node_renderer.data_source.data = node_source.data

graph_renderer.edge_renderer.data_source.data = edge_source.data

hover = HoverTool(tooltips = [(“index”, “@index”), (“text”, “@text”), (“tag”, “@tag”), (“edge_label”, “@edge_label”])

plot = figure(tools=[hover])

plot.renderers.append(graph_renderer)

show(plot)

However, this only displays the index, text, and tag data in the node_source. The edge_labels are in the node tooltips and display as ???

Thanks.

Same thing here.

  At the moment I cannot make it to run as expected.

  Tooltips on Nodes and other tooltips on Edges.

  Tried with:
  ```
  graph.inspection_policy = NodesOnly() ---> No tooltips on edges
  graph.inspection_policy = NodesAndLinkedEdges() ---> No

tooltips when hover edges
graph.inspection_policy = EdgesAndLinkedNodes() —> Tooltip of
edge located on the source node, No tooltips on edges
```

···

On 2017-09-06 20:05, kernelmachine
wrote:

Hi there,

      Is it possible to hover over edges in a bokeh-visualized

network and display tooltip of associated ColumnDataSource?

For example, I have the following code:

        node_source =

ColumnDataSource(data=dict(index=node_index,

text=node_text,

tag=node_tag))

edge_source = ColumnDataSource(data=dict(

start=edges_start,

end=edges_end,

edge_label=edge_labels

))

graph_renderer = GraphRenderer()

        graph_renderer.node_renderer.data_source.data =

node_source.data

        graph_renderer.edge_renderer.data_source.data =

edge_source.data

      hover = HoverTool(tooltips = [("index", "@index"), ("text",

@text”), (“tag”, “@tag”), (“edge_label”, “@edge_label”])

plot = figure(tools=[hover])

plot.renderers.append(graph_renderer)

show(plot)

      However, this only displays the index, text, and tag data

in the node_source. The edge_labels are in the node tooltips
and display as ???

Thanks.

  You received this message because you are subscribed to the Google

Groups “Bokeh Discussion - Public” group.

  To unsubscribe from this group and stop receiving emails from it,

send an email to [email protected].

  To post to this group, send email to [email protected]

  To view this discussion on the web visit [https://groups.google.com/a/continuum.io/d/msgid/bokeh/2b56d413-d021-411a-b30c-f817e1d0eebf%40continuum.io](https://groups.google.com/a/continuum.io/d/msgid/bokeh/2b56d413-d021-411a-b30c-f817e1d0eebf%40continuum.io?utm_medium=email&utm_source=footer).

  For more options, visit [https://groups.google.com/a/continuum.io/d/optout](https://groups.google.com/a/continuum.io/d/optout).

It’s possible by setting the GraphRenderer.inspection_policy=EdgesAndLinkedNodes(). By default the tooltip will hover over one of the nodes (this is the standard behavior for inspecting lines), but you can change that by setting the HoverTool.line_policy attr (see reference: http://bokeh.pydata.org/en/latest/docs/reference/models/tools.html#bokeh.models.tools.HoverTool.line_policy)

Here’s an example of inspecting metadata associated with the edges:

(requires Bokeh>=0.12.7)

Please let me know if the example doesn’t work for you or if I’m not understanding your issue correctly.

-lukec

···

On Wednesday, September 6, 2017 at 1:06:27 PM UTC-5, Suchin Gururangan wrote:

Hi there,

Is it possible to hover over edges in a bokeh-visualized network and display tooltip of associated ColumnDataSource?

For example, I have the following code:

node_source = ColumnDataSource(data=dict(index=node_index,

text=node_text,

tag=node_tag))

edge_source = ColumnDataSource(data=dict(

start=edges_start,

end=edges_end,

edge_label=edge_labels

))

graph_renderer = GraphRenderer()

graph_renderer.node_renderer.data_source.data = node_source.data

graph_renderer.edge_renderer.data_source.data = edge_source.data

hover = HoverTool(tooltips = [(“index”, “@index”), (“text”, “@text”), (“tag”, “@tag”), (“edge_label”, “@edge_label”])

plot = figure(tools=[hover])

plot.renderers.append(graph_renderer)

show(plot)

However, this only displays the index, text, and tag data in the node_source. The edge_labels are in the node tooltips and display as ???

Thanks.

Hi lukec,

This parameter was very helpful.

But I would like to have differnt tooltips on Nodes **and**
Edges?
Do I have to add two Hovertools with specific renderers assigned?

And I would like to `OpenUrl` on the Edges **and** Nodes to open

detail-view of these objects.

Do you have a solution for this two problems?

Raphael
···

On 2017-09-08 21:41, Luke Canavan
wrote:

    It's possible by setting the

GraphRenderer.inspection_policy=EdgesAndLinkedNodes(). By
default the tooltip will hover over one of the nodes (this is
the standard behavior for inspecting lines), but you can change
that by setting the HoverTool.line_policy attr (see
reference: )

  You received this message because you are subscribed to the Google

Groups “Bokeh Discussion - Public” group.

  To unsubscribe from this group and stop receiving emails from it,

send an email to [email protected].

  To post to this group, send email to [email protected]

  To view this discussion on the web visit [https://groups.google.com/a/continuum.io/d/msgid/bokeh/e4195e5a-609d-49a8-b030-61cfcb4fb2fa%40continuum.io](https://groups.google.com/a/continuum.io/d/msgid/bokeh/e4195e5a-609d-49a8-b030-61cfcb4fb2fa%40continuum.io?utm_medium=email&utm_source=footer).

  For more options, visit [https://groups.google.com/a/continuum.io/d/optout](https://groups.google.com/a/continuum.io/d/optout).

http://bokeh.pydata.org/en/latest/docs/reference/models/tools.html#bokeh.models.tools.HoverTool.line_policy

      Here's an example of inspecting metadata associated with

the edges:

https://gist.github.com/canavandl/8cb5ecece6ba720d09c0d1aef1666642

(requires Bokeh>=0.12.7)

      Please let me know if the example doesn't work for you or

if I’m not understanding your issue correctly.

-lukec

      On Wednesday, September 6, 2017 at 1:06:27 PM UTC-5, Suchin

Gururangan wrote:

Hi there,

            Is it possible to hover over edges in a

bokeh-visualized network and display tooltip of
associated ColumnDataSource?

For example, I have the following code:

node_source = ColumnDataSource(data=dict(index=node_index,

text=node_text,

tag=node_tag))

edge_source = ColumnDataSource(data=dict(

start=edges_start,

end=edges_end,

edge_label=edge_labels

))

graph_renderer = GraphRenderer()

graph_renderer.node_renderer. data_source.data
= node_source.data

graph_renderer.edge_renderer. data_source.data
= edge_source.data

            hover = HoverTool(tooltips = [("index", "@index"),

(“text”, “@text”), (“tag”, “@tag”), (“edge_label”,
@edge_label”])

plot = figure(tools=[hover])

plot.renderers.append(graph_renderer)

show(plot)

            However, this only displays the index, text, and tag

data in the node_source. The edge_labels are in the node
tooltips and display as ???

Thanks.

Hi,

But I would like to have differnt tooltips on Nodes and
Edges?

Do I have to add two Hovertools with specific renderers assigned?

I have exactly the same problem.

Howerver, adding two HoverTools with specific renderers does not work since it completly disable all tooltips.

Actually, using renderers=[graph.node_renderer, graph.edge_renderer] or any sublist also disable tooltips.

Using renderers=[graph] yields the same behaviour as not using the renderer option at all (that is tooltips depend on graph.inspection_policy).

Did you solve this problem?

Thanks in advance!

Franck

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.

Thanks,

Bryan

···

On Apr 16, 2019, at 6:56 AM, [email protected] wrote:

Hi,

But I would like to have differnt tooltips on Nodes and Edges?
Do I have to add two Hovertools with specific renderers assigned?

I have exactly the same problem.

Howerver, adding two HoverTools with specific renderers does not work since it completly disable all tooltips.
Actually, using renderers=[graph.node_renderer, graph.edge_renderer] or any sublist also disable tooltips.
Using renderers=[graph] yields the same behaviour as not using the renderer option at all (that is tooltips depend on graph.inspection_policy).

Did you solve this problem?

Thanks in advance!
Franck

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/8f359eb3-f9bc-486d-9d6a-05fbb9f24819%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

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=(-2nodes_size, 1 + 2nodes_size),
y_range=(-2nodes_size, 1 + 2nodes_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.```EdgesAndLinkedNodes()`` `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 :

Screenshot from 2019-04-16 18-33-37.png

Thank you for your help!

Franck

Hi,

By default hover tools try to add themselves to every available renderer. You will probably want to e.g. restrict the hover tool for nodes to just the nodes renderer by setting the "renderers" property on the tool. I believe that this is causing the spurious extra arrow.

As for popping up different hover tools on both, just by hovering over one, that is not currently supported AFAIK. The inspection policy only covers the highlighting that happens as a result of inspection. Hover popups only follow and respond directly to actual mouse events at present.

Thanks,

Bryan

···

On Apr 16, 2019, at 9:47 AM, [email protected] wrote:

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.EdgesAndLinkedNodes() 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 :
<Screenshot from 2019-04-16 18-33-37.png>

Thank you for your help!
Franck

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/43972f5f-58cb-48e8-bff0-6fc2fcc96bc8%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.
<Screenshot from 2019-04-16 18-33-37.png>

By default hover tools try to add themselves to every available renderer. You will probably want to e.g. restrict the hover tool for nodes to just the nodes renderer by setting the “renderers” property on the tool. I believe that this is causing the spurious extra arrow.

As soon as I use renderers option, I have no tooltip at all. I’ve tried with graph.node_renderer, graph.edge_rendered, and both. I tried to add several HoverTool() with specific renderers for each but that’s the same problem.

As for popping up different hover tools on both, just by hovering over one, that is not currently supported AFAIK. The inspection policy only covers the highlighting that happens as a result of inspection. Hover popups only follow and respond directly to actual mouse events at present.

I don’t really understand how this works, but I noticed that using graph.inspection_policy=EdgesAndLinkedNodes() results in displaying the tooltips for edges only, using graph.inspection_policy=NodesAndLinkedEdges() results in displaying the tooltips for nodes only. So, I’d need to have either a new EdgesAndNodes() policy or two separate policies. Perhaps it is possible to manage inspection and tooltips at the level of the edges/nodes directly, without relying on the graph object.

Thanks again.

Franck
``

Hi, do you guys solve the problem. I have the same problem and I check different ways you guys mentioned but it is not successfully.