How to Visualize Multilayer Networks

Goal: Create publication-ready visualizations of multilayer networks.

Prerequisites: A loaded network (see How to Load and Build Networks).

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 network visualization.

Save to File

network.visualize_network(
    output_file='network.png',
    show=False
)

Customizing Visualizations

Node Sizes and Colors

from py3plex.visualization import visualize_network_custom

# Compute node importance
from py3plex.dsl import Q
result = Q.nodes().compute("degree").execute(network)
degrees = {node: data['degree'] for node, data in result.items()}

# Visualize with custom node sizes
visualize_network_custom(
    network,
    node_sizes=degrees,
    node_color_by_layer=True,
    output_file='network_sized.png'
)

Edge Weights

visualize_network_custom(
    network,
    edge_width_by_weight=True,
    show=True
)

Layout Algorithms

Force-Directed Layout

from py3plex.visualization.multilayer import hairball_plot

hairball_plot(
    network,
    layout_algorithm='force_directed',
    output_file='force_layout.png'
)

Circular Layout

hairball_plot(
    network,
    layout_algorithm='circular',
    output_file='circular_layout.png'
)

Layer-Specific Visualization

Visualize Single Layer

from py3plex.dsl import Q, L

# Extract layer
layer_network = Q.edges().from_layers(L["friends"]).execute(network)

# Visualize
layer_network.visualize_network(
    output_file='friends_layer.png',
    show=True
)

Side-by-Side Layer Comparison

import matplotlib.pyplot as plt

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

for idx, layer in enumerate(['friends', 'work', 'family']):
    layer_net = Q.edges().from_layers(L[layer]).execute(network)
    # Visualize on specific axis
    visualize_on_axis(layer_net, axes[idx], title=f'Layer: {layer}')

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.

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")

Expected output:

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 Q, 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 = execute_query(
        network,
        f'SELECT nodes WHERE community={comm_id}'
    )

    # 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:

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

# 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")

Expected output:

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

Interactive Visualization with DSL Filtering

Create interactive plots with Plotly:

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.

Next Steps