def draw_graph(G, labels=None, node_colors=None, node_shapes=None, node_scale=1.0, edge_style='solid', edge_cmap=None, colorbar=False, vrange=None, layout=None, title=None, font_family='sans-serif', font_size=9, stretch_factor=1.0, edge_alpha=True, fig_size=None): """Draw a weighted graph with options to visualize link weights. The resulting diagram uses the rank of each node as its size, and the weight of each link (after discarding thresholded values, see below) as the link opacity. It maps edge weight to color as well as line opacity and thickness, allowing the color part to be hardcoded over a value range (to permit valid cross-figure comparisons for different graphs, so the same color corresponds to the same link weight even if each graph has a different range of weights). The nodes sizes are proportional to their degree, computed as the sum of the weights of all their links. The layout defaults to circular, but any nx layout function can be passed in, as well as a statically precomputed layout. Parameters ---------- G : weighted graph The values must be of the form (v1,v2), with all v2 in [0,1]. v1 are used for colors, v2 for thickness/opacity. labels : list or dict, optional. An indexable object that maps nodes to strings. If not given, the string form of each node is used as a label. If False, no labels are drawn. node_colors : list or dict, optional. An indexable object that maps nodes to valid matplotlib color specs. See matplotlib's plot() function for details. node_shapes : list or dict, optional. An indexable object that maps nodes to valid matplotlib shape specs. See matplotlib's scatter() function for details. If not given, circles are used. node_scale : float, optional A scale factor to globally stretch or shrink all nodes symbols by. edge_style : string, optional Line style for the edges, defaults to 'solid'. edge_cmap : matplotlib colormap, optional. A callable that returns valid color specs, like matplotlib colormaps. If not given, edges are colored black. colorbar : bool If true, automatically add a colorbar showing the mapping of graph weight values to colors. vrange : pair of floats If given, this indicates the total range of values that the weights can in principle occupy, and is used to set the lower/upper range of the colormap. This allows you to set the range of multiple different figures to the same values, even if each individual graph has range variations, so that visual color comparisons across figures are valid. layout : function or layout dict, optional A NetworkX-like layout function or the result of a precomputed layout for the given graph. NetworkX produces layouts as dicts keyed by nodes and with (x,y) pairs of coordinates as values, any function that produces this kind of output is acceptable. Defaults to nx.circular_layout. title : string, optional. If given, title to put on the main plot. font_family : string, optional. Font family used for the node labels and title. font_size : int, optional. Font size used for the node labels and title. stretch_factor : float, optional A global scaling factor to make the graph larger (or smaller if <1). This can be used to separate the nodes if they start overlapping. edge_alpha: bool, optional Whether to weight the transparency of each edge by a factor equivalent to its relative weight fig_size: list of height by width, the size of the figure (in inches). Defaults to [6,6] Returns ------- fig The matplotlib figure object with the plot. """ if fig_size is None: figsize = [6, 6] scaler = figsize[0] / 6. # For the size of the node symbols node_size_base = 1000 * scaler node_min_size = 200 * scaler default_node_shape = 'o' # Default colors if none given default_node_color = 'r' default_edge_color = 'k' # Max edge width max_width = 13 * scaler min_width = 2 * scaler font_family = 'sans-serif' # We'll use the nodes a lot, let's make a numpy array of them nodes = np.array(sorted(G.nodes())) nnod = len(nodes) # Build a 'weighted degree' array obtained by adding the (absolute value) # of the weights for all edges pointing to each node: amat = nx.adj_matrix(G).A # get a normal array out of it degarr = abs(amat).sum(0) # weights are sums across rows # Map the degree to the 0-1 range so we can use it for sizing the nodes. try: odegree = rescale_arr(degarr, 0, 1) # Make an array of node sizes based on node degree node_sizes = odegree * node_size_base + node_min_size except ZeroDivisionError: # All nodes same size node_sizes = np.empty(nnod, float) node_sizes.fill(0.5 * node_size_base + node_min_size) # Adjust node size list. We square the scale factor because in mpl, node # sizes represent area, not linear size, but it's more intuitive for the # user to think of linear factors (the overall figure scale factor is also # linear). node_sizes *= node_scale ** 2 # Set default node properties if node_colors is None: node_colors = [default_node_color] * nnod if node_shapes is None: node_shapes = [default_node_shape] * nnod # Set default edge colormap if edge_cmap is None: # Make an object with the colormap API, that maps all input values to # the default color (with proper alhpa) edge_cmap = (lambda val, alpha: colors.colorConverter.to_rgba(default_edge_color, alpha)) # if vrange is None, we set the color range from the values, else the user # can specify it # e[2] is edge value: edges_iter returns (i,j,data) gvals = np.array([e[2]['weight'] for e in G.edges(data=True)]) gvmin, gvmax = gvals.min(), gvals.max() gvrange = gvmax - gvmin if vrange is None: vrange = gvmin, gvmax # Now, construct the normalization for the colormap cnorm = mpl.colors.Normalize(vmin=vrange[0], vmax=vrange[1]) # Create the actual plot where the graph will be displayed figsize = np.array(figsize, float) figsize *= stretch_factor fig = plt.figure(figsize=figsize) ax_graph = fig.add_subplot(1, 1, 1) fig.sca(ax_graph) if layout is None: layout = nx.circular_layout # Compute positions for all nodes - nx has several algorithms if callable(layout): pos = layout(G) else: # The user can also provide a precomputed layout pos = layout # Draw nodes for nod in nodes: nx.draw_networkx_nodes(G, pos, nodelist=[nod], node_color=node_colors[nod], node_shape=node_shapes[nod], node_size=node_sizes[nod], edgecolors='k') # Draw edges if not isinstance(G, nx.DiGraph): # Undirected graph, simple lines for edges # We need the size of the value range to properly scale colors vsize = vrange[1] - vrange[0] gvals_normalized = G.metadata['vals_norm'] for (u, v, y) in G.edges(data=True): # The graph value is the weight, and the normalized values are in # [0,1], used for thickness/transparency alpha = gvals_normalized[u, v] # Scale the color choice to the specified vrange, so that ecol = (y['weight'] - vrange[0]) / vsize #print 'u,v:',u,v,'y:',y,'ecol:',ecol # dbg if edge_alpha: fade = alpha else: fade = 1.0 edge_color = [tuple(edge_cmap(ecol, fade))] #dbg: #print u,v,y nx.draw_networkx_edges(G, pos, edgelist=[(u, v)], width=min_width + alpha * max_width, edge_color=edge_color, style=edge_style) else: # Directed graph, use arrows. # XXX - this is currently broken. raise NotImplementedError("arrow drawing currently broken") ## for (u,v,x) in G.edges(data=True): ## y,w = x ## draw_arrows(G,pos,edgelist=[(u,v)], ## edge_color=[w], ## alpha=w, ## edge_cmap=edge_cmap, ## width=w*max_width) # Draw labels. If not given, we use the string form of the nodes. If # labels is False, no labels are drawn. if labels is None: labels = map(str, nodes) if labels: lab_idx = list(range(len(labels))) labels_dict = dict(zip(lab_idx, labels)) nx.draw_networkx_labels(G, pos, labels_dict, font_size=font_size, font_family=font_family) if title: plt.title(title, fontsize=font_size) # Turn off x and y axes labels in pylab plt.xticks([]) plt.yticks([]) # Add a colorbar if requested if colorbar: divider = make_axes_locatable(ax_graph) ax_cb = divider.new_vertical(size="20%", pad=0.2, pack_start=True) fig.add_axes(ax_cb) cb = mpl.colorbar.ColorbarBase(ax_cb, cmap=edge_cmap, norm=cnorm, #boundaries = np.linspace(min((gvmin,0)), # max((gvmax,0)), # 256), orientation='horizontal', format='%.2f') # Always return the MPL figure object so the user can further manipulate it return fig
# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" _version_major = 0 _version_minor = 6 _version_micro = '' # use '' for first of series, number for 1 and above #_version_extra = 'dev' _version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor] if _version_micro: _ver.append(_version_micro) if _version_extra: _ver.append(_version_extra) __version__ = '.'.join(map(str, _ver)) CLASSIFIERS = ["Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Scientific/Engineering"] description = "Nitime: timeseries analysis for neuroscience data" # Note: this long_description is actually a copy/paste from the top-level # README.txt, so that it shows up nicely on PyPI. So please remember to edit # it only in one place and sync it correctly. long_description = """
# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" _version_major = 0 _version_minor = 6 _version_micro = '' # use '' for first of series, number for 1 and above _version_extra = 'dev' #_version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor] if _version_micro: _ver.append(_version_micro) if _version_extra: _ver.append(_version_extra) __version__ = '.'.join(map(str, _ver)) CLASSIFIERS = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Scientific/Engineering" ] description = "Nitime: timeseries analysis for neuroscience data" # Note: this long_description is actually a copy/paste from the top-level # README.txt, so that it shows up nicely on PyPI. So please remember to edit # it only in one place and sync it correctly. long_description = """
def draw_graph(G, labels=None, node_colors=None, node_shapes=None, node_scale=1.0, edge_style='solid', edge_cmap=None, colorbar=False, vrange=None, layout=None, title=None, font_family='sans-serif', font_size=9, stretch_factor=1.0, edge_alpha=True, fig_size=None): """Draw a weighted graph with options to visualize link weights. The resulting diagram uses the rank of each node as its size, and the weight of each link (after discarding thresholded values, see below) as the link opacity. It maps edge weight to color as well as line opacity and thickness, allowing the color part to be hardcoded over a value range (to permit valid cross-figure comparisons for different graphs, so the same color corresponds to the same link weight even if each graph has a different range of weights). The nodes sizes are proportional to their degree, computed as the sum of the weights of all their links. The layout defaults to circular, but any nx layout function can be passed in, as well as a statically precomputed layout. Parameters ---------- G : weighted graph The values must be of the form (v1,v2), with all v2 in [0,1]. v1 are used for colors, v2 for thickness/opacity. labels : list or dict, optional. An indexable object that maps nodes to strings. If not given, the string form of each node is used as a label. If False, no labels are drawn. node_colors : list or dict, optional. An indexable object that maps nodes to valid matplotlib color specs. See matplotlib's plot() function for details. node_shapes : list or dict, optional. An indexable object that maps nodes to valid matplotlib shape specs. See matplotlib's scatter() function for details. If not given, circles are used. node_scale : float, optional A scale factor to globally stretch or shrink all nodes symbols by. edge_style : string, optional Line style for the edges, defaults to 'solid'. edge_cmap : matplotlib colormap, optional. A callable that returns valid color specs, like matplotlib colormaps. If not given, edges are colored black. colorbar : bool If true, automatically add a colorbar showing the mapping of graph weight values to colors. vrange : pair of floats If given, this indicates the total range of values that the weights can in principle occupy, and is used to set the lower/upper range of the colormap. This allows you to set the range of multiple different figures to the same values, even if each individual graph has range variations, so that visual color comparisons across figures are valid. layout : function or layout dict, optional A NetworkX-like layout function or the result of a precomputed layout for the given graph. NetworkX produces layouts as dicts keyed by nodes and with (x,y) pairs of coordinates as values, any function that produces this kind of output is acceptable. Defaults to nx.circular_layout. title : string, optional. If given, title to put on the main plot. font_family : string, optional. Font family used for the node labels and title. font_size : int, optional. Font size used for the node labels and title. stretch_factor : float, optional A global scaling factor to make the graph larger (or smaller if <1). This can be used to separate the nodes if they start overlapping. edge_alpha: bool, optional Whether to weight the transparency of each edge by a factor equivalent to its relative weight fig_size: list of height by width, the size of the figure (in inches). Defaults to [6,6] Returns ------- fig The matplotlib figure object with the plot. """ if fig_size is None: figsize = [6, 6] scaler = figsize[0] / 6. # For the size of the node symbols node_size_base = 1000 * scaler node_min_size = 200 * scaler default_node_shape = 'o' # Default colors if none given default_node_color = 'r' default_edge_color = 'k' # Max edge width max_width = 13 * scaler min_width = 2 * scaler font_family = 'sans-serif' # We'll use the nodes a lot, let's make a numpy array of them nodes = np.array(sorted(G.nodes())) nnod = len(nodes) # Build a 'weighted degree' array obtained by adding the (absolute value) # of the weights for all edges pointing to each node: amat = nx.adj_matrix(G).A # get a normal array out of it degarr = abs(amat).sum(0) # weights are sums across rows # Map the degree to the 0-1 range so we can use it for sizing the nodes. try: odegree = rescale_arr(degarr, 0, 1) # Make an array of node sizes based on node degree node_sizes = odegree * node_size_base + node_min_size except ZeroDivisionError: # All nodes same size node_sizes = np.empty(nnod, float) node_sizes.fill(0.5 * node_size_base + node_min_size) # Adjust node size list. We square the scale factor because in mpl, node # sizes represent area, not linear size, but it's more intuitive for the # user to think of linear factors (the overall figure scale factor is also # linear). node_sizes *= node_scale**2 # Set default node properties if node_colors is None: node_colors = [default_node_color] * nnod if node_shapes is None: node_shapes = [default_node_shape] * nnod # Set default edge colormap if edge_cmap is None: # Make an object with the colormap API, that maps all input values to # the default color (with proper alhpa) edge_cmap = (lambda val, alpha: colors.colorConverter.to_rgba( default_edge_color, alpha)) # if vrange is None, we set the color range from the values, else the user # can specify it # e[2] is edge value: edges_iter returns (i,j,data) gvals = np.array([e[2]['weight'] for e in G.edges(data=True)]) gvmin, gvmax = gvals.min(), gvals.max() gvrange = gvmax - gvmin if vrange is None: vrange = gvmin, gvmax # Now, construct the normalization for the colormap cnorm = mpl.colors.Normalize(vmin=vrange[0], vmax=vrange[1]) # Create the actual plot where the graph will be displayed figsize = np.array(figsize, float) figsize *= stretch_factor fig = plt.figure(figsize=figsize) ax_graph = fig.add_subplot(1, 1, 1) fig.sca(ax_graph) if layout is None: layout = nx.circular_layout # Compute positions for all nodes - nx has several algorithms if callable(layout): pos = layout(G) else: # The user can also provide a precomputed layout pos = layout # Draw nodes for nod in nodes: nx.draw_networkx_nodes(G, pos, nodelist=[nod], node_color=node_colors[nod], node_shape=node_shapes[nod], node_size=node_sizes[nod], edgecolors='k') # Draw edges if not isinstance(G, nx.DiGraph): # Undirected graph, simple lines for edges # We need the size of the value range to properly scale colors vsize = vrange[1] - vrange[0] gvals_normalized = G.metadata['vals_norm'] for (u, v, y) in G.edges(data=True): # The graph value is the weight, and the normalized values are in # [0,1], used for thickness/transparency alpha = gvals_normalized[u, v] # Scale the color choice to the specified vrange, so that ecol = (y['weight'] - vrange[0]) / vsize #print 'u,v:',u,v,'y:',y,'ecol:',ecol # dbg if edge_alpha: fade = alpha else: fade = 1.0 edge_color = [tuple(edge_cmap(ecol, fade))] #dbg: #print u,v,y nx.draw_networkx_edges(G, pos, edgelist=[(u, v)], width=min_width + alpha * max_width, edge_color=edge_color, style=edge_style) else: # Directed graph, use arrows. # XXX - this is currently broken. raise NotImplementedError("arrow drawing currently broken") ## for (u,v,x) in G.edges(data=True): ## y,w = x ## draw_arrows(G,pos,edgelist=[(u,v)], ## edge_color=[w], ## alpha=w, ## edge_cmap=edge_cmap, ## width=w*max_width) # Draw labels. If not given, we use the string form of the nodes. If # labels is False, no labels are drawn. if labels is None: labels = map(str, nodes) if labels: lab_idx = list(range(len(labels))) labels_dict = dict(zip(lab_idx, labels)) nx.draw_networkx_labels(G, pos, labels_dict, font_size=font_size, font_family=font_family) if title: plt.title(title, fontsize=font_size) # Turn off x and y axes labels in pylab plt.xticks([]) plt.yticks([]) # Add a colorbar if requested if colorbar: divider = make_axes_locatable(ax_graph) ax_cb = divider.new_vertical(size="20%", pad=0.2, pack_start=True) fig.add_axes(ax_cb) cb = mpl.colorbar.ColorbarBase( ax_cb, cmap=edge_cmap, norm=cnorm, #boundaries = np.linspace(min((gvmin,0)), # max((gvmax,0)), # 256), orientation='horizontal', format='%.2f') # Always return the MPL figure object so the user can further manipulate it return fig