1
2 '''
3 A dynadag-ish graph layout calculator...
4 '''
5
6 import itertools
7
8 import visgraph
9 import visgraph.layouts as vg_layout
10 import visgraph.graphcore as vg_graphcore
11 import visgraph.drawing.bezier as vg_bezier
12 import visgraph.drawing.catmullrom as vg_catmullrom
13
14 zero_zero = (0,0)
15
17 return itertools.izip(xrange(len(l)-1, -1, -1), reversed(l))
18
19 SCOOCH_LEFT = 0
20 SCOOCH_RIGHT = 1
21
23
32
34 '''
35 Return the width,height of this layout.
36 '''
37 height = 0
38 width = 0
39 for layer in self.layers:
40
41 lheight = 0
42 lwidth = 0
43
44 for nid,ninfo in layer:
45 xsize, ysize = ninfo.get('size', zero_zero)
46 lheight = max(lheight, ysize + self.height_pad)
47 lwidth += xsize + self.width_pad
48
49 height += lheight
50 width = max(lwidth, width)
51
52 return width, height
53
55 tot = 0
56 cnt = 0
57 for eid, fnode, tnode, einfo in self.graph.getRefsFrom(nid):
58 tot += self.graph.getNodeInfo(tnode, 'layerpos')
59 cnt += 1
60 for eid, fnode, tnode, einfo in self.graph.getRefsTo(nid):
61 tot += self.graph.getNodeInfo(fnode, 'layerpos')
62 cnt += 1
63 b = tot / float(cnt)
64 ninfo['barycenter'] = b
65
67 n1bary = node1[1].get('barycenter')
68 n2bary = node2[1].get('barycenter')
69 return cmp(n1bary, n2bary)
70
71
73
74
75 for i in xrange(10):
76 for layer in self.layers:
77 for nid,ninfo in layer:
78 self._baryCenter(nid, ninfo)
79
80 for layer in self.layers:
81 layer.sort(cmp=self._cmpBaryCenter)
82 for i,(nid,ninfo) in enumerate(layer):
83 ninfo['layerpos'] = i
84
86
87 weight = ninfo['weight']
88
89 abovepos = []
90 for eid, fromid, toid, einfo in self.graph.getRefsTo(nid):
91 fprops = self.graph.getNodeProps(fromid)
92 if fprops['weight'] != weight-1:
93 continue
94 abovepos.append(fprops['layerpos'])
95 abovepos.sort()
96
97 belowpos = []
98 for eid, fromid, toid, einfo in self.graph.getRefsFrom(nid):
99 tprops = self.graph.getNodeProps(toid)
100 if tprops['weight'] != weight+1:
101 continue
102 belowpos.append(tprops['layerpos'])
103 belowpos.sort()
104
105 return abovepos, belowpos
106
108 ccount = 0
109
110 layer = self.layers[layernum]
111 for i in xrange(1, len(layer)):
112
113 myabove, mybelow = self._getNodeRelPos(*layer[i])
114 hisabove, hisbelow = self._getNodeRelPos(*layer[i-1])
115
116
117
118 for mya in myabove:
119 for hisa in hisabove:
120 if mya < hisa:
121 ccount += 1
122
123
124
125 for myb in mybelow:
126 for hisb in hisbelow:
127 if myb < hisb:
128 ccount += 1
129
130 return ccount
131
133
134
135
136
137 for i in xrange(len(self.layers)):
138
139 layer = self.layers[i]
140
141 reduced = True
142 while reduced:
143
144 reduced = False
145
146
147 score = self._getLayerCross(i)
148
149
150
151 for j in xrange(len(layer)-1):
152
153 n1 = layer[j]
154 n2 = layer[j+1]
155
156 layer[j] = n2
157 layer[j+1] = n1
158
159 newscore = self._getLayerCross(i)
160
161 if newscore < score:
162 reduced = True
163 n1[1]['layerpos'] = j+1
164 n2[1]['layerpos'] = j
165 break
166
167
168 layer[j] = n1
169 layer[j+1] = n2
170
172 '''
173 Translate the hierarchical graph we are given into dynadag
174 friendly graph with ghost nodes....
175 '''
176 weights = self.graph.getNodeWeights()
177
178
179
180
181 for eid, fromid, toid, einfo in self.graph.getEdges():
182
183 if not einfo.get('reverse'):
184 continue
185
186 topweight = weights.get(fromid)
187 botweight = weights.get(toid)
188
189
190
191
192 if topweight == botweight:
193 bridgenode = self.graph.addNode(ghost=True, weight=topweight)
194 weights[bridgenode] = topweight
195
196 self.graph.delEdge(eid)
197 self.graph.addEdge(fromid, bridgenode, looptop=True)
198 self.graph.addEdge(bridgenode, toid, loopbot=True)
199 continue
200
201
202
203 topnode = self.graph.addNode(ghost=True, weight=topweight)
204 weights[topnode] = topweight
205 botnode = self.graph.addNode(ghost=True, weight=botweight)
206 weights[botnode] = botweight
207
208 self.graph.addEdge(topnode, botnode)
209
210
211 self.graph.delEdge(eid)
212 self.graph.addEdge(fromid, topnode, looptop=True)
213 self.graph.addEdge(botnode, toid, loopbot=True)
214
215
216 for eid, fromid, toid, einfo in self.graph.getEdges():
217 xweight = weights.get(fromid, 0)
218 yweight = weights.get(toid)
219 if xweight + 1 < yweight:
220 self.graph.delEdge(eid)
221 while xweight + 1 < yweight:
222 xweight += 1
223 ghostid = self.graph.addNode(ghost=True, weight=xweight)
224 self.graph.addEdge(fromid, ghostid)
225
226 fromid = ghostid
227 self.graph.addEdge(fromid, toid)
228
230
231 self.maxweight = 0
232
233 for nid, ninfo in self.graph.getNodes():
234 self.maxweight = max(ninfo.get('weight', 0), self.maxweight)
235
236
237 self.layers = [ [] for i in xrange(self.maxweight + 1) ]
238
239 doit_done = {}
240 def doit(nid, ninfo):
241
242 if doit_done.get(nid):
243 return
244
245 doit_done[nid] = True
246
247 for eid, fromid, toid, einfo in self.graph.getRefsFrom(nid):
248 doit(toid, self.graph.getNodeProps(toid))
249
250 w = ninfo.get('weight', 0)
251
252 layer = self.layers[w]
253 ninfo['layerpos'] = len(layer)
254 layer.append((nid,ninfo))
255
256
257 rootnode = self.graph.getRootNodes()[0]
258 doit(rootnode, self.graph.getNodeProps(rootnode))
259
260
261 self._orderNodesByBary()
262 self._bubSortNodes()
263
264 self.maxwidth = 0
265
266
267 lwidths = []
268 self.lheights = []
269 for layer in self.layers:
270 x = self.width_pad
271 y = 0
272
273 heightmax = 0
274 for nid,ninfo in layer:
275 size = ninfo.get('size', zero_zero)
276 xx,yy = size
277
278 heightmax = max(heightmax, yy)
279
280 x += xx
281 y += yy
282
283 lwidths.append(x)
284 self.lheights.append(heightmax)
285
286 self.maxwidth = max(self.maxwidth, x)
287
288
289 vpad = 0
290 for i,layer in enumerate(self.layers):
291 hpad = (self.maxwidth - lwidths[i]) / 2
292 hpad += self.width_pad
293 for nid,ninfo in layer:
294 xpos = hpad
295 ypos = vpad
296
297 xsize, ysize = ninfo.get('size', zero_zero)
298
299 ninfo['position'] = (xpos,ypos)
300 ninfo['vert_pad'] = self.lheights[i] - ysize
301
302 hpad += xsize
303
304 hpad += self.width_pad
305
306 vpad += self.lheights[i]
307 vpad += self.height_pad
308
309
310
311
312
313 for i, layer in enumerate(self.layers):
314
315 layermid = len(layer) / 2
316
317
318 for j, (nid,ninfo) in enumerate(layer):
319
320 if not ninfo.get('ghost'):
321 break
322
323 self._scoochXKids(nid, ninfo, SCOOCH_LEFT)
324
325
326 for j, (nid, ninfo) in revenumerate(layer):
327
328 if not ninfo.get('ghost'):
329 break
330
331 self._scoochXKids(nid, ninfo, SCOOCH_RIGHT)
332
333
334 for i, layer in revenumerate(self.layers):
335
336 layermid = len(layer) / 2
337
338
339 for j, (nid,ninfo) in enumerate(layer):
340
341 if not ninfo.get('ghost'):
342 break
343
344 self._scoochXParents(nid, ninfo, SCOOCH_LEFT)
345
346
347 for j, (nid, ninfo) in revenumerate(layer):
348
349 if not ninfo.get('ghost'):
350 break
351
352 self._scoochXParents(nid, ninfo, SCOOCH_RIGHT)
353
354
355 self._calcEdgeLines()
356
358
359 weight = ninfo['weight']
360
361 for eid, n1, n2, einfo in self.graph.getRefsTo(nid):
362
363 pinfo = self.graph.getNodeProps(n1)
364
365
366 if not pinfo.get('ghost'):
367 continue
368
369
370 if pinfo['weight'] != weight-1:
371 continue
372
373 self._scoochXAlign(ninfo, pinfo, lr=lr)
374
376
377 weight = ninfo['weight']
378
379 for eid, n1, n2, einfo in self.graph.getRefsFrom(nid):
380
381 kinfo = self.graph.getNodeProps(n2)
382
383
384 if not kinfo.get('ghost'):
385 continue
386
387
388 if kinfo['weight'] != weight+1:
389 continue
390
391 self._scoochXAlign(ninfo, kinfo, lr=lr)
392
394 '''
395 If possible, move the "kidinfo" node toward ninfo
396 along the X axis... If "lr" is specified, only move
397 the "kidnode" (which may be "above" you...) if it is
398 moving either SCOOCH_LEFT or SCOOCH_RIGHT as specified.
399 '''
400 xpos, ypos = ninfo['position']
401 xsize, ysize = ninfo.get('size', zero_zero)
402 xmid = xpos + ( xsize / 2 )
403
404 kxpos, kypos = kinfo['position']
405 kxsize, kysize = kinfo.get('size', zero_zero)
406 kxmid = kxpos + ( kxsize / 2 )
407
408 xdelta = xmid - kxmid
409
410
411
412 if lr == SCOOCH_LEFT and xdelta >= 0:
413 return
414
415
416
417 if lr == SCOOCH_RIGHT and xdelta <= 0:
418 return
419
420 self._scoochX(kinfo, xdelta)
421
423
424 layerpos = ninfo.get('layerpos')
425 x, y = ninfo['position']
426 xsize, ysize = ninfo.get('size', zero_zero)
427 layer = self.layers[ninfo['weight']]
428 layermax = len(layer) - 1
429
430
431 if layerpos == 0 and xdelta < 0:
432 ninfo['position'] = (x+xdelta, y)
433 return
434
435
436 if layerpos == layermax and xdelta > 0:
437 ninfo['position'] = (x+xdelta, y)
438 return
439
440
441
442
443
444 if xdelta < 0:
445 snid, sinfo = layer[layerpos - 1]
446 sx, sy = sinfo['position']
447 sxsize, sysize = sinfo.get('size', zero_zero)
448
449 sright = (sx + sxsize) + self.width_pad
450
451
452
453 xdelta = max(xdelta, sright - x)
454 ninfo['position'] = (x+xdelta, y)
455 return
456
457
458
459 if xdelta > 0:
460 snid, sinfo = layer[layerpos + 1]
461 sx, sy = sinfo['position']
462 sxsize, sysize = sinfo.get('size', zero_zero)
463
464 myright = x + xsize + self.width_pad
465
466 xdelta = min(xdelta, sx-myright)
467 ninfo['position'] = (x+xdelta, y)
468 return
469
471
472 h_hpad = self.width_pad / 2
473 h_vpad = self.height_pad / 2
474
475 for eid, fromid, toid, einfo in self.graph.getEdges():
476
477 pre_lines = []
478 post_lines = []
479
480 pinfo = self.graph.getNodeProps(fromid)
481 kinfo = self.graph.getNodeProps(toid)
482
483 pwidth, pheight = pinfo.get('size', (0,0))
484 pweight = pinfo.get('weight')
485 lheight = self.lheights[pweight]
486 voffset = lheight - pheight
487
488 if einfo.get('looptop'):
489
490 x1, y1 = vg_layout.entry_pos(pinfo)
491 x2, y2 = vg_layout.entry_pos(kinfo)
492
493 xhalf = (x1 - x2) / 2
494
495 b = [ (x1, y1),
496 (x1, y1 - h_vpad),
497 (x2, y2 - h_vpad),
498 (x2, y2),
499 ]
500
501 elif einfo.get('loopbot'):
502
503 x1, y1 = vg_layout.exit_pos(pinfo)
504 x2, y2 = vg_layout.exit_pos(kinfo)
505
506 kwidth, kheight = kinfo.get('size', (0,0))
507 kweight = kinfo.get('weight')
508 klheight = self.lheights[kweight]
509
510 kvoffset = klheight - kheight
511
512 pre_lines = [(x1, y1), (x1, y1 + voffset)]
513 post_lines = [(x2, y2), (x2, y2 + kvoffset)]
514
515 b = [ (x1, y1 + voffset),
516 (x1, y1 + voffset + h_vpad),
517 (x2, y2 + kvoffset + h_vpad),
518 (x2, y2 + kvoffset),
519 ]
520
521 else:
522
523 x1, y1 = vg_layout.exit_pos(pinfo)
524 x2, y2 = vg_layout.entry_pos(kinfo)
525
526 pre_lines = [(x1,y1), (x1, y1 + voffset)]
527
528 b = [ (x1, y1 + voffset),
529 (x1, y1 + voffset + h_vpad),
530 (x2, y2 - h_vpad),
531 (x2, y2),
532 ]
533
534 bez_lines = vg_bezier.calculate_bezier(b, 20)
535
536 einfo['edge_points'] = pre_lines + bez_lines + post_lines
537
538