Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NetworkX cluster nodes in a circular formation based on node color

I had the same question as this one. The solution works, however, I cannot seem to space out the nodes and make them appear in a circular format with my dataset. I have around 30 nodes in total that are color-coded.

The nodes of the same color are overlapping instead of being clustered in a circular format/more concentric.

I used the code in the question above, and tried all radii values possible but cannot seem to make the nodes of the same color cluster in a circle.

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

I'm getting the following output: enter image description here

Desired output (as in the solution): enter image description here

like image 987
nandz123 Avatar asked Nov 30 '20 07:11

nandz123


People also ask

What is the local clustering coefficient for node C in the graph?

Number of possible pairs that can be formed using these 4 nodes are 4* (4-1)/2 = 6. Number of actual pairs that are adjacent to each other = 2. These are (A, B) and (E, F). Thus Local Clustering Coefficient for node C in the given Graph = 2/6 = 0.667.

How many examples of NetworkX clustering are there?

The following are 30 code examples for showing how to use networkx.clustering () . These examples are extracted from open source projects. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.

What is the difference between radius and periphery in NetworkX?

Radius : It is the minimum eccentricity value of a node. Periphery : It is the set of nodes that have their eccentricity equal to their Diameter. Center : Center of a Graph is the set of nodes whose eccentricity is equal to the radius of the Graph. Networkx offers built-in function for computing all these properties.

How to add an edge between two nodes in NetworkX?

This network has now 3 nodes .. To add an edge between two nodes, name the first and the second node that you want to connect with each other. We are also able to plot this network to get a visual understanding too. So to summarize this paragraph, we know what a network consists of and how it can be built and visualized with networkx.


1 Answers

As @willcrack suggested, slightly adapting this answer works well.

You can adjust the node overlap by changing the ratio parameter in partition_layout.

enter image description here

#!/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()

Addendum

Example with additional inter-cluster edges as requested in comments:

enter image description here

like image 190
Paul Brodersen Avatar answered Sep 20 '22 11:09

Paul Brodersen