def clustering_spectrum(network, style='nobin', Nbins=25): '''Calculates clustering coeff as function of degree. Inputs: network, style=('nobin'|'logbin'),Nbins=number of logbins Outputs: [k,c(k)]''' if style == 'logbin': d = netext.deg(network) degrees = d.values() maxk = max(degrees) cvalues = clustering_valuelist(network) factor = (maxk / 10.5)**(1.0 / Nbins) bins = generateLogbins(1.0, maxk, factor) bc = binCenters(bins) temp = binAverages(bins, bc, cvalues[0], cvalues[1]) else: Nk = calculateNk(network) cvalues = clustering_valuelist( network) # two lists - degrees & c-coeffs temp = degreeAverages(cvalues[0], Nk, cvalues[1]) return temp
def clustering_spectrum(network,style='nobin',Nbins=25): '''Calculates clustering coeff as function of degree. Inputs: network, style=('nobin'|'logbin'),Nbins=number of logbins Outputs: [k,c(k)]''' if style=='logbin': d=netext.deg(network) degrees=d.values() maxk=max(degrees) cvalues=clustering_valuelist(network) factor=(maxk/10.5)**(1.0/Nbins) bins=generateLogbins(1.0,maxk,factor) bc=binCenters(bins) temp=binAverages(bins,bc,cvalues[0],cvalues[1]) else: Nk=calculateNk(network) cvalues=clustering_valuelist(network) # two lists - degrees & c-coeffs temp=degreeAverages(cvalues[0],Nk,cvalues[1]) return temp
def knn_spectrum(network,style='logbin',weighted=False,Nbins=25): '''Returns (binned) degree vs average nearest neighbour degree. Optional inputs: style='logbin' or 'linbin', Nbins = # of bins, weighted = False or True (True: neigh degrees weighted by link weights) Output: list [k,knn(k)]''' knni=nodelevelKnn(network,weighted) # returns 2 lists [degrees,knn] d=netext.deg(network) degrees=d.values() maxk=max(degrees) if style=='logbin': factor=(maxk/10.5)**(1/float(Nbins)) bins=generateLogbins(1.0,maxk,factor) bc=binCenters(bins) temp=binAverages(bins,bc,knni[0],knni[1]) else: Nk=calculateNk(network) temp=degreeAverages(knni[0],Nk,knni[1]) return temp
def knn_spectrum(network, style='logbin', weighted=False, Nbins=25): '''Returns (binned) degree vs average nearest neighbour degree. Optional inputs: style='logbin' or 'linbin', Nbins = # of bins, weighted = False or True (True: neigh degrees weighted by link weights) Output: list [k,knn(k)]''' knni = nodelevelKnn(network, weighted) # returns 2 lists [degrees,knn] d = netext.deg(network) degrees = d.values() maxk = max(degrees) if style == 'logbin': factor = (maxk / 10.5)**(1 / float(Nbins)) bins = generateLogbins(1.0, maxk, factor) bc = binCenters(bins) temp = binAverages(bins, bc, knni[0], knni[1]) else: Nk = calculateNk(network) temp = degreeAverages(knni[0], Nk, knni[1]) return temp
def calculateNk(network): '''Calculates # of nodes of degree k (N(k)) for a network. Input: network, output: N(k) as list''' d=netext.deg(network) degrees=d.values() Nk=[] for i in range(max(degrees)+1): Nk.append(0) for i in range(len(degrees)): Nk[degrees[i]]+=1 return Nk
def calculateNk(network): '''Calculates # of nodes of degree k (N(k)) for a network. Input: network, output: N(k) as list''' d = netext.deg(network) degrees = d.values() Nk = [] for i in range(max(degrees) + 1): Nk.append(0) for i in range(len(degrees)): Nk[degrees[i]] += 1 return Nk
def degree_distribution(network,style='logbin',Nbins=25.0): '''Calculates degree distribution. Inputs: network, style=('nobin'|'logbin'|'cumulative'), Nbins=number of logbins. Output: [k,P(k)]''' if style=='logbin': d=netext.deg(network) degs=d.values() maxk=max(degs) factor=(maxk/10.5)**(1/float(Nbins)) bins=generateLogbins(1.0,maxk,factor) Nk=calculateNk(network) Nbin=binDensity(bins,Nk) bc=binCenters(bins) NNbin=normalize(Nbin) temp=[bc,NNbin] elif style=='cumulative': Nk=calculateNk(network) Pk=cumulativePk(Nk) k=range(len(Pk)) temp=[k,Pk] else: Nk=calculateNk(network) k=range(len(Nk)) NNk=normalize(Nk) temp=[k,NNk] return temp
def degree_distribution(network, style='logbin', Nbins=25.0): '''Calculates degree distribution. Inputs: network, style=('nobin'|'logbin'|'cumulative'), Nbins=number of logbins. Output: [k,P(k)]''' if style == 'logbin': d = netext.deg(network) degs = d.values() maxk = max(degs) factor = (maxk / 10.5)**(1 / float(Nbins)) bins = generateLogbins(1.0, maxk, factor) Nk = calculateNk(network) Nbin = binDensity(bins, Nk) bc = binCenters(bins) NNbin = normalize(Nbin) temp = [bc, NNbin] elif style == 'cumulative': Nk = calculateNk(network) Pk = cumulativePk(Nk) k = range(len(Pk)) temp = [k, Pk] else: Nk = calculateNk(network) k = range(len(Nk)) NNk = normalize(Nk) temp = [k, NNk] return temp
def visualizeNet(net, coords=None, axes=None, frame=False, animated=False, scaling=True, margin=0.025, nodeShapes=None, defaultNodeShape='o', nodeSizes=None, defaultNodeSize=None, nodeColors=None, defaultNodeColor=None, nodeEdgeColors=None, defaultNodeEdgeColor='black', nodeLabels=None, nodeLabelSize=None, labelAllNodes=False, labelPositions=None, defaultLabelPosition='out', edgeColors=None, defaultEdgeColor=None, edgeWidths=None, defaultEdgeWidth=None, nodeEdgeWidths=None, defaultNodeEdgeWidth=0.2, edgeLabels=None, edgeLabelSize=None, labelAllEdges=False, nodePlotOrders=None, defaultNodePlotOrder=1, edgePlotOrders=None, defaultEdgePlotOrder=0): """Visualize a network. Note that all sizes (node size, link width, etc.) are given in points: one point equals 1/72th of an inch. Basic parameters ---------------- net : pynet.SymmNet The network to visualize coords : dictionary of tuples {node_ID: (x,y)} Coordinates of all nodes. If None, the coordinates will be calculated. The x and y coordinates are assumed to have the same scale, so for example an edge between nodes 0 and 1 with `coords[0]=(0,0)` and `coords[1]=(2,2)` will be at an angle of 45 degrees. axes : pylab.axes object If given, the network will be drawn in this axis. Otherwise a new figure is created for the plot and the figure handle is then returned. frame : bool If False, the frame will be not be shown in the plot. You can still use axis labels. scaling : bool If True, the coordinate axes will be scaled for best fit. If false, the coordinate axes will not be altered. margin : float (>= 0) The relative size of empty margin around the network. Margin of 0.0 means that some nodes touch the edge of the plot, margin of 0.2 adds 20 % on all sides etc. This parameter has an effect only if scaling is True. Defining node and edge colors ----------------------------- Colors for nodes and edges are defined similarly. The following explains the procedure for nodes; to control edge coloring simply replace the word 'node' (or 'Node') with the word 'edge' (or 'Edge') in the parameter names. The color of a node is defined with the dictionary `nodeColors`: key is the node index and the value is any valid coloring scheme (see below under 'Coloring schemes'). If a node index is not in `nodeColors`, it is colored according to `defaultNodeColor`. This variable can have the same values as the values in `nodeColors`. Coloring schemes ---------------- A constant color can be defined in any way allowed by pylab. For example 'k', 'black' and (0,0,0) all give black color. Alternatively the color can be based on the node strength, degree or any node property. In this case the coloring definition is a dictionary. The following examples illustrate the idea: color_scheme = {'by':'weight', 'scale':'log', 'cmap':'winter'} color_scheme = {'by':'degree', 'scale':'lin', 'min':1, 'max':10} color_scheme = {'by':'property:myProperty', 'scale':'log'} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'strength'/'weight' 'degree', 'property:<property_name>' 'scale' 'log' 'lin' 'cmap' 'jet' Any colormap 'min' (Min value in data) Any integer x, 1 <= x <= 'max' 'max' (Max value in data) Any integer x, 'min' <= x Any keys that are omitted are filled in with the default value. Note the syntax for using node properties, where the word 'property' is followed by a semicolon and the property name. Node size --------- The node size is controlled with a syntax similar to that used with colors. Node size is defined by dictionary `nodeSizes`, and if a node is not in it, the default value given by `defaultNodeSize` is used. The value can be a single integer, which gives the node size in pixels. Alternatively the node size can be controlled by node strength, degree or any property: node_size_scheme = {'by':'strength', 'scale':'log', 'min':2, 'max':10} node_size_scheme = {'by':'degree', 'scale':'lin'} node_size_scheme = {'by':'property:myProperty', 'scale':'log'} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'strength' 'degree', 'property:<property_name>' 'scale' 'log' 'lin' 'min' (Min value in data) int; 1 <= x <= 'max' 'max' (Max value in data) int; 'min' <= x 'min_size' 1 int; 1 <= x <= 'max_size' 'max_size' 6 int; 'min_size' <= x Again, keys that are omitted are filled with default values. Edge width ---------- Edge width is defined by dictionary `edgeWidths`, and if an edge is not in it, the default value given by `defaultEdgeWidth` is used. The value can be a single integer, which gives the edge width in pixels. Alternatively the edge width can be controlled by edge weight: edge_width_scheme = {'by':'weight', 'scale':'log', 'min':1, 'max':5} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'weight' 'scale' 'log' 'lin' 'min' (Min value in data) int; 1 <= x <= 'max' 'max' (Max value in data) int; 'min' <= x 'min_size' 0.2 float; 1 <= x <= 'max_width' 'max_size' 2.0 float; 'min_width' <= x Note that the 'by'-key can always be omitted since it has only one possible value. Node labels ----------- Node labels can be given in `nodeLabels` dictionary, where the key is node index and the value is the corresponding labels. If `labelAllNodes` is True, also nodes not in `nodeLabels` will receive a label, which is the node index. The values in `nodeLabels` are converted to string with str(). There are two possibilities for the positioning of node labels: 'in' and 'out' (default). 'in' means that the label is printed at the exact position of the node; if the node is hollow, this effectively prints the node labes inside the nodes (make sure the node size and font sizes are compatible). 'out' prints the label next to the node. Edge labels ----------- Also edges can have labels, given in `edgeLabels` dictionary, where the key is a tuple (i,j) of end node indices. The edge labels are always printed on the right side of each edge (with direction defined from i to j). If `labelAllEdges` is True, also the edges not listed in `edgeLabels` will be given a label. In this case the label is '(i,j)', where i and j are the indices of the end nodes. Return ------ fig : pylab.Figure (None if `axes` is given.) Figure object with one axes containing the plotted network figure. Examples -------- >>> # Construct an example network. >>> from netpython import pynet, visuals >>> net = pynet.SymmNet() >>> net[0][1] = 1.0 >>> net[1][2] = 3.5 >>> net[0][2] = 5.0 >>> # Simplest case: get coordinates, plot into a >>> # new figure and save it to disk >>> fig = visuals.visualizeNet(net) >>> fig.savefig('myNet.eps') >>> # Draw the figure in the upper left subfigure, with predefined >>> # coordinates. Note that drawNet does not return anything. >>> import pylab >>> coords = {0:(0,0), 1:(4,0), 2:(2,3)} >>> fig = pylab.figure() >>> ax = fig.add_subplot(2,2,1) >>> visuals.visualizeNet(net, coords=coords, axes=ax) """ # # DEFAULT VALUES. These will be used whenever the user has not # defined a given value for defaultNodeColor etc. # internal_defaultNodeColor = {'by':'strength', 'scale':'log', 'cmap':'jet'} internal_defaultEdgeColor = {'by':'weight', 'scale':'log', 'cmap':'jet'} internal_defaultNodeSize = {'by':'strength', 'scale':'log', 'min_size':2, 'max_size':6} internal_defaultEdgeWidth = {'by':'weight', 'scale':'log', 'min_size':0.2, 'max_size':2.0} node_label_font_color = 'k' node_label_font_size = (8 if nodeLabelSize==None else nodeLabelSize) edge_label_font_color = None #'k' edge_label_font_size = (5 if edgeLabelSize==None else edgeLabelSize) # # PROCESS INPUT PARAMETERS # if coords is None: coords = calculateCoordinates(net) fig = None if axes is None: fig = figure() axes = fig.add_subplot(111) nodeShapes = (nodeShapes or {}) nodeColors = (nodeColors or {}) defaultNodeColor = (defaultNodeColor or {}) if isinstance(defaultNodeColor, dict): for k,v in internal_defaultNodeColor.iteritems(): if k not in defaultNodeColor: defaultNodeColor[k] = v nodeEdgeColors = (nodeEdgeColors or {}) edgeColors = (edgeColors or {}) defaultEdgeColor = (defaultEdgeColor or {}) if isinstance(defaultEdgeColor, dict): for k,v in internal_defaultEdgeColor.iteritems(): if k not in defaultEdgeColor: defaultEdgeColor[k] = v nodeSizes = (nodeSizes or {}) if defaultNodeSize is None: defaultNodeSize = {} if isinstance(defaultNodeSize, dict): for k,v in internal_defaultNodeSize.iteritems(): if k not in defaultNodeSize: defaultNodeSize[k] = v edgeWidths = (edgeWidths or {}) if defaultEdgeWidth is None: defaultEdgeWidth = {} if isinstance(defaultEdgeWidth, dict): for k,v in internal_defaultEdgeWidth.iteritems(): if k not in defaultEdgeWidth: defaultEdgeWidth[k] = v nodeEdgeWidths = (nodeEdgeWidths or {}) nodeLabels = (nodeLabels or {}) labelPositions = (labelPositions or {}) edgeLabels = (edgeLabels or {}) nodePlotOrders = (nodePlotOrders or {}) edgePlotOrders = (edgePlotOrders or {}) if margin < 0: margin = 0.0 # Initialize dictionary where the returned plotted artist objects will be collected. obj = dict() # # AUXILIARY FUNCTIONS # def scaled(scaling_type, value, value_limits, final_limits): def lin_scaling(value, value_limits, final_limits): value_span = value_limits[1] - value_limits[0] final_span = final_limits[1] - final_limits[0] if final_span == 0: return final_limits[0] if value_span == 0: p = 0.5 else: p = float(value - value_limits[0])/value_span return final_limits[0]+p*final_span if value <= value_limits[0]: return final_limits[0] if value >= value_limits[1]: return final_limits[1] if scaling_type == 'log' or scaling_type == 'logarithmic': return lin_scaling(np.log(value), np.log(value_limits), final_limits) else: return lin_scaling(value, value_limits, final_limits) def determine_size(scheme, i, net, values, limits, defaults): if not isinstance(scheme, dict): return scheme else: # Determine what defines the size. Calculate the limits # for this property if not yet done. size_by = scheme.get('by', defaults['by']) if size_by not in limits: property_name = "".join(size_by.split(':')[1:]) np_ = sorted(net.nodeProperty[property_name].values()) limits[size_by] = (np_[0], np_[-1]) if size_by not in values: property_name = "".join(size_by.split(':')[1:]) values[size_by] = net.nodeProperty[property_name][i] scale = scheme.get('scale', defaults['scale']) val_min = scheme.get('min', limits[size_by][0]) val_max = scheme.get('max', limits[size_by][1]) size_min = scheme.get('min_size', defaults['min_size']) size_max = scheme.get('max_size', defaults['max_size']) #print size_by, scale, val_min, val_max, size_min, size_max return scaled(scale, values[size_by], [val_min, val_max], [size_min, size_max]) def determine_color(scheme, i, net, values, limits, defaults): if not isinstance(scheme, dict): return scheme else: color_by = scheme.get('by', defaults['by']) if color_by not in limits: property_name = "".join(color_by.split(':')[1:]) np_ = sorted(net.nodeProperty[property_name].values()) limits[color_by] = (np_[0], np_[-1]) if color_by not in values: property_name = "".join(color_by.split(':')[1:]) values[color_by] = net.nodeProperty[property_name][i] scale = scheme.get('scale', defaults['scale']) cmap = scheme.get('cmap', defaults['cmap']) val_min = scheme.get('min', limits[color_by][0]) val_max = scheme.get('max', limits[color_by][1]) cval = scaled(scale, values[color_by], [val_min, val_max], [0.0,1.0]) cm = setColorMap(cmap) return cm(float(cval)) def luminance(c): """Return luminance of color `c`.""" c_vec = matplotlib.colors.colorConverter.to_rgb(c) return 0.2126*c_vec[0]+0.7152*c_vec[1]+0.0722*c_vec[2] def edge_label_pos(axes, xcoords, ycoords, edge_width, label_size, offset=1.5): """Return the baseline position and label rotation (in angles) for an edge label. The label will be on the right side of the edge, in proper orientation for reading, and located `offset` points from the edge. """ theta = get_edge_angle(xcoords, ycoords) if theta > -np.pi/2 and theta < np.pi/2: # Edge goes from left to right. label_rotation = theta else: # Edge goes from right to left. label_rotation = theta - np.pi offset_points = offset+0.5*edge_width+0.5*label_size label_position = (0.5*sum(xcoords), 0.5*sum(ycoords)) offset_dir = theta - np.pi/2 label_offset = (offset_points*np.cos(offset_dir), offset_points*np.sin(offset_dir)) return label_position, label_offset, 180*label_rotation/np.pi # # INITIALIZE SOME DATA STRUCTURES # # Find out the minimum and maximum value for strength and degree. strengths = netext.strengths(net) smin, smax = min(strengths.values()), max(strengths.values()) degrees = netext.deg(net) dmin, dmax = min(degrees.values()), max(degrees.values()) limits = {"strength":(smin, smax), "degree":(dmin,dmax)} # # INITIALIZE AXES SIZE # node_diameters = {}; for nodeIndex in net: values = {"strength": strengths[nodeIndex], "degree": degrees[nodeIndex]} node_diameters[nodeIndex] = (determine_size(nodeSizes.get(nodeIndex, defaultNodeSize), nodeIndex, net, values, limits, defaultNodeSize) + determine_size(nodeEdgeWidths.get(nodeIndex, defaultNodeEdgeWidth), nodeIndex, net, values, limits, defaultNodeEdgeWidth)) if scaling: # Make axis equal making sure the nodes on the edges are not # clipped. We cannot use `axis('equal')` because nothing has been # drawn yet, but we still need to do this so the arrows will be # draw properly in the plotting phase. This is a bit tricky # because the axes might not be square. max_node_diameter = max(node_diameters.values()) y_coords = sorted(map(operator.itemgetter(1), coords.values())) x_coords = sorted(map(operator.itemgetter(0), coords.values())) ax_pos = axes.get_position() x_span = x_coords[-1] - x_coords[0] y_span = y_coords[-1] - y_coords[0] ax_width_inches = ax_pos.width*axes.get_figure().get_figwidth() ax_height_inches = ax_pos.height*axes.get_figure().get_figheight() if (x_span*ax_height_inches > y_span*ax_width_inches): # The x-span dictates the coordinates. Calculate the margin # necessary to fit in the nodes on the edges. rad_frac = 0.5*max_node_diameter/(72*ax_width_inches) x_margin = x_span*rad_frac/(1-2*rad_frac) x_min, x_max = x_coords[0]-x_margin, x_coords[-1]+x_margin y_mid = 0.5*(y_coords[-1] + y_coords[0]) y_axis_span = (x_max-x_min)*ax_height_inches/ax_width_inches y_min, y_max = y_mid - 0.5*y_axis_span, y_mid + 0.5*y_axis_span, else: # The y-span dictates the coordinates. Calculate the margin # necessary to fit in the nodes on the edges. rad_frac = 0.5*max_node_diameter/(72*ax_height_inches) y_margin = y_span*rad_frac/(1-2*rad_frac) y_min, y_max = y_coords[0]-y_margin, y_coords[-1]+y_margin x_mid = 0.5*(x_coords[-1] + x_coords[0]) x_axis_span = (y_max-y_min)*ax_width_inches/ax_height_inches x_min, x_max = x_mid - 0.5*x_axis_span, x_mid + 0.5*x_axis_span, y_span, x_span = y_max - y_min, x_max - x_min axes.set_ylim(ymin=y_min-margin*y_span, ymax=y_max+margin*y_span) axes.set_xlim(xmin=x_min-margin*x_span, xmax=x_max+margin*x_span) prev_autoscale = axes.get_autoscale_on() axes.set_autoscale_on(False) point_trans = lambda x_: points_to_data(ax,x_) # Transform points to data coordinates. # # DRAW EDGES # edges = list(net.edges) if edges: # Sort by edge weight. edges.sort(key=operator.itemgetter(2)) limits['weight'] = (edges[0][2], edges[-1][2]) # DEBUGGING: Print statistics of edge weights. #import data_utils #weights_ = map(operator.itemgetter(2), edges) #print "Edges:", limits['weight'], " 10/25/50/75/90:", #mp_ = len(edges)/2 #print "%d, %d, %d, %d, %d" % (int(data_utils.percentile(weights_, 0.1)), # int(data_utils.percentile(weights_, 0.25)), # int(data_utils.percentile(weights_, 0.5)), # int(data_utils.percentile(weights_, 0.75)), # int(data_utils.percentile(weights_, 0.9))) net_edges = NetEdges(axes, net.isSymmetric()) for i,j,w in edges: values = {"weight": w, "strength": strengths[j], "degree": degrees[j]} # Determine edge width. if (i,j) in edgeWidths: width = determine_size(edgeWidths[(i,j)], (i,j), net, values, limits, defaultEdgeWidth) elif (j,i) in edgeWidths: width = determine_size(edgeWidths[(j,i)], (j,i), net, values, limits, defaultEdgeWidth) else: width = determine_size(defaultEdgeWidth, (i,j), net, values, limits, defaultEdgeWidth) # Determine edge color. if (i,j) in edgeColors: color = determine_color(edgeColors[(i,j)], (i,j), net, values, limits, defaultEdgeColor) elif (j,i) in edgeColors: color = determine_color(edgeColors[(j,i)], (j,i), net, values, limits, defaultEdgeColor) else: color = determine_color(defaultEdgeColor, (j,i), net, values, limits, defaultEdgeColor) if (i,j) in edgePlotOrders: zorder = edgePlotOrders[(i,j)] elif (j,i) in edgePlotOrders: zorder = edgePlotOrders[(j,i)] else: zorder = defaultEdgePlotOrder # FOR DEBUGGING: #print "Edge (%d,%d / %.2f) : %.1f %s %f" % (i,j,w,width,str(color),zorder) xcoords, ycoords = (coords[i][0], coords[j][0]), (coords[i][1], coords[j][1]) #obj[(i,j)] = draw_edge(axes, xcoords, ycoords, width, color, # net.isSymmetric(), zorder, node_diameters[j]) net_edges.add(xcoords, ycoords, width, color, zorder, node_diameters[j]) # Add edge label. if (labelAllEdges or (i,j) in edgeLabels or (net.isSymmetric() and (j,i) in edgeLabels)): if (i,j) in edgeLabels: label = str(edgeLabels[(i,j)]) elif (net.isSymmetric() and (j,i) in edgeLabels): label = str(edgeLabels[(j,i)]) else: label = "(%d,%d)" % (i,j) lpos, loffset, lrot = edge_label_pos(axes, xcoords, ycoords, width, edge_label_font_size) axes.annotate(label, lpos, xytext=loffset, textcoords='offset points', color=edge_label_font_color, size=edge_label_font_size, horizontalalignment='center', verticalalignment='center', rotation=lrot, zorder=zorder+0.5) # # DRAW NODES # max_node_diameter = 0; net_nodes = NetNodes(axes) node_internal_color = {} for nodeIndex in net: values = {"strength": strengths[nodeIndex], "degree": degrees[nodeIndex]} # Determine node shape. shape = nodeShapes.get(nodeIndex, defaultNodeShape) # Determine node size (and update max size). size = determine_size(nodeSizes.get(nodeIndex, defaultNodeSize), nodeIndex, net, values, limits, defaultNodeSize) # Determine node edge width. edgewidth = determine_size(nodeEdgeWidths.get(nodeIndex, defaultNodeEdgeWidth), nodeIndex, net, values, limits, defaultNodeEdgeWidth) max_node_diameter = max(max_node_diameter, size+edgewidth) # Determine node color color = determine_color(nodeColors.get(nodeIndex, defaultNodeColor), nodeIndex, net, values, limits, defaultNodeColor) node_internal_color[nodeIndex] = color # Determine node edge color edgecolor = determine_color(nodeEdgeColors.get(nodeIndex, defaultNodeEdgeColor), nodeIndex, net, values, limits, defaultNodeEdgeColor) # Determine z-order. zorder = nodePlotOrders.get(nodeIndex, defaultNodePlotOrder) # FOR DEBUGGING: #print "Node %d : %f %s %f %s" % (nodeIndex, size, str(color), edgewidth, str(edgecolor)) #obj[nodeIndex] = draw_node(axes, coords[nodeIndex][0], coords[nodeIndex][1], # shape, color, size, edgecolor, edgewidth, zorder) net_nodes.add(coords[nodeIndex][0], coords[nodeIndex][1], color, size, edgecolor, edgewidth, zorder) # Add node label. if nodeIndex in nodeLabels or labelAllNodes: if nodeIndex in nodeLabels: label = str(nodeLabels[nodeIndex]) else: label = str(nodeIndex) label_pos = labelPositions.get(nodeIndex, defaultLabelPosition) if label_pos == 'out': nodeLabel_offset = int(np.ceil(float(size)/2))+1 axes.annotate(label, (coords[nodeIndex][0],coords[nodeIndex][1]), textcoords='offset points', xytext=(nodeLabel_offset, nodeLabel_offset), color=node_label_font_color, size=node_label_font_size, zorder=zorder+0.5) elif label_pos == 'in': axes.annotate(label, (coords[nodeIndex][0],coords[nodeIndex][1]), color=("black" if luminance(node_internal_color[nodeIndex]) > 0.5 else "white"), size=max(5, min(size-1, 0.7*size)), horizontalalignment='center', verticalalignment='center', zorder=zorder+0.5) # Remove frame. if not frame: # Using 'axes.set_axis_off()' would also turn of the axis # labels, which is too much. The following lines are required # to turn off the frame and tick labels while keeping axis # labels. axes.set_frame_on(False) axes.set_xticklabels([]) axes.xaxis.set_ticks_position('none') axes.set_yticklabels([]) axes.yaxis.set_ticks_position('none') # Return autoscaling to the original value. axes.set_autoscale_on(prev_autoscale) # Return figure (or objects drawn). return (obj if animated else axes.get_figure())
def visualizeNet(net, coords=None, axes=None, frame=False, animated=False, scaling=True, margin=0.025, nodeShapes=None, defaultNodeShape='o', nodeSizes=None, defaultNodeSize=None, nodeColors=None, defaultNodeColor=None, nodeEdgeColors=None, defaultNodeEdgeColor='black', nodeLabels=None, nodeLabelSize=None, labelAllNodes=False, labelPositions=None, defaultLabelPosition='out', edgeColors=None, defaultEdgeColor=None, edgeWidths=None, defaultEdgeWidth=None, nodeEdgeWidths=None, defaultNodeEdgeWidth=0.2, edgeLabels=None, edgeLabelSize=None, labelAllEdges=False, nodePlotOrders=None, defaultNodePlotOrder=1, edgePlotOrders=None, defaultEdgePlotOrder=0): """Visualize a network. Note that all sizes (node size, link width, etc.) are given in points: one point equals 1/72th of an inch. Basic parameters ---------------- net : pynet.SymmNet The network to visualize coords : dictionary of tuples {node_ID: (x,y)} Coordinates of all nodes. If None, the coordinates will be calculated. The x and y coordinates are assumed to have the same scale, so for example an edge between nodes 0 and 1 with `coords[0]=(0,0)` and `coords[1]=(2,2)` will be at an angle of 45 degrees. axes : pylab.axes object If given, the network will be drawn in this axis. Otherwise a new figure is created for the plot and the figure handle is then returned. frame : bool If False, the frame will be not be shown in the plot. You can still use axis labels. scaling : bool If True, the coordinate axes will be scaled for best fit. If false, the coordinate axes will not be altered. margin : float (>= 0) The relative size of empty margin around the network. Margin of 0.0 means that some nodes touch the edge of the plot, margin of 0.2 adds 20 % on all sides etc. This parameter has an effect only if scaling is True. Defining node and edge colors ----------------------------- Colors for nodes and edges are defined similarly. The following explains the procedure for nodes; to control edge coloring simply replace the word 'node' (or 'Node') with the word 'edge' (or 'Edge') in the parameter names. The color of a node is defined with the dictionary `nodeColors`: key is the node index and the value is any valid coloring scheme (see below under 'Coloring schemes'). If a node index is not in `nodeColors`, it is colored according to `defaultNodeColor`. This variable can have the same values as the values in `nodeColors`. Coloring schemes ---------------- A constant color can be defined in any way allowed by pylab. For example 'k', 'black' and (0,0,0) all give black color. Alternatively the color can be based on the node strength, degree or any node property. In this case the coloring definition is a dictionary. The following examples illustrate the idea: color_scheme = {'by':'weight', 'scale':'log', 'cmap':'winter'} color_scheme = {'by':'degree', 'scale':'lin', 'min':1, 'max':10} color_scheme = {'by':'property:myProperty', 'scale':'log'} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'strength'/'weight' 'degree', 'property:<property_name>' 'scale' 'log' 'lin' 'cmap' 'jet' Any colormap 'min' (Min value in data) Any integer x, 1 <= x <= 'max' 'max' (Max value in data) Any integer x, 'min' <= x Any keys that are omitted are filled in with the default value. Note the syntax for using node properties, where the word 'property' is followed by a semicolon and the property name. Node size --------- The node size is controlled with a syntax similar to that used with colors. Node size is defined by dictionary `nodeSizes`, and if a node is not in it, the default value given by `defaultNodeSize` is used. The value can be a single integer, which gives the node size in pixels. Alternatively the node size can be controlled by node strength, degree or any property: node_size_scheme = {'by':'strength', 'scale':'log', 'min':2, 'max':10} node_size_scheme = {'by':'degree', 'scale':'lin'} node_size_scheme = {'by':'property:myProperty', 'scale':'log'} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'strength' 'degree', 'property:<property_name>' 'scale' 'log' 'lin' 'min' (Min value in data) int; 1 <= x <= 'max' 'max' (Max value in data) int; 'min' <= x 'min_size' 1 int; 1 <= x <= 'max_size' 'max_size' 6 int; 'min_size' <= x Again, keys that are omitted are filled with default values. Edge width ---------- Edge width is defined by dictionary `edgeWidths`, and if an edge is not in it, the default value given by `defaultEdgeWidth` is used. The value can be a single integer, which gives the edge width in pixels. Alternatively the edge width can be controlled by edge weight: edge_width_scheme = {'by':'weight', 'scale':'log', 'min':1, 'max':5} The possible keys and their default values are KEY DEFAULT VALUE OTHER POSSIBLE VALUES 'by' 'weight' 'scale' 'log' 'lin' 'min' (Min value in data) int; 1 <= x <= 'max' 'max' (Max value in data) int; 'min' <= x 'min_size' 0.2 float; 1 <= x <= 'max_width' 'max_size' 2.0 float; 'min_width' <= x Note that the 'by'-key can always be omitted since it has only one possible value. Node labels ----------- Node labels can be given in `nodeLabels` dictionary, where the key is node index and the value is the corresponding labels. If `labelAllNodes` is True, also nodes not in `nodeLabels` will receive a label, which is the node index. The values in `nodeLabels` are converted to string with str(). There are two possibilities for the positioning of node labels: 'in' and 'out' (default). 'in' means that the label is printed at the exact position of the node; if the node is hollow, this effectively prints the node labes inside the nodes (make sure the node size and font sizes are compatible). 'out' prints the label next to the node. Edge labels ----------- Also edges can have labels, given in `edgeLabels` dictionary, where the key is a tuple (i,j) of end node indices. The edge labels are always printed on the right side of each edge (with direction defined from i to j). If `labelAllEdges` is True, also the edges not listed in `edgeLabels` will be given a label. In this case the label is '(i,j)', where i and j are the indices of the end nodes. Return ------ fig : pylab.Figure (None if `axes` is given.) Figure object with one axes containing the plotted network figure. Examples -------- >>> # Construct an example network. >>> from netpython import pynet, visuals >>> net = pynet.SymmNet() >>> net[0][1] = 1.0 >>> net[1][2] = 3.5 >>> net[0][2] = 5.0 >>> # Simplest case: get coordinates, plot into a >>> # new figure and save it to disk >>> fig = visuals.visualizeNet(net) >>> fig.savefig('myNet.eps') >>> # Draw the figure in the upper left subfigure, with predefined >>> # coordinates. Note that drawNet does not return anything. >>> import pylab >>> coords = {0:(0,0), 1:(4,0), 2:(2,3)} >>> fig = pylab.figure() >>> ax = fig.add_subplot(2,2,1) >>> visuals.visualizeNet(net, coords=coords, axes=ax) """ # # DEFAULT VALUES. These will be used whenever the user has not # defined a given value for defaultNodeColor etc. # internal_defaultNodeColor = { 'by': 'strength', 'scale': 'log', 'cmap': 'jet' } internal_defaultEdgeColor = {'by': 'weight', 'scale': 'log', 'cmap': 'jet'} internal_defaultNodeSize = { 'by': 'strength', 'scale': 'log', 'min_size': 2, 'max_size': 6 } internal_defaultEdgeWidth = { 'by': 'weight', 'scale': 'log', 'min_size': 0.2, 'max_size': 2.0 } node_label_font_color = 'k' node_label_font_size = (8 if nodeLabelSize == None else nodeLabelSize) edge_label_font_color = None #'k' edge_label_font_size = (5 if edgeLabelSize == None else edgeLabelSize) # # PROCESS INPUT PARAMETERS # if coords is None: coords = calculateCoordinates(net) fig = None if axes is None: fig = figure() axes = fig.add_subplot(111) nodeShapes = (nodeShapes or {}) nodeColors = (nodeColors or {}) defaultNodeColor = (defaultNodeColor or {}) if isinstance(defaultNodeColor, dict): for k, v in internal_defaultNodeColor.iteritems(): if k not in defaultNodeColor: defaultNodeColor[k] = v nodeEdgeColors = (nodeEdgeColors or {}) edgeColors = (edgeColors or {}) defaultEdgeColor = (defaultEdgeColor or {}) if isinstance(defaultEdgeColor, dict): for k, v in internal_defaultEdgeColor.iteritems(): if k not in defaultEdgeColor: defaultEdgeColor[k] = v nodeSizes = (nodeSizes or {}) if defaultNodeSize is None: defaultNodeSize = {} if isinstance(defaultNodeSize, dict): for k, v in internal_defaultNodeSize.iteritems(): if k not in defaultNodeSize: defaultNodeSize[k] = v edgeWidths = (edgeWidths or {}) if defaultEdgeWidth is None: defaultEdgeWidth = {} if isinstance(defaultEdgeWidth, dict): for k, v in internal_defaultEdgeWidth.iteritems(): if k not in defaultEdgeWidth: defaultEdgeWidth[k] = v nodeEdgeWidths = (nodeEdgeWidths or {}) nodeLabels = (nodeLabels or {}) labelPositions = (labelPositions or {}) edgeLabels = (edgeLabels or {}) nodePlotOrders = (nodePlotOrders or {}) edgePlotOrders = (edgePlotOrders or {}) if margin < 0: margin = 0.0 # Initialize dictionary where the returned plotted artist objects will be collected. obj = dict() # # AUXILIARY FUNCTIONS # def scaled(scaling_type, value, value_limits, final_limits): def lin_scaling(value, value_limits, final_limits): value_span = value_limits[1] - value_limits[0] final_span = final_limits[1] - final_limits[0] if final_span == 0: return final_limits[0] if value_span == 0: p = 0.5 else: p = float(value - value_limits[0]) / value_span return final_limits[0] + p * final_span if value <= value_limits[0]: return final_limits[0] if value >= value_limits[1]: return final_limits[1] if scaling_type == 'log' or scaling_type == 'logarithmic': return lin_scaling(np.log(value), np.log(value_limits), final_limits) else: return lin_scaling(value, value_limits, final_limits) def determine_size(scheme, i, net, values, limits, defaults): if not isinstance(scheme, dict): return scheme else: # Determine what defines the size. Calculate the limits # for this property if not yet done. size_by = scheme.get('by', defaults['by']) if size_by not in limits: property_name = "".join(size_by.split(':')[1:]) np_ = sorted(net.nodeProperty[property_name].values()) limits[size_by] = (np_[0], np_[-1]) if size_by not in values: property_name = "".join(size_by.split(':')[1:]) values[size_by] = net.nodeProperty[property_name][i] scale = scheme.get('scale', defaults['scale']) val_min = scheme.get('min', limits[size_by][0]) val_max = scheme.get('max', limits[size_by][1]) size_min = scheme.get('min_size', defaults['min_size']) size_max = scheme.get('max_size', defaults['max_size']) #print size_by, scale, val_min, val_max, size_min, size_max return scaled(scale, values[size_by], [val_min, val_max], [size_min, size_max]) def determine_color(scheme, i, net, values, limits, defaults): if not isinstance(scheme, dict): return scheme else: color_by = scheme.get('by', defaults['by']) if color_by not in limits: property_name = "".join(color_by.split(':')[1:]) np_ = sorted(net.nodeProperty[property_name].values()) limits[color_by] = (np_[0], np_[-1]) if color_by not in values: property_name = "".join(color_by.split(':')[1:]) values[color_by] = net.nodeProperty[property_name][i] scale = scheme.get('scale', defaults['scale']) cmap = scheme.get('cmap', defaults['cmap']) val_min = scheme.get('min', limits[color_by][0]) val_max = scheme.get('max', limits[color_by][1]) cval = scaled(scale, values[color_by], [val_min, val_max], [0.0, 1.0]) cm = setColorMap(cmap) return cm(float(cval)) def luminance(c): """Return luminance of color `c`.""" c_vec = matplotlib.colors.colorConverter.to_rgb(c) return 0.2126 * c_vec[0] + 0.7152 * c_vec[1] + 0.0722 * c_vec[2] def edge_label_pos(axes, xcoords, ycoords, edge_width, label_size, offset=1.5): """Return the baseline position and label rotation (in angles) for an edge label. The label will be on the right side of the edge, in proper orientation for reading, and located `offset` points from the edge. """ theta = get_edge_angle(xcoords, ycoords) if theta > -np.pi / 2 and theta < np.pi / 2: # Edge goes from left to right. label_rotation = theta else: # Edge goes from right to left. label_rotation = theta - np.pi offset_points = offset + 0.5 * edge_width + 0.5 * label_size label_position = (0.5 * sum(xcoords), 0.5 * sum(ycoords)) offset_dir = theta - np.pi / 2 label_offset = (offset_points * np.cos(offset_dir), offset_points * np.sin(offset_dir)) return label_position, label_offset, 180 * label_rotation / np.pi # # INITIALIZE SOME DATA STRUCTURES # # Find out the minimum and maximum value for strength and degree. strengths = netext.strengths(net) smin, smax = min(strengths.values()), max(strengths.values()) degrees = netext.deg(net) dmin, dmax = min(degrees.values()), max(degrees.values()) limits = {"strength": (smin, smax), "degree": (dmin, dmax)} # # INITIALIZE AXES SIZE # node_diameters = {} for nodeIndex in net: values = { "strength": strengths[nodeIndex], "degree": degrees[nodeIndex] } node_diameters[nodeIndex] = ( determine_size(nodeSizes.get(nodeIndex, defaultNodeSize), nodeIndex, net, values, limits, defaultNodeSize) + determine_size(nodeEdgeWidths.get( nodeIndex, defaultNodeEdgeWidth), nodeIndex, net, values, limits, defaultNodeEdgeWidth)) if scaling: # Make axis equal making sure the nodes on the edges are not # clipped. We cannot use `axis('equal')` because nothing has been # drawn yet, but we still need to do this so the arrows will be # draw properly in the plotting phase. This is a bit tricky # because the axes might not be square. max_node_diameter = max(node_diameters.values()) y_coords = sorted(map(operator.itemgetter(1), coords.values())) x_coords = sorted(map(operator.itemgetter(0), coords.values())) ax_pos = axes.get_position() x_span = x_coords[-1] - x_coords[0] y_span = y_coords[-1] - y_coords[0] ax_width_inches = ax_pos.width * axes.get_figure().get_figwidth() ax_height_inches = ax_pos.height * axes.get_figure().get_figheight() if (x_span * ax_height_inches > y_span * ax_width_inches): # The x-span dictates the coordinates. Calculate the margin # necessary to fit in the nodes on the edges. rad_frac = 0.5 * max_node_diameter / (72 * ax_width_inches) x_margin = x_span * rad_frac / (1 - 2 * rad_frac) x_min, x_max = x_coords[0] - x_margin, x_coords[-1] + x_margin y_mid = 0.5 * (y_coords[-1] + y_coords[0]) y_axis_span = (x_max - x_min) * ax_height_inches / ax_width_inches y_min, y_max = y_mid - 0.5 * y_axis_span, y_mid + 0.5 * y_axis_span, else: # The y-span dictates the coordinates. Calculate the margin # necessary to fit in the nodes on the edges. rad_frac = 0.5 * max_node_diameter / (72 * ax_height_inches) y_margin = y_span * rad_frac / (1 - 2 * rad_frac) y_min, y_max = y_coords[0] - y_margin, y_coords[-1] + y_margin x_mid = 0.5 * (x_coords[-1] + x_coords[0]) x_axis_span = (y_max - y_min) * ax_width_inches / ax_height_inches x_min, x_max = x_mid - 0.5 * x_axis_span, x_mid + 0.5 * x_axis_span, y_span, x_span = y_max - y_min, x_max - x_min axes.set_ylim(ymin=y_min - margin * y_span, ymax=y_max + margin * y_span) axes.set_xlim(xmin=x_min - margin * x_span, xmax=x_max + margin * x_span) prev_autoscale = axes.get_autoscale_on() axes.set_autoscale_on(False) point_trans = lambda x_: points_to_data( ax, x_) # Transform points to data coordinates. # # DRAW EDGES # edges = list(net.edges) if edges: # Sort by edge weight. edges.sort(key=operator.itemgetter(2)) limits['weight'] = (edges[0][2], edges[-1][2]) # DEBUGGING: Print statistics of edge weights. #import data_utils #weights_ = map(operator.itemgetter(2), edges) #print "Edges:", limits['weight'], " 10/25/50/75/90:", #mp_ = len(edges)/2 #print "%d, %d, %d, %d, %d" % (int(data_utils.percentile(weights_, 0.1)), # int(data_utils.percentile(weights_, 0.25)), # int(data_utils.percentile(weights_, 0.5)), # int(data_utils.percentile(weights_, 0.75)), # int(data_utils.percentile(weights_, 0.9))) net_edges = NetEdges(axes, net.isSymmetric()) for i, j, w in edges: values = { "weight": w, "strength": strengths[j], "degree": degrees[j] } # Determine edge width. if (i, j) in edgeWidths: width = determine_size(edgeWidths[(i, j)], (i, j), net, values, limits, defaultEdgeWidth) elif (j, i) in edgeWidths: width = determine_size(edgeWidths[(j, i)], (j, i), net, values, limits, defaultEdgeWidth) else: width = determine_size(defaultEdgeWidth, (i, j), net, values, limits, defaultEdgeWidth) # Determine edge color. if (i, j) in edgeColors: color = determine_color(edgeColors[(i, j)], (i, j), net, values, limits, defaultEdgeColor) elif (j, i) in edgeColors: color = determine_color(edgeColors[(j, i)], (j, i), net, values, limits, defaultEdgeColor) else: color = determine_color(defaultEdgeColor, (j, i), net, values, limits, defaultEdgeColor) if (i, j) in edgePlotOrders: zorder = edgePlotOrders[(i, j)] elif (j, i) in edgePlotOrders: zorder = edgePlotOrders[(j, i)] else: zorder = defaultEdgePlotOrder # FOR DEBUGGING: #print "Edge (%d,%d / %.2f) : %.1f %s %f" % (i,j,w,width,str(color),zorder) xcoords, ycoords = (coords[i][0], coords[j][0]), (coords[i][1], coords[j][1]) #obj[(i,j)] = draw_edge(axes, xcoords, ycoords, width, color, # net.isSymmetric(), zorder, node_diameters[j]) net_edges.add(xcoords, ycoords, width, color, zorder, node_diameters[j]) # Add edge label. if (labelAllEdges or (i, j) in edgeLabels or (net.isSymmetric() and (j, i) in edgeLabels)): if (i, j) in edgeLabels: label = str(edgeLabels[(i, j)]) elif (net.isSymmetric() and (j, i) in edgeLabels): label = str(edgeLabels[(j, i)]) else: label = "(%d,%d)" % (i, j) lpos, loffset, lrot = edge_label_pos(axes, xcoords, ycoords, width, edge_label_font_size) axes.annotate(label, lpos, xytext=loffset, textcoords='offset points', color=edge_label_font_color, size=edge_label_font_size, horizontalalignment='center', verticalalignment='center', rotation=lrot, zorder=zorder + 0.5) # # DRAW NODES # max_node_diameter = 0 net_nodes = NetNodes(axes) node_internal_color = {} for nodeIndex in net: values = { "strength": strengths[nodeIndex], "degree": degrees[nodeIndex] } # Determine node shape. shape = nodeShapes.get(nodeIndex, defaultNodeShape) # Determine node size (and update max size). size = determine_size(nodeSizes.get(nodeIndex, defaultNodeSize), nodeIndex, net, values, limits, defaultNodeSize) # Determine node edge width. edgewidth = determine_size( nodeEdgeWidths.get(nodeIndex, defaultNodeEdgeWidth), nodeIndex, net, values, limits, defaultNodeEdgeWidth) max_node_diameter = max(max_node_diameter, size + edgewidth) # Determine node color color = determine_color(nodeColors.get(nodeIndex, defaultNodeColor), nodeIndex, net, values, limits, defaultNodeColor) node_internal_color[nodeIndex] = color # Determine node edge color edgecolor = determine_color( nodeEdgeColors.get(nodeIndex, defaultNodeEdgeColor), nodeIndex, net, values, limits, defaultNodeEdgeColor) # Determine z-order. zorder = nodePlotOrders.get(nodeIndex, defaultNodePlotOrder) # FOR DEBUGGING: #print "Node %d : %f %s %f %s" % (nodeIndex, size, str(color), edgewidth, str(edgecolor)) #obj[nodeIndex] = draw_node(axes, coords[nodeIndex][0], coords[nodeIndex][1], # shape, color, size, edgecolor, edgewidth, zorder) net_nodes.add(coords[nodeIndex][0], coords[nodeIndex][1], color, size, edgecolor, edgewidth, zorder) # Add node label. if nodeIndex in nodeLabels or labelAllNodes: if nodeIndex in nodeLabels: label = str(nodeLabels[nodeIndex]) else: label = str(nodeIndex) label_pos = labelPositions.get(nodeIndex, defaultLabelPosition) if label_pos == 'out': nodeLabel_offset = int(np.ceil(float(size) / 2)) + 1 axes.annotate(label, (coords[nodeIndex][0], coords[nodeIndex][1]), textcoords='offset points', xytext=(nodeLabel_offset, nodeLabel_offset), color=node_label_font_color, size=node_label_font_size, zorder=zorder + 0.5) elif label_pos == 'in': axes.annotate( label, (coords[nodeIndex][0], coords[nodeIndex][1]), color=("black" if luminance(node_internal_color[nodeIndex]) > 0.5 else "white"), size=max(5, min(size - 1, 0.7 * size)), horizontalalignment='center', verticalalignment='center', zorder=zorder + 0.5) # Remove frame. if not frame: # Using 'axes.set_axis_off()' would also turn of the axis # labels, which is too much. The following lines are required # to turn off the frame and tick labels while keeping axis # labels. axes.set_frame_on(False) axes.set_xticklabels([]) axes.xaxis.set_ticks_position('none') axes.set_yticklabels([]) axes.yaxis.set_ticks_position('none') # Return autoscaling to the original value. axes.set_autoscale_on(prev_autoscale) # Return figure (or objects drawn). return (obj if animated else axes.get_figure())