Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange Axis BoundingBox "diamond" #79

Open
rtbs-dev opened this issue Dec 6, 2023 · 3 comments
Open

Strange Axis BoundingBox "diamond" #79

rtbs-dev opened this issue Dec 6, 2023 · 3 comments

Comments

@rtbs-dev
Copy link

rtbs-dev commented Dec 6, 2023

Running into a pretty consistent "diamond" bound on my node layouts for some reason. I've tried playing with many layout settings, but so far it only changes the sizes within this odd diamond.

Here's a min. example:

from netgraph import Graph
Graph(G) # pre-existing NX graph
plt.show()

image

Adjusting the scale doesn't seem to do anything to the boundary:

Graph(G, scale=(2,2))

image

Replicating in networkx:

nx.draw_networkx_nodes(
    G, pos=(testpos:=nx.fruchterman_reingold_layout(G)), 
    node_color='white', edgecolors='k', 
    node_size=50, linewidths=0.5,
)
nx.draw_networkx_edges(
    G, pos=testpos
)

image

So I don't think my networkx layouts are reaching some kind of border.

Similarly, the diamond starts appearing for the karate graph:

Graph(nx.karate_club_graph(), scale=(2,2))

image

vs:

nx.draw_networkx_nodes(
    K:=nx.karate_club_graph(), pos=(testpos:=nx.fruchterman_reingold_layout(K)), 
    node_color='white', edgecolors='k', 
    node_size=100, linewidths=0.5,
)
nx.draw_networkx_edges(
    K, pos=testpos
)

image

@paulbrodersen
Copy link
Owner

paulbrodersen commented Dec 7, 2023

Hi, thanks for raising the issue. This is clearly not the intended behaviour.

I think there are two things happening here.

  1. The attractive force acting along the edges isn't enough to counter the repulsion between all nodes. Hence many nodes are pushed to the bounding box, which is a square by default. NetworkX doesn't run into that problem, because their implementation of the FR algorithm differs significantly from the algorithm presented in the FR paper.
  2. After computing a layout, Netgraph determines the major axis of the node positions and then rotates the graph such that this major axis matches the largest extent of the bounding box (width or height, whichever is larger; width if tied). This creates the diamond.

The solution is to increase the spring constant k, e.g.:

Graph(G, node_layout="spring", node_layout_kwargs=dict(k=0.1))

I am unsure why the default value for k is currently so small but I will investigate. In the meantime, please just set it explicitly.
Do let me know if that doesn't fix the issue on your end.

@rtbs-dev
Copy link
Author

rtbs-dev commented Dec 8, 2023

Thanks! Let me see here:

k not set:
image

k=0.01
image

k = 0.1
image

k = 0.2
image

k = 0.5
image

Super interesting. setting scale=(2,2)

k=0.1 looks great
image

k=0.5 actually goes back to the diamond?
image

Reading that issue, there's a super interesting conclusion at the end

So we are in a bit of a dilemma here: If we have no frame, then examples like the ball and chain are not good. If we add a frame, then the ball and chain can be good. However this makes many other layouts bad. To fix that we have to reduce the optimal edge length (e.g. by reducing C), however this reintroduces the original issue.

So in this case I would propose to do the following:

  • implement a frame with inelastic boundaries that is optional and disabled by default.
  • add the constant C as a possible parameter, defaulting to 1.
  • keep the force-calculation and temperature as it was in the original implementation.

So if I'm honest, I nearly always fall in the "not ball and chain" category... stuff like social and semantic (sparse) networks. Border frames...not so useful? Is there a chance the frame could be disabled and avoid this node-size/spring-constant iteration? It's going to make automating my publication plots a bit of a headache 😅

Loving this API, btw, so glad to see a more capable plotting library!

@paulbrodersen
Copy link
Owner

Border frames...not so useful? Is there a chance the frame could be disabled and avoid this node-size/spring-constant iteration?

Border frames are quite useful in other cases but I do agree that there maybe should be an option to use the NetworkX variant instead of the original FR algorithm. I will add it to the list of planned features, but it will be a while until I get to it. In the meantime, if you prefer the NetworkX spring layout, you can simply pre-compute the positions with NetworkX and then pass the node position dictionary to Netgraph:

import matplotlib.pyplot as plt
import networkx as nx
from netgraph import Graph

G = nx.karate_club_graph()
node_positions = nx.spring_layout(G)
Graph(G, node_layout=node_positions)
plt.show()

Loving this API, btw, so glad to see a more capable plotting library!

Glad you like it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants