I'm trying to make a graph to visualize parts in a product and their type of connection inbetween.
The labels between the nodes are important, not the labels on the nodes themselves. Sometimes the connection lines are not long enough to fit their label or are intersecting.
I have seen another SO question but that one is about the labels ON the nodes.
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
# add nodes
nodes = set(df["part_a"]).union(set(df["part_b"]))
G.add_nodes_from(nodes)
# add edges
for _, row in df.iterrows():
G.add_edge(row["part_a"], row["part_b"], type=row["type_of_connection"], force=row["force"], tool=row["tool"])
# set positions
pos = nx.spring_layout(G, k=0.1, scale=2)
# draw
plt.figure(figsize=(10, 6))
nx.draw(G, pos, with_labels=True, node_size=700, node_color="lightblue", edge_color="gray", alpha=0.8)
# tryin to reduce overlap
edge_labels = {(row["part_a"], row["part_b"]): row["type_of_connection"] for _, row in df.iterrows()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=9, label_pos=0.3)
plt.title(f"Node Graph of Part {df_product['id'].values[0]}: {df_product['brand'].values[0]} {df_product['model'].values[0]}")
plt.show()
However in the following picture you can see that sometimes the connection lines are too short to fit the label, or connection lines are intersecting. What can I do? Tried messing with k value or scale but it does not seem to help
Unfortunately, networkx doesn't have a graph layout function (that I know of) that allows you to place constraints on the length of connections. Also, spring_layout (which is what you have opted for and the networkx default) tends to behave poorly for disconnected graphs; the connected components will be slowly pushed apart if the algorithm is allowed to go on for too many iterations.
The approach that I think works best here is to plot the two connected components in separate subplots. I also found it useful to move the label position back into the center of the edge.
Here's some code that does this
df = pd.DataFrame({
"part_a": [1, 2, 2, 4, 2, 2, 9, 9, 6, 7, 8, 8, 8, 8, 10],
"part_b": [2, 3, 5, 5, 9, 10, 11, 14, 7, 8, 13, 12, 15, 16, 9],
"type_of_connection": ["snap-fit",
"snap-fit",
"screw",
"screw",
"snap-fit",
"screw",
"clip",
"snap-fit",
"screw",
"screw",
"screw",
"snap-fit",
"screw",
"other",
"screw"]})
G = nx.Graph()
# add nodes
nodes = set(df["part_a"]).union(set(df["part_b"]))
G.add_nodes_from(nodes)
# add edges
for _, row in df.iterrows():
G.add_edge(row["part_a"], row["part_b"], type=row["type_of_connection"])
components = list(nx.connected_components(G))
n = len(components)
all_edge_labels = {(row["part_a"], row["part_b"]): row["type_of_connection"] for _, row in df.iterrows()}
# draw
plt.figure(figsize=(10, 6))
for i, component in enumerate(components, 1):
H = nx.induced_subgraph(G, component)
pos = nx.spring_layout(H, k=0.1, scale=2)
edge_labels = {(a, b): conn_type for (a,b), conn_type in all_edge_labels.items() if a in component}
plt.subplot(1, n, i)
nx.draw(H, pos, with_labels=True, node_size=700, node_color="lightblue", edge_color="gray", alpha=0.8)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=9, label_pos=0.5)
plt.show()
This isn't guaranteed to completely remove node on edge-label overlap, but I found that after 1-3 tries I seem to end up with a "lucky" layout with the overlap removed. Here's an example
For some alternative strategies, you could use the same method but reduce the iterations
parameter of nx.spring_layout
(somewhere between 5 to 10 seems to work well here; the default is 50) and you could try a completely different layout function (nx.planar_layout
seems to do surprisingly well here, but I don't think you can guarantee its performance for more complicated graphs).