Source code for nngt.plot.plt_networks

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#
# This file is part of the NNGT project to generate and analyze
# neuronal networks and their activity.
# Copyright (C) 2015-2017  Tanguy Fardet
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy as np

from .custom_plt import palette, format_exponent
from nngt.lib import POS, nonstring_container



'''
Network plotting
================

Implemented
-----------

Simple representation for spatial graphs, random distribution if non-spatial.
Support for edge-size (according to betweenness or synaptic weight).


Objectives
----------

Implement the spring-block minimization.

If edges have varying size, plot only those that are visible (size > min)

'''

__all__ = ["draw_network"]


# ------- #
# Drawing #
# ------- #

[docs]def draw_network(network, nsize="total-degree", ncolor="group", nshape="o", nborder_color="k", nborder_width=0.5, esize=1., ecolor="k", max_nsize=5., max_esize=2., threshold=0.5, decimate=None, spatial=True, size=(600,600), xlims=None, ylims=None, dpi=75, axis=None, show=False, **kwargs): ''' Draw a given graph/network. Parameters ---------- network : :class:`~nngt.Graph` or subclass The graph/network to plot. nsize : float, array of float or string, optional (default: "total-degree") Size of the nodes as a percentage of the canvas length. Otherwise, it can be a string that correlates the size to a node attribute among "in/out/total-degree", or "betweenness". ncolor : float, array of floats or string, optional (default: 0.5) Color of the nodes; if a float in [0, 1], position of the color in the current palette, otherwise a string that correlates the color to a node attribute among "in/out/total-degree", "betweenness" or "group". nshape : char or array of chars, optional (default: "o") Shape of the nodes (see `Matplotlib markers <http://matplotlib.org/api/ markers_api.html?highlight=marker#module-matplotlib.markers>`_). nborder_color : char, float or array, optional (default: "k") Color of the node's border using predefined `Matplotlib colors <http://matplotlib.org/api/colors_api.html?highlight=color #module-matplotlib.colors>`_). or floats in [0, 1] defining the position in the palette. nborder_width : float or array of floats, optional (default: 0.5) Width of the border in percent of canvas size. esize : float, str, or array of floats, optional (default: 0.5) Width of the edges in percent of canvas length. Available string values are "betweenness" and "weight". ecolor : char, float or array, optional (default: "k") Edge color. max_esize : float, optional (default: 5.) If a custom property is entered as `esize`, this normalizes the edge width between 0. and `max_esize`. decimate : int, optional (default: keep all connections) Plot only one connection every `decimate`. spatial : bool, optional (default: True) If True, use the neurons' positions to draw them. size : tuple of ints, optional (default: (600,600)) (width, height) tuple for the canvas size (in px). dpi : int, optional (default: 75) Resolution (dot per inch). ''' import matplotlib.pyplot as plt size_inches = (size[0]/float(dpi), size[1]/float(dpi)) if axis is None: fig = plt.figure(facecolor='white', figsize=size_inches, dpi=dpi) axis = fig.add_subplot(111, frameon=0, aspect=1) axis.set_axis_off() pos, layout = None, None n = network.node_nb() e = network.edge_nb() # compute properties decimate = 1 if decimate is None else decimate if isinstance(nsize, str): if e: nsize = _node_size(network, nsize) nsize *= max_nsize else: nsize = np.ones(n) elif isinstance(nsize, float): nsize = np.repeat(nsize, n) nsize *= 0.01 * size[0] if isinstance(esize, str): if e: esize = _edge_size(network, esize) esize *= max_esize esize[esize < threshold] = 0. elif isinstance(esize, float): esize = np.repeat(esize, e) esize *= 0.005 * size[0] # border on each side (so 0.5 %) ncolor = _node_color(network, ncolor) c = ncolor # remove the edges if isinstance(nborder_color, float): nborder_color = np.repeat(nborder_color, n) if isinstance(ecolor, float): ecolor = np.repeat(ecolor, e) # draw pos = np.zeros((n, 2)) if spatial and network.is_spatial(): pos = network.get_positions() else: pos[:,0] = size[0]*(np.random.uniform(size=n)-0.5) pos[:,1] = size[1]*(np.random.uniform(size=n)-0.5) if hasattr(network, "population"): for group in network.population.values(): idx = group.ids if nonstring_container(ncolor): c = palette(ncolor[idx[0]]) # scatter required because of different markersize axis.scatter(pos[idx,0], pos[idx,1], s=nsize, marker=nshape, c=c, edgecolors=nborder_color, zorder=2) else: if not isinstance(c, str): c = palette(ncolor) axis.scatter(pos[:,0], pos[:,1], s=nsize, marker=nshape, c=c, edgecolors=nborder_color, zorder=2) _set_ax_lim(axis, pos[:,0], pos[:,1], xlims, ylims) # use quiver to draw the edges if e: adj_mat = network.adjacency_matrix(weights=None) edges = np.array(adj_mat.nonzero()) if nonstring_container(esize): edges = edges[:, esize > 0] esize = esize[esize > 0] if decimate > 1: edges = edges[:, ::decimate] if nonstring_container(esize): esize = esize[::decimate] arrow_x = pos[edges[1], 0] - pos[edges[0], 0] arrow_y = pos[edges[1], 1] - pos[edges[0], 1] axis.quiver(pos[edges[0], 0], pos[edges[0], 1], arrow_x, arrow_y, scale_units='xy', angles='xy', scale=1, alpha=0.5, width=1.5e-3, linewidths=esize, edgecolors=ecolor, zorder=1) if kwargs.get('tight', True): plt.tight_layout() plt.subplots_adjust( hspace=0., wspace=0., left=0., right=1., top=1., bottom=0.) if show: plt.show()
#-----------------------------------------------------------------------------# # Tools #------------------------ # def _set_ax_lim(ax, xdata, ydata, xlims, ylims): if xlims is not None: ax.set_xlim(*xlims) else: x_min, x_max = np.min(xdata), np.max(xdata) width = x_max - x_min ax.set_xlim(x_min - 0.05*width, x_max + 0.05*width) if ylims is not None: ax.set_ylim(*ylims) else: y_min, y_max = np.min(ydata), np.max(ydata) height = y_max - y_min ax.set_ylim(y_min - 0.05*height, y_max + 0.05*height) def _node_size(network, nsize): size = np.ones(network.node_nb()) if "degree" in nsize: deg_type = nsize[:nsize.index("-")] size = network.get_degrees(deg_type) if size.max() > 15*size.min(): size = np.power(size, 0.4) if nsize == "betweenness": size = network.betweenness_list("node") if size.max() > 15*size.min(): min_size = size[size!=0].min() size[size == 0.] = min_size size = np.log(size) if size.min()<0: size -= 1.1*size.min() size /= size.max() return size def _edge_size(network, esize): size = np.repeat(1., network.edge_nb()) if esize == "betweenness": size = network.betweenness_list("edge") if esize == "weight": size = network.get_weights() size /= size.max() return size def _node_color(network, ncolor): color = ncolor if issubclass(float, ncolor.__class__): color = np.repeat(ncolor, n) elif ncolor == "group": color = np.zeros(network.node_nb()) if hasattr(network, "population"): l = len(network.population) c = np.linspace(0,1,l) for i,group in enumerate(network.population.values()): color[group.ids] = c[i] return color