Skip to content

Instantly share code, notes, and snippets.

@Zulko
Last active April 27, 2021 19:15
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zulko/7629965 to your computer and use it in GitHub Desktop.
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()
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()
@VictorienMarette
Copy link

I have problemes with line 80. But great idea!

@Zulko
Copy link
Author

Zulko commented Aug 18, 2020

Thanks for spotting that, I updated the script (this may have been a syntax that worked with early py2 but not py3)

@VictorienMarette
Copy link

thx!!!!

@1kastner
Copy link

1kastner commented Apr 25, 2021

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