Version: 8.3.0
graph.py
Go to the documentation of this file.
1 # Copyright (C) 2006-2016 CEA/DEN, EDF R&D
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19 
20 import sys
21 import pilot
22 import Item
23 from qt import *
24 from qtcanvas import *
25 from GraphViewer import GraphViewer
26 import Editor
27 import CItems
28 import pygraphviz
29 from pygraphviz import graphviz as gv
30 import traceback
31 import CONNECTOR
32 import bisect
33 
35  def customEvent(self,event):
36  object=event.data()
37  object.customEvent(event)
38  self.update()
39 
40 class Graph:
41  def __init__(self,item,parent):
42  self.parent=parent
43  self.item=item
44  self.node=item.node
45  #initial canvas size : 1000x1000
46  self.canvas=MyCanvas(1000,1000)
47  self.editor=GraphViewer(self.canvas,parent,"example",0)
48  self.createGraph()
49  root=self.node.getRootNode()
50  rootItem=Item.adapt(root)
51  CONNECTOR.Connect(rootItem,"selected",self.selectItem,())
52  CONNECTOR.Connect(self.item,"add",self.addItem,())
53  CONNECTOR.Connect(self.item.datalinks,"add",self.addLink,())
54 
55  def createGraph(self):
56  #citems dict helps finding items in canvas from swig proxy
57  #To find an item node make : citems[node.ptr()]
58  citems={}
59  self.citems=citems
60  #pitems dict helps finding items in canvas from swig proxy
61  #To find an item port make : pitems[port.ptr()]
62  pitems={}
63 
64  y=0
65  lnode=self.node.edGetDirectDescendants()
66  for n in lnode:
67  c=CItems.Cell(n,self.canvas)
68  citems[n.ptr()]=c
69  c.show()
70 
71  for k,n in citems.items():
72  for p in n.inports:
73  pitems[p.port.ptr()]=p
74  for p in n.outports:
75  pitems[p.port.ptr()]=p
76 
77  for pout,pin in self.node.getSetOfInternalLinks():
78  if pout.getNode().getFather() != self.node and pin.getNode().getFather() != self.node:
79  continue
80  po=pitems.get(pout.ptr())
81  pi=pitems.get(pin.ptr())
82  if pi and po:
83  CItems.LinkItem(po,pi,self.canvas)
84 
85  for n in lnode:
86  itemup=citems[n.ptr()]
87  for ndown in n.getOutNodes():
88  itemdown=citems[ndown.ptr()]
89  CItems.ControlLinkItem(itemup.outgate,itemdown.ingate,self.canvas)
90 
91  self.layout("LR")
92 
93  def addLink(self,link):
94  print "graph.addLink",link
95  #CItem for outport
96  nodeS=self.citems[link.pout.getNode().ptr()]
97  nodeE=self.citems[link.pin.getNode().ptr()]
98  po=pi=None
99  for p in nodeS.outports:
100  if p.port == link.pout:
101  po=p
102  break
103  for p in nodeE.inports:
104  if p.port == link.pin:
105  pi=p
106  break
107 
108  if pi and po:
109  l=CItems.LinkItem(po,pi,self.canvas)
110  self.canvas.update()
111 
112  def addItem(self,item):
113  #print "graph.addItem",item
114  node=CItems.Cell(item.node,self.canvas)
115  self.citems[item.node.ptr()]=node
116  node.show()
117  self.canvas.update()
118 
119  def selectItem(self,item):
120  #print "graph.selectItem",item
121  self.editor.selectItem(item)
122 
123  def layout(self,rankdir):
124  """Compute graph layout with graphviz package"""
125  G=pygraphviz.AGraph(strict=False,directed=True)
126  G.graph_attr["rankdir"]=rankdir
127  G.graph_attr["dpi"]="72"
128  dpi=72.
129  aspect=dpi/72
130  for k,n in self.citems.items():
131  #k is node address (YACS)
132  #n is item in canvas
133  G.add_node(k)
134 
135  for pout,pin in self.node.getSetOfInternalLinks():
136  if pout.getNode().ptr() not in self.citems :
137  continue
138  if pin.getNode().ptr() not in self.citems:
139  continue
140  G.add_edge(pout.getNode().ptr(),pin.getNode().ptr())
141 
142  for k,n in self.citems.items():
143  for ndown in n.node.getOutNodes():
144  G.add_edge(n.node.ptr(),ndown.ptr())
145 
146  #By default graphviz uses 96.0 pixel per inch (dpi=96.0)
147  for n in G.nodes():
148  item=self.citems[int(n)]
149  h=item.height()/dpi #height in inch
150  w=item.width()/dpi #width in inch
151  n.attr['height']=str(h)
152  n.attr['width']=str(w)
153  n.attr['fixedsize']="true"
154  n.attr['shape']="box"
155  #n.attr['label']=item.node.getName()
156 
157  G.layout(prog='dot') # use dot
158  #G.write("layout.dot")
159  #G.draw("layout.png")
160 
161  graph_attr=dict(attrs(G))
162  bbox=graph_attr["bb"]
163  x1,y1,x2,y2=eval(bbox)
164  h=self.canvas.height()
165  w=self.canvas.width()
166  h2=max(h,y2-y1+100)
167  w2=max(w,x2-x1+100)
168  if h2 > h or w2 > w:
169  self.canvas.resize(w2,h2)
170 
171  for n in G:
172  pos=n.attr['pos'] #position is given in points (72 points par inch, so 1 point = dpi/72=1.34)
173  x,y=eval(pos)
174  x=aspect*x
175  y=aspect*y
176  item=self.citems[int(n)]
177  x0=item.x()
178  y0=item.y()
179  x=x-x0
180  y=y-y0
181  item.moveBy(x,y)
182 
183  self.canvas.update()
184 
185  def clearLinks(self):
186  items=self.citems.values()
187  for node in items:
188  for port in node.outports:
189  if not hasattr(port,"links"):
190  continue
191  for link in port.links():
192  link.clearPoints()
193  self.canvas.update()
194 
195  def orthoLinks(self):
196  items=self.citems.values()
197  g=grid(items)
198  for node in items:
199  for port in node.outports:
200  if not hasattr(port,"links"):
201  continue
202  for link in port.links():
203  #clear all intermediate points of the link
204  link.clearPoints()
205  #if isinstance(link,CItems.ControlLinkItem):
206  # print port.port.getNode().getName() +"->"+link.toPort.port.getNode().getName()
207  #else:
208  # print port.port.getNode().getName() +":"+port.port.getName()+"->"+link.toPort.port.getNode().getName()+":"+link.toPort.port.getName()
209  #print (port.x(),port.y()),(link.toPort.x(),link.toPort.y())
210  x0,y0=port.x()+5,port.y()
211  while g.get((x0,y0)).blocked:
212  x0=x0+1
213  x1,y1=link.toPort.x()-5,link.toPort.y()
214  while g.get((x1,y1)).blocked:
215  x1=x1-1
216  path=g.findPath((x0,y0),(x1,y1))
217  #print path
218  if len(path)==1:
219  if port.y() == link.toPort.y():
220  #near ports face to face
221  continue
222  else:
223  x,y=path[0]
224  path=[(x,port.y()),(x,link.toPort.y())]
225  elif len(path)==2:
226  x1,y1=path[0]
227  x2,y2=path[1]
228  if x1 == x2:
229  #vertical line
230  path=[(x1,port.y()),(x1,link.toPort.y())]
231  else:
232  #horizontal line
233  if port.y() == link.toPort.y():
234  #near ports face to face
235  continue
236  else:
237  #transform it into a vertical line
238  x=(x1+x2)/2
239  path=[(x,port.y()),(x,link.toPort.y())]
240 
241  #adjust the first point to the same y as port
242  x0,y0=path[0]
243  x1,y1=path[1]
244  if y0==y1:
245  #remove first point and adjust second one
246  del path[0]
247  x0=x1
248  path[0]=x0,port.y()
249  #adjust the last point to the same y as link.toPort
250  x0,y0=path[-1]
251  x1,y1=path[-2]
252  if y0==y1:
253  #remove last point and adjust new last one
254  del path[-1]
255  x0=x1
256  path[-1]=x0,link.toPort.y()
257  #print path
258 
259  #add intermediate points
260  for x,y in path:
261  line=link.lines[-1]
262  link.splitLine(line,x,y)
263  self.canvas.update()
264 
265 def attrs(g,t=gv.AGRAPH):
266  ah=None
267  while 1:
268  ah=gv.agnxtattr(g.handle,t,ah)
269  value=gv.agxget(g.handle,ah)
270  yield gv.agattrname(ah),value
271 
272 def h(x,y,destx,desty):
273  return abs(destx-x)+abs(desty-y)
274 
275 def distance(node,new_node):
276  x,y=node.coord
277  x1,y1=new_node.coord
278  d= abs(x1-x)+abs(y1-y)
279  if node.parent != None:
280  x0,y0=node.parent.coord
281  if (x1-x)*(y0-y)-(y1-y)*(x0-x) != 0:
282  #corner add some cost to penalize
283  d=d+1
284  return d
285 
286 class node:
287  def __init__(self,coord,index):
288  self.coord=coord
289  self.index=index
290  self.blocked=0
291  self.total=0
292  self.path_cost=0
293  self.parent=None
294  self.open=0
295  self.close=0
296 
297 class grid:
298  def __init__(self,graph):
299  self.graph=graph
300  xs=set()
301  ys=set()
302  xmargin=5
303  ymargin=5
304  for n in graph:
305  h=n.height()
306  w=n.width()
307  x=n.x()
308  y=n.y()
309  xs.add(x-xmargin)
310  xs.add(x+w+xmargin)
311  ys.add(y-ymargin)
312  ys.add(y+h+ymargin)
313 
314  xs=list(xs)
315  xs.sort()
316  x0=xs[0]-36
317  xs.insert(0,x0)
318  x0=xs[-1]+36
319  xs.append(x0)
320 
321  ys=list(ys)
322  ys.sort()
323  y0=ys[0]-36
324  ys.insert(0,y0)
325  y0=ys[-1]+36
326  ys.append(y0)
327 
328  self.xs=xs
329  self.ys=ys
330  self.cols=[]
331  for w in xrange(len(xs)-1):
332  col=[]
333  x=(xs[w]+xs[w+1])/2
334  for h in xrange(len(ys)-1):
335  y=(ys[h]+ys[h+1])/2
336  col.append(node((x,y),(w,h)))
337  self.cols.append(col)
338 
339  for n in graph:
340  h=n.height()
341  w=n.width()
342  x=n.x()
343  y=n.y()
344  l1,l2=bisect.bisect_left(ys,y-ymargin),bisect.bisect_left(ys,y+h+ymargin)
345  i1,i2=bisect.bisect_left(xs,x-xmargin),bisect.bisect_left(xs,x+w+xmargin)
346  for c in self.cols[i1:i2]:
347  for e in c[l1:l2]:
348  e.blocked=1
349 
350  #for col in self.cols:
351  # print [e.coord +(e.blocked,) for e in col]
352 
353  def reset(self):
354  for c in self.cols:
355  for n in c:
356  n.open=n.close=0
357  n.total= n.path_cost=0
358  n.parent=None
359 
360  def get(self,coord):
361  x,y=coord
362  col= bisect.bisect_left(self.xs,x)-1
363  if col < 0 or col >= len(self.cols):
364  return None
365  col=self.cols[col]
366  row=bisect.bisect_left(self.ys,y)-1
367  if row < 0 or row >= len(col):
368  return None
369  return col[row]
370 
371  def getPath(self,node):
372  path=[node.coord]
373  parent=node.parent
374  while parent:
375  prev=node
376  node=parent
377  parent=node.parent
378  if parent:
379  #if points are aligned don't keep the middle point
380  x,y=node.coord
381  x0,y0=prev.coord
382  x1,y1=parent.coord
383  if (x1-x)*(y0-y)-(y1-y)*(x0-x) == 0:
384  continue
385  path.append(node.coord)
386  path.reverse()
387  return path
388 
389  def neighbours(self,node):
390  col,row=node.index
391  l=[]
392  steps=((0, +1), (+1, 0), (0, -1), (-1, 0 ))
393  for step in steps:
394  c=col+step[0]
395  r=row+step[1]
396  if c < 0 or c >=len(self.cols):
397  continue
398  co=self.cols[c]
399  if r <0 or r >= len(co):
400  continue
401  n=co[r]
402  if n.blocked:
403  continue
404  l.append(n)
405  return l
406 
407  def findPath(self,fromLoc,toLoc):
408  """Find shortest path from fromLoc to toLoc"""
409  self.reset()
410  self.open=[]
411  fromNode=self.get(fromLoc)
412  self.open.append((fromNode.total,fromNode))
413  toNode=self.get(toLoc)
414  if toNode.blocked:
415  print "toNode is blocked"
416  return []
417  destx,desty=toNode.coord
418  while self.open:
419  #if open is not void, take the best node (the first one)
420  t,node=self.open.pop(0)
421  node.close=1
422  if node == toNode:
423  #got toLoc
424  return self.getPath(node)
425 
426  for new_node in self.neighbours(node):
427  if new_node.close :
428  continue
429  x,y =new_node.coord
430  path_cost=node.path_cost+distance(node,new_node)
431  total=path_cost+h(x,y,destx,desty)
432  if new_node.open :
433  #the node is already in open
434  if total < new_node.total:
435  self.open.remove((new_node.total,new_node))
436  new_node.path_cost=path_cost
437  new_node.parent=node
438  new_node.total=total
439  bisect.insort(self.open,(new_node.total,new_node))
440  else:
441  #the node is not yet in open
442  new_node.path_cost=path_cost
443  new_node.parent=node
444  new_node.total=total
445  bisect.insort(self.open,(new_node.total,new_node))
446  new_node.open=1
447 
448  return []
449