dot_to_qt.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 # work around for https://bugs.launchpad.net/ubuntu/+source/pydot/+bug/1321135
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 # hack required by pydot
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         # not part of the string family
00056         return value
00057 
00058 # approximately, for workarounds (TODO: get this from dotfile somehow)
00059 LABEL_HEIGHT = 30
00060 
00061 
00062 # Class generating Qt Elements from doctcode
00063 class DotToQtGenerator():
00064 
00065     def __init__(self):
00066         pass
00067 
00068     def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None):
00069         # let pydot imitate pygraphviz api
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             # no bounding box
00086             return None
00087         bb = bb.strip('"').split(',')
00088         if len(bb) < 4:
00089             # bounding box is empty
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         # With clusters we have the problem that mouse hovers cannot
00108         # decide whether to be over the cluster or a subnode. Using
00109         # just the "title area" solves this. TODO: Maybe using a
00110         # border region would be even better (multiple RectF)
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         # let pydot imitate pygraphviz api
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         # decrease rect by one so that edges do not reach inside
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                              #label_pos=None
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         # let pydot imitate pygraphviz api
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         # try pydot, fallback for pygraphviz
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         # create edge with from-node and to-node
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                 # for sibling detection
00221                 label = "%s_%s" % (source_node, destination_node)
00222             # symmetrically add all sibling edges with same label
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         # layout graph
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         #graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
00252         #graph.layout(prog='dot')
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         # let pydot imitate pygraphviz api
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             # skip subgraphs with empty bounding boxes
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                 # hack required by pydot
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             # hack required by pydot
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         # let pydot imitate pygraphviz api
00291         graph.subgraphs_iter = graph.get_subgraph_list
00292         graph.edges_iter = graph.get_edge_list
00293 
00294         edges = {} # Empty dictionary
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)) # Get edges of subgraph
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


qt_dotgraph
Author(s): Thibault Kruse
autogenerated on Thu Jun 6 2019 18:07:32