使用networkx显示自循环 - Python

时间:2017-05-25 19:51:54

标签: python matplotlib networkx

我正在使用networkx中的DiGraph类,文档应​​该允许自循环。但是,当使用Matplotlib绘图时,我无法看到任何自循环,无论是否

print(G.nodes_with_selfloops())

返回具有自循环的节点列表。 我想知道如何显示这些自循环。

我正在使用这些函数绘制:

nx.draw_networkx_edge_labels(G,pos,edge_labels=edge_labels)
nx.draw_networkx(G,pos,font_color='k',node_size=500, edge_color='b', alpha=0.5)

2 个答案:

答案 0 :(得分:0)

https://networkx.github.io/documentation/networkx-1.10/reference/drawing.html

  

将来,可以删除图形可视化功能   NetworkX或仅作为附加软件包提供。

     

我们强烈建议人们使用工具可视化他们的图表   致力于这项任务。

上面的链接提供了许多内置可视化的替代方案。请考虑他们提供的替代方案以节省自己很多时间。

我个人使用cytoscape,它接受​​.graphml格式的文件。将图表导出到.graphml非常简单:

nx.write_graphml(graph, path_to_file)

答案 1 :(得分:0)

我在尝试使用 networkx 绘制和弦图时遇到了同样的问题。在旧版本的 networkx (2.5) 上,自循环在节点后面画了一个点(这意味着您根本看不到它们)。在较新的版本 (2.6.2) 上,自循环的绘制方向与下图相同

Self-loops in the networkx 2.6.2

如果这对您来说已经足够了,请尝试更新 networkx。看起来这个问题已经解决了。至少,documentation 有一些关于它的信息

但是,如果这对您来说还不够(就像对我一样),您可以编写一个自定义代码来更好地绘制自循环。我为该任务创建了一个 repository。它允许绘制不同方向的自环,远离中心:

Self-loops with my code

以下是其背后的简单想法:

  1. 您知道循环的开始和结束坐标。这些只是您节点的坐标
  2. 您还需要 2 个点来绘制带有 Bézier 曲线的自环。您希望它们离中心更远,而不是图的节点。您还希望它们在正交方向上远离节点到从中心到节点的向量

如果这听起来太复杂,我希望图片能说清楚: Visualization of anchors

  1. 获得锚点后,我们可以通过它们绘制一条贝塞尔曲线。这是它的外观: Self-loops

代码如下:

from typing import Optional

import matplotlib.pyplot as plt
from matplotlib.path import Path as MplPath  # To avoid collisions with pathlib.Path
import matplotlib.patches as patches
import networkx as nx
import numpy as np


# Some useful functions
def normalize_vector(vector: np.array, normalize_to: float) -> np.array:
    """Make `vector` norm equal to `normalize_to`
    
    vector: np.array
        Vector with 2 coordinates
    
    normalize_to: float
        A norm of the new vector
        
    Returns
    -------
    Vector with the same direction, but length normalized to `normalize_to`
    """
    
    vector_norm = np.linalg.norm(vector)
    
    return vector * normalize_to / vector_norm


def orthogonal_vector(point: np.array, width: float, normalize_to: Optional[float] = None) -> np.array:
    """Get orthogonal vector to a `point`

    point: np.array
        Vector with x and y coordinates of a point

    width: float
        Distance of the x-coordinate of the new vector from the `point` (in orthogonal direction)

    normalize_to: Optional[float] = None
        If a number is provided, normalize a new vector length to this number
    
    Returns
    -------
    Array with x and y coordinates of the vector, which is orthogonal to the vector from (0, 0) to `point` 
    """
    EPSILON = 0.000001

    x = width
    y = -x * point[0] / (point[1] + EPSILON)

    ort_vector = np.array([x, y])

    if normalize_to is not None:
        ort_vector = normalize_vector(ort_vector, normalize_to)

    return ort_vector


def draw_self_loop(
    point: np.array,
    ax: Optional[plt.Axes] = None,
    padding: float = 1.5,
    width: float = 0.3,
    plot_size: int = 10,
    linewidth = 0.2,
    color: str = "pink",
    alpha: float = 0.5
) -> plt.Axes:
    """Draw a loop from `point` to itself

    !Important! By "center" we assume a (0, 0) point. If your data is centered around a different points,
    it is strongly recommended to center it around zero. Otherwise, you will probably get ugly plots

    Parameters
    ----------
    point: np.array
        1D array with 2 coordinates of the point. Loop will be drawn from and to these coordinates.
    padding: float = 1.5
        Controls how the distance of the loop from the center. If `padding` > 1, the loop will be
        from the outside of the `point`. If `padding` < 1, the loop will be closer to the center
    width: float = 0.3
        Controls the width of the loop
    linewidth: float = 0.2
        Width of the line of the loop
    ax: Optional[matplotlib.pyplot.Axes]:
        Axis on which to draw a plot. If None, a new Axis is generated
    plot_size: int = 7
        Size of the plot sides in inches. Ignored if `ax` is provided    
    color: str = "pink"
        Color of the arrow
    alpha: float = 0.5
        Opacity of the edge
    
    Returns
    -------
    Matplotlib axes with the self-loop drawn
    """

    if ax is None:
        fig, ax = plt.subplots(figsize=(plot_size, plot_size))
    
    point_with_padding = padding * point

    ort_vector = orthogonal_vector(point, width, normalize_to=width)

    first_anchor = ort_vector + point_with_padding
    second_anchor = -ort_vector + point_with_padding

    verts = [point, first_anchor, second_anchor, point]
    codes = [MplPath.MOVETO, MplPath.CURVE4, MplPath.CURVE4, MplPath.CURVE4]

    path = MplPath(verts, codes)

    patch = patches.FancyArrowPatch(
        path=path,
        facecolor='none',
        lw=linewidth,
        arrowstyle="-|>",
        color=color,
        alpha=alpha,
        mutation_scale=30  # arrowsize in draw_networkx_edges()
    )
    ax.add_patch(patch)

    return ax

绘制绘图的代码示例:

fig, ax = plt.subplots(figsize=(6, 6))

graph = nx.DiGraph(
    np.array([
        [1, 1, 1, 1, 1],
        [1, 0, 1, 0, 0],
        [1, 1, 1, 0, 1],
        [0, 0, 1, 0, 1],
        [1, 1, 1, 1, 1]
    ])
)

pos = nx.circular_layout(graph, center=(0, 0))

nx.draw_networkx_nodes(graph, pos, ax=ax)
nx.draw_networkx_edges(graph, pos, ax=ax)

for node in graph.nodes:
    if (node, node) in graph.edges:
        draw_self_loop(point=pos[node], ax=ax, color="k", alpha=1, linewidth=1)
        
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)

结果:chord diagram

您可以在我的repository

中找到更多示例和函数来绘制漂亮的和弦图