Effective Graph Visualization Using NetworkX, PyVis, igraph, and Gephi
NetworkX is fully integrated with matplotlib, making it suitable for static graph visualization.
import networkx as nx
import matplotlib.pyplot as plt
# Create graph
G = nx.karate_club_graph()
# Basic visualization
plt.figure(figsize=(12, 8))
nx.draw(G, with_labels=True, node_color='lightblue',
node_size=500, font_size=10, font_weight='bold')
plt.title('Karate Club Network')
plt.axis('off')
plt.tight_layout()
plt.savefig('karate_network.png', dpi=300, bbox_inches='tight')
plt.show()
Selecting the appropriate layout is essential for understanding network structure.
import numpy as np
# Comparison of various layouts
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
layouts = {
'Spring': nx.spring_layout(G, k=0.3, iterations=50),
'Circular': nx.circular_layout(G),
'Kamada-Kawai': nx.kamada_kawai_layout(G),
'Spectral': nx.spectral_layout(G)
}
for ax, (name, pos) in zip(axes.flat, layouts.items()):
nx.draw(G, pos, ax=ax, node_color='lightblue',
node_size=300, with_labels=True, font_size=8)
ax.set_title(f'{name} Layout', fontsize=14, fontweight='bold')
ax.axis('off')
plt.tight_layout()
plt.savefig('layout_comparison.png', dpi=300, bbox_inches='tight')
plt.show()
# Layout algorithm selection criteria
# - Spring: General-purpose, force-directed balance (O(n²))
# - Circular: Visualizes symmetry (O(n))
# - Kamada-Kawai: More accurate distance representation (O(n³))
# - Spectral: Emphasizes community structure (O(n²))
Visually representing network attributes deepens data insights.
# Customization based on degree centrality
degree_centrality = nx.degree_centrality(G)
betweenness_centrality = nx.betweenness_centrality(G)
# Node size: degree centrality
node_sizes = [v * 3000 for v in degree_centrality.values()]
# Node color: betweenness centrality
node_colors = list(betweenness_centrality.values())
# Edge width: weight (in this example, product of degrees)
edge_weights = [G.degree(u) * G.degree(v) * 0.1
for u, v in G.edges()]
plt.figure(figsize=(14, 10))
pos = nx.spring_layout(G, k=0.3, seed=42)
# Draw
nx.draw_networkx_nodes(G, pos, node_size=node_sizes,
node_color=node_colors, cmap='YlOrRd',
alpha=0.9, edgecolors='black', linewidths=1.5)
nx.draw_networkx_edges(G, pos, width=edge_weights,
alpha=0.5, edge_color='gray')
nx.draw_networkx_labels(G, pos, font_size=9, font_weight='bold')
plt.title('Network Visualization Using Centrality Metrics',
fontsize=16, fontweight='bold')
plt.colorbar(plt.cm.ScalarMappable(cmap='YlOrRd'),
label='Betweenness Centrality', ax=plt.gca())
plt.axis('off')
plt.tight_layout()
plt.savefig('customized_network.png', dpi=300, bbox_inches='tight')
plt.show()
# Visualization best practices
# 1. Node size: Represents importance (centrality)
# 2. Node color: Represents category or continuous value
# 3. Edge width: Represents relationship strength
# 4. Layout: Choose according to data characteristics
PyVis generates interactive network visualizations in HTML format.
from pyvis.network import Network
import networkx as nx
# Create PyVis network
net = Network(height='750px', width='100%', bgcolor='#222222',
font_color='white', notebook=True)
# Import from NetworkX graph
G = nx.karate_club_graph()
# Community detection
from networkx.algorithms import community
communities = community.greedy_modularity_communities(G)
community_map = {}
for i, comm in enumerate(communities):
for node in comm:
community_map[node] = i
# Set node colors and sizes
for node in G.nodes():
# Change color by community
color = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A'][community_map[node]]
# Change size by degree
size = G.degree(node) * 3
net.add_node(node, label=str(node), color=color, size=size,
title=f'Node {node}
Degree: {G.degree(node)}')
# Add edges
for edge in G.edges():
net.add_edge(edge[0], edge[1])
# Physics simulation settings
net.set_options("""
var options = {
"physics": {
"forceAtlas2Based": {
"gravitationalConstant": -50,
"centralGravity": 0.01,
"springLength": 100,
"springConstant": 0.08
},
"maxVelocity": 50,
"solver": "forceAtlas2Based",
"timestep": 0.35,
"stabilization": {"iterations": 150}
}
}
""")
# HTML output
net.save_graph('interactive_network.html')
print("Interactive graph saved to interactive_network.html")
Plotly provides highly customizable interactive graphs.
import plotly.graph_objects as go
# Calculate layout
pos = nx.spring_layout(G, k=0.5, seed=42)
# Create edge trace
edge_x = []
edge_y = []
for edge in G.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 = []
for node in G.nodes():
x, y = pos[node]
node_x.append(x)
node_y.append(y)
node_text.append(f'Node {node}
Degree: {G.degree(node)}')
node_sizes.append(G.degree(node) * 5)
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=[G.degree(node) for node in G.nodes()],
colorbar=dict(
thickness=15,
title='Node Degree',
xanchor='left',
titleside='right'
),
line=dict(width=2, color='white')))
# Create figure
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(
title='Plotly Interactive Network',
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),
plot_bgcolor='rgba(240,240,240,0.9)'))
fig.write_html('plotly_network.html')
fig.show()
Large graphs with over 10,000 nodes require special approaches.
# Efficient visualization of large graphs
def visualize_large_network(G, max_nodes=5000, sample_method='degree'):
"""
Sample and visualize large networks
Parameters:
- G: NetworkX graph
- max_nodes: Maximum number of nodes to display
- sample_method: 'degree', 'random', 'pagerank'
"""
if len(G.nodes()) > max_nodes:
print(f"Sampling nodes from {len(G.nodes())} to {max_nodes}")
if sample_method == 'degree':
# Prioritize high-degree nodes
top_nodes = sorted(G.degree(), key=lambda x: x[1],
reverse=True)[:max_nodes]
nodes_to_keep = [n for n, d in top_nodes]
elif sample_method == 'pagerank':
# Select high PageRank nodes
pr = nx.pagerank(G)
top_nodes = sorted(pr.items(), key=lambda x: x[1],
reverse=True)[:max_nodes]
nodes_to_keep = [n for n, p in top_nodes]
else: # random
import random
nodes_to_keep = random.sample(list(G.nodes()), max_nodes)
G_sample = G.subgraph(nodes_to_keep).copy()
else:
G_sample = G
# Visualize
plt.figure(figsize=(16, 12))
pos = nx.spring_layout(G_sample, k=1/np.sqrt(len(G_sample.nodes())),
iterations=20)
degree_centrality = nx.degree_centrality(G_sample)
node_sizes = [v * 1000 for v in degree_centrality.values()]
nx.draw_networkx(G_sample, pos,
node_size=node_sizes,
node_color=list(degree_centrality.values()),
cmap='viridis',
with_labels=False,
alpha=0.7,
edge_color='gray',
width=0.5)
plt.title(f'Sampled Network ({len(G_sample.nodes())} nodes)',
fontsize=16, fontweight='bold')
plt.axis('off')
plt.tight_layout()
plt.savefig('large_network_sampled.png', dpi=300, bbox_inches='tight')
plt.show()
# Usage example
G_large = nx.barabasi_albert_graph(10000, 3, seed=42)
visualize_large_network(G_large, max_nodes=500, sample_method='pagerank')
igraph is implemented in C and excels at high-speed processing of large graphs.
| Feature | NetworkX | igraph |
|---|---|---|
| Implementation Language | Python | C (with Python bindings) |
| Speed | Moderate | Fast (10-100x faster) |
| Memory Efficiency | Standard | Efficient |
| Learning Curve | Gentle | Somewhat steep |
| Ecosystem | Rich (matplotlib, etc.) | Custom visualization |
| Applicability | Small to medium scale (~10K nodes) | Large scale (100K+ nodes) |
import igraph as ig
import time
# Convert NetworkX graph to igraph
def nx_to_igraph(G_nx):
"""Convert NetworkX graph to igraph"""
G_ig = ig.Graph()
G_ig.add_vertices(list(G_nx.nodes()))
G_ig.add_edges(list(G_nx.edges()))
return G_ig
# Performance comparison
G_nx = nx.barabasi_albert_graph(5000, 3, seed=42)
G_ig = nx_to_igraph(G_nx)
# NetworkX: PageRank
start = time.time()
pr_nx = nx.pagerank(G_nx)
time_nx = time.time() - start
# igraph: PageRank
start = time.time()
pr_ig = G_ig.pagerank()
time_ig = time.time() - start
print(f"NetworkX PageRank: {time_nx:.4f} seconds")
print(f"igraph PageRank: {time_ig:.4f} seconds")
print(f"Speedup: {time_nx/time_ig:.2f}x")
# Community detection with igraph
start = time.time()
communities = G_ig.community_multilevel()
time_community = time.time() - start
print(f"\nigraph community detection: {time_community:.4f} seconds")
print(f"Communities detected: {len(communities)}")
print(f"Modularity: {communities.modularity:.4f}")
# Efficient processing of large graphs using igraph
def analyze_large_graph_igraph(n_nodes=100000, m_edges=3):
"""Efficient analysis of large graphs"""
print(f"Generating graph: {n_nodes} nodes...")
G = ig.Graph.Barabasi(n_nodes, m_edges)
print("Calculating centrality metrics...")
start = time.time()
# Calculate various centralities
degree = G.degree()
betweenness = G.betweenness()
closeness = G.closeness()
pagerank = G.pagerank()
calc_time = time.time() - start
print(f"Calculation time: {calc_time:.2f} seconds")
# Community detection
print("Detecting communities...")
start = time.time()
communities = G.community_multilevel()
comm_time = time.time() - start
print(f"Detection time: {comm_time:.2f} seconds")
print(f"Number of communities: {len(communities)}")
# Visualization (sampling)
print("Sampling for visualization...")
# Select top 500 nodes
top_nodes = sorted(range(len(pagerank)),
key=lambda i: pagerank[i], reverse=True)[:500]
G_sample = G.subgraph(top_nodes)
# igraph visualization
visual_style = {
"vertex_size": [pagerank[i] * 1000 for i in top_nodes],
"vertex_color": [communities.membership[i] for i in top_nodes],
"vertex_label": None,
"edge_width": 0.5,
"edge_color": "#cccccc",
"layout": G_sample.layout_fruchterman_reingold()
}
ig.plot(G_sample,
"large_graph_igraph.png",
bbox=(1200, 1200),
**visual_style)
print("Visualization complete: large_graph_igraph.png")
return {
'nodes': n_nodes,
'edges': G.ecount(),
'calc_time': calc_time,
'comm_time': comm_time,
'communities': len(communities),
'modularity': communities.modularity
}
# Execute
results = analyze_large_graph_igraph(n_nodes=100000, m_edges=3)
print(f"\nResults summary: {results}")
Gephi is a powerful desktop application for interactive visualization and network exploration.
Main Advantages of Gephi:
- Real-time visual feedback
- Advanced layout algorithms (ForceAtlas2, etc.)
- Statistical analysis and filtering capabilities
- High-quality output (publication-ready graphics)
- Plugin ecosystem
# Export from NetworkX to Gephi format
import networkx as nx
# Create sample graph and add attributes
G = nx.karate_club_graph()
# Add node attributes
degree_centrality = nx.degree_centrality(G)
betweenness_centrality = nx.betweenness_centrality(G)
communities = nx.community.greedy_modularity_communities(G)
# Add community ID to nodes
community_map = {}
for i, comm in enumerate(communities):
for node in comm:
community_map[node] = i
for node in G.nodes():
G.nodes[node]['degree_centrality'] = degree_centrality[node]
G.nodes[node]['betweenness_centrality'] = betweenness_centrality[node]
G.nodes[node]['community'] = community_map[node]
G.nodes[node]['label'] = f'Node_{node}'
# Add edge attributes
for u, v in G.edges():
G[u][v]['weight'] = G.degree(u) + G.degree(v)
# Export as GEXF format (Gephi recommended format)
nx.write_gexf(G, 'network_for_gephi.gexf')
print("GEXF file created: network_for_gephi.gexf")
# Can also export as GraphML format
nx.write_graphml(G, 'network_for_gephi.graphml')
print("GraphML file created: network_for_gephi.graphml")
# CSV edge list format (simple method)
import pandas as pd
edges_data = []
for u, v, data in G.edges(data=True):
edges_data.append({
'Source': u,
'Target': v,
'Weight': data.get('weight', 1)
})
edges_df = pd.DataFrame(edges_data)
edges_df.to_csv('edges.csv', index=False)
# Node list CSV
nodes_data = []
for node, data in G.nodes(data=True):
nodes_data.append({
'Id': node,
'Label': data.get('label', str(node)),
'Community': data.get('community', 0),
'Degree_Centrality': data.get('degree_centrality', 0),
'Betweenness_Centrality': data.get('betweenness_centrality', 0)
})
nodes_df = pd.DataFrame(nodes_data)
nodes_df.to_csv('nodes.csv', index=False)
print("CSV files created: edges.csv, nodes.csv")
# Implementation of various sampling techniques
class NetworkSampler:
"""Network sampling class for large networks"""
@staticmethod
def random_node_sampling(G, sample_size):
"""Random node sampling"""
import random
nodes = random.sample(list(G.nodes()),
min(sample_size, len(G.nodes())))
return G.subgraph(nodes).copy()
@staticmethod
def random_edge_sampling(G, sample_ratio=0.1):
"""Random edge sampling"""
import random
n_edges = int(len(G.edges()) * sample_ratio)
edges = random.sample(list(G.edges()), n_edges)
H = nx.Graph()
H.add_edges_from(edges)
return H
@staticmethod
def induced_subgraph_sampling(G, sample_size):
"""Induced subgraph sampling (prioritizing important nodes)"""
# Select important nodes using PageRank
pr = nx.pagerank(G)
top_nodes = sorted(pr.items(), key=lambda x: x[1],
reverse=True)[:sample_size]
nodes = [n for n, _ in top_nodes]
return G.subgraph(nodes).copy()
@staticmethod
def snowball_sampling(G, seed_nodes, k=2):
"""Snowball sampling (k-hop neighborhood)"""
sampled_nodes = set(seed_nodes)
for _ in range(k):
new_nodes = set()
for node in sampled_nodes:
new_nodes.update(G.neighbors(node))
sampled_nodes.update(new_nodes)
return G.subgraph(sampled_nodes).copy()
@staticmethod
def forest_fire_sampling(G, sample_size, p=0.4):
"""Forest Fire sampling"""
import random
sampled_nodes = set()
queue = [random.choice(list(G.nodes()))]
while len(sampled_nodes) < sample_size and queue:
current = queue.pop(0)
if current not in sampled_nodes:
sampled_nodes.add(current)
neighbors = list(G.neighbors(current))
# Add neighboring nodes with probability p
n_select = int(len(neighbors) * p)
queue.extend(random.sample(neighbors,
min(n_select, len(neighbors))))
return G.subgraph(sampled_nodes).copy()
# Comparison of sampling techniques
G_large = nx.barabasi_albert_graph(10000, 3, seed=42)
sample_size = 500
samplers = {
'Random Node': NetworkSampler.random_node_sampling(G_large, sample_size),
'Induced (PageRank)': NetworkSampler.induced_subgraph_sampling(G_large, sample_size),
'Snowball (k=2)': NetworkSampler.snowball_sampling(
G_large, [0, 1, 2], k=2),
'Forest Fire': NetworkSampler.forest_fire_sampling(
G_large, sample_size, p=0.4)
}
# Compare characteristics of each sampling technique
fig, axes = plt.subplots(2, 2, figsize=(16, 14))
for ax, (name, G_sample) in zip(axes.flat, samplers.items()):
pos = nx.spring_layout(G_sample, k=0.5, iterations=20)
degree_centrality = nx.degree_centrality(G_sample)
node_sizes = [v * 500 for v in degree_centrality.values()]
nx.draw_networkx(G_sample, pos, ax=ax,
node_size=node_sizes,
node_color=list(degree_centrality.values()),
cmap='viridis',
with_labels=False,
alpha=0.7,
edge_color='gray',
width=0.5)
# Statistical information
density = nx.density(G_sample)
avg_degree = sum(dict(G_sample.degree()).values()) / len(G_sample.nodes())
ax.set_title(f'{name}\nNodes: {len(G_sample.nodes())}, '
f'Density: {density:.4f}, Avg Degree: {avg_degree:.2f}',
fontsize=12, fontweight='bold')
ax.axis('off')
plt.tight_layout()
plt.savefig('sampling_comparison.png', dpi=300, bbox_inches='tight')
plt.show()
# Hierarchical network visualization
def hierarchical_visualization(G, threshold_degree=10):
"""
Hierarchical visualization: Display important nodes and their surroundings progressively
"""
# Level 1: High-degree nodes (hubs)
high_degree_nodes = [n for n, d in G.degree() if d >= threshold_degree]
# Level 2: Direct neighbors of hubs
level2_nodes = set()
for hub in high_degree_nodes:
level2_nodes.update(G.neighbors(hub))
level2_nodes = list(level2_nodes - set(high_degree_nodes))
# Level 3: Other nodes (sampled)
remaining = set(G.nodes()) - set(high_degree_nodes) - set(level2_nodes)
import random
level3_nodes = random.sample(list(remaining),
min(100, len(remaining)))
# Hierarchical layout
fig = plt.figure(figsize=(18, 6))
# Level 1 visualization
ax1 = plt.subplot(131)
G1 = G.subgraph(high_degree_nodes).copy()
pos1 = nx.spring_layout(G1, k=1, seed=42)
nx.draw_networkx(G1, pos1, ax=ax1,
node_color='red', node_size=500,
with_labels=True, font_size=8)
ax1.set_title(f'Level 1: Hub Nodes ({len(high_degree_nodes)})',
fontsize=14, fontweight='bold')
ax1.axis('off')
# Level 2 visualization
ax2 = plt.subplot(132)
G2 = G.subgraph(high_degree_nodes + level2_nodes).copy()
pos2 = nx.spring_layout(G2, k=0.5, seed=42)
node_colors = ['red' if n in high_degree_nodes else 'lightblue'
for n in G2.nodes()]
node_sizes = [500 if n in high_degree_nodes else 200
for n in G2.nodes()]
nx.draw_networkx(G2, pos2, ax=ax2,
node_color=node_colors, node_size=node_sizes,
with_labels=False)
ax2.set_title(f'Level 2: +Direct Neighbors ({len(G2.nodes())})',
fontsize=14, fontweight='bold')
ax2.axis('off')
# Level 3 visualization
ax3 = plt.subplot(133)
all_nodes = high_degree_nodes + level2_nodes + level3_nodes
G3 = G.subgraph(all_nodes).copy()
pos3 = nx.spring_layout(G3, k=0.3, seed=42)
node_colors = ['red' if n in high_degree_nodes
else 'lightblue' if n in level2_nodes
else 'lightgreen' for n in G3.nodes()]
node_sizes = [500 if n in high_degree_nodes
else 200 if n in level2_nodes
else 100 for n in G3.nodes()]
nx.draw_networkx(G3, pos3, ax=ax3,
node_color=node_colors, node_size=node_sizes,
with_labels=False, alpha=0.8)
ax3.set_title(f'Level 3: +Others ({len(G3.nodes())})',
fontsize=14, fontweight='bold')
ax3.axis('off')
plt.tight_layout()
plt.savefig('hierarchical_viz.png', dpi=300, bbox_inches='tight')
plt.show()
# Usage example
G = nx.barabasi_albert_graph(1000, 3, seed=42)
hierarchical_visualization(G, threshold_degree=20)
# Interactive dashboard using Plotly Dash
from dash import Dash, dcc, html, Input, Output
import plotly.graph_objects as go
import networkx as nx
# Create graph
G = nx.karate_club_graph()
# Dashboard application
app = Dash(__name__)
# Layout
app.layout = html.Div([
html.H1("Network Visualization Dashboard",
style={'textAlign': 'center'}),
html.Div([
html.Label("Layout Algorithm:"),
dcc.Dropdown(
id='layout-dropdown',
options=[
{'label': 'Spring Layout', 'value': 'spring'},
{'label': 'Circular Layout', 'value': 'circular'},
{'label': 'Kamada-Kawai', 'value': 'kamada_kawai'},
{'label': 'Spectral Layout', 'value': 'spectral'}
],
value='spring'
)
], style={'width': '300px', 'margin': '20px'}),
html.Div([
html.Label("Node Color Metric:"),
dcc.Dropdown(
id='metric-dropdown',
options=[
{'label': 'Degree Centrality', 'value': 'degree'},
{'label': 'Betweenness Centrality', 'value': 'betweenness'},
{'label': 'Eigenvector Centrality', 'value': 'eigenvector'},
{'label': 'PageRank', 'value': 'pagerank'}
],
value='degree'
)
], style={'width': '300px', 'margin': '20px'}),
dcc.Graph(id='network-graph', style={'height': '800px'})
])
# Callback
@app.callback(
Output('network-graph', 'figure'),
[Input('layout-dropdown', 'value'),
Input('metric-dropdown', 'value')]
)
def update_graph(layout_type, metric_type):
# Calculate layout
if layout_type == 'spring':
pos = nx.spring_layout(G, k=0.5, seed=42)
elif layout_type == 'circular':
pos = nx.circular_layout(G)
elif layout_type == 'kamada_kawai':
pos = nx.kamada_kawai_layout(G)
else: # spectral
pos = nx.spectral_layout(G)
# Calculate metrics
if metric_type == 'degree':
metric = nx.degree_centrality(G)
elif metric_type == 'betweenness':
metric = nx.betweenness_centrality(G)
elif metric_type == 'eigenvector':
metric = nx.eigenvector_centrality(G)
else: # pagerank
metric = nx.pagerank(G)
# Edge trace
edge_x, edge_y = [], []
for edge in G.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'
)
# Node trace
node_x = [pos[node][0] for node in G.nodes()]
node_y = [pos[node][1] for node in G.nodes()]
node_text = [f'Node {node}
{metric_type}: {metric[node]:.4f}'
for node in G.nodes()]
node_trace = go.Scatter(
x=node_x, y=node_y,
mode='markers+text',
hoverinfo='text',
text=[str(n) for n in G.nodes()],
textposition="top center",
hovertext=node_text,
marker=dict(
showscale=True,
colorscale='YlOrRd',
size=[metric[node] * 50 for node in G.nodes()],
color=[metric[node] for node in G.nodes()],
colorbar=dict(
thickness=15,
title=metric_type,
xanchor='left',
titleside='right'
),
line=dict(width=2, color='white')
)
)
# Create figure
fig = go.Figure(
data=[edge_trace, node_trace],
layout=go.Layout(
title=f'{layout_type.title()} Layout - {metric_type.title()}',
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),
plot_bgcolor='white'
)
)
return fig
# Run application
# if __name__ == '__main__':
# app.run_server(debug=True, port=8050)
print("Dashboard code ready")
print("To execute, uncomment the last 2 lines")
Visualization Tool Selection Guidelines:
- NetworkX + Matplotlib: Static visualization, for papers/reports, small scale (~1K nodes)
- PyVis: Quick interactive exploration, for presentations
- Plotly: Highly customizable interactive visualization, dashboards
- igraph: Fast processing and analysis of large graphs (10K+ nodes)
- Gephi: Publication-quality visualization, detailed visual exploration, large graphs (100K+ nodes)