I'm working on a project where I need to create a preview of nx.Graph()
which allows to change position of nodes dragging them with a mouse. My current code is able to redraw whole figure immediately after each motion of mouse if it's clicked on specific node. However, this increases latency significantly. How can I update only artists needed, it is, clicked node, its label text and adjacent edges instead of refreshing every artist of plt.subplots()
? Can I at least get a reference to all the artists that need to be relocated?
I started from a standard way of displaying a graph in networkx
:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial
def refresh(G):
plt.axis((-4, 4, -1, 3))
nx.draw_networkx_labels(G, pos = nx.get_node_attributes(G, 'pos'),
bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
nx.draw_networkx_edges(G, pos = nx.get_node_attributes(G, 'pos'), width=1.0, alpha=0.5)
plt.show()
nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])
G = nx.Graph()
# IG = InteractiveGraph(G) #>>>>> add this line in the next step
G.add_nodes_from(nodes)
G.add_edges_from(edges)
nx.set_node_attributes(G, dict(zip(G.nodes(), pos.astype(float))), 'pos')
fig, ax = plt.subplots()
# fig.canvas.mpl_connect('button_press_event', lambda event: IG.on_press(event))
# fig.canvas.mpl_connect('motion_notify_event', lambda event: IG.on_motion(event))
# fig.canvas.mpl_connect('button_release_event', lambda event: IG.on_release(event))
refresh(G) # >>>>> replace it with IG.refresh() in the next step
In the next step I changed 5 line of previous script (4 is uncommented and 1 replaced) plus used InteractiveGraph
instance to make it interactive:
class InteractiveGraph:
def __init__(self, G, node_pressed=None, xydata=None):
self.G = G
self.node_pressed = node_pressed
self.xydata = xydata
def refresh(self, show=True):
plt.clf()
nx.draw_networkx_labels(self.G, pos = nx.get_node_attributes(self.G, 'pos'),
bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
nx.draw_networkx_edges(self.G, pos = nx.get_node_attributes(self.G, 'pos'), width=1.0, alpha=0.5)
plt.axis('off')
plt.axis((-4, 4, -1, 3))
fig.patch.set_facecolor('white')
if show:
plt.show()
def on_press(self, event):
if event.inaxes is not None and len(self.G.nodes()) > 0:
nodelist, coords = zip(*nx.get_node_attributes(self.G, 'pos').items())
kdtree = scipy.spatial.KDTree(coords)
self.xydata = np.array([event.xdata, event.ydata])
close_idx = kdtree.query_ball_point(self.xydata, np.sqrt(0.1))
i = close_idx[0]
self.node_pressed = nodelist[i]
def on_motion(self, event):
if event.inaxes is not None and self.node_pressed:
new_xydata = np.array([event.xdata, event.ydata])
self.xydata += new_xydata - self.xydata
#print(d_xy, self.G.nodes[self.node_pressed])
self.G.nodes[self.node_pressed]['pos'] = self.xydata
self.refresh(show=False)
event.canvas.draw()
def on_release(self, event):
self.node_pressed = None
Related sources:
You can view the nodes and edges in a Networkx Graph using the attributes midsummer.nodes and midsummer.edges. We then need to get the positions for the nodes in the graph. There are a few different layouts to choose from. I thought the spring layout looked the best.
In this article, we will be discussing how to plot a graph generated by NetworkX in Python using Matplotlib. NetworkX is not a graph visualizing package but basic drawing with Matplotlib is included in the software package. Step 1 : Import networkx and matplotlib.pyplot in the project file. Step 2 : Generate a graph using networkx.
Below is the Python code: To add numbering in the node add one argument with_labels=True in draw () function. Different graph types and plotting can be done using networkx drawing and matplotlib. Note** : Here keywords is referred to optional keywords that we can mention use to format the graph plotting. Some of the general graph layouts are :
NetworkX provides basic functionality for visualizing graphs, but its main goal is to enable graph analysis rather than perform graph visualization. In the future, graph visualization functionality may be removed from NetworkX or only available as an add-on package.
To expand on my comment above, in netgraph
, your example can be reproduced with
import numpy as np
import matplotlib.pyplot as plt; plt.ion()
import networkx as nx
import netgraph
nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
I = netgraph.InteractiveGraph(G,
node_positions=dict(zip(nodes, pos)),
node_labels=dict(zip(nodes,nodes)),
node_label_bbox=dict(fc="lightgreen", ec="black", boxstyle="square", lw=3),
node_size=12,
)
# move stuff with mouse
Regarding the code you wrote, the kd-tree is unnecessary if you have handles of all the artists. In general, matplotlib artists have a contains
method, such that when you log button press events, you can simply check artist.contains(event)
to find out if the button press occurred over the artist. Of course, if you use networkx to do the plotting, you can't get the handles in a nice, query-able form (ax.get_children()
is neither) so that is not possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With