Source code for networkx.drawing.layout
方便学习与重新封装
Node positioning algorithms for graph drawing.
"""
copyright (C) 2004-2015 by
Aric Hagberg hagberg@lanl.gov
Dan Schult dschult@colgate.edu
Pieter Swart swart@lanl.gov
All rights reserved.
BSD license.
import collections
import networkx as nx
author = """Aric Hagberg (hagberg@lanl.gov)\nDan Schult(dschult@colgate.edu)"""
all = ['circular_layout','random_layout','shell_layout','spring_layout','spectral_layout','fruchterman_reingold_layout']
def process_params(G,center,dim):
Some boilerplate code.
import numpy as np
if not <a href="https://www.jb51.cc/tag/isinstance/" target="_blank" class="keywords">isinstance</a>(G,nx.Graph):
empty_graph = nx.Graph()
empty_graph.add_nodes_from(G)
G = empty_graph
if center is None:
center = np.zeros(dim)
else:
center = np.asarray(center)
if len(center) != dim:
msg = "length of center coordinates must match dimension of layout"
raise ValueError(msg)
return G,center
[docs]def random_layout(G,dim=2,center=None):
"""Position nodes uniformly at random in the unit square.
For every node,a position is generated by choosing each of dim
coordinates unifo<a href="https://www.jb51.cc/tag/rml/" target="_blank" class="keywords">rml</a>y at random on the interval [0.0,1.0).
NumPy (http://scipy.org) is <a href="https://www.jb51.cc/tag/required/" target="_blank" class="keywords">required</a> for this function.
P<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>meters
----------
G : NetworkX graph or list of nodes
A position will be assigned to every node in G.
dim : int
Dimension of layout.
center : array-like or None
Coordinate pair around which to center the layout.
Returns
-------
pos : dict
A dictionary of positions keyed by node
Examples
--------
>>> G = nx.lollipop_graph(4,3)
>>> pos = nx.random_layout(G)
"""
import numpy as np
G,center = process_p<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>ms(G,dim)
shape = (len(G),dim)
pos = np.random.random(shape) + center
pos = pos.astype(np.float32)
pos = dict(zip(G,pos))
return pos
[docs]def circular_layout(G,scale=1,center=None):
dim=2 only
"""Position nodes on a circle.
P<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>meters
----------
G : NetworkX graph or list of nodes
dim : int
Dimension of layout,currently only dim=2 is supported
scale : float
Scale factor for positions
center : array-like or None
Coordinate pair around which to center the layout.
Returns
-------
dict :
A dictionary of positions keyed by node
Examples
--------
>>> G=nx.path_graph(4)
>>> pos=nx.cir<a href="https://www.jb51.cc/tag/cula/" target="_blank" class="keywords">cula</a>r_layout(G)
Notes
------
This algorithm currently only works in two dimensions and does not
try to minimize edge crossings.
"""
import numpy as np
G,dim)
if len(G) == 0:
pos = {}
elif len(G) == 1:
pos = {G.nodes()[0]: center}
else:
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>card the extra angle since it matches 0 <a href="https://www.jb51.cc/tag/radians/" target="_blank" class="keywords">radians</a>.
theta = np.linspace(0,1,len(G) + 1)[:-1] * 2 * np.pi
theta = theta.astype(np.float32)
pos = np.column_stack([np.cos(theta),np.sin(theta)])
pos = _rescale_layout(pos,scale=scale) + center
pos = dict(zip(G,pos))
return pos
[docs]def shell_layout(G,nlist=None,center=None):
"""Position nodes in concentric circles.
P<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>meters
----------
G : NetworkX graph or list of nodes
nlist : list of lists
List of node lists for each shell.
dim : int
Dimension of layout,currently only dim=2 is supported
scale : float
Scale factor for positions
center : array-like or None
Coordinate pair around which to center the layout.
Returns
-------
dict :
A dictionary of positions keyed by node
Examples
--------
>>> G = nx.path_graph(4)
>>> shells = [[0],[1,2,3]]
>>> pos = nx.shell_layout(G,shells)
Notes
------
This algorithm currently only works in two dimensions and does not
try to minimize edge crossings.
"""
import numpy as np
G,dim)
if len(G) == 0:
return {}
elif len(G) == 1:
return {G.nodes()[0]: center}
if nlist is None:
# draw the whole graph in one shell
nlist = [list(G.nodes())]
if len(nlist[0]) == 1:
# single node at center
radius = 0.0
else:
# else start at r=1
radius = 1.0
npos={}
for nodes in nlist:
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>card the extra angle since it matches 0 <a href="https://www.jb51.cc/tag/radians/" target="_blank" class="keywords">radians</a>.
theta = np.linspace(0,len(nodes) + 1)[:-1] * 2 * np.pi
theta = theta.astype(np.float32)
pos = np.column_stack([np.cos(theta),scale=scale * radius / len(nlist)) + center
npos.update(zip(nodes,pos))
radius += 1.0
return npos
def fruchterman_reingold_layout(G,k=None,pos=None,fixed=None,iterations=50,weight='weight',scale=1.0,center=None):
"""Position nodes using Fruchterman-Reingold force-directed algorithm.
P<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>meters
----------
G : NetworkX graph or list of nodes
dim : int
Dimension of layout
k : float (default=None)
Optimal <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance between nodes. If None the <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance is set to
1/sqrt(n) where n is the number of nodes. Increase this value
to move nodes farther apart.
pos : dict or None optional (default=None)
Initial positions for nodes as a dictionary with node as keys
and values as a list or tuple. If None,then use random initial
positions.
fixed : list or None optional (default=None)
Nodes to keep fixed at initial position.
i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions : int optional (default=50)
Number of i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions of spring-force relaxation
weight : string or None optional (default='weight')
The edge attribute that holds the numerical value used for
the edge weight. If None,then all edge weights are 1.
scale : float (default=1.0)
Scale factor for positions. The nodes are positioned
in a <a href="https://www.jb51.cc/tag/Box/" target="_blank" class="keywords">Box</a> of size [0,scale] x [0,scale].
center : array-like or None
Coordinate pair around which to center the layout.
Returns
-------
dict :
A dictionary of positions keyed by node
Examples
--------
>>> G=nx.path_graph(4)
>>> pos=nx.spring_layout(G)
# The same using longer function name
>>> pos=nx.fruchterman_reingold_layout(G)
"""
import numpy as np
G,dim)
if fixed is not None:
nfixed = dict(zip(G,range(len(G))))
fixed = np.asarray([nfixed[v] for v in fixed])
if pos is not None:
# Deter<a href="https://www.jb51.cc/tag/mine/" target="_blank" class="keywords">mine</a> size of existing domain to adjust initial positions
dom_size = max(flatten(pos.values()))
shape = (len(G),dim)
pos_arr = np.random.random(shape) * dom_size + center
for i,n in enumerate(G):
if n in pos:
pos_arr[i] = np.asarray(pos[n])
else:
pos_arr=None
if len(G) == 0:
return {}
if len(G) == 1:
return {G.nodes()[0]: center}
try:
# Sparse matrix
if len(G) < 500: # sparse solver for large graphs
raise ValueError
A = nx.to_scipy_sparse_matrix(G,weight=weight,dtype='f')
if k is None and fixed is not None:
# We must adjust k by domain size for layouts that are not near 1x1
nnodes,_ = A.shape
k = dom_size / np.sqrt(nnodes)
pos = _sparse_fruchterman_reingold(A,dim,k,pos_arr,fixed,i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions)
except:
A = nx.to_numpy_matrix(G,weight=weight)
if k is None and fixed is not None:
# We must adjust k by domain size for layouts that are not near 1x1
nnodes,_ = A.shape
k = dom_size / np.sqrt(nnodes)
pos = _fruchterman_reingold(A,i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions)
if fixed is None:
pos = _rescale_layout(pos,scale=scale) + center
pos = dict(zip(G,pos))
return pos
spring_layout=fruchterman_reingold_layout
def _fruchterman_reingold(A,iterations=50):
Position nodes in adjacency matrix A using Fruchterman-Reingold
# Entry point for NetworkX graph is fruchterman_reingold_layout()
try:
import numpy as np
except ImportError:
raise ImportError("_fruchterman_reingold() requires numpy: http://scipy.org/ ")
try:
nnodes,_=A.shape
except AttributeError:
raise nx.NetworkXError(
"fruchterman_reingold() takes an adjacency matrix as input")
A=np.asarray(A) # make sure we have an array instead of a matrix
if pos==None:
# random initial positions
pos=np.asarray(np.random.random((nnodes,dim)),dtype=A.dtype)
else:
# make sure positions are of same type as matrix
pos=pos.astype(A.dtype)
# optimal <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance between nodes
if k is None:
k=np.sqrt(1.0/nnodes)
# the initial "temperature" is about .1 of domain area (=1x1)
# this is the largest step allowed in the dynamics.
# We need to cal<a href="https://www.jb51.cc/tag/cula/" target="_blank" class="keywords">cula</a>te this in case our fixed positions force our domain
# to be much bigger than 1x1
t = max(max(pos.T[0]) - min(pos.T[0]),max(pos.T[1]) - min(pos.T[1]))*0.1
# simple cooling scheme.
# linearly step down by dt on each i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion so last i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion is size dt.
dt=t/float(i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions+1)
delta = np.zeros((pos.shape[0],pos.shape[0],pos.shape[1]),dtype=A.dtype)
# the inscrutable (but fast) version
# this is still O(V^2)
# <a href="https://www.jb51.cc/tag/Could/" target="_blank" class="keywords">Could</a> use multilevel methods to speed this up significantly
for i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion in range(i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions):
# matrix of difference between points
for i in range(pos.shape[1]):
delta[:,:,i]= pos[:,i,None]-pos[:,i]
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance between points
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance=np.sqrt((delta**2).sum(axis=-1))
# enforce minimum <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance of 0.01
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance=np.where(<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance<0.01,0.01,<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance)
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement "force"
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement=np.transpose(np.transpose(delta)*\
(k*k/<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance**2-A*<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance/k))\
.sum(axis=1)
# update positions
length=np.sqrt((<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement**2).sum(axis=1))
length=np.where(length<0.01,0.1,length)
delta_pos=np.transpose(np.transpose(<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement)*t/length)
if fixed is not None:
# don't change positions of fixed nodes
delta_pos[fixed]=0.0
pos+=delta_pos
# cool temperature
t-=dt
return pos
def _sparse_fruchterman_reingold(A,iterations=50):
Position nodes in adjacency matrix A using Fruchterman-Reingold
# Entry point for NetworkX graph is fruchterman_reingold_layout()
# Sparse version
try:
import numpy as np
except ImportError:
raise ImportError("_sparse_fruchterman_reingold() requires numpy: http://scipy.org/ ")
try:
nnodes,_=A.shape
except AttributeError:
raise nx.NetworkXError(
"fruchterman_reingold() takes an adjacency matrix as input")
try:
from scipy.sparse import spdiags,coo_matrix
except ImportError:
raise ImportError("_sparse_fruchterman_reingold() scipy numpy: http://scipy.org/ ")
# make sure we have a LIst of Lists representation
try:
A=A.tolil()
except:
A=(coo_matrix(A)).tolil()
if pos==None:
# random initial positions
pos=np.asarray(np.random.random((nnodes,dtype=A.dtype)
else:
# make sure positions are of same type as matrix
pos=pos.astype(A.dtype)
# no fixed nodes
if fixed==None:
fixed=[]
# optimal <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance between nodes
if k is None:
k=np.sqrt(1.0/nnodes)
# the initial "temperature" is about .1 of domain area (=1x1)
# this is the largest step allowed in the dynamics.
t=0.1
# simple cooling scheme.
# linearly step down by dt on each i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion so last i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion is size dt.
dt=t/float(i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions+1)
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement=np.zeros((dim,nnodes))
for i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tion in range(i<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>tions):
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement*=0
# loop over rows
for i in range(A.shape[0]):
if i in fixed:
continue
# difference between this row's node position and all others
delta=(pos[i]-pos).T
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance between points
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance=np.sqrt((delta**2).sum(axis=0))
# enforce minimum <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance of 0.01
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance=np.where(<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance<0.01,<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance)
# the adjacency matrix row
Ai=np.asarray(A.getrowview(i).toarray())
# <a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement "force"
<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement[:,i]+=\
(delta*(k*k/<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance**2-Ai*<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>tance/k)).sum(axis=1)
# update positions
length=np.sqrt((<a href="https://www.jb51.cc/tag/dis/" target="_blank" class="keywords">dis</a>placement**2).sum(axis=0))
length=np.where(length<0.01,length)
pos+=(displacement*t/length).T
# cool temperature
t-=dt
return pos
[docs]def spectral_layout(G,center=None):
"""Position nodes using the eigenvectors of the graph Laplacian.
Parameters
----------
G : NetworkX graph or list of nodes
dim : int
Dimension of layout
weight : string or None optional (default='weight')
The edge attribute that holds the numerical value used for
the edge weight. If None,then all edge weights are 1.
scale : float
Scale factor for positions
center : array-like or None
Coordinate pair around which to center the layout.
Returns
-------
dict :
A dictionary of positions keyed by node
Examples
--------
>>> G=nx.path_graph(4)
>>> pos=nx.spectral_layout(G)
Notes
-----
Directed graphs will be considered as undirected graphs when
positioning the nodes.
For larger graphs (>500 nodes) this will use the SciPy sparse
eigenvalue solver (ARPACK).
"""
# handle some special cases that break the eigensolvers
import numpy as np
G,dim)
if len(G) <= 2:
if len(G) == 0:
pos = np.array([])
elif len(G) == 1:
pos = np.array([center])
else:
pos = np.array([np.zeros(dim),np.array(center)*2.0])
return dict(zip(G,pos))
try:
# Sparse matrix
if len(G)< 500: # dense solver is faster for small graphs
raise ValueError
A = nx.to_scipy_sparse_matrix(G,dtype='d')
# Symmetrize directed graphs
if G.is_directed():
A = A + np.transpose(A)
pos = _sparse_spectral(A,dim)
except (ImportError,ValueError):
# Dense matrix
A = nx.to_numpy_matrix(G,weight=weight)
# Symmetrize directed graphs
if G.is_directed():
A = A + np.transpose(A)
pos = _spectral(A,dim)
pos = _rescale_layout(pos,scale) + center
pos = dict(zip(G,pos))
return pos
def _spectral(A,dim=2):
Input adjacency matrix A
# Uses dense eigenvalue solver from numpy
try:
import numpy as np
except ImportError:
raise ImportError("spectral_layout() requires numpy: http://scipy.org/ ")
try:
nnodes,_=A.shape
except AttributeError:
raise nx.NetworkXError(\
"spectral() takes an adjacency matrix as input")
# form Laplacian matrix
# make sure we have an array instead of a matrix
A=np.asarray(A)
I=np.identity(nnodes,dtype=A.dtype)
D=I*np.sum(A,axis=1) # diagonal of <a href="https://www.jb51.cc/tag/degrees/" target="_blank" class="keywords">degrees</a>
L=D-A
eigenvalues,eigenvectors=np.linalg.eig(L)
# sort and keep smallest nonzero
index=np.argsort(eigenvalues)[1:dim+1] # 0 index is zero eigenvalue
return np.real(eigenvectors[:,index])
def _sparse_spectral(A,dim=2):
Input adjacency matrix A
# Uses sparse eigenvalue solver from scipy
# <a href="https://www.jb51.cc/tag/Could/" target="_blank" class="keywords">Could</a> use multilevel methods here,see Koren "On spectral graph drawing"
try:
import numpy as np
from scipy.sparse import spdiags
except ImportError:
raise ImportError("_sparse_spectral() requires scipy & numpy: http://scipy.org/ ")
try:
from scipy.sparse.linalg.eigen import eigsh
except ImportError:
# scipy <0.9.0 names eigsh differently
from scipy.sparse.linalg import eigen_symmetric as eigsh
try:
nnodes,_=A.shape
except AttributeError:
raise nx.NetworkXError(\
"sparse_spectral() takes an adjacency matrix as input")
# form Laplacian matrix
data=np.asarray(A.sum(axis=1).T)
D=spdiags(data,nnodes,nnodes)
L=D-A
k=dim+1
# number of <a href="https://www.jb51.cc/tag/lanczos/" target="_blank" class="keywords">lanczos</a> vectors for ARPACK solver.What is the right scaling?
ncv=max(2*k+1,int(np.sqrt(nnodes)))
# return smallest k eigenvalues and eigenvectors
eigenvalues,eigenvectors=eigsh(L,which='SM',ncv=ncv)
index=np.argsort(eigenvalues)[1:k] # 0 index is zero eigenvalue
return np.real(eigenvectors[:,index])
def _rescale_layout(pos,scale=1):
rescale to (-scale,scale) in all axes
# shift origin to (0,0)
lim=0 # max coordinate for all axes
for i in range(pos.shape[1]):
pos[:,i]-=pos[:,i].mean()
lim=max(pos[:,i].max(),lim)
# rescale to (-scale,scale) in all directions,preserves aspect
for i in range(pos.shape[1]):
pos[:,i]*=scale/lim
return pos
fixture for nose tests
def setup_module(module):
from nose import SkipTest
try:
import numpy
except:
raise SkipTest("NumPy not available")
try:
import scipy
except:
raise SkipTest("SciPy not available")
def flatten(l):
try:
bs = basestring
except NameError:
Py3k
bs = str
for el in l:
if <a href="https://www.jb51.cc/tag/isinstance/" target="_blank" class="keywords">isinstance</a>(el,collections.I<a href="https://www.jb51.cc/tag/tera/" target="_blank" class="keywords">tera</a>ble) and not <a href="https://www.jb51.cc/tag/isinstance/" target="_blank" class="keywords">isinstance</a>(el,bs):
for sub in flatten(el):
yield sub
else:
yield el</code></pre><a href="https://networkx.github.io/documentation/latest/_modules/networkx/drawing/layout.html" rel="<a href="https://www.jb51.cc/tag/nofollow/" target="_blank" class="keywords">nofollow</a>">官网</a><br /><br />
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。