Reputation: 81
I have:
a list of Q NODES = [(x, y)_1, ........, (x, y)_Q]
, where each element (x, y)
represents the spatial position of a node in 2D Cartesian space.
a QxQ matrix H
, where H[k, l]
is the length of the edge connecting nodes k
and l
, and H[k, l] == 0
if k
and l
are unconnected.
a QxQ matrix Z
, where Z[k, l]
is a scalar 'intensity' value for the edge connecting nodes k
and l
. Again, Z[k, l] == 0
if k
and l
are unconnected.
I want to nicely draw the nodes in their spatial position, connected by the edges, and using a color scale to represent the 'intensity'.
How can I do this? (I use python, sage, matplotlib and numpy)
Upvotes: 1
Views: 659
Reputation: 74222
Here's an example function that uses only numpy and matplotlib to draw an undirected graph with edge weights represented by a colormap:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
def plot_undirected_graph(xy, z):
fig, ax = plt.subplots(1, 1)
ax.hold(True)
# the indices of the start, stop nodes for each edge
i, j = np.where(z)
# an array of xy values for each line to draw, with dimensions
# [nedges, start/stop (2), xy (2)]
segments = np.hstack((xy[i, None, :], xy[j, None, :]))
# the 'intensity' values for each existing edge
z_connected = z[i, j]
# this object will normalize the 'intensity' values into the range [0, 1]
norm = plt.Normalize(z_connected.min(), z_connected.max())
# LineCollection wants a sequence of RGBA tuples, one for each line
colors = plt.cm.jet(norm(z_connected))
# we can now create a LineCollection from the xy and color values for each
# line
lc = LineCollection(segments, colors=colors, linewidths=2,
antialiased=True)
# add the LineCollection to the axes
ax.add_collection(lc)
# we'll also plot some markers and numbers for the nodes
ax.plot(xy[:, 0], xy[:, 1], 'ok', ms=10)
for ni in xrange(z.shape[0]):
ax.annotate(str(ni), xy=xy[ni, :], xytext=(5, 5),
textcoords='offset points', fontsize='large')
# to make a color bar, we first create a ScalarMappable, which will map the
# intensity values to the colormap scale
sm = plt.cm.ScalarMappable(norm, plt.cm.jet)
sm.set_array(z_connected)
cb = plt.colorbar(sm)
ax.set_xlabel('X position')
ax.set_ylabel('Y position')
cb.set_label('Edge intensity')
return fig, ax
For simplicity I've changed the format of your NODES
variable to just be an (n_nodes, 2)
array of (x, y)
values, although you could easily get this using np.array(NODES)
. I have also ignored H
for the time being, since the euclidean distances between the nodes are given implicity by their (x, y)
positions. You could always represent the values in H
some other way, for example using the linewidths
of the LineCollection
.
Here's a quick demo:
# some random xy positions:
xy = np.random.rand(10, 2)
# a random adjacency matrix
adj = np.random.poisson(0.2, (10, 10))
# we multiply by this by a matrix of random edge 'intensities'
z = adj * np.random.randn(*adj.shape)
# do the plotting
plot_undirected_graph(xy, z)
Output:
Note that this example is only really suitable for undirected graphs. If both Z[k, l]
and Z[l, k]
exist, there will be two overlapping lines drawn between nodes l
and k
, so if the two edges differ in their intensity values they will be impossible to distinguish by their color.
There are lots of specialized Python libraries available for constructing, analyzing and plotting graphs, for example igraph, graphtool and networkx, which are capable of drawing directed graphs nicely.
Upvotes: 1