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:
Full DSL tutorial: How to Query Multilayer Graphs with the SQL-like DSL - Complete DSL reference
Community detection: How to Run Community Detection on Multilayer Networks - Visualize communities
Dynamics: How to Simulate Multilayer Dynamics - Visualize dynamics results
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
Compute statistics to visualize: How to Compute Network Statistics
Detect communities: How to Run Community Detection on Multilayer Networks
API reference: API Documentation