How to get the coordinates from layout from graphv

2020-04-07 19:34发布

问题:

I have been working with pygraph on some project. I completed this example, it works fine.

Now, the problem is the following: the graph is drawn in a picture format (gif). What I need is to get the actual coordinates for each node for the graph layout shown on the gif image. How do I do this? I've been trying and trying, but couldn't find solution to this problem. I thought the the problem's solution would be somehow with manipulating one of the two following lines:

gv.layout(gvv,'dot')
gv.render(gvv,'png','europe.png')

Thanks in advance!

回答1:

You can add the layout information into the graph with :

gv.render(gvv)

and then find out the position of a node getting its attribute pos :

n_france = gv.findnode(gvv, "France")
pos = gv.getv(n_france, "pos")

Then depending on what you want to do, you might need to convert dot coordinates into png image coordinates. You can get useful information from here :

http://www.graphviz.org/faq/#FaqCoordTransformation

It explains in great details the computation from the graph-units to image pixels.

Hope that this is what you are looking for.



回答2:

Networkx can do this:

import networkx as nx

def setup_europe():
    G = nx.Graph()

    G.add_edge("Portugal", "Spain")
    G.add_edge("Spain","France")
    G.add_edge("France","Belgium")
    G.add_edge("France","Germany")
    G.add_edge("France","Italy")
    G.add_edge("Belgium","Netherlands")
    G.add_edge("Germany","Belgium")
    G.add_edge("Germany","Netherlands")
    G.add_edge("England","Wales")
    G.add_edge("England","Scotland")
    G.add_edge("Scotland","Wales")
    G.add_edge("Switzerland","Austria")
    G.add_edge("Switzerland","Germany")
    G.add_edge("Switzerland","France")
    G.add_edge("Switzerland","Italy")
    G.add_edge("Austria","Germany")
    G.add_edge("Austria","Italy")
    G.add_edge("Austria","Czech Republic")
    G.add_edge("Austria","Slovakia")
    G.add_edge("Austria","Hungary")
    G.add_edge("Denmark","Germany")
    G.add_edge("Poland","Czech Republic")
    G.add_edge("Poland","Slovakia")
    G.add_edge("Poland","Germany")
    G.add_edge("Czech Republic","Slovakia")
    G.add_edge("Czech Republic","Germany")
    G.add_edge("Slovakia","Hungary")
    return G

G = setup_europe()

Write a dot file:

nx.write_dot(G, '/tmp/out.dot')

Compute the position of the nodes:

pos = nx.pygraphviz_layout(G, prog = 'dot')
print(pos)
# {'Netherlands': (713.86, 167.0), 'Italy': (473.86, 389.0), 'Czech Republic': (100.86, 241.0), 'Portugal': (879.86, 315.0), 'England': (1024.9, 241.0), 'Denmark': (568.86, 167.0), 'Poland': (100.86, 167.0), 'Scotland': (1024.9, 389.0), 'France': (571.86, 315.0), 'Belgium': (713.86, 19.0), 'Austria': (320.86, 167.0), 'Slovakia': (156.86, 315.0), 'Wales': (990.86, 315.0), 'Switzerland': (473.86, 241.0), 'Hungary': (294.86, 241.0), 'Germany': (465.86, 93.0), 'Spain': (879.86, 241.0)}

Render an png:

agraph = nx.to_agraph(G)
agraph.draw("/tmp/europe.png", format = 'png', prog = 'dot')



回答3:

I just found a similar solution that works perfectly for my needs

pos = nx.drawing.nx_agraph.graphviz_layout(G, prog='dot', args='-Grankdir=LR')

cheers!



回答4:

Using just pydot/dot you can do it by generating the SVG and then reading the position of the nodes from the SVG. It is a bit hacky, but works reliably enough

from xml.dom import minidom
import pydot
from io import BytesIO
def extract_node_positions(
    in_dot: pydot.Dot
) -> Dict[str, Tuple[str, float, float]]:
    """
    Extract the x,y positions from a pydot graph by rendering
    Args:
        in_dot: the graph to render

    Returns:
        a list of all the nodes

    Examples:
        >>> g = pydot.Dot()
        >>> g.add_node(pydot.Node("A"))
        >>> g.add_node(pydot.Node("B"))
        >>> g.add_node(pydot.Node("C"))
        >>> g.add_edge(pydot.Edge("A", "B"))
        >>> g.add_edge(pydot.Edge("B", "C"))
        >>> extract_node_positions(g)
        {'A': ('A', 27.0, -157.8), 'B': ('B', 27.0, -85.8), 'C': ('C', 27.0, -13.8)}
    """
    node_mapping = {f'node{i}': k.get_name() 
                    for i, k in enumerate(in_dot.get_nodes(), 1)}
    svg_io = BytesIO(in_dot.create_svg())
    doc = minidom.parse(svg_io)  # parseString also exists
    node_dict = {node_mapping[p.getAttribute('id')]: (c_text.firstChild.data,
                                        float(c_text.getAttribute('x')),
                                        float(c_text.getAttribute('y')))

                 for p in doc.getElementsByTagName("g") if "node" == p.getAttribute('class').lower()
                 for c_text in p.getElementsByTagName('text')
                 }
    doc.unlink()
    return node_dict