Построение ориентированных графов в Python таким образом, чтобы показать все ребра отдельно

Я использую Python для имитации процесса, который выполняется на ориентированных графах. Я хотел бы создать анимацию этого процесса.

Проблема, с которой я столкнулся, заключается в том, что большинство библиотек визуализации графиков Python объединяют пары направленных ребер в один край. Например, NetworkX рисует только два ребра при отображении следующего графика, тогда как я хотел бы отображать каждый из четырех ребер отдельно:

import networkx as nx
import matplotlib.pyplot as plt 

G = nx.MultiDiGraph()

G.add_edges_from([
    (1, 2),
    (2, 3),
    (3, 2),
    (2, 1),
])

plt.figure(figsize=(8,8))
nx.draw(G)

Output from NetworkX; parallel edges are overlapping, so only two lines are displayed

Я хотел бы отобразить что-то вроде этого, каждый отдельный параллельный край:

Desired output format; parallel edges are drawn separately

Вопрос R обратных ребер в igraph в R, похоже, касается одной и той же проблемы, но решение для библиотеки R igraph, а не Python, существует.

Есть ли простой способ создать этот стиль сюжета, используя существующую библиотеку визуализации графиков Python? Это был бы бонус, если он мог поддерживать мультиграфы.

Я открыт для решений, которые вызывают внешнюю программу для создания изображений. Я бы хотел создать целую серию анимационных кадров, поэтому решение должно быть автоматизировано.

Ответы

Ответ 1

В инструментах Graphviz отображаются различные ребра.

Например, давая это:

digraph G {
  A -> B;
  A -> B;
  A -> B;
  B -> C;

  B -> A;
  C -> B;
}

to dot производит:

example graph

Язык ввода Graphviz довольно прост, поэтому вы можете генерировать его самостоятельно, хотя поиск "python graphviz" вызывает несколько библиотек.

Ответ 2

Использование NetworkX, возможное обходное решение, которое позволяет избежать ввода/вывода файлов и использует dot через pydot для макета:

import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from cStringIO import StringIO

g = nx.dodecahedral_graph()
d = nx.drawing.nx_pydot.to_pydot(g) # d is a pydot graph object, dot options can be easily set
# attributes get converted from networkx,
# use set methods to control dot attributes after creation

png_str = d.create_png()
sio = StringIO() # file-like string, appropriate for imread below
sio.write(png_str)
sio.seek(0)

img = mpimg.imread(sio)
imgplot = plt.imshow(img)

для чего требуется seek(0), см. Как создать образ из строки в python

Если в консоли IPython (qt), то приведенное выше будет печатать inline и более прямой подход:

import networkx as nx
from IPython.display import Image

g = nx.dodecahedral_graph()
d = nx.drawing.nx_pydot.to_pydot(g)

png_str = d.create_png()
Image(data=png_str)