How to Visualize Multilayer Networks

Goal: Create publication-ready visualizations of multilayer networks.

Prerequisites: A loaded network (see How to Load and Build Networks). Most snippets reuse a network object created in the quick plot example; swap in your own network if you already have one loaded.

Basic Visualization

Quick Plot

from py3plex.core import multinet

# Load network
network = multinet.multi_layer_network()
network.add_edges([
    ['Alice', 'friends', 'Bob', 'friends', 1],
    ['Bob', 'friends', 'Carol', 'friends', 1],
    ['Alice', 'work', 'Carol', 'work', 1],
], input_type="list")

# Visualize
network.visualize_network(show=True)

This opens an interactive window with the aggregated multilayer visualization. Set show=False for headless environments.

Save to File

network.visualize_network(
    output_file='network.png',  # png, pdf, svg are supported
    show=False
)

Customizing Visualizations

Node Sizes and Colors

The built-in hairball plot draws the aggregated network with layer-based colors. You can override sizing and colors explicitly to emphasize node importance:

import matplotlib.pyplot as plt
from py3plex.visualization import hairball_plot
from py3plex.dsl import Q

# Compute degrees for sizing
degree_data = Q.nodes().compute("degree").execute(network)
node_order = list(network.core_network.nodes())
node_sizes = [
    degree_data.get(node, {'degree': 1})['degree'] * 50
    for node in node_order
]

fig, ax = plt.subplots(figsize=(8, 8))
hairball_plot(
    network.core_network,
    ax=ax,
    node_sizes=node_sizes,
    legend=True,
    layout_algorithm="force",
    alpha_channel=0.6,
)
fig.savefig("network_sized.png", dpi=300, bbox_inches="tight")

Edge Weights

import matplotlib.pyplot as plt
import networkx as nx

# Widths based on stored weight (default to 1); clamp to keep faint edges visible
edge_widths = [
    max(data.get('weight', 1), 0.5)
    for _, _, data in network.core_network.edges(data=True)
]

pos = nx.spring_layout(network.core_network, k=0.6)
plt.figure(figsize=(8, 8))
nx.draw_networkx(
    network.core_network,
    pos,
    with_labels=False,
    node_size=50,
    edge_color="gray",
    width=edge_widths,
    alpha=0.7,
)
plt.axis("off")
plt.tight_layout()
plt.show()

Layout Algorithms

Switch layouts to highlight different structures: force-directed for overall connectivity, circular for equal treatment of nodes within a layer, or diagonal layouts for explicit layer separation.

Force-Directed Layout

from py3plex.visualization import hairball_plot

hairball_plot(
    network.core_network,
    layout_algorithm='force',
    legend=True,
    display=False
)

Circular Layout

import matplotlib.pyplot as plt
import networkx as nx

pos = nx.circular_layout(network.core_network)

plt.figure(figsize=(8, 8))
nx.draw_networkx(
    network.core_network,
    pos,
    node_size=80,
    edge_color='lightgray',
    with_labels=False,
    alpha=0.8
)
plt.axis('off')
plt.tight_layout()
plt.savefig('circular_layout.png', dpi=300, bbox_inches='tight')

Diagonal Multilayer Layout

Use the built-in diagonal style to keep layer membership explicit (orientation controls whether layers slope up or down):

network.visualize_network(
    style="diagonal",
    orientation="upper",
    legend=True,
    show=True
)

Layer-Specific Visualization

Visualize Single Layer

from py3plex.dsl import Q, L
import matplotlib.pyplot as plt
import networkx as nx

# Extract nodes belonging to one layer
layer_nodes = Q.nodes().from_layers(L["friends"]).execute(network)
friends_subgraph = network.core_network.subgraph(layer_nodes.keys())

pos = nx.spring_layout(friends_subgraph, k=0.6)
plt.figure(figsize=(6, 6))
nx.draw_networkx(
    friends_subgraph,
    pos,
    node_color='steelblue',
    edge_color='gray',
    node_size=150,
    with_labels=True,
    alpha=0.8
)
plt.axis('off')
plt.tight_layout()
plt.savefig('friends_layer.png', dpi=300, bbox_inches='tight')

Side-by-Side Layer Comparison

import networkx as nx
import matplotlib.pyplot as plt
from py3plex.dsl import Q, L

fig, axes = plt.subplots(1, 3, figsize=(18, 6))

for idx, layer in enumerate(['friends', 'work', 'family']):
    layer_nodes = Q.nodes().from_layers(L[layer]).execute(network)
    layer_subgraph = network.core_network.subgraph(layer_nodes.keys())
    pos = nx.spring_layout(layer_subgraph, k=0.6)

    nx.draw_networkx(
        layer_subgraph,
        pos,
        ax=axes[idx],
        node_color='lightblue',
        edge_color='gray',
        with_labels=False,
        node_size=120,
        alpha=0.8
    )
    axes[idx].set_title(f'Layer: {layer}')
    axes[idx].axis('off')

plt.tight_layout()
plt.savefig('layers_comparison.png', dpi=300)

DSL-Driven Visualization Workflows

Goal: Use DSL queries to select and visualize specific network subsets.

The DSL enables you to create targeted visualizations by filtering nodes and edges before rendering. This is essential for large multilayer networks where visualizing everything is impractical. Unless otherwise noted, the examples below reuse the network instance built in the basic visualization section.

Visualize High-Degree Subnetwork

Focus visualization on hub nodes:

from py3plex.core import multinet
from py3plex.dsl import Q
import matplotlib.pyplot as plt

# Load network
network = multinet.multi_layer_network(directed=False)
network.load_network(
    "py3plex/datasets/_data/synthetic_multilayer.edges",
    input_type="multiedgelist"
)

# Query high-degree nodes
hubs = (
    Q.nodes()
     .compute("degree")
     .where(degree__gt=6)
     .execute(network)
)

print(f"Hub nodes (degree > 6): {len(hubs)}")

# Extract hub subgraph
hub_subgraph = network.core_network.subgraph(hubs.keys())

# Create visualization
import networkx as nx
pos = nx.spring_layout(hub_subgraph, k=0.5, iterations=50)

plt.figure(figsize=(12, 10))

# Color by layer
layer_colors = {'layer1': 'red', 'layer2': 'blue', 'layer3': 'green'}
node_colors = [layer_colors.get(node[1], 'gray') for node in hub_subgraph.nodes()]

# Size by degree
node_sizes = [hubs[node]['degree'] * 100 for node in hub_subgraph.nodes()]

nx.draw_networkx(
    hub_subgraph,
    pos,
    node_color=node_colors,
    node_size=node_sizes,
    with_labels=True,
    font_size=8,
    alpha=0.7
)

plt.title('Hub Nodes Subnetwork (degree > 6)', fontsize=14)
plt.axis('off')
plt.tight_layout()
plt.savefig('hub_subnetwork.png', dpi=300, bbox_inches='tight')
print("Saved: hub_subnetwork.png")

Example output (counts depend on the dataset):

Hub nodes (degree > 6): 18
Saved: hub_subnetwork.png

Visualize Community Structure with DSL

Community-aware visualization:

from py3plex.algorithms.community_detection.community_wrapper import louvain_communities
from py3plex.dsl import execute_query
import matplotlib.pyplot as plt
import networkx as nx

# Detect communities
communities = louvain_communities(network)

# Attach community labels
for (node, layer), comm_id in communities.items():
    network.core_network.nodes[(node, layer)]['community'] = comm_id

# Visualize each community separately
community_ids = set(communities.values())
n_communities = len(community_ids)

fig, axes = plt.subplots(1, min(n_communities, 4), figsize=(20, 5))
if n_communities == 1:
    axes = [axes]

for idx, comm_id in enumerate(sorted(community_ids)[:4]):
    # Query community members
    comm_nodes_result = execute_query(
        network,
        f'SELECT nodes WHERE community={comm_id}'
    )
    comm_nodes = comm_nodes_result['nodes']

    # Extract community subgraph
    comm_subgraph = network.core_network.subgraph(comm_nodes)

    # Visualize
    pos = nx.spring_layout(comm_subgraph, k=0.3)

    # Color by layer
    layer_colors = {'layer1': '#FF6B6B', 'layer2': '#4ECDC4', 'layer3': '#45B7D1'}
    node_colors = [layer_colors.get(node[1], 'gray') for node in comm_subgraph.nodes()]

    nx.draw_networkx(
        comm_subgraph,
        pos,
        ax=axes[idx],
        node_color=node_colors,
        node_size=200,
        with_labels=False,
        alpha=0.8
    )

    axes[idx].set_title(f'Community {comm_id}\n({len(comm_nodes)} nodes)')
    axes[idx].axis('off')

plt.tight_layout()
plt.savefig('communities_separate.png', dpi=300, bbox_inches='tight')
print(f"Saved: communities_separate.png with {min(n_communities, 4)} communities")

Layer-Specific Visualizations with DSL

Compare layer structures visually:

from py3plex.dsl import Q, L
import matplotlib.pyplot as plt
import networkx as nx

layers = network.get_layers()
n_layers = len(layers)

fig, axes = plt.subplots(1, n_layers, figsize=(6*n_layers, 5))
if n_layers == 1:
    axes = [axes]

for idx, layer in enumerate(layers):
    # Query layer nodes and edges
    layer_nodes = Q.nodes().from_layers(L[layer]).execute(network)
    layer_edges = Q.edges().from_layers(L[layer]).execute(network)

    # Extract layer subgraph
    layer_subgraph = network.core_network.subgraph(layer_nodes.keys())

    # Compute metrics for sizing
    layer_metrics = (
        Q.nodes()
         .from_layers(L[layer])
         .compute("degree")
         .execute(network)
    )

    # Visualization
    pos = nx.spring_layout(layer_subgraph, k=0.5, iterations=50)

    # Size by degree
    node_sizes = [layer_metrics[node]['degree'] * 50 for node in layer_subgraph.nodes()]

    nx.draw_networkx(
        layer_subgraph,
        pos,
        ax=axes[idx],
        node_size=node_sizes,
        node_color='lightblue',
        edge_color='gray',
        with_labels=False,
        alpha=0.7
    )

    axes[idx].set_title(f'{layer}\n{len(layer_nodes)} nodes, {len(layer_edges)} edges')
    axes[idx].axis('off')

plt.tight_layout()
plt.savefig('layer_comparison.png', dpi=300, bbox_inches='tight')
print("Saved: layer_comparison.png")

Expected output:

Saved: layer_comparison.png

Visualize Central Nodes

Highlight nodes by centrality (betweenness for color, degree for size):

from py3plex.dsl import Q
import matplotlib.pyplot as plt
import networkx as nx

# Compute centrality for all nodes
centrality_result = (
    Q.nodes()
     .compute("betweenness_centrality", "degree")
     .execute(network)
)

# Extract betweenness values for coloring
betweenness = {
    node: data['betweenness_centrality']
    for node, data in centrality_result.items()
}

# Create visualization
plt.figure(figsize=(14, 10))
pos = nx.spring_layout(network.core_network, k=0.5, iterations=50)

# Node colors based on betweenness (using colormap)
node_list = list(network.core_network.nodes())
betw_values = [betweenness.get(node, 0) for node in node_list]

# Node sizes based on degree
degrees = {node: data['degree'] for node, data in centrality_result.items()}
node_sizes = [degrees.get(node, 1) * 100 for node in node_list]

# Draw
nodes = nx.draw_networkx_nodes(
    network.core_network,
    pos,
    node_size=node_sizes,
    node_color=betw_values,
    cmap=plt.cm.YlOrRd,
    alpha=0.8
)

nx.draw_networkx_edges(
    network.core_network,
    pos,
    alpha=0.2,
    edge_color='gray'
)

# Add colorbar
plt.colorbar(nodes, label='Betweenness Centrality')
plt.title('Network Centrality Visualization\n(size=degree, color=betweenness)', fontsize=14)
plt.axis('off')
plt.tight_layout()
plt.savefig('centrality_viz.png', dpi=300, bbox_inches='tight')
print("Saved: centrality_viz.png")

Dynamics State Visualization

Visualize epidemic simulation results:

from py3plex.dynamics import SIRDynamics
from py3plex.dsl import Q
import matplotlib.pyplot as plt
import networkx as nx

# Run SIR simulation
sir = SIRDynamics(network, beta=0.3, gamma=0.1, initial_infected=0.05)
sir.set_seed(42)
results = sir.run(steps=100)

# Attach final state
final_state = results.trajectory[-1]
for node, state in final_state.items():
    network.core_network.nodes[node]['sir_state'] = state

# Query infected and recovered nodes
infected = Q.nodes().where(sir_state='I').execute(network)
recovered = Q.nodes().where(sir_state='R').execute(network)
susceptible = Q.nodes().where(sir_state='S').execute(network)

print(f"Infected: {len(infected)}, Recovered: {len(recovered)}, Susceptible: {len(susceptible)}")

# Visualization
plt.figure(figsize=(14, 10))
pos = nx.spring_layout(network.core_network, k=0.5, iterations=50)

# Color by SIR state
state_colors = {'S': 'lightblue', 'I': 'red', 'R': 'green'}
node_colors = [
    state_colors.get(network.core_network.nodes[node].get('sir_state'), 'gray')
    for node in network.core_network.nodes()
]

nx.draw_networkx(
    network.core_network,
    pos,
    node_color=node_colors,
    node_size=300,
    with_labels=False,
    alpha=0.8
)

# Legend
from matplotlib.patches import Patch
legend_elements = [
    Patch(facecolor='lightblue', label=f'Susceptible ({len(susceptible)})'),
    Patch(facecolor='red', label=f'Infected ({len(infected)})'),
    Patch(facecolor='green', label=f'Recovered ({len(recovered)})')
]
plt.legend(handles=legend_elements, loc='upper right')

plt.title('SIR Epidemic Simulation (t=100)', fontsize=14)
plt.axis('off')
plt.tight_layout()
plt.savefig('sir_visualization.png', dpi=300, bbox_inches='tight')
print("Saved: sir_visualization.png")

Example output (depends on network size and random seed):

Infected: 8, Recovered: 82, Susceptible: 30
Saved: sir_visualization.png

Interactive Visualization with DSL Filtering

Create interactive plots with Plotly (browser-rendered HTML):

from py3plex.dsl import Q
import plotly.graph_objects as go
import networkx as nx

# Query nodes with metrics
metrics = (
    Q.nodes()
     .compute("degree", "betweenness_centrality")
     .execute(network)
)

# Extract subgraph (optional: filter for clarity)
high_degree = {
    node: data for node, data in metrics.items()
    if data['degree'] > 4
}

subgraph = network.core_network.subgraph(high_degree.keys())

# Layout
pos = nx.spring_layout(subgraph, k=0.5)

# Create edge trace
edge_x = []
edge_y = []
for edge in subgraph.edges():
    x0, y0 = pos[edge[0]]
    x1, y1 = pos[edge[1]]
    edge_x.extend([x0, x1, None])
    edge_y.extend([y0, y1, None])

edge_trace = go.Scatter(
    x=edge_x, y=edge_y,
    line=dict(width=0.5, color='#888'),
    hoverinfo='none',
    mode='lines'
)

# Create node trace
node_x = []
node_y = []
node_text = []
node_sizes = []
node_colors = []

for node in subgraph.nodes():
    x, y = pos[node]
    node_x.append(x)
    node_y.append(y)

    degree = high_degree[node]['degree']
    betw = high_degree[node]['betweenness_centrality']

    node_text.append(f'{node}<br>Degree: {degree}<br>Betweenness: {betw:.4f}')
    node_sizes.append(degree * 5)
    node_colors.append(betw)

node_trace = go.Scatter(
    x=node_x, y=node_y,
    mode='markers',
    hoverinfo='text',
    text=node_text,
    marker=dict(
        showscale=True,
        colorscale='YlOrRd',
        size=node_sizes,
        color=node_colors,
        colorbar=dict(
            title="Betweenness"
        ),
        line_width=2
    )
)

# Create figure
fig = go.Figure(
    data=[edge_trace, node_trace],
    layout=go.Layout(
        title='Interactive Network Visualization (degree > 4)',
        showlegend=False,
        hovermode='closest',
        margin=dict(b=0, l=0, r=0, t=40),
        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)
    )
)

# Save to HTML
fig.write_html('interactive_network.html')
print("Saved: interactive_network.html (open in browser)")

Why use DSL for visualization?

  • Targeted views: Filter nodes/edges to reduce visual clutter

  • Multi-scale: Visualize different network scales (layer, community, subnetwork)

  • Attribute-driven: Color/size nodes based on computed metrics

  • Reproducible: Query-based selections are version-controllable

  • Efficient: Process only relevant subsets for large networks

Next steps with DSL visualization:

Community Visualization

Color by Communities

from py3plex.algorithms.community_detection import louvain_communities
from py3plex.visualization import visualize_communities

# Detect communities
communities = louvain_communities(network)

# Visualize with community colors
visualize_communities(
    network,
    communities,
    output_file='communities.png',
    show=True
)

Interactive Visualization

Using Plotly

from py3plex.visualization import plotly_visualization

# Create interactive plot
fig = plotly_visualization(network)

# Show in browser
fig.show()

# Or save as HTML
fig.write_html('network_interactive.html')

Export for Gephi/Cytoscape

Export to GraphML

import networkx as nx

# Convert to NetworkX
G = network.to_networkx()

# Export
nx.write_graphml(G, 'network.graphml')

Then open network.graphml in Gephi or Cytoscape for advanced visualization of the aggregated network.

Next Steps