Drawing and Visualization

This tutorial covers drawing spaces, visualization techniques, and integration with matplotlib.

Understanding Drawing Spaces

egraph supports multiple geometric spaces for graph layout:

  • Euclidean: Standard 2D and n-dimensional space

  • Hyperbolic: Poincaré disk model for hierarchical graphs

  • Spherical: Sphere surface for global connectivity

  • Torus: Periodic boundaries for wrapping layouts

Euclidean 2D Drawing

The most common drawing space for standard graph visualization.

Creating a Drawing

import egraph as eg

graph = eg.Graph()
nodes = [graph.add_node(i) for i in range(5)]
for i in range(4):
    graph.add_edge(nodes[i], nodes[i+1], (i, i+1))

# Create 2D Euclidean drawing with random initial positions
drawing = eg.DrawingEuclidean2d.initial_placement(graph)

print(f"Created 2D drawing for {graph.node_count()} nodes")
Created 2D drawing for 5 nodes

Accessing Coordinates

import egraph as eg

graph = eg.Graph()
nodes = [graph.add_node(i) for i in range(3)]

drawing = eg.DrawingEuclidean2d.initial_placement(graph)

# Get coordinates for each node
for i in range(3):
    x = drawing.x(i)
    y = drawing.y(i)
    print(f"Node {i}: ({x:.2f}, {y:.2f})")
Node 0: (0.00, 0.00)
Node 1: (1.23, 4.56)
Node 2: (-2.34, 3.45)

Setting Coordinates

import egraph as eg

graph = eg.Graph()
nodes = [graph.add_node(i) for i in range(3)]

drawing = eg.DrawingEuclidean2d.initial_placement(graph)

# Set specific positions
drawing.set_x(0, 0.0)
drawing.set_y(0, 0.0)
drawing.set_x(1, 1.0)
drawing.set_y(1, 0.0)
drawing.set_x(2, 0.5)
drawing.set_y(2, 1.0)

print("Coordinates set manually")
Coordinates set manually

N-Dimensional Euclidean Drawing

For higher-dimensional layouts (3D, 4D, etc.), see the 3D Stochastic Gradient Descent example which demonstrates 3D graph layouts using SGD algorithms.

Hyperbolic 2D Drawing

Hyperbolic space is ideal for hierarchical graphs and trees.

import egraph as eg

# Create a tree-like graph
graph = eg.Graph()
root = graph.add_node(0)

# Add two levels
level1 = [graph.add_node(i) for i in range(1, 4)]
for node in level1:
    graph.add_edge(root, node, (root, node))

level2 = [graph.add_node(i) for i in range(4, 10)]
for i, node in enumerate(level2):
    parent = level1[i % 3]
    graph.add_edge(parent, node, (parent, node))

# Create hyperbolic drawing
drawing = eg.DrawingHyperbolic2d.initial_placement(graph)

# Apply layout
rng = eg.Rng.seed_from(42)
sgd = eg.SparseSgd().h(5).build(graph, lambda _: 0.5, rng)
scheduler = sgd.scheduler(50, 0.1)
def step(eta):
    sgd.shuffle(rng)
    sgd.apply(drawing, eta)
scheduler.run(step)

print(f"Hyperbolic layout for {graph.node_count()} nodes")
Hyperbolic layout for 10 nodes

Spherical 2D Drawing

Spherical layouts are useful for global networks.

import egraph as eg
import networkx as nx

# Create a graph
nx_graph = nx.karate_club_graph()
graph = eg.Graph()
for node in nx_graph.nodes:
    graph.add_node(node)
for u, v in nx_graph.edges:
    graph.add_edge(u, v, (u, v))

# Create spherical drawing
drawing = eg.DrawingSpherical2d.initial_placement(graph)

# Apply layout
rng = eg.Rng.seed_from(42)
sgd = eg.SparseSgd().h(10).build(graph, lambda _: 0.3, rng)
scheduler = sgd.scheduler(50, 0.1)
def step(eta):
    sgd.shuffle(rng)
    sgd.apply(drawing, eta)
scheduler.run(step)

print(f"Spherical layout for {graph.node_count()} nodes")
Spherical layout for 34 nodes

Torus 2D Drawing

Torus layouts have periodic boundaries, useful for certain network types.

import egraph as eg

graph = eg.Graph()
nodes = [graph.add_node(i) for i in range(10)]
for i in range(9):
    graph.add_edge(nodes[i], nodes[i+1], (i, i+1))
graph.add_edge(nodes[9], nodes[0], (9, 0))  # Wrap around

# Create torus drawing
drawing = eg.DrawingTorus2d.initial_placement(graph)

# Apply layout
rng = eg.Rng.seed_from(42)
sgd = eg.SparseSgd().h(5).build(graph, lambda _: 0.1, rng)
scheduler = sgd.scheduler(50, 0.1)
def step(eta):
    sgd.shuffle(rng)
    sgd.apply(drawing, eta)
scheduler.run(step)

print(f"Torus layout for {graph.node_count()} nodes")
Torus layout for 10 nodes

Visualization with Matplotlib

Basic Visualization

import networkx as nx
import egraph as eg
import matplotlib.pyplot as plt

# Create and layout graph
nx_graph = nx.karate_club_graph()
graph = eg.Graph()
indices = {}
for node in nx_graph.nodes:
    indices[node] = graph.add_node(node)
for u, v in nx_graph.edges:
    graph.add_edge(indices[u], indices[v], (u, v))

drawing = eg.DrawingEuclidean2d.initial_placement(graph)
sm = eg.StressMajorization(graph, drawing, lambda _: 100)
sm.run(drawing)

# Extract positions for NetworkX
pos = {node: (drawing.x(idx), drawing.y(idx))
       for node, idx in indices.items()}

# Visualize
plt.figure(figsize=(10, 8))
nx.draw(nx_graph, pos, node_color='lightblue',
        node_size=300, with_labels=True)
# plt.savefig('karate_club.png')
# plt.show()

print("Visualization created")
Visualization created

Customizing Visualization

import networkx as nx
import egraph as eg
import matplotlib.pyplot as plt

nx_graph = nx.karate_club_graph()
graph = eg.Graph()
indices = {}
for node in nx_graph.nodes:
    indices[node] = graph.add_node(node)
for u, v in nx_graph.edges:
    graph.add_edge(indices[u], indices[v], (u, v))

drawing = eg.DrawingEuclidean2d.initial_placement(graph)
sm = eg.StressMajorization(graph, drawing, lambda _: 100)
sm.run(drawing)

pos = {node: (drawing.x(idx), drawing.y(idx))
       for node, idx in indices.items()}

# Compute node degrees for sizing
degrees = dict(nx_graph.degree())
node_sizes = [degrees[node] * 50 for node in nx_graph.nodes]

# Color by community
communities = nx.community.greedy_modularity_communities(nx_graph)
node_colors = []
for node in nx_graph.nodes:
    for i, comm in enumerate(communities):
        if node in comm:
            node_colors.append(i)
            break

# Create visualization
plt.figure(figsize=(12, 10))
nx.draw(nx_graph, pos,
        node_color=node_colors,
        node_size=node_sizes,
        cmap=plt.cm.Set3,
        with_labels=True,
        font_size=8,
        edge_color='gray',
        alpha=0.7)
# plt.title('Karate Club Network')
# plt.savefig('karate_club_styled.png', dpi=300, bbox_inches='tight')
# plt.show()

print("Styled visualization created")
Styled visualization created

Choosing the Right Drawing Space

Euclidean 2D - Most common choice - Good for general-purpose visualization - Easy to interpret

Euclidean nD - For high-dimensional data - Useful for dimensionality reduction - Can project to 2D/3D for visualization

Hyperbolic 2D - Hierarchical structures - Trees and DAGs - Focus+context visualization

Spherical 2D - Global networks - No preferred direction - Uniform connectivity

Torus 2D - Periodic structures - Grid-like networks - Avoiding edge effects

Best Practices

  1. Start with Euclidean 2D: It’s the most intuitive and widely supported

  2. Match space to structure: Use hyperbolic for trees, spherical for global networks

  3. Iterate on layout: Run algorithms multiple times with different parameters

  4. Visualize incrementally: Check intermediate results during development

  5. Export high-quality: Use high DPI for publication-quality figures

Next Steps