Nœuds de cluster NetworkX dans une formation circulaire basée sur la couleur des nœuds
J'avais la même question que celle-ci . La solution fonctionne, cependant, je n'arrive pas à espacer les nœuds et à les faire apparaître dans un format circulaire avec mon ensemble de données. J'ai environ 30 nœuds au total qui sont codés par couleur.
Les nœuds de la même couleur se chevauchent au lieu d'être regroupés dans un format circulaire / plus concentrique.
J'ai utilisé le code dans la question ci-dessus et j'ai essayé toutes les valeurs de rayons possibles, mais je n'arrive pas à créer les nœuds du même groupe de couleurs dans un cercle .
Code:
import networkx
import numpy as np
import matplotlib.pyplot as plt
nodesWithGroup = {'A':'#7a8eff', 'B': '#7a8eff', 'C': '#eb2c30', 'D':'#eb2c30', 'E': '#eb2c30', 'F':'#730a15', 'G': '#730a15'}
# Set up graph, adding nodes and edges
G = nx.Graph()
G.add_nodes_from(nodesWithGroup.keys())
# Create a dictionary mapping color to a list of nodes
nodes_by_color = {}
for k, v in nodesWithGroup.items():
if v not in nodes_by_color:
nodes_by_color[v] = [k]
else:
nodes_by_color[v].append(k)
# Create initial circular layout
pos = nx.circular_layout(RRR)
# Get list of colors
colors2 = list(nodes_by_color.keys())
# clustering
angs = np.linspace(0, 2*np.pi, 1+len(colors))
repos = []
rad = 13
for ea in angs:
if ea > 0:
repos.append(np.array([rad*np.cos(ea), rad*np.sin(ea)]))
for color, nodes in nodes_by_color.items():
posx = colors.index(color)
for node in nodes:
pos[node] += repos[posx]
# Plot graph
fig,ax = plt.subplots(figsize=(5, 5))
# node colors
teamX = ['A', 'B']
teamY = ['C', 'D', 'E']
teamZ = ['F', 'G']
for n in G.nodes():
if n in teamX:
G.nodes[n]['color'] = '#7a8eff'
elif n in teamY:
G.nodes[n]['color'] = '#eb2c30'
else:
G.nodes[n]['color'] = '#730a15'
colors = [node[1]['color'] for node in G.nodes(data=True)]
# edges
zorder_edges = 3
zorder_nodes = 4
zorder_node_labels = 5
for edge in G.edges():
source, target = edge
rad = 0.15
node_color_dict = dict(G.nodes(data='color'))
if node_color_dict[source] == node_color_dict[target]:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='blue',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
alpha=0.65, zorder=zorder_edges)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
else:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='purple',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
alpha=0.65, zorder=zorder_edges)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
# drawing
node_labels_dict = nx.draw_networkx_labels(G, pos, font_size=5, font_family="monospace", font_color='white', font_weight='bold')
for color, nodes in nodes_by_color.items():
nodes_draw = nx.draw_networkx_nodes(G, pos=pos, nodelist=nodes, node_color=color, edgecolors=[(0,0,0,1)])
nodes_draw.set_zorder(zorder_nodes)
for node_labels_draw in node_labels_dict.values():
node_labels_draw.set_zorder(zorder_node_labels)
plt.show()
J'obtiens la sortie suivante:

Sortie souhaitée (comme dans la solution):

Réponses
Comme @willcrack l'a suggéré, une légère adaptation de cette réponse fonctionne bien.
Vous pouvez ajuster le chevauchement des nœuds en modifiant le ratio
paramètre dans partition_layout
.

#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
NODE_LAYOUT = nx.circular_layout
COMMUNITY_LAYOUT = nx.circular_layout
def partition_layout(g, partition, ratio=0.3):
"""
Compute the layout for a modular graph.
Arguments:
----------
g -- networkx.Graph or networkx.DiGraph instance
network to plot
partition -- dict mapping node -> community or None
Network partition, i.e. a mapping from node ID to a group ID.
ratio: 0 < float < 1.
Controls how tightly the nodes are clustered around their partition centroid.
If 0, all nodes of a partition are at the centroid position.
if 1, nodes are positioned independently of their partition centroid.
Returns:
--------
pos -- dict mapping int node -> (float x, float y)
node positions
"""
pos_communities = _position_communities(g, partition)
pos_nodes = _position_nodes(g, partition)
pos_nodes = {k : ratio * v for k, v in pos_nodes.items()}
# combine positions
pos = dict()
for node in g.nodes():
pos[node] = pos_communities[node] + pos_nodes[node]
return pos
def _position_communities(g, partition, **kwargs):
# create a weighted graph, in which each node corresponds to a community,
# and each edge weight to the number of edges between communities
between_community_edges = _find_between_community_edges(g, partition)
communities = set(partition.values())
hypergraph = nx.DiGraph()
hypergraph.add_nodes_from(communities)
for (ci, cj), edges in between_community_edges.items():
hypergraph.add_edge(ci, cj, weight=len(edges))
# find layout for communities
pos_communities = COMMUNITY_LAYOUT(hypergraph, **kwargs)
# set node positions to position of community
pos = dict()
for node, community in partition.items():
pos[node] = pos_communities[community]
return pos
def _find_between_community_edges(g, partition):
edges = dict()
for (ni, nj) in g.edges():
ci = partition[ni]
cj = partition[nj]
if ci != cj:
try:
edges[(ci, cj)] += [(ni, nj)]
except KeyError:
edges[(ci, cj)] = [(ni, nj)]
return edges
def _position_nodes(g, partition, **kwargs):
"""
Positions nodes within communities.
"""
communities = dict()
for node, community in partition.items():
if community in communities:
communities[community] += [node]
else:
communities[community] = [node]
pos = dict()
for community, nodes in communities.items():
subgraph = g.subgraph(nodes)
pos_subgraph = NODE_LAYOUT(subgraph, **kwargs)
pos.update(pos_subgraph)
return pos
def _layout(networkx_graph):
edge_list = [edge for edge in networkx_graph.edges]
node_list = [node for node in networkx_graph.nodes]
pos = circular_layout(edge_list)
# NB: some nodes might not be connected and hence will not be in the edge list.
# Assuming a [0, 0, 1, 1] canvas, we assign random positions on the periphery
# of the existing node positions.
# We define the periphery as the region outside the circle that covers all
# existing node positions.
xy = list(pos.values())
centroid = np.mean(xy, axis=0)
delta = xy - centroid[np.newaxis, :]
distance = np.sqrt(np.sum(delta**2, axis=1))
radius = np.max(distance)
connected_nodes = set(_flatten(edge_list))
for node in node_list:
if not (node in connected_nodes):
pos[node] = _get_random_point_on_a_circle(centroid, radius)
return pos
def _flatten(nested_list):
return [item for sublist in nested_list for item in sublist]
def _get_random_point_on_a_circle(origin, radius):
x0, y0 = origin
random_angle = 2 * np.pi * np.random.random()
x = x0 + radius * np.cos(random_angle)
y = y0 + radius * np.sin(random_angle)
return np.array([x, y])
def test():
# create test data
cliques = 8
clique_size = 7
g = nx.connected_caveman_graph(cliques, clique_size)
partition = {ii : np.int(ii/clique_size) for ii in range(cliques * clique_size)}
pos = partition_layout(g, partition, ratio=0.2)
nx.draw(g, pos, node_color=list(partition.values()))
plt.show()
def test2():
# create test data
cliques = 8
clique_size = 7
g = nx.connected_caveman_graph(cliques, clique_size)
partition = {ii : np.int(ii/clique_size) for ii in range(cliques * clique_size)}
# add additional between-clique edges
total_nodes = cliques*clique_size
for ii in range(cliques):
start = ii*clique_size + int(clique_size/2)
stop = (ii+cliques/2)*clique_size % total_nodes + int(clique_size/2)
g.add_edge(start, stop)
pos = partition_layout(g, partition, ratio=0.2)
nx.draw(g, pos, node_color=list(partition.values()))
plt.show()
if __name__ == '__main__':
test()
test2()
Addenda
Exemple avec des arêtes inter-cluster supplémentaires comme demandé dans les commentaires:
