00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 import pyparsing
00035 pyparsing._noncomma = "".join([c for c in pyparsing.printables if c != ","])
00036 import pydot
00037
00038 from python_qt_binding.QtCore import QPointF, QRectF
00039 from python_qt_binding.QtGui import QColor
00040
00041 from .edge_item import EdgeItem
00042 from .node_item import NodeItem
00043
00044 POINTS_PER_INCH = 72
00045
00046
00047
00048 def get_unquoted(item, name):
00049 value = item.get(name)
00050 if value is None:
00051 return None
00052 try:
00053 return value.strip('"\n"')
00054 except AttributeError:
00055
00056 return value
00057
00058
00059 LABEL_HEIGHT = 30
00060
00061
00062
00063 class DotToQtGenerator():
00064
00065 def __init__(self):
00066 pass
00067
00068 def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None):
00069
00070 attr = {}
00071 for name in subgraph.get_attributes().iterkeys():
00072 value = get_unquoted(subgraph, name)
00073 attr[name] = value
00074 obj_dic = subgraph.__getattribute__("obj_dict")
00075 for name in obj_dic:
00076 if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
00077 attr[name] = get_unquoted(obj_dic, name)
00078 elif name == 'nodes':
00079 for key in obj_dic['nodes']['graph'][0]['attributes']:
00080 attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
00081 subgraph.attr = attr
00082
00083 bb = subgraph.attr.get('bb', None)
00084 if bb is None:
00085
00086 return None
00087 bb = bb.strip('"').split(',')
00088 if len(bb) < 4:
00089
00090 return None
00091 bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
00092 if 'lp' in subgraph.attr:
00093 label_pos = subgraph.attr['lp'].strip('"').split(',')
00094 else:
00095 label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2)
00096 bounding_box.moveCenter(QPointF(float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
00097 name = subgraph.attr.get('label', '')
00098 color = QColor(subgraph.attr['color']) if 'color' in subgraph.attr else None
00099 subgraph_nodeitem = NodeItem(highlight_level,
00100 bounding_box,
00101 label=name,
00102 shape='box',
00103 color=color,
00104 parent=scene.activePanel() if scene is not None else None,
00105 label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
00106 bounding_box = QRectF(bounding_box)
00107
00108
00109
00110
00111 bounding_box.setHeight(LABEL_HEIGHT)
00112 subgraph_nodeitem.set_hovershape(bounding_box)
00113
00114 if scene is not None:
00115 scene.addItem(subgraph_nodeitem)
00116 return subgraph_nodeitem
00117
00118 def getNodeItemForNode(self, node, highlight_level, scene=None):
00119 """
00120 returns a pyqt NodeItem object, or None in case of error or invisible style
00121 """
00122
00123 attr = {}
00124 for name in node.get_attributes().iterkeys():
00125 value = get_unquoted(node, name)
00126 attr[name] = value
00127 obj_dic = node.__getattribute__("obj_dict")
00128 for name in obj_dic:
00129 if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
00130 attr[name] = get_unquoted(obj_dic, name)
00131 node.attr = attr
00132
00133 if node.attr.get('style') == 'invis':
00134 return None
00135
00136 color = QColor(node.attr['color']) if 'color' in node.attr else None
00137
00138 name = node.attr.get('label', node.attr.get('name'))
00139 if name is None:
00140 print("Error, no label defined for node with attr: %s" % node.attr)
00141 return None
00142 name = name.decode('string_escape')
00143
00144
00145 bb_width = node.attr.get('width', len(name) / 5)
00146 bb_height = node.attr.get('height', 1.0)
00147 bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00148 pos = node.attr.get('pos', '0,0').split(',')
00149 bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00150
00151 node_item = NodeItem(highlight_level=highlight_level,
00152 bounding_box=bounding_box,
00153 label=name,
00154 shape=node.attr.get('shape', 'ellipse'),
00155 color=color,
00156 tooltip=node.attr.get('tooltip'),
00157 parent=scene.activePanel() if scene is not None else None
00158
00159 )
00160 if scene is not None:
00161 scene.addItem(node_item)
00162 return node_item
00163
00164 def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False, scene=None):
00165 """
00166 adds EdgeItem by data in edge to edges
00167 :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00168 """
00169
00170 attr = {}
00171 for name in edge.get_attributes().keys():
00172 value = get_unquoted(edge, name)
00173 attr[name] = value
00174 edge.attr = attr
00175
00176 style = edge.attr.get('style')
00177 if style == 'invis':
00178 return
00179
00180 label = edge.attr.get('label', None)
00181 label_pos = edge.attr.get('lp', None)
00182 label_center = None
00183 if label_pos is not None:
00184 label_pos = label_pos.split(',')
00185 label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
00186
00187
00188 source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
00189 destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
00190
00191
00192 edge_pos = edge.attr.get('pos')
00193 if edge_pos is None:
00194 return
00195 if label is not None:
00196 label = label.decode('string_escape')
00197
00198 penwidth = int(edge.attr.get('penwidth', 1))
00199
00200 color = None
00201 if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
00202 r = edge.attr['colorR']
00203 g = edge.attr['colorG']
00204 b = edge.attr['colorB']
00205 color = QColor(float(r), float(g), float(b))
00206
00207 edge_item = EdgeItem(highlight_level=highlight_level,
00208 spline=edge_pos,
00209 label_center=label_center,
00210 label=label,
00211 from_node=nodes[source_node],
00212 to_node=nodes[destination_node],
00213 penwidth=penwidth,
00214 parent=scene.activePanel() if scene is not None else None,
00215 edge_color=color,
00216 style=style)
00217
00218 if same_label_siblings:
00219 if label is None:
00220
00221 label = "%s_%s" % (source_node, destination_node)
00222
00223 if label in edges:
00224 for sibling in edges[label]:
00225 edge_item.add_sibling_edge(sibling)
00226 sibling.add_sibling_edge(edge_item)
00227
00228 edge_name = source_node.strip('"\n"') + '_TO_' + destination_node.strip('"\n"')
00229 if label is not None:
00230 edge_name = edge_name + '_' + label
00231
00232 if edge_name not in edges:
00233 edges[edge_name] = []
00234 edges[edge_name].append(edge_item)
00235 if scene is not None:
00236 edge_item.add_to_scene(scene)
00237
00238 def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False, scene=None):
00239 """
00240 takes dotcode, runs layout, and creates qt items based on the dot layout.
00241 returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
00242 :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00243 """
00244
00245 if dotcode is None:
00246 return {}, {}
00247 graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
00248 if isinstance(graph, list):
00249 graph = graph[0]
00250
00251
00252
00253
00254 nodes = self.parse_nodes(graph, highlight_level, scene=scene)
00255 edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings, scene=scene)
00256 return nodes, edges
00257
00258 def parse_nodes(self, graph, highlight_level, scene=None):
00259 """Recursively searches all nodes inside the graph and all subgraphs."""
00260
00261 graph.nodes_iter = graph.get_node_list
00262 graph.edges_iter = graph.get_edge_list
00263
00264 graph.subgraphs_iter = graph.get_subgraph_list
00265
00266 nodes = {}
00267 for subgraph in graph.subgraphs_iter():
00268 subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level, scene=scene)
00269 nodes.update(self.parse_nodes(subgraph, highlight_level, scene=scene))
00270
00271 if subgraph_nodeitem is None:
00272 continue
00273
00274 subgraph.nodes_iter = subgraph.get_node_list
00275 nodes[subgraph.get_name()] = subgraph_nodeitem
00276 for node in subgraph.nodes_iter():
00277
00278 if node.get_name() in ('graph', 'node', 'empty'):
00279 continue
00280 nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene)
00281 for node in graph.nodes_iter():
00282
00283 if node.get_name() in ('graph', 'node', 'empty'):
00284 continue
00285 nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene)
00286 return nodes
00287
00288 def parse_edges(self, graph, nodes, highlight_level, same_label_siblings, scene=None):
00289 """Recursively searches all edges inside the graph and all subgraphs."""
00290
00291 graph.subgraphs_iter = graph.get_subgraph_list
00292 graph.edges_iter = graph.get_edge_list
00293
00294 edges = {}
00295
00296 for subgraph in graph.subgraphs_iter():
00297 subgraph.edges_iter = subgraph.get_edge_list
00298 edges.update(self.parse_edges(subgraph, nodes, highlight_level,
00299 same_label_siblings, scene=scene))
00300 for edge in subgraph.edges_iter():
00301 self.addEdgeItem(edge, nodes, edges,
00302 highlight_level=highlight_level,
00303 same_label_siblings=same_label_siblings,
00304 scene=scene)
00305
00306 for edge in graph.edges_iter():
00307 self.addEdgeItem(edge, nodes, edges,
00308 highlight_level=highlight_level,
00309 same_label_siblings=same_label_siblings,
00310 scene=scene)
00311
00312 return edges