def run(self): with SDHistoryUtils.UndoGroup("Auto-Format Layout"): selected_nodes = uiMgr.getCurrentGraphSelection() hierarchy = NodesHierarchy(ctx, selected_nodes) vertexes = [Vertex(data) for data in range(len(hierarchy.nodes))] nodesConnections = hierarchy.getEdges() edges = [Edge(vertexes[vertex], vertexes[w]) for (vertex, w) in nodesConnections] graph = SubstanceGraph(vertexes, edges) for vertex in vertexes: vertex.view = VertexView(hierarchy.nodes[vertex.data]) # For each separate graph for subGraph in range(len(graph.C)): center = graph.getSubgraphCenter(subGraph) sug = SugiyamaLayout(graph.C[subGraph]) # Vertexes without input edges roots = list(filter(lambda x: len(x.e_in()) == 0, graph.C[subGraph].sV)) sug.init_all(roots=roots) sug.draw() newCenter = graph.getSubgraphCenter(subGraph) if len(selected_nodes) == 1: # Offset everything to save the selected node initial position rootPosition = hierarchy.nodes[roots[0].data].getPosition() # Have to compensate the final root node offset offset = float2(rootPosition[1] - roots[0].view.xy[0], -rootPosition[0] - roots[0].view.xy[1]) else: offset = float2(center[0] - newCenter[0], center[1] - newCenter[1]) for i, vertex in enumerate(graph.C[subGraph].sV): vertex.view.node.setPosition(float2(-vertex.view.xy[1] - offset[1], vertex.view.xy[0] + offset[0]))
def initView(self): cfg = self.func.cfg vertexs = {} done = set() for block in cfg.blocks: if block in done: continue done.add(block) startLockey = block.loc_key endLockey = block.loc_key blocks = [block] while (block.lines[-1].name == 'CALL') and (len(cfg.predecessors(block.get_next())) == 1): nextLockey = block.get_next() if block.loc_key in cfg.successors(nextLockey): break block = cfg.loc_key_to_block(nextLockey) blocks.append(block) endLockey = block.loc_key done.add(block) vertex = Vertex(startLockey) vertexs[startLockey] = vertex vertex.view = AsmBlockView(startLockey, endLockey, blocks, self.func) edges = [] for src in vertexs.values(): successLocKeys = cfg.successors(src.view.endLockey) for key in successLocKeys: vSrc = vertexs[src.view.lockey] vDst = vertexs[key] edge = Edge(vSrc, vDst) edge.view = BasicEdge(vDst) edges.append(edge) vSrc.view.outBlocks.append(vDst.view) vDst.view.inBlocks.append(vSrc.view) self.graph = Graph(vertexs.values(), edges) sugLayout = SugiyamaLayout(self.graph.C[0]) sugLayout.route_edge = route_with_lines sugLayout.init_all() sugLayout.draw() for v in vertexs.values(): self.addBlock(v.view, v.view.xy[0] - (v.view.w / 2), v.view.xy[1] - (v.view.h / 2)) v.view.gotoAddress.connect(self.selectAddress) for e in edges: srcView = e.v[0].view srcBlock = e.v[0].data dstBlock = e.v[1].data if len(srcView.outBlocks) == 1: color = Qt.darkBlue elif dstBlock == cfg.successors(srcBlock)[0]: color = Qt.darkRed else: color = Qt.darkGreen edge_view = e.view edge_view.color = color self.scene.addItem(edge_view)
def draw_graph(G, simple=False, pause=False): print("...drawing graph...") if simple: poses = simple_sugi.pos(G) else: g = grutils.convert_nextworkx_graph_to_grandalf(G) from grandalf.layouts import SugiyamaLayout class DefaultView(object): w, h = 10, 10 for v in g.V(): v.view = DefaultView() sug = SugiyamaLayout(g.C[0]) sug.init_all() # roots=[V[0]]) #, inverted_edges=[V[4].e_to(V[0])]) sug.draw() poses = {v.data: (-v.view.xy[0], -v.view.xy[1]) for v in g.C[0].sV} node_colors = nx.get_node_attributes(G, 'color') if node_colors: nx.draw(G, pos=poses, with_labels=True, node_color=node_colors.values()) else: global colors try: node_colors = {node: colors[node.o_id] for node in G.nodes} nx.draw(G, pos=poses, with_labels=True, node_color=node_colors.values()) except AttributeError: warnings.warn("Node didn't have color?") nx.draw(G, pos=poses, with_labels=True) # x.draw_networkx_edges(G, pos=poses) if pause: plt.show() else: # plt.pause(0.01) pauses for 0.01s, and runs plt's GUI main loop plt.pause(0.01) fixins._is_plot_active = True
def __init__(self, vertexes, edges): # # Just a reminder about naming conventions: # +------------X # | # | # | # | # Y # V = {v: Vertex(" {} ".format(v)) for v in vertexes} # NOTE: reverting edges to correctly orientate the graph E = [Edge(V[e], V[s]) for s, e in edges] V = V.values() g = Graph(V, E) class VertexViewer(object): h = 3 # top and bottom box edges + text def __init__(self, name): self.w = len(name) + 2 # right and left bottom edges + text for v in V: v.view = VertexViewer(v.data) # NOTE: determine min box length to create the best layout minw = min([v.view.w for v in V]) for e in E: e.view = EdgeViewer() sug = SugiyamaLayout(g.C[0]) gr = g.C[0] r = list(filter(lambda x: len(x.e_in()) == 0, gr.sV)) sug.init_all(roots=r, optimize=True) sug.yspace = VertexViewer.h sug.xspace = minw sug.route_edge = route_with_lines sug.draw() self.sug = sug
def sugiyama_layout(g): """ Position nodes using Sugiyama algorithm. Returns dictionary of positions keyed by node. :param g: NetworkX graph. A position will be assigned to every node in G. :type g: networkx.classes.digraph.DiGraph :return: dict """ # verteces (for grandalf lib) vertices = {node: Vertex(node) for node in g.nodes} # edges (for grandalf lib) edges = [ Edge(vertices[v_from], vertices[v_to]) for v_from, v_to in g.edges ] # build graph for v_name in vertices: vertices[v_name].view = _DefaultView() g = Graph(vertices.values(), edges) sug = SugiyamaLayout(g.C[0]) sug.init_all(optimize=True) sug.draw() pos_names = [] pos_xs = [] pos_ys = [] for v in g.C[0].sV: pos_names.append(v.data) pos_xs.append(v.view.xy[0]) pos_ys.append(v.view.xy[1]) scaler = MinMaxScaler() scaled_x = scaler.fit_transform(np.array(pos_xs).reshape(-1, 1))[:, 0] scaled_y = scaler.fit_transform(np.array(pos_ys).reshape(-1, 1))[:, 0] pos = { pos_names[i]: np.array([scaled_x[i], scaled_y[i]]) for i in range(len(pos_names)) } return pos
def _build_sugiyama_layout(vertexes: Union[List, Dict, ValuesView], edges: List) -> SugiyamaLayout: # # Just a reminder about naming conventions: # +------------X # | # | # | # | # Y # vertexes = {v: Vertex(f" {v} ") for v in vertexes} # NOTE: reverting edges to correctly orientate the graph edges = [Edge(vertexes[e], vertexes[s]) for s, e in edges] vertexes = vertexes.values() graph = Graph(vertexes, edges) for vertex in vertexes: vertex.view = VertexViewer(vertex.data) # NOTE: determine min box length to create the best layout minw = min(v.view.w for v in vertexes) for edge in edges: edge.view = EdgeViewer() sug = SugiyamaLayout(graph.C[0]) graph = graph.C[0] roots = list(filter(lambda x: len(x.e_in()) == 0, graph.sV)) sug.init_all(roots=roots, optimize=True) sug.yspace = VertexViewer.HEIGHT sug.xspace = minw sug.route_edge = route_with_lines sug.draw() return sug
def test_sugiyama_ranking(): gr, data_to_vertex = create_scenario() sug = SugiyamaLayout(gr) sug.route_edge = route_with_rounded_corners sug.init_all() # rank 0: v4 v0 # \ / | # rank 1: \ v1 | # \ / | # rank 2: v5 / # \ / # rank 3: v2 # | # rank 4: v3 rank_to_data = _compute_rank_to_data(sug) assert rank_to_data == { 0: ['v4', 'v0'], 1: ['v1'], 2: ['v5'], 3: ['v2'], 4: ['v3'], } sug.draw(N=10)
def initView(self): vertexs = {} for lockey, block in self.ircfg.blocks.items(): vertexs[lockey] = Vertex(block) vertexs[lockey].view = IRBlockView(lockey, block, False) edges = [] for src in vertexs: successLocKeys = self.ircfg.successors(src) for key in successLocKeys: if key in vertexs: vSrc = vertexs[src] vDst = vertexs[key] edge = Edge(vSrc, vDst) edge.view = BasicEdge(vDst) edges.append(edge) vSrc.view.outBlocks.append(vDst.view) vDst.view.inBlocks.append(vSrc.view) self.graph = Graph(vertexs.values(), edges) sugLayout = SugiyamaLayout(self.graph.C[0]) sugLayout.route_edge = route_with_lines sugLayout.init_all() sugLayout.draw() for v in vertexs.values(): self.addBlock(v.view, v.view.xy[0] - (v.view.w / 2), v.view.xy[1] - (v.view.h / 2)) for e in edges: srcView = e.v[0].view srcBlock = e.v[0].data dstBlock = e.v[1].data if len(srcView.outBlocks) == 1: color = Qt.darkBlue elif dstBlock.loc_key == self.ircfg.successors(srcBlock.loc_key)[0]: color = Qt.darkRed else: color = Qt.darkGreen edge_view = e.view edge_view.color = color self.scene.addItem(edge_view)
G.add_edge(row[0], row[1]) g = grandalf.utils.convert_nextworkx_graph_to_grandalf( G) # undocumented function class defaultview(object): w, h = 10, 10 for v in g.C[0].sV: v.view = defaultview() sug = SugiyamaLayout(g.C[0]) sug.init_all() # roots=[V[0]]) sug.draw( ) # This is a bit of a misnomer, as grandalf doesn't actually come with any visualization methods. This method instead calculates positions poses = {v.data: (v.view.xy[0], v.view.xy[1]) for v in g.C[0].sV} # Extracts the positions nodesWithPosition = list(poses.values()) nodesWithPosition.sort(key=lambda x: x[1], reverse=True) counter = -1 distance = 30 previousY = nodesWithPosition[0][1] offsetedNodes = [] for node in nodesWithPosition: currentY = node[1] if currentY == previousY: counter += 1
def _layout_graph_sugiyama(graph): """ Arrange the graph automatically using grandalf package """ from grandalf.graphs import Graph, Vertex, Edge from grandalf.layouts import SugiyamaLayout # Build the viez class for grandalf vertices class View(object): def __init__(self, w, h): self.w = w self.h = h self.xy = (0, 0) # Build the grandalf graph ggraph = Graph() # Vertices vertices_by_id = {} all_nodes_by_id = {n.id: n for n in graph.all_nodes()} # Get average width for distance between nodes avg_width = 0 avg_height = 0 for n in all_nodes_by_id.values(): v = Vertex(n.id) if NODE_LAYOUT_DIRECTION == NODE_LAYOUT_HORIZONTAL: view = View(n.view.height, n.view.width) else: view = View(n.view.width, n.view.height) avg_width += n.view.width avg_height += n.view.height v.view = view vertices_by_id[n.id] = v avg_width /= len(all_nodes_by_id) avg_height /= len(all_nodes_by_id) for v in vertices_by_id.values(): ggraph.add_vertex(v) # Edges inverted_edges = [] looked_up_nodes = set([]) for n in all_nodes_by_id.values(): for cns in n.connected_output_nodes().values(): for cn in cns: e = Edge(vertices_by_id[n.id], vertices_by_id[cn.id]) if cn.id in looked_up_nodes: inverted_edges.append(e) else: looked_up_nodes.add(cn.id) ggraph.add_edge(e) # Build the layout sug = SugiyamaLayout(ggraph.C[0]) sug.yspace = avg_width if NODE_LAYOUT_DIRECTION == NODE_LAYOUT_HORIZONTAL else avg_height sug.xspace = avg_height if NODE_LAYOUT_DIRECTION == NODE_LAYOUT_HORIZONTAL else avg_width print(inverted_edges) # sug.init_all(inverted_edges=inverted_edges or None) sug.init_all() sug.draw(10) for v in ggraph.C[0].sV: node = all_nodes_by_id[v.data] if NODE_LAYOUT_DIRECTION == NODE_LAYOUT_HORIZONTAL: node.set_pos(*reversed(v.view.xy)) else: node.set_pos(*v.view.xy)
(67.53662520941695,200), (49.73541984499222,200), ] for i,v in enumerate(V): v.view = defaultview() v.view.w, v.view.h = sizeList[i] packSpace = 4 sug = SugiyamaLayout(g.C[0]) #sug.init_all(roots=[V[0]],inverted_edges=[V[4].e_to(V[0])]) sug.xspace = packSpace sug.yspace = packSpace #sug.order_iter = 32 sug.dirvh = 3 sug.init_all() sug.draw() # pos:0 (0.0,100.0) # pos:1 (-62.14111896447103,304.0) # pos:2 (2.867435005484097,304.0) # pos:3 (-79.36272225037119,304.0) # pos:4 (-378.7738580569366,508.0) # pos:5 (-309.1942538205011,508.0) # pos:6 (-247.25828385704486,508.0) # pos:7 (-190.04345576434469,508.0) # pos:8 (-138.28357561291625,508.0) # pos:9 (-78.42501252755235,508.0) # pos:10 (-15.966297766115446,508.0) # pos:11 (31.318011263602294,508.0) # pos:12 (-63.59926743717183,712.0) # pos:13 (-0.9632449099672442,712.0)
def get_json(self, params, id): persons = PersonSet() persons.add_folks(int(id), Relationship.ANCESTORS) persons.add_folks(int(id), Relationship.DESCENDANTS) persons.fetch_p2e( event_types=(models.Event_Type.PK_birth, models.Event_Type.PK_marriage)) decujus = persons.get_from_id(int(id)) # Recreate the birth events, we need child, father and mother to build # the links. Likewise for marriage events. births = collections.defaultdict(BirthEvent) marriages = collections.defaultdict(MarriageEvent) for a in persons.asserts: if isinstance(a, models.P2E) and not a.disproved: if a.event.type_id == models.Event_Type.PK_birth: if a.role_id == models.Event_Type_Role.PK_principal: births[a.event.id].set_child(a.person_id) elif a.role_id == models.Event_Type_Role.PK_birth__father: births[a.event.id].add_father(a.person_id) elif a.role_id == models.Event_Type_Role.PK_birth__mother: births[a.event.id].add_mother(a.person_id) elif a.event.type_id == models.Event_Type.PK_marriage: if a.role_id == models.Event_Type_Role.PK_principal: marriages[a.event.id].add_spouse(a.person_id) # Organize persons into layers, based on child->parent relationships nodes = {main_id: Vertex(person) for main_id, person in persons.persons.items()} edges = [Edge(nodes[b.child], nodes[p]) for b in births.values() for p in itertools.chain(b.fathers, b.mothers) if b.child] g = Graph(list(nodes.values()), edges) for n in nodes.values(): n.view = NodeLayout() perlayer = collections.defaultdict(list) # main_id for each layer person_to_layer = {} # main_id => layer for core in g.C: sug = SugiyamaLayout(core) sug.init_all(optimize=True) sug.draw() for layerIndex, layer in enumerate(sug.layers): for node in layer: # ignore dummy vertices if hasattr(node, "data"): perlayer[layerIndex].append(node.data.main_id) person_to_layer[node.data.main_id] = layerIndex # Build the list of families. Each family is described as a list # [parent1, parent2, child1, child2,...] children = collections.defaultdict(list) for b in births.values(): if b.child: for f in (b.fathers or [None]): for m in (b.mothers or [None]): if f is not None or m is not None: children[(f, m)].append(b.child) couples = set(children.keys()) for event in marriages.values(): for idx, p1 in enumerate(event.spouses): for p2 in event.spouses[idx + 1:]: couples.add((p1, p2)) families_by_layer = collections.defaultdict(list) for c in couples: family = list(a for a in c) + children[c] layer = min(person_to_layer[p] for p in family if p is not None) # If there are no children: if len(family) <= 2: layer -= 1 families_by_layer[layer].append(family) flatten_families = [ families_by_layer[layerIndex] for layerIndex, layer in enumerate(sug.layers)] return { "persons": persons.persons, "perlayer": [perlayer[a]for a in sorted(perlayer)], "families": flatten_families, "decujus_name": decujus.display_name, "decujus": decujus.main_id}
def updateLayeredLayoutWithComp(self): class Vtx(object): def __init__(self, name, idx, radius = 1, height = 1): self.inNodes = set() self.outNodes = set() self.name = name self.idx = idx self.comp = None self.compIdx = None self.pos = None self.radius = radius self.fontHeight = height self.height = max(radius, height) self.firstKey = 0.0 self.secondKey = 0.0 self.thirdKey = 0.0 def getLayoutHeight(self): return self.height + self.radius def setLayoutPos(self,x,y): self.pos = (x, y-0.5*(self.height - self.radius)) def getMinX(self): return self.pos[0] - self.radius def getMaxX(self): return self.pos[0] + self.radius def getMinY(self): return self.pos[1] - self.radius def getMaxY(self): return self.pos[1] + self.height def getPos(self): return self.pos if len(self.scene.itemDict) == 0: return vtxName2Id = {} vtxList = [] edgeList = [] for name, item in self.scene.itemDict.items(): ithVtx = len(vtxList) v = Vtx(name, ithVtx, item.getRadius(), item.getHeight()) v.dispName = item.name vtxList.append(v) vtxName2Id[name] = ithVtx for edgeKey, edge in self.scene.edgeDict.items(): v1 = vtxName2Id[edgeKey[0]] v2 = vtxName2Id[edgeKey[1]] vtxList[v1].outNodes.add(v2) vtxList[v2].inNodes.add(v1) edgeList.append((v1,v2)) # 构造连通分量 remainSet = set([i for i in range(len(vtxList))]) compList = [] # [[idx1,idx2,...],[...]] while len(remainSet) > 0: # 找出剩余一个顶点并加入队列 ids = [list(remainSet)[0]] ithComp = len(compList) vtxList[ids[0]].comp = ithComp compMap = [] # 遍历一个连通分量 while len(ids) > 0: newIds = [] for id in ids: # 把一个顶点加入连通分量 vtx = vtxList[id] vtx.compIdx = len(compMap) compMap.append(id) # 把周围未遍历顶点加入队列 for inId in vtx.inNodes: inVtx = vtxList[inId] if inVtx.comp is None: inVtx.comp = ithComp newIds.append(inId) for outId in vtx.outNodes: outVtx = vtxList[outId] if outVtx.comp is None: outVtx.comp = ithComp newIds.append(outId) remainSet.discard(id) ids = newIds # 增加一个连通分量 compList.append(compMap) from grandalf.graphs import Vertex, Edge, Graph class VtxView(object): def __init__(self, w, h): self.w = w self.h = h # 构造每个连通分量的图结构 offset = (0,0) bboxMin = [1e6,1e6] bboxMax = [-1e6,-1e6] self.scene.boxTest = [] for ithComp, compMap in enumerate(compList): minPnt = [1e6,1e6] maxPnt = [-1e6,-1e6] # 构造图数据并布局 V = [] for oldId in compMap: w = vtxList[oldId].getLayoutHeight() vtx = Vertex(oldId) height = 200 vtx.view = VtxView(w, height) V.append(vtx) E = [] for edgeKey in edgeList: if vtxList[edgeKey[0]].comp == ithComp: E.append(Edge(V[vtxList[edgeKey[0]].compIdx], V[vtxList[edgeKey[1]].compIdx])) g = Graph(V,E) from grandalf.layouts import SugiyamaLayout packSpace = 4 sug = SugiyamaLayout(g.C[0]) sug.xspace = packSpace sug.yspace = packSpace #sug.order_iter = 32 sug.dirvh = 3 sug.init_all() sug.draw(10) # 统计包围盒 for v in g.C[0].sV: oldV = vtxList[v.data] x= v.view.xy[1] y= v.view.xy[0] oldV.setLayoutPos(x,y) minPnt[0] = min(minPnt[0],oldV.getMinX()) minPnt[1] = min(minPnt[1],oldV.getMinY()) maxPnt[0] = max(maxPnt[0],oldV.getMaxX()) maxPnt[1] = max(maxPnt[1],oldV.getMaxY()) for v in g.C[0].sV: oldV = vtxList[v.data] posInComp = oldV.getPos() newPos = (posInComp[0], posInComp[1]-minPnt[1]+offset[1]) self.scene.itemDict[oldV.name].setTargetPos(QtCore.QPointF(newPos[0], newPos[1])) bboxMin[0] = min(bboxMin[0], newPos[0]) bboxMin[1] = min(bboxMin[1], newPos[1]) bboxMax[0] = max(bboxMax[0], newPos[0]) bboxMax[1] = max(bboxMax[1], newPos[1]) offset = (offset[0], offset[1]+maxPnt[1]-minPnt[1]+packSpace) # 设置四个角的item cornerList = self.scene.cornerItem mar = 2000 cornerList[0].setPos(bboxMin[0]-mar, bboxMin[1]-mar) cornerList[1].setPos(bboxMin[0]-mar, bboxMax[1]+mar) cornerList[2].setPos(bboxMax[0]+mar, bboxMin[1]-mar) cornerList[3].setPos(bboxMax[0]+mar, bboxMax[1]+mar) # 更新标志数据 self.itemSet = set(self.scene.itemDict.keys()) self.edgeNum = len(self.scene.edgeDict)
(67.53662520941695, 200), (49.73541984499222, 200), ] for i, v in enumerate(V): v.view = defaultview() v.view.w, v.view.h = sizeList[i] packSpace = 4 sug = SugiyamaLayout(g.C[0]) #sug.init_all(roots=[V[0]],inverted_edges=[V[4].e_to(V[0])]) sug.xspace = packSpace sug.yspace = packSpace #sug.order_iter = 32 sug.dirvh = 3 sug.init_all() sug.draw() # pos:0 (0.0,100.0) # pos:1 (-62.14111896447103,304.0) # pos:2 (2.867435005484097,304.0) # pos:3 (-79.36272225037119,304.0) # pos:4 (-378.7738580569366,508.0) # pos:5 (-309.1942538205011,508.0) # pos:6 (-247.25828385704486,508.0) # pos:7 (-190.04345576434469,508.0) # pos:8 (-138.28357561291625,508.0) # pos:9 (-78.42501252755235,508.0) # pos:10 (-15.966297766115446,508.0) # pos:11 (31.318011263602294,508.0) # pos:12 (-63.59926743717183,712.0) # pos:13 (-0.9632449099672442,712.0)
def _layout_nodes_and_edges(self, start): """ Compute coordinates for all CFG nodes and edges in the view :param int start: The starting address :return: a mapping between nodes' names and their coordinates (dict), and a list of edge coordinates (list) :rtype: tuple """ coordinates = {} node_map = {} # Create the map for child in self.children(): if not isinstance(child, QtContainer): continue node_map[child.declaration.addr] = child if start not in node_map: return { } vertices = {} edges = [ ] # Create all edges for s, d in self.declaration.edges: src, dst = int(s), int(d) if src in vertices: src_v = vertices[src] else: src_v = Vertex(src) vertices[src] = src_v if dst in vertices: dst_v = vertices[dst] else: dst_v = Vertex(dst) vertices[dst] = dst_v edge = Edge(src_v, dst_v) edge.view = EdgeViewer() edges.append(edge) # Create all vertices for child in self.children(): addr = child.declaration.addr if addr not in vertices: vertices[addr] = Vertex(addr) g = Graph(vertices.values(), edges) # create a view for each node for addr, vertex in vertices.iteritems(): node = node_map[addr] width, height = node._layout_manager.best_size() vertex.view = VertexViewer(width, height) sug = SugiyamaLayout(g.C[0]) sug.route_edge = self._route_edges sug.init_all(roots=[vertices[start]]) sug.draw() # extract coordinates for nodes for addr, vertex in vertices.iteritems(): x, y = vertex.view.xy # Convert the center coordinate to left corner coordinate coordinates[addr] = (x - vertex.view.w / 2, y - vertex.view.h / 2) # extract coordinates for edges edge_coordinates = [ ] for edge in edges: if hasattr(edge.view, '_pts'): edge_coordinates.append(edge.view._pts) return coordinates, edge_coordinates
def create_sug_layout(self, graph): sug = SugiyamaLayout(graph.C[0]) sug.route_edge = route_with_splines sug.init_all(optimize=True) sug.draw(10) return sug
def json(self, subset=None): """ Return a json structure that can be sent to the GUI. :param set[PersonaNode] subset: which persons to take into account """ def __build_families(): """ Return a list of families found in the subset of self. :rtype list[list[id]]: [parent1,parent2,child1.child2,...] """ tmp = {} # indexed on (father, mother) for n in self: if not subset or n in subset: father = mother = None for e in self.in_edges(n): if e.kind == P2P_Link.KIND_FATHER: father = e[0] elif e.kind == P2P_Link.KIND_MOTHER: mother = e[0] elif e.kind == P2P_Link.KIND_SPOUSE: n2 = e[0].main_id if not subset or n2 in subset: tmp.setdefault((n2, n.main_id), [n2, n.main_id]) else: raise Exception("Unknown edge in graph: %s" % e.kind) # Filter events irrelevant to our subset of the graph if subset: if father and father not in subset: logger.debug('Cancel father') father = None if mother and mother not in subset: logger.debug('Cancel mother') mother = None if father: father = father.main_id if mother: mother = mother.main_id if father or mother: t = tmp.setdefault((father, mother), [father, mother]) if n: t.append(n.main_id) return list(tmp.values()) # Prepare a temporary graph: it is used to subset the list of nodes # to those specified in argument, and the list of edges to the # parent/child relationships tmp = Digraph() for e in self.edges(): if e.kind in (P2P_Link.KIND_MOTHER, P2P_Link.KIND_FATHER) and \ (subset is None or ( (e[0] in subset) and \ (e[1] in subset))): tmp.add_edge(e) families = __build_families() logger.debug('%s families' % (len(families), )) # Using an external library for layout subset2 = [n.main_id for n in subset] if subset else None Vmap = {v.main_id: Vertex(v) for v in self if subset2 is None or not v.ids.isdisjoint(subset2)} V = [v for v in Vmap.values()] E = [Edge(Vmap[e[0].main_id], Vmap[e[1].main_id]) for e in self.edges() if e.kind in (P2P_Link.KIND_MOTHER, P2P_Link.KIND_FATHER) and \ (e[0].main_id in Vmap) and (e[1].main_id in Vmap) ] g = Graph(V, E) # for v in V: # logger.info('%s [label="%s"];' % (v.data.main_id, v.data.name)) # for e in E: # logger.info('%s -> %s;' % (e.v[0].data.main_id, e.v[1].data.main_id)) class defaultview(object): w = 10 h = 10 for v in V: v.view = defaultview() persons = {} perlayer = [] persons_to_layer = {} logger.debug('%s components in graph' % (len(g.C), )) for core in g.C: sug = SugiyamaLayout(core) sug.init_all(optimize=True, cons=False) sug.draw() sug.layers.reverse() for layerIndex, layer in enumerate(sug.layers): if layerIndex not in perlayer: perlayer.extend([[]] * (layerIndex + 1 - len(perlayer))) for node in layer: # ignore dummy vertices if hasattr(node, "data"): persons[node.data.main_id] = { "id": node.data.main_id, "name": node.data.name, "sex": node.data.sex, } perlayer[layerIndex].append(node.data.main_id) for node in layer: if hasattr(node, "data"): persons_to_layer[node.data.main_id] = layerIndex families_by_layer = {} for f in families: l = min(persons_to_layer[id] for id in f if id is not None) # If there are no children if len(f) <= 2: l -= 1 families_by_layer.setdefault(l, []).append(f) flatten_families = [ families_by_layer.get(layerIndex, []) for layerIndex, layer in enumerate(sug.layers) ] return { "perlayer": perlayer, "persons": persons, "families": flatten_families }
def updateLayeredLayoutWithComp(self): class Vtx(object): def __init__(self, name, idx, radius = 1, height = 1): self.inNodes = set() self.outNodes = set() self.name = name self.idx = idx self.comp = None self.compIdx = None self.pos = None self.radius = radius self.fontHeight = height self.height = max(radius, height) self.firstKey = 0.0 self.secondKey = 0.0 self.thirdKey = 0.0 def getLayoutHeight(self): return self.height + self.radius def setLayoutPos(self,x,y): self.pos = (x, y-0.5*(self.height - self.radius)) def getMinX(self): return self.pos[0] - self.radius def getMaxX(self): return self.pos[0] + self.radius def getMinY(self): return self.pos[1] - self.radius def getMaxY(self): return self.pos[1] + self.height def getPos(self): return self.pos if len(self.scene.itemDict) == 0: return vtxName2Id = {} vtxList = [] edgeList = [] for name, item in self.scene.itemDict.items(): ithVtx = len(vtxList) v = Vtx(name, ithVtx, item.getRadius(), item.getHeight()) v.dispName = item.name vtxList.append(v) vtxName2Id[name] = ithVtx for edgeKey, edge in self.scene.edgeDict.items(): v1 = vtxName2Id[edgeKey[0]] v2 = vtxName2Id[edgeKey[1]] vtxList[v1].outNodes.add(v2) vtxList[v2].inNodes.add(v1) edgeList.append((v1,v2)) # 构造连通分量 remainSet = set([i for i in range(len(vtxList))]) compList = [] # [[idx1,idx2,...],[...]] while len(remainSet) > 0: # 找出剩余一个顶点并加入队列 ids = [list(remainSet)[0]] ithComp = len(compList) vtxList[ids[0]].comp = ithComp compMap = [] # 遍历一个连通分量 while len(ids) > 0: newIds = [] for id in ids: # 把一个顶点加入连通分量 vtx = vtxList[id] vtx.compIdx = len(compMap) compMap.append(id) # 把周围未遍历顶点加入队列 for inId in vtx.inNodes: inVtx = vtxList[inId] if inVtx.comp is None: inVtx.comp = ithComp newIds.append(inId) for outId in vtx.outNodes: outVtx = vtxList[outId] if outVtx.comp is None: outVtx.comp = ithComp newIds.append(outId) remainSet.discard(id) ids = newIds # 增加一个连通分量 compList.append(compMap) from grandalf.graphs import Vertex, Edge, Graph class VtxView(object): def __init__(self, w, h): self.w = w self.h = h # 构造每个连通分量的图结构 offset = (0,0) bboxMin = [1e6,1e6] bboxMax = [-1e6,-1e6] self.scene.boxTest = [] for ithComp, compMap in enumerate(compList): minPnt = [1e6,1e6] maxPnt = [-1e6,-1e6] # 构造图数据并布局 V = [] for oldId in compMap: w = vtxList[oldId].getLayoutHeight() vtx = Vertex(oldId) height = 200 vtx.view = VtxView(w, height) V.append(vtx) E = [] for edgeKey in edgeList: if vtxList[edgeKey[0]].comp == ithComp: E.append(Edge(V[vtxList[edgeKey[0]].compIdx], V[vtxList[edgeKey[1]].compIdx])) g = Graph(V,E) from grandalf.layouts import SugiyamaLayout packSpace = 4 sug = SugiyamaLayout(g.C[0]) sug.xspace = packSpace sug.yspace = packSpace #sug.order_iter = 32 sug.dirvh = 3 sug.init_all() sug.draw(10) # 统计包围盒 for v in g.C[0].sV: oldV = vtxList[v.data] x= v.view.xy[1] y= v.view.xy[0] oldV.setLayoutPos(x,y) minPnt[0] = min(minPnt[0],oldV.getMinX()) minPnt[1] = min(minPnt[1],oldV.getMinY()) maxPnt[0] = max(maxPnt[0],oldV.getMaxX()) maxPnt[1] = max(maxPnt[1],oldV.getMaxY()) for v in g.C[0].sV: oldV = vtxList[v.data] posInComp = oldV.getPos() newPos = (posInComp[0], posInComp[1]-minPnt[1]+offset[1]) self.scene.itemDict[oldV.name].setTargetPos(QtCore.QPointF(newPos[0], newPos[1])) bboxMin[0] = min(bboxMin[0], newPos[0]) bboxMin[1] = min(bboxMin[1], newPos[1]) bboxMax[0] = max(bboxMax[0], newPos[0]) bboxMax[1] = max(bboxMax[1], newPos[1]) offset = (offset[0], offset[1]+maxPnt[1]-minPnt[1]+packSpace) # 设置四个角的item cornerList = self.scene.cornerItem mar = 2000 cornerList[0].setPos(bboxMin[0]-mar, bboxMin[1]-mar) cornerList[1].setPos(bboxMin[0]-mar, bboxMax[1]+mar) cornerList[2].setPos(bboxMax[0]+mar, bboxMin[1]-mar) cornerList[3].setPos(bboxMax[0]+mar, bboxMax[1]+mar) # 更新标志数据 self.itemSet = set(self.scene.itemDict.keys()) self.edgeNum = len(self.scene.edgeDict)