0

I am attempting to autoscale a networkx graph based on the number of nodes and whether there are nodes that overlap in the horizontal direction or the vertical direction (ie. if all nodes were on the same line, they would intersect). To do this, I look at the position of each node and check whether the position plus the node size is between any other nodes coordinates plus the node size. To get the node positions, I am using networkx.nx_agraph.graphviz_layout. The issue is that the positions and the node size are not of the same unit.

Networkx calls pyplot.scatter underneath the hood (source: https://networkx.github.io/documentation/networkx-1.10/_modules/networkx/drawing/nx_pylab.html#draw_networkx_nodes), which takes the node size as an area in pixels (source: https://stackoverflow.com/questions/14827650/pyplot-scatter-plot-marker-size).

Networkx draws the graph with circle nodes, so based on this it would make sense that to convert the node size to inches, I would do node_size * 4 / (math.pi * DPI) where DPI is the DPI used by Matplotlib. In my code this does not seem to work. I have also tried taking the square root of the node size and dividing it by the DPI but that doesn't seem to work either.

Currently the issue is that I am not able to detect what nodes are overlapping.

In summary: If I am plotting a networkx graph with circle nodes, how can I convert the node size and/or the Pygraphviz positions to a common unit?

It could also be that my code is wrong, so here is a short reproducible example:

NODE_SIZE = 300
DPI = 100
mpl.rcParams['figure.dpi'] = DPI

G = nx.gnp_random_graph(50, 0)
pos = nx.nx_agraph.graphviz_layout(G)
tmp_pos = sorted(pos.items(), key=lambda x: x[1][0]) # Sort it by x value so you only have to check to the right of the node in the list of positions
node_size_inches = NODE_SIZE * 4 / (math.pi * DPI) # This is my attempt at getting the height/width of the nodes

i = 0
for key, xy in tmp_pos:
    x = xy[0]
    overlapping = []

    # Since list is sorted by x, you only need to check nodes to the right
    for k, xyt in tmp_pos[i+1:]:

        xt = xyt[0]

        # Check for overlapping nodes to the right
        if x <= xt <= x + node_size_inches:
            overlapping.append(k)

        # Since list is sorted by x, if the previous condition fails, nothing after can be applicable
        else:
            break

    if overlapping: print("{}:{}".format(key, overlapping))

    i += 1

nx.draw(G, pos=pos, with_labels=True)
Robbie
  • 1,733
  • 2
  • 13
  • 20
  • Your code is not the issue. As you noticed, positions are in data coordinates, **node sizes are in axis coordinates**. For a given plot, fixed zoom, and fixed figure dimensions, there is a way to convert between them. I would have a look at the [matplotlib tutorial](https://matplotlib.org/3.1.1/tutorials/advanced/transforms_tutorial.html). Personally, I think that design decision is absolute madness, so I forked my own [network plotting utilities from networkx](https://github.com/paulbrodersen/netgraph) some time ago, which has consistent units for both, positions and plot elements. – Paul Brodersen Oct 24 '19 at 10:13
  • @PaulBrodersen by figure dimensions do you mean the figuresize? I'm trying to scale the figuresize automatically. – Robbie Oct 24 '19 at 19:49
  • Yes, that can influence the transformation from data coordinates to axis coordinates. – Paul Brodersen Oct 25 '19 at 09:02
  • @PaulBrodersen so basically what I am trying to do is not possible? – Robbie Oct 25 '19 at 13:57
  • Everything is possible, solving this particular problem will be very hard though. Basically, you have to convert your positions from data space to axis space, and check overlap there; then rescale your nodes and check again, as the node rescaling can change the dimensions of the axis. Give `netgraph` a try. Using the version on the dev branch, if you give the `draw` function a value for `node_size`, it will prevent node collisions by default. – Paul Brodersen Oct 28 '19 at 10:31

0 Answers0