How to get the coordinates from layout from graphviz? - python

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!

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.

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!

Using pydotplus you can load in and parse a dot/gv file and interrogate the data structure pydotplus produces, but this internal representation seems not to initially possess all the node attributes, like pos, unless they were already in the file.
But you can also call .write_dot() to produce a much more verbose dot file version. If you parse this then the resulting data structure seems to have pos of all the nodes (and even pos for the splines)
Note: maybe best to index the nodes by name not by index because any text with square brackets after it in the verbose file will be parsed as a node, so the node list may have spurious extra elements.
In the following (slightly edited) experiment at the Spyder prompt I have a terse dot file interior.gv (that does not have pos for nodes) which I .graph_from_dot_file(), then .write_dot(). Then .graph_from_dot_file() again on the verbose generated file, and so find the pos as required.
import pydotplus as pdp
interior = pdp.graphviz.graph_from_dot_file('interior.gv')
interior.write_dot('interior2.dot')
Out[210]: True
interior2 = pdp.graphviz.graph_from_dot_file('interior2.dot')
interior2.get_nodes()[3].get_pos()
Out[214]: '"213.74,130"'

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

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

To directly access the results from the Graphviz rendering command (e.g. dot) as binary data string from within Python instead of writing to a file, use the pipe()-method of your Graph or Digraph object:
h = Graph('hello', format='svg')
h.edge('Hello', 'World')
print(h.pipe().decode('utf-8'))

I struggled with this recently, and the answers here were some help but not quite there. The suggestions about networkx are on the right track. Networkx uses pygraphviz to interface with graphviz, and so you can instead use pygraphviz directly if you wish:
import pygraphviz as pgv
G = pgv.AGraph(strict=False,directed=True)
# add nodes and edges
G.add_edge(1,2)
G.add_edge(2,1)
# generate a layout--this creates `pos` attributes for both nodes and edges
G.layout(prog="dot")
#change something about the graph that would change the layout, e.g.,
edge = G.get_edge("1", "2")
edge.attr["label"] = "test label"
# create the graph using the layout already generated; note, do not provide `prog`
G.draw("test.pdf")
# compare it to a newly generated layout
G.draw("test2.pdf", prog="dot")
The important part is to not provide prog to the draw command if you want to use the node and edge positions already generated by the layout command. I see now this is stated in the pygraphviz docstring for draw. BTW, it is does the same as prog="neato" with the -n2 argument. Looking at the sources, pygraphviz calls graphviz to generate the layout.

Related

How to make a tree network graph in python with networkx (algorithm question)?

I have a list with 7 elements:
elements = ['A','B','C','D','E','F','G']
and I want to make a network starting from A in the center, and going down all the possible paths to each other element, so it would look something like this:
## A->[B1,C1,D1,E1,F1,G1]
## B1 -> [C2,D2,E2,F2,G2] for each of [B1,C1,...,G1]
## C2 -> [D3,E3,F3,G3] for each of the above, etc.
I'm currently trying with the networkx package, the end goal would then be to make it a circular tree graph, maybe color the nodes by the letter or assign some weights etc.
For now though I'm kinda stuck on the aforementioned problem, I've tried many things and it feels like it shouldn't be too tough but I'm not too experienced in these algorithm problems. It looks like it should be some kind of recursion problem. Here's one of the things I've tried if it helps:
def add_edges(network, edge_list,i,previous_ele):
edge_list1 = edge_list.copy()
for ele in edge_list:
network.add_edge(previous_ele+str(i),ele+str(i+1))
edge_list1.remove(ele)
add_edges(network, edge_list1, i+1, ele)
N = nx.DiGraph()
elements = ['A','B','C','D','E','F','G']
elements.remove('A')
for ele in elements:
N.add_edge('A',ele+'1')
for i in range(len(elements)):
add_edges(N, elements, 1, elements[i])
Thanks in advance for any tips!
I'm not sure if I completely understood what you're trying to do, but here's a script that does my best guess. The same script works for any number of elements, but it becomes difficult to see the graph clearly.
import networkx as nx
from itertools import combinations
elements = ['A','B','C','D']
G = nx.DiGraph()
for c in elements[1:]:
G.add_edge(elements[0], c+'1')
for i in range(1,len(elements)-1):
for a,b in combinations(elements[i:],2):
G.add_edge(a+str(i),b+str(i+1))
The resulting graph, drawn using nx.draw(G, with_labels = True, node_color = "orange"):
As a bonus, here's a way to draw the graph so that the hierarchy is clear. After the first block of code, add the following:
pos = {elements[0]:(0,0)}
for i in range(1,len(elements)):
for j in range(1,len(elements)):
pos[elements[j]+str(i)] = (j-1,-i)
nx.draw(G, pos = pos, with_labels = True, node_color = "orange")
The resulting drawing for your original list of elements:

Converting a graphviz Source object to graphviz Graph object

I've got a template schematic made in raw .dot format, but I now want to populate the labels using python.
with the https://pypi.org/project/graphviz/ library I've managed to load the .dot file but don't see how I can edit it. Is it possible to convert a Source object to a Graph object, or otherwise use the methods available to the Graph object?
trying:
from graphviz import Source
src = Source('digraph "the holy hand grenade" { rankdir=LR; 1 -> 2 -> 3 -> lob }')
src.node("foo")
_ = src.render('test.gv', view=False)
Source.from_file('test.gv')
I get the error AttributeError: 'Source' object has no attribute 'node'
Following the great answer of #Ray Ronnaret, this is what worked for me.
I have no comments in my dot file, thus it becomes as simple as follows:
from graphviz import Source, Digraph
s = Source.from_file('viz.dot')
g = Digraph()
source_lines = str(s).splitlines()
# Remove 'digraph tree {'
source_lines.pop(0)
# Remove the closing brackets '}'
source_lines.pop(-1)
# Append the nodes to body
g.body += source_lines
Then, I was able to edit the graph.
g.graph_attr['rankdir'] = 'LR'
g.unflatten(stagger=3).render()
I do parse the grapviz.source eliminate non necessary char and append back. This work for me. Note that this assume first line may be contain comment. Thing remains is to make it be function.
src = Source.from_file('Source.gv')
lst = str(src).splitlines()
HasComment = (lst[0].find('//') != -1)
IsDirectGraph = False
skipIndex = 0
if HasComment:
skipIndex = 1
if lst[skipIndex].find('graph {')!=-1 :
IsDirectGraph= False
else:
if lst[0].find('graph {')!=-1 :
IsDirectGraph= False
if IsDirectGraph:
g = Digraph()
else:
g = Graph()
g.body.clear()
s = str()
for i in range(len(lst)):
if( (i>skipIndex) and (i!=len(lst)-1) ):
if HasComment:
g.comment = lst[0].replace('//','')
g.body.append(lst[i])
print(g.source)
display(g)
https://github.com/xflr6/graphviz/issues/76
answers the question that it is not possible with that library, but other ones are available which can.

graphviz Python Node Shapes-Pie Chart in a node

I am writing a Python script to generate a network graph using graphviz. Some of my nodes represents injection into a network and I am wondering if it is possible to have a Pie-Chart inside some of the nodes.
Python code for a simple two node network is following:
import graphviz as gv
g1 = gv.Graph(format='svg')
g1.node('A')
g1.node('B')
g1.edge('A', 'B')
filename = g1.render(filename='img/g1')
I let the PyGraphViz implementation up to you. But to answer to the core of your question, since graphviz 2.30, you can use the wedged style for nodes, to achieve the desired result. Here is an example in plain dot:
digraph G {
{
node [shape=circle style=wedged fillcolor="red;0.3:green;0.6:orange"]
A
node [style=solid fillcolor="white" ]
B
C
}
B -> A
B -> C
}
The list of colors is expressed as a colon separated list. The value after the **semi-colon* in the weight of the given color. The sum of all weights must be equal to 1.0. See the colorList attribute

How to construct graphs in Metis for Python

I am using Metis for Python, a Python wrapper for Metis (a graphs partitioning software). I have everything installed and it seems to work correctly, however I do not understand how can I construct a graph to input.
There is an online example in: http://metis.readthedocs.org/en/latest/#example
>>> import networkx as nx
>>> import metis
>>> G = metis.example_networkx()
>>> (edgecuts, parts) = metis.part_graph(G, 3)
>>> colors = ['red','blue','green']
>>> for i, p in enumerate(parts):
... G.node[i]['color'] = colors[p]
...
>>> nx.write_dot(G, 'example.dot') # Requires pydot or pygraphviz
I ran this example and it works fine. However in this example they never specify how to construct the graph “example_networkx()”.
I have tried to construct graphs by networkx : http://metis.readthedocs.org/en/latest/#metis.networkx_to_metis
my code is:
>>> A=nx.Graph()
>>> A.add_edges_from([(3,1),(2,3),(1,2),(3,4),(4,5),(5,6),(5,7),(7,6),(4,10),(10,8),(10,9),(8,9)])
>>> G = metis.networkx_to_metis(A)
>>> (edgecuts, parts) = metis.part_graph(G, 3)
I get an error in the last line. The error is traced back to these lines in the Metis built-in code:
in part_graph(graph, nparts, tpwgts, ubvec, recursive, **opts)
graph = adjlist_to_metis(graph, nodew, nodesz)
in adjlist_to_metis(adjlist, nodew, nodesz)
m2 = sum(map(len, adjlist))
TypeError: object of type 'c_long' has no len()
I have also tried to construct graphs by adjacency list: http://metis.readthedocs.org/en/latest/#metis.adjlist_to_metis
but this gives the same error as before.
I was wondering if anyone has had this problem, or has any idea what I'm doing wrong.
I'm using python 2.7 on Centos 6.5
The metis.part_graph accepts both networkx and adjacency list representation of graphs.
you were almost right when you constructed a networkx graph. However, you should directly pass this graph to part_graph function rather than first converting it to a metis object since part_graph function does not directly accept a metis type graph.
Given an adjajancy matrix A in numpy, an example can be:
# since weights should all be integers
G = networkx.from_numpy_matrix(np.int32(A))
# otherwise metis will not recognize you have a weighted graph
G.graph['edge_weight_attr']='weight'
[cost, parts] = metis.part_graph(G, nparts=30, recursive=True)

How to add and show weights on edges of a undirected graph using PyGraphviz?

import pygraphviz as pgv
A = pgv.AGraph()
A.add_node('Alice')
A.add_node('Emma')
A.add_node('John')
A.add_edge('Alice', 'Emma')
A.add_edge('Alice', 'John')
A.add_edge('Emma', 'John')
print A.string()
print "Wrote simple.dot"
A.write('simple.dot') # write to simple.dot
B = pgv.AGraph('simple.dot') # create a new graph from file
B.layout() # layout with default (neato)
B.draw('simple.png') # draw png
print 'Wrote simple.png'
I want to add weights to the edges which should also show up on the figure.
You can add attributes to the edges when you create them:
A.add_edge('Alice', 'Emma', weight=5)
or you can set them later with:
edge = A.get_edge('Alice', 'Emma')
edge.attr['weight'] = 5
To add textual information to edges, give them a label attribute instead:
edge = A.get_edge('Alice', 'Emma')
edge.attr['label'] = '5'
All attributes are internally stored as strings but GraphViz interprets these as specific types; see the attribute documentation.

Categories

Resources