Last active
April 27, 2021 19:15
-
-
Save Zulko/7629965 to your computer and use it in GitHub Desktop.
This function enables to manually draw a graph (nodes and edges) that can then be used in Python (with Networkx for instance)It's extremely simple: >>> nodes, edges, nodes_positions = graph_editor()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import matplotlib.pyplot as plt | |
import numpy as np | |
def graph_editor(grid=True, grid_N = 12): | |
""" | |
This function enables to draw a graph manually using | |
Matplotlib's interactive plotting capabilites. | |
In a first phase you are asked to place the nodes | |
(left-click to place a node, right-click to remove | |
the last node, Enter when finished) | |
In a second phase you are asked to place the edges | |
( click on two nodes to select an edge, remove an edge | |
by selecting it again, Enter when finished). To go faster in this | |
phase you can also select an edge by clicking just one node and | |
hitting enter, it will be assumed that the first node was the last | |
node of the previous edge. This enables to rapidly draw "paths" | |
of edges. | |
Args | |
----- | |
- `grid` : if `True`, the points positions are a posteriori | |
projected on a grid for better visual appeal | |
- `grid_N` : resolution of the grid, if any. | |
Returns | |
-------- | |
A tuple (nodes, edges, nodes_pos) where | |
- `nodes` is a list [0,1,2,3..] of the nodes names | |
- `edges` is a the list [(0,1),(0,2)..] of the edges. | |
- `nodes_pos` is the list [(x0,y0),(x1,y1)..] of the | |
positions of the different nodes (to draw the graph). | |
Example | |
-------- | |
# draw the graph by hand | |
nodes, edges, nodes_pos = graph_editor() | |
# make it a Networkx graph and plot it | |
import networkx as nx | |
import matplotlib.pyplot as plt | |
G = nx.Graph() # or nx.DiGraph() for an oriented graph | |
G.add_nodes_from(nodes) | |
G.add_edges_from(edges) | |
nx.draw(G, pos = dict(zip(nodes, nodes_pos)) ) | |
plt.show() | |
""" | |
plt.ion() # interactive mode ! | |
fig, ax = plt.subplots(1) | |
ticks = np.linspace(0,1,grid_N) | |
def init_ax(): | |
ax.clear() | |
ax.set_xlim(0,1) | |
ax.set_xticks(ticks) | |
ax.set_xticklabels([]) | |
ax.set_ylim(0,1) | |
ax.set_yticks(ticks) | |
ax.set_yticklabels([]) | |
if grid: | |
ax.grid() | |
# 1st STEP | |
print ("Place the nodes and press enter") | |
init_ax() | |
fig.canvas.draw | |
nodes_pos = plt.ginput(-1,timeout=-1) | |
if grid: | |
# project the points on the nearest grid sections | |
nodes_pos = [ | |
[ | |
ticks[np.argmin([abs(u-t) for t in ticks])] | |
for u in (x,y) | |
] | |
for x,y in nodes_pos | |
] | |
nodes_posx, nodes_posy = zip(*nodes_pos) | |
nodes = range(len(nodes_pos)) | |
# 2nd STEP | |
print ("Place the edges and press enter") | |
fig.canvas.draw() | |
edges = [] | |
while True: | |
# This loops goes as the user selects edges, and breaks when the | |
# user presses enter without having selected an edge. | |
# plot the current nodes and edges | |
init_ax() | |
for i,j in edges: | |
x1,y1 = nodes_pos[i] | |
x2,y2 = nodes_pos[j] | |
ax.plot([x1,x2],(y1,y2),lw=2,c='k') | |
ax.scatter(nodes_posx, nodes_posy, s=30) | |
fig.canvas.draw() | |
l = plt.ginput(2,timeout=-1) | |
if len(l) == 0: # Enter has been pressed with no selection: end. | |
break | |
elif len(l) == 1: # only one point has been selected | |
(x1,y1),(x2,y2) = nodes_pos[edges[-1][1]], l[0] | |
else: # only one point has been selected | |
(x1,y1),(x2,y2) = l | |
# find the nodes nearest from the positions of the clicks | |
n1 = nodes[np.argmin([(x1-x)**2+(y1-y)**2 for x,y in nodes_pos])] | |
n2 = nodes[np.argmin([(x2-x)**2+(y2-y)**2 for x,y in nodes_pos])] | |
if (n1,n2) in edges: # a re-selection of an edge : remove | |
edges.remove((n1,n2)) | |
else: | |
edges.append((n1,n2)) # yeah ! one new edge in the graph | |
plt.ioff() | |
return nodes, edges, nodes_pos | |
if __name__ == '__main__': | |
# AN EXAMPLE OF USE | |
import networkx as nx | |
import matplotlib.pyplot as plt | |
# draw the graph by hand | |
nodes, edges, nodes_pos = graph_editor() | |
# make it a Networkx graph and plot it | |
G = nx.Graph() # or nx.DiGraph() for an oriented graph | |
G.add_nodes_from(nodes) | |
G.add_edges_from(edges) | |
fig, ax = plt.subplots(1) | |
nx.draw(G, pos = dict(zip(nodes, nodes_pos)), ax=ax ) | |
plt.show() |
Thanks for spotting that, I updated the script (this may have been a syntax that worked with early py2 but not py3)
thx!!!!
I added an arrow which indicates the direction of the edge and a background image. I just wanted to share it with whoever might need it. You can ignore my other changes as I even started to remove some features I didn't need.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.image as mpimg
plt.ion()
fig, ax = plt.subplots(1)
img = mpimg.imread('my_image.jpg')
ticks = np.linspace(0, 1, 20)
ax.clear()
plt.imshow(img, alpha=.6)
ax.set_xticks(ticks)
ax.set_xticklabels([])
ax.set_yticks(ticks)
ax.set_yticklabels([])
ax.grid()
print("Place the nodes and press enter")
fig.canvas.draw
nodes_pos = plt.ginput(-1, timeout=-1)
nodes_posx, nodes_posy = zip(*nodes_pos)
nodes = list(range(len(nodes_pos)))
print("Place the edges and press enter")
fig.canvas.draw()
edges = []
while True:
# This loops goes as the user selects edges, and breaks when the
# user presses enter without having selected an edge.
# plot the current nodes and edges
ax.clear()
plt.imshow(img)
plt.imshow(img, alpha=.6)
ax.set_xticks(ticks)
ax.set_xticklabels([])
ax.set_yticks(ticks)
ax.set_yticklabels([])
ax.grid()
for i,j in edges:
x1, y1 = nodes_pos[i]
x2, y2 = nodes_pos[j]
ax.plot([x1, x2], (y1, y2), lw=2, c='red')
dx = (x2 - x1) / 50
dy = (y2 - y1) / 50
plt.arrow(x2 - dx, y2 - dy, dx, dy, length_includes_head=True, width=10)
ax.scatter(nodes_posx, nodes_posy, s=30)
fig.canvas.draw()
l = plt.ginput(2, timeout=-1)
if len(l) == 0: # Enter has been pressed with no selection: end.
break
elif len(l) == 1: # only one point has been selected
(x1, y1), (x2, y2) = nodes_pos[edges[-1][1]], l[0]
else: # only one point has been selected
(x1, y1), (x2, y2) = l
# find the nodes nearest from the positions of the clicks
n1 = nodes[np.argmin([(x1-x)**2 + (y1-y)**2 for x, y in nodes_pos])]
n2 = nodes[np.argmin([(x2-x)**2 + (y2-y)**2 for x, y in nodes_pos])]
if (n1, n2) in edges: # a re-selection of an edge : remove
edges.remove((n1, n2))
else:
edges.append((n1, n2)) # yeah ! one new edge in the graph
plt.ioff()
print("nodes = ", nodes)
print("edges = ", edges)
print("node_positions = ", nodes_pos)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have problemes with line 80. But great idea!