def _layoutDynadag(self, data): for nid, nprops in self.graph.getNodes(): width, height = data[str(nid)] self.graph.setNodeProp((nid, nprops), "size", (width, height+7)) self.dylayout = vg_dynadag.DynadagLayout(self.graph, barry=20) self.dylayout.layoutGraph() width, height = self.dylayout.getLayoutSize() svgid = 'funcgraph_%.8x' % self.fva svgjs = f'svgwoot("vbody", "{svgid}", {width+18}, {height});' for nid, nprops in self.graph.getNodes(): cbva = nprops.get('cbva') if cbva is None: continue xpos, ypos = nprops.get('position') foid = 'fo_cb_%.8x' % cbva cbid = 'codeblock_%.8x' % cbva js = f''' var node = document.getElementById("{cbid}"); addSvgForeignObject("{svgid}", "{foid}", node.offsetWidth+16, node.offsetHeight+7); addSvgForeignHtmlElement("{foid}", "{cbid}"); moveSvgElement("{foid}", {xpos}, {ypos}); ''' svgjs += js self.mem_canvas.page().runJavaScript(svgjs, self._layoutEdges)
def renderFunctionGraph(self, fva): self.fva = fva #self.graph = self.vw.getFunctionGraph(fva) self.graph = viv_graphutil.buildFunctionGraph(self.vw, fva, revloop=True) # Go through each of the nodes and render them so we know sizes for node in self.graph.getNodes(): #cbva,cbsize = self.graph.getCodeBlockBounds(node) cbva = node[1].get('cbva') cbsize = node[1].get('cbsize') self.mem_canvas.renderMemory(cbva, cbsize) # Let the renders complete... eatevents() frame = self.mem_canvas.page().mainFrame() frame.evaluateJavaScript(funcgraph_js) for nid,nprops in self.graph.getNodes(): cbva = nprops.get('cbva') cbname = 'codeblock_%.8x' % cbva girth, ok = frame.evaluateJavaScript('document.getElementById("%s").offsetWidth;' % cbname).toInt() height, ok = frame.evaluateJavaScript('document.getElementById("%s").offsetHeight;' % cbname).toInt() self.graph.setNodeProp((nid,nprops), "size", (girth, height)) self.dylayout = vg_dynadag.DynadagLayout(self.graph) self.dylayout._barry_count = 20 self.dylayout.layoutGraph() width, height = self.dylayout.getLayoutSize() svgid = 'funcgraph_%.8x' % fva frame.evaluateJavaScript('svgwoot("vbody", "%s", %d, %d);' % (svgid, width+18, height)) for nid,nprops in self.graph.getNodes(): cbva = nprops.get('cbva') if cbva == None: continue xpos, ypos = nprops.get('position') girth, height = nprops.get('size') foid = 'fo_cb_%.8x' % cbva cbid = 'codeblock_%.8x' % cbva frame.evaluateJavaScript('addSvgForeignObject("%s", "%s", %d, %d);' % (svgid, foid, girth+16, height)) frame.evaluateJavaScript('addSvgForeignHtmlElement("%s", "%s");' % (foid, cbid)) frame.evaluateJavaScript('moveSvgElement("%s", %d, %d);' % (foid, xpos, ypos)) # Draw in some edge lines! for eid, n1, n2, einfo in self.graph.getEdges(): points = einfo.get('edge_points') pointstr = ' '.join(['%d,%d' % (x,y) for (x,y) in points ]) frame.evaluateJavaScript('drawSvgLine("%s", "edge_%.8s", "%s");' % (svgid, eid, pointstr)) self.updateWindowTitle()
def test_visgraph_dynadag(self): g1 = self.sampGraph1() lyt = v_dynadag.DynadagLayout(g1) lyt.layoutGraph() self.assertEqual(g1.getNode('a')[1].get('position'), (20, 0)) self.assertEqual(g1.getNode('b')[1].get('position'), (20, 40)) self.assertEqual(g1.getNode('c')[1].get('position'), (40, 40)) self.assertEqual(g1.getNode('d')[1].get('position'), (20, 80)) g2 = self.sampGraph2() lyt = v_dynadag.DynadagLayout(g2) lyt.layoutGraph() self.assertEqual(g2.getNode('a')[1].get('position'), (20, 0)) self.assertEqual(g2.getNode('b')[1].get('position'), (20, 40)) self.assertEqual(g2.getNode('c')[1].get('position'), (40, 120)) self.assertEqual(g2.getNode('d')[1].get('position'), (20, 80)) self.assertEqual(g2.getNode('e')[1].get('position'), (20, 120))
class VQVivFuncgraphView(vq_hotkey.HotKeyMixin, e_qt_memory.EnviNavMixin, QWidget, vq_save.SaveableWidget, viv_base.VivEventCore): _renderDoneSignal = pyqtSignal() viewidx = itertools.count() def __init__(self, vw, vwqgui): self.vw = vw self.fva = None self.graph = None self.vwqgui = vwqgui self._last_viewpt = None self.history = collections.deque((), 100) QWidget.__init__(self, parent=vwqgui) vq_hotkey.HotKeyMixin.__init__(self) viv_base.VivEventCore.__init__(self, vw) e_qt_memory.EnviNavMixin.__init__(self) self.setEnviNavName('FuncGraph%d' % self.viewidx.next()) self._renderDoneSignal.connect(self._refresh_cb) self.top_box = QWidget(parent=self) hbox = QHBoxLayout(self.top_box) hbox.setContentsMargins(2, 2, 2, 2) hbox.setSpacing(4) self.histmenu = QMenu(parent=self) self.histmenu.aboutToShow.connect(self._histSetupMenu) self.hist_button = QPushButton('History', parent=self.top_box) self.hist_button.setMenu(self.histmenu) self.addr_entry = QLineEdit(parent=self.top_box) self.mem_canvas = VQVivFuncgraphCanvas(vw, syms=vw, parent=self) self.mem_canvas.setNavCallback(self.enviNavGoto) self.mem_canvas.refreshSignal.connect(self.refresh) self.mem_canvas.paintUp.connect(self._hotkey_paintUp) self.mem_canvas.paintDown.connect(self._hotkey_paintDown) self.mem_canvas.paintMerge.connect(self._hotkey_paintMerge) self.loadDefaultRenderers() self.addr_entry.returnPressed.connect(self._renderMemory) hbox.addWidget(self.hist_button) hbox.addWidget(self.addr_entry) vbox = QVBoxLayout(self) vbox.setContentsMargins(4, 4, 4, 4) vbox.setSpacing(4) vbox.addWidget(self.top_box) vbox.addWidget(self.mem_canvas, stretch=100) self.top_box.setLayout(hbox) self.setLayout(vbox) self.updateWindowTitle() # Do these last so we are all setup... vwqgui.addEventCore(self) vwqgui.vivMemColorSignal.connect(self.mem_canvas._applyColorMap) self.addHotKey('esc', 'mem:histback') self.addHotKeyTarget('mem:histback', self._hotkey_histback) self.addHotKey('ctrl+0', 'funcgraph:resetzoom') self.addHotKeyTarget('funcgraph:resetzoom', self._hotkey_resetzoom) self.addHotKey('ctrl+=', 'funcgraph:inczoom') self.addHotKeyTarget('funcgraph:inczoom', self._hotkey_inczoom) self.addHotKey('ctrl+-', 'funcgraph:deczoom') self.addHotKeyTarget('funcgraph:deczoom', self._hotkey_deczoom) self.addHotKey('f5', 'funcgraph:refresh') self.addHotKeyTarget('funcgraph:refresh', self.refresh) self.addHotKey('ctrl+u', 'funcgraph:paintup') self.addHotKeyTarget('funcgraph:paintup', self._hotkey_paintUp) self.addHotKey('ctrl+d', 'funcgraph:paintdown') self.addHotKeyTarget('funcgraph:paintdown', self._hotkey_paintDown) self.addHotKey('ctrl+m', 'funcgraph:paintmerge') self.addHotKeyTarget('funcgraph:paintmerge', self._hotkey_paintMerge) def _hotkey_histback(self): if len(self.history) >= 2: self.history.pop() expr = self.history.pop() self.enviNavGoto(expr) def _hotkey_resetzoom(self): self.mem_canvas.setZoomFactor(1) def _hotkey_inczoom(self): newzoom = self.mem_canvas.zoomFactor() if 1 > newzoom > .75: newzoom = 1 elif newzoom < .5: newzoom += .125 else: newzoom += .25 if newzoom < 0: return #self.vw.vprint("NEW ZOOM %f" % newzoom) self.mem_canvas.setZoomFactor(newzoom) def _hotkey_deczoom(self): newzoom = self.mem_canvas.zoomFactor() if newzoom <= .5: newzoom -= .125 else: newzoom -= .25 #self.vw.vprint("NEW ZOOM %f" % newzoom) self.mem_canvas.setZoomFactor(newzoom) def refresh(self): ''' Cause the Function Graph to redraw itself. This is particularly helpful because comments and name changes don't immediately display. Perhaps someday this will update only the blocks that have changed since last update, and be fast, so we can update after every change. ''' self._last_viewpt = self.mem_canvas.page().mainFrame().scrollPosition() # FIXME: history should track this as well and return to the same place self.clearText() self.fva = None self._renderMemory() @workthread def _refresh_cb(self): ''' This is a hack to make sure that when _renderMemory() completes, _refresh_3() gets run after all other rendering events yet to come. ''' if self._last_viewpt == None: return self.mem_canvas.setScrollPosition(self._last_viewpt.x(), self._last_viewpt.y()) self._last_viewpt = None def _histSetupMenu(self): self.histmenu.clear() history = [] for expr in self.history: addr = self.vw.parseExpression(expr) menustr = '0x%.8x' % addr sym = self.vw.getSymByAddr(addr) if sym != None: menustr += ' - %s' % repr(sym) history.append((menustr, expr)) history.reverse() for menustr, expr in history: self.histmenu.addAction(menustr, ACT(self._histSelected, expr)) def _histSelected(self, expr): while self.history.pop() != expr: pass self.enviNavGoto(expr) def enviNavGoto(self, expr, sizeexpr=None): self.addr_entry.setText(expr) self.history.append(expr) self.updateWindowTitle() self._renderMemory() def vqGetSaveState(self): return { 'expr': str(self.addr_entry.text()), } def vqSetSaveState(self, state): expr = state.get('expr', '') self.enviNavGoto(expr) def updateWindowTitle(self): ename = self.getEnviNavName() expr = str(self.addr_entry.text()) try: va = self.vw.parseExpression(expr) smartname = self.vw.getName(va, smart=True) self.setWindowTitle('%s: %s (0x%x)' % (ename, smartname, va)) except: self.setWindowTitle('%s: %s (0x----)' % (ename, expr)) def _buttonSaveAs(self): frame = self.mem_canvas.page().mainFrame() elem = frame.findFirstElement('#mainhtml') h = elem.toOuterXml() #h = frame.toHtml() file('test.html', 'wb').write(str(h)) def renderFunctionGraph(self, fva, graph=None): self.fva = fva #self.graph = self.vw.getFunctionGraph(fva) if graph == None: try: graph = viv_graphutil.buildFunctionGraph(self.vw, fva, revloop=True) except Exception, e: import sys sys.excepthook(*sys.exc_info()) return self.graph = graph # Go through each of the nodes and render them so we know sizes for node in self.graph.getNodes(): #cbva,cbsize = self.graph.getCodeBlockBounds(node) cbva = node[1].get('cbva') cbsize = node[1].get('cbsize') self.mem_canvas.renderMemory(cbva, cbsize) # Let the renders complete... eatevents() frame = self.mem_canvas.page().mainFrame() frame.evaluateJavaScript(funcgraph_js) for nid, nprops in self.graph.getNodes(): cbva = nprops.get('cbva') cbname = 'codeblock_%.8x' % cbva #girth, ok = frame.evaluateJavaScript('document.getElementById("%s").offsetWidth;' % cbname).toInt() #girth = int(frame.evaluateJavaScript('document.getElementById("%s").offsetWidth;' % cbname)) #height, ok = frame.evaluateJavaScript('document.getElementById("%s").offsetHeight;' % cbname).toInt() #height = frame.evaluateJavaScript('document.getElementById("%s").offsetHeight;' % cbname) girth, height = compat_getFrameDimensions(frame, cbname) self.graph.setNodeProp((nid, nprops), "size", (girth, height)) self.dylayout = vg_dynadag.DynadagLayout(self.graph) self.dylayout._barry_count = 20 self.dylayout.layoutGraph() width, height = self.dylayout.getLayoutSize() svgid = 'funcgraph_%.8x' % fva frame.evaluateJavaScript('svgwoot("vbody", "%s", %d, %d);' % (svgid, width + 18, height)) for nid, nprops in self.graph.getNodes(): cbva = nprops.get('cbva') if cbva == None: continue xpos, ypos = nprops.get('position') girth, height = nprops.get('size') foid = 'fo_cb_%.8x' % cbva cbid = 'codeblock_%.8x' % cbva frame.evaluateJavaScript( 'addSvgForeignObject("%s", "%s", %d, %d);' % (svgid, foid, girth + 16, height)) frame.evaluateJavaScript('addSvgForeignHtmlElement("%s", "%s");' % (foid, cbid)) frame.evaluateJavaScript('moveSvgElement("%s", %d, %d);' % (foid, xpos, ypos)) # Draw in some edge lines! for eid, n1, n2, einfo in self.graph.getEdges(): points = einfo.get('edge_points') pointstr = ' '.join(['%d,%d' % (x, y) for (x, y) in points]) frame.evaluateJavaScript('drawSvgLine("%s", "edge_%.8s", "%s");' % (svgid, eid, pointstr)) self.updateWindowTitle()
if __name__ == '__main__': import visgraph.graphcore as vg_graphcore import visgraph.layouts.dynadag as vg_dynadag import visgraph.renderers.svgrend as vg_svgrend g = vg_graphcore.HierarchicalGraph() g.addNode('A', rootnode=True) g.addNode('B') g.addNode('C') g.addNode('D') g.addNode('E') g.addNode('F') g.addNode('G') g.addEdge('A', 'B') g.addEdge('A', 'C') g.addEdge('B', 'D') g.addEdge('B', 'E') g.addEdge('C', 'F') g.addEdge('C', 'G') layout = vg_dynadag.DynadagLayout(g) rend = vg_svgrend.SvgGraphRenderer(g, 'test.svg') layout.renderGraph(rend)