Layered Hierarchical Layout#
Note: We strongly recommend considering alternate layouts like the ring layouts (pygraphistry) and the igraph plugin’s dot engine
[ ]:
from graphistry.layout.sugiyama import SugiyamaLayout
from graphistry.layout.graph import Graph, Vertex, Edge
import pandas as pd
import networkx as nx
import matplotlib
import matplotlib.pyplot as plt
[ ]:
def from_networkx(nxg):
"""
Converts a networkx graph to a sugiyama graph.
"""
vertices = []
data_to_v = {}
for x in nxg.nodes():
vertex = Vertex(x)
vertices.append(vertex)
data_to_v[x] = vertex
E = [Edge(data_to_v[xy[0]], data_to_v[xy[1]], data = xy) for xy in nxg.edges()]
g = Graph(vertices, E)
return g
def to_networkx(g):
"""
Converts a sugiyama graph to a networkx graph.
"""
from networkx import MultiDiGraph
nxg = MultiDiGraph()
for v in g.vertices():
nxg.add_node(v.data)
for e in g.edges():
# todo: this leads to issues when the data is more than an id
nxg.add_edge(e.v[0].data, e.v[1].data)
return nxg
def draw_graph(g, layout_direction = 0, source_column = "source", target_column = "target", root=None):
"""
Renders the given graph after applying the layered layout.
:param g: Graphistry Graph or NetworkX Graph.
"""
if isinstance(g, nx.Graph):
gg = from_networkx(g)
nxg = g
elif isinstance(g, Graph):
gg = g
nxg = to_networkx(g)
elif isinstance(g, pd.DataFrame):
gg = SugiyamaLayout.graph_from_pandas(g, source_column = source_column, target_column = target_column)
nxg = to_networkx(gg)
else:
raise ValueError
# apply layout
positions = SugiyamaLayout.arrange(gg, layout_direction = layout_direction, root=root)
nx.draw(nxg, pos = positions, with_labels = True, verticalalignment = 'bottom', arrowsize = 3, horizontalalignment = "left", font_size = 20)
plt.show()
def scatter_graph(g, root=None):
"""
Renders the given graph as a scatter plot after applying the layered layout.
:param g: Graphistry Graph or NetworkX Graph.
"""
if isinstance(g, nx.Graph):
gg = from_networkx(g)
nxg = g
elif isinstance(g, Graph):
gg = g
nxg = to_networkx(g)
# apply layout
coords = list(SugiyamaLayout.arrange(gg, root=root).values())
x = [c[0] for c in coords]
y = [c[1] for c in coords]
fig, ax = plt.subplots()
ax.scatter(x, y)
for i, v in enumerate(gg.vertices()):
ax.annotate(v.data, (x[i], y[i]))
plt.axis('off')
plt.show()
def arrange(g, layout_direction = 0, source_column = "source", target_column = "target", topological_coordinates = False):
"""
Returns the positions of the given graph after applying the layered layout.
:param g: Graphistry Graph, Pandas frame or NetworkX Graph.
"""
if isinstance(g, nx.Graph):
gg = from_networkx(g)
nxg = g
elif isinstance(g, Graph):
gg = g
nxg = to_networkx(g)
elif isinstance(g, pd.DataFrame):
gg = SugiyamaLayout.graph_from_pandas(g, source_column = source_column, target_column = target_column)
nxg = to_networkx(gg)
else:
raise ValueError
# apply layout
positions = SugiyamaLayout.arrange(gg, layout_direction = layout_direction, topological_coordinates = topological_coordinates)
return positions
Explicit Simple Graph#
The Graph
object can be used to create an explicit graph:
[ ]:
matplotlib.rc('figure', figsize = [8, 5])
g = Graph()
bosons = Vertex("Boson")
higgs = Vertex("Higgs")
pions = Vertex("Pions")
kaons = Vertex("Kaons")
hadrons = Vertex("Hadrons")
e1 = Edge(bosons, higgs)
e2 = Edge(bosons, kaons)
e3 = Edge(bosons, pions)
e4 = Edge(pions, hadrons)
e5 = Edge(kaons, hadrons)
g.add_edges([e1, e2, e3, e4, e5])
scatter_graph(g)
Pandas Graph#
[ ]:
g = nx.generators.balanced_tree(2, 3)
df = nx.to_pandas_edgelist(g, "source", "target")
df.head()
[ ]:
matplotlib.rc('figure', figsize = [5, 5])
draw_graph(df, 3)
You can set the root like so
[ ]:
matplotlib.rc('figure', figsize = [5, 5])
draw_graph(df, 3, root=[3,12])
Trees#
A genuine tree will be arranged as expected:
[ ]:
matplotlib.rc('figure', figsize = [120, 30])
g = nx.generators.balanced_tree(5, 3)
draw_graph(g, 2)
Real-world Graphs#
Barabasi-Albert graphs represent scale-free networks mimicing biological and other realworld netowrks:
[ ]:
matplotlib.rc('figure', figsize = [120, 90])
g = nx.generators.barabasi_albert_graph(500, 3)
draw_graph(g)
Layout Direction#
0: top-to-bottom
1: right-to-left
2: bottom-to-top
3: left-to-right
[ ]:
matplotlib.rc('figure', figsize = [10, 5])
g = nx.generators.balanced_tree(3, 2)
draw_graph(g, layout_direction = 2)
Topological coordinates#
Instead of absolute coordinates you can also request the topological coordinates which correspond to the layer index for the vertical axis and a value in the unit interval for the horizontal axis. Multiplying these values with an actual total width and height results in coordinates within the given (width, height) rectangle. Note that the layering is according to the standard coordinate system, ie. upwards and to the right. When setting the layout direction to horizontal (layout_direction equal to 1 or 3), the first coordinate is the layer index and the second will be in the unit interval.
[ ]:
g = nx.from_edgelist([(1,2),(1,3),(3,4)])
positions = arrange(g, topological_coordinates=True, layout_direction=0)
print(positions)
The topological coordinates can be used as-is with NetworX as well:
[ ]:
nx.draw(g, pos = positions, with_labels = True, verticalalignment = 'bottom', arrowsize = 3, horizontalalignment = "left", font_size = 20)
Complete Graph#
The layered layout attempts to minimize crossings but with something like a complete graph you have an extreme example where, no matter how many times the algorithm tries to adjust, the crossings persist.
[ ]:
matplotlib.rc('figure', figsize = [120, 90])
g = nx.generators.complete_graph(10)
draw_graph(g,root=4)
Only Positions#
You can fetch the positions of the nodes without the visualization via the arrange
method. You can also just plot the points without the edges like so:
[ ]:
matplotlib.rc('figure', figsize = [20, 30])
g = nx.generators.random_lobster(100, 0.3, 0.3)
scatter_graph(g)
Watts Strogatz#
The Watts-Strogatz model is another kind of real-world graph exhibiting the so-called small world phenomenon:
[ ]:
matplotlib.rc('figure', figsize = [120, 70])
g = nx.generators.connected_watts_strogatz_graph(1000, 2, 0.3)
draw_graph(g)
[ ]: