pythonnetworkxnetgraph

Is there an option to add two labels each at the other end of edge in netgraph?


I am working on the app to draw the computer network topology. I've wanted to use the Python netgraph library because it adds the possibility of interaction with the network.

I've tried to edit the code from the documentation so I can use two labels, each on the other end of the edge. I wanted it to be the description of the type of interface like eth0, gig0, etc., but with the code below, there is still one label for edge, e.g., '('eth0','eth1')'. Is it even possible to achive that with netgraph/networkx?

import matplotlib.pyplot as plt
import networkx as nx
from netgraph import InteractiveGraph

g = nx.house_x_graph()

edge_color = dict()
for ii, edge in enumerate(g.edges):
  edge_color[edge] = 'tab:gray' if ii%2 else 'tab:orange'

node_color = dict()
for node in g.nodes:
  node_color[node] = 'tab:red' if node%2 else 'tab:blue'

edge_labels = {(1, 2): ('eth0','eth1'), (2, 3): ('gig0','gig1') }

plot_instance = InteractiveGraph(
g, node_size=5, node_color=node_color,
node_labels=True, node_label_offset=0.1, node_label_fontdict=dict(size=20),
edge_color=edge_color, edge_width=1,edge_labels=edge_labels, edge_label_position=0.2)

plt.show()

(https://i.sstatic.net/OUs2M.png)


Solution

  • Is it even possible to achieve that with netgraph/networkx?

    Not out of the box, no. However, as Netgraph InteractiveGraph object expose the current node positions, it is not too complicated to implement the desired functionality using standard matplotlib event handling.

    The code below initializes the InteractiveGraph object, computes separate interface label positions based InteractiveGraph.node_positions, and then updates the interface label positions whenever a node is dragged (on release, to be precise).

    enter image description here

    import numpy as np
    import matplotlib.pyplot as plt
    import networkx as nx
    
    from netgraph import InteractiveGraph
    
    g = nx.house_x_graph()
    
    edge_color = dict()
    for ii, edge in enumerate(g.edges):
        edge_color[edge] = 'tab:gray' if ii%2 else 'tab:orange'
    
    node_color = dict()
    for node in g.nodes:
        node_color[node] = 'tab:red' if node%2 else 'tab:blue'
    
    fig, ax = plt.subplots()
    plot_instance = InteractiveGraph(
        g, node_size=5, node_color=node_color,
        node_labels=True, node_label_offset=0.1, node_label_fontdict=dict(size=20),
        edge_color=edge_color, edge_width=1, ax=ax)
    
    interface_labels = [
        ((1, 2), 0.2, 'eth0'),
        ((1, 2), 0.8, 'eth1'),
        ((2, 3), 0.2, 'gig0'),
        ((2, 3), 0.8, 'gig0'),
    ]
    
    def get_label_position(edge, edge_label_position, node_positions):
        v1, v2 = edge
        dxy = node_positions[v2] - node_positions[v1]
        xy = edge_label_position * dxy + node_positions[v1]
        angle = np.arctan2(dxy[1], dxy[0]) * 360 / (2.0 * np.pi)
    
        # reduce overlap with nodes
        if 90 <= (angle % 360) < 270:
            if edge_label_position < 0.5:
                horizontal_alignment = "right"
            else:
                horizontal_alignment = "left"
        else:
            if edge_label_position < 0.5:
                horizontal_alignment = "left"
            else:
                horizontal_alignment = "right"
    
        # make label orientation "right-side-up"
        if angle > 90:
            angle -= 180
        if angle < - 90:
            angle += 180
    
        return xy, angle, horizontal_alignment
    
    interface_label_text_objects = []
    for edge, edge_label_position, label in interface_labels:
        (x, y), angle, ha = get_label_position(edge, edge_label_position, plot_instance.node_positions)
        text_object = ax.text(x, y, label, rotation=angle, rotation_mode="anchor", va="center", ha=ha)
        interface_label_text_objects.append(text_object)
    
    def update_label_positions(event):
        for (edge, edge_label_position, _), text_object in zip(interface_labels, interface_label_text_objects):
            xy, angle, ha = get_label_position(
                edge, edge_label_position, plot_instance.node_positions)
            text_object.set_position(xy)
            text_object.set_rotation(angle)
            text_object.set_horizontalalignment(ha)
    
    fig.canvas.mpl_connect('button_release_event', update_label_positions)
    
    plt.show()