Skip to content
Snippets Groups Projects
draw.py 4.89 KiB
Newer Older
Fize Jacques's avatar
Fize Jacques committed
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
import seaborn as sns
Fize Jacques's avatar
Fize Jacques committed
import networkx as nx
import pandas as pd
import numpy as np
from glob import glob
Fize Jacques's avatar
Fize Jacques committed

from fa2 import ForceAtlas2


def get_force_atlas(weight_influence=0, scaling_ratio=3.0, gravity=5):
    """
    Return an instance of ForceAtlas with a specific configuration
    Parameters
    ----------
    weight_influence: float
        between 0 and 1 (default 0)
    scaling_ratio : float or int
        see fa2 documentation(default 3)
    gravity : float or int
        see fa2 documentation (default 5)

    Returns
    -------
    ForceAtlas2
        instance of ForceAtlas2
    """
Fize Jacques's avatar
Fize Jacques committed
    forceatlas2 = ForceAtlas2(
        # Behavior alternatives
        outboundAttractionDistribution=True,  # Dissuade hubs
        linLogMode=False,  # NOT IMPLEMENTED
        adjustSizes=False,  # Prevent overlap (NOT IMPLEMENTED)
        edgeWeightInfluence=weight_influence,

        # Performance
        jitterTolerance=1.0,  # Tolerance
        barnesHutOptimize=True,
        barnesHutTheta=1.2,
        multiThreaded=False,  # NOT IMPLEMENTED

        # Tuning
        scalingRatio=scaling_ratio,
        strongGravityMode=False,
        gravity=gravity,

        # Log
        verbose=False)
    return forceatlas2


def draw(G, labels_dict={}, iteration_force_atlase=2000, figsize=(40, 20), font_size=12, stroke_width=3,
         stroke_color="black", font_color="white", edge_cmap=plt.cm.viridis, weight=True):
Fize Jacques's avatar
Fize Jacques committed
    """
    Return a figure of a NetworkX graph
Fize Jacques's avatar
Fize Jacques committed
    Parameters
    ----------
    G : nx.Graph
        graph instance
    labels_dict: dict
        label for each node id
    iteration_force_atlase: int
        nb of iteration for the Force Atlas algorithm
    figsize: tuple
        figure size (matplotlib)
    font_size: int
        font size
    stroke_width : int
        text contour size
    stroke_color: str
        text contour color
    font_color : str
        text color
    edge_cmap: matplotlib.pyplot.cm
        Matplotlib Colormap instance used when edges are associated with a weight
Fize Jacques's avatar
Fize Jacques committed

    Returns
    -------
    AxesSubplot
        matplotlib canvas
    """
    plt.gcf()  # Clean previous figure associated with the 'plt' instance

    # Compute node position using the Force Atlas algorithm
    force_atlas = get_force_atlas()
    positions = force_atlas.forceatlas2_networkx_layout(G,
                                                        pos=None,
                                                        iterations=iteration_force_atlase)
    # Initialise the figure canvas
    fig, ax = plt.subplots(1, figsize=figsize)

    # Draw nodes
    nodes = nx.draw_networkx_nodes(G, positions, node_color='#999', ax=ax)
    edges = None
Fize Jacques's avatar
Fize Jacques committed
    # Draw edges
    if weight:
        weights_width = [G[u][v]['weight'] * 200 for u, v in list(G.edges())]
        colors = [G[u][v]['weight'] for u, v in list(G.edges())]
        edges = nx.draw_networkx_edges(G, positions, edge_color=colors, width=weights_width,
                                       edge_cmap=edge_cmap, ax=ax)
    else:
        edges = nx.draw_networkx_edges(G, positions, ax=ax, edge_color="#999")
Fize Jacques's avatar
Fize Jacques committed

    # Plot nodes label
    for node, pos in positions.items():
        if labels_dict:
            text = ax.text(pos[0], pos[1], labels_dict[node], color=font_color,
                           ha='center', va='center', size=font_size)
        else:
            text = ax.text(pos[0], pos[1], node, color=font_color,
                           ha='center', va='center', size=font_size)

        text.set_path_effects([path_effects.Stroke(linewidth=stroke_width, foreground=stroke_color),
                               path_effects.Normal()])  # effet de style
    # Plot colorbar
    if weight:
        sm = plt.cm.ScalarMappable(cmap=edge_cmap, norm=plt.Normalize(vmin=min(colors), vmax=max(colors)))
        sm.set_array([])
        fig.colorbar(sm)
Fize Jacques's avatar
Fize Jacques committed
    plt.axis("off")
    plt.tight_layout()
    return fig, ax


def average_degree(graph_dir, ext=".txt"):
    """
    Produce a figure that shows the average degree per number of edges in a graph dataset.
    Parameters
    ----------
    graph_dir: str
        graph dataset directory path
    ext : str
        extension of the graph file (must be edgelist format)

    Returns
    -------
        Figure, AxesSubplot
    """
    plt.gcf()
    fns = glob(graph_dir + "/*" + ext)
    data = []
    for fn in fns:
        df = pd.read_csv(fn, header=None, names="source target".split())
        G = nx.from_pandas_edgelist(df, create_using=nx.DiGraph())
        degree_values = np.asarray(list(G.degree()))[:, 1]
        data.append([len(list(G.edges())), degree_values.mean()])
    df = pd.DataFrame(data, columns="nb_edges avg_degree".split())
    fig, ax = plt.subplots(1, figsize=(10, 5))
    ax = sns.scatterplot(data=df, x="nb_edges", y="avg_degree", hue="nb_edges", legend=False, ax=ax)
    ax.set(xlabel="Number of edges", ylabel="Average Degree")
Fize Jacques's avatar
Fize Jacques committed
    return fig, ax