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):
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                                      label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
00105         bounding_box = QRectF(bounding_box)
00106         # With clusters we have the problem that mouse hovers cannot
00107         # decide whether to be over the cluster or a subnode. Using
00108         # just the "title area" solves this. TODO: Maybe using a
00109         # border region would be even better (multiple RectF)
00110         bounding_box.setHeight(LABEL_HEIGHT)
00111         subgraph_nodeitem.set_hovershape(bounding_box)
00112         return subgraph_nodeitem
00113 
00114     def getNodeItemForNode(self, node, highlight_level):
00115         """
00116         returns a pyqt NodeItem object, or None in case of error or invisible style
00117         """
00118         # let pydot imitate pygraphviz api
00119         attr = {}
00120         for name in node.get_attributes().iterkeys():
00121             value = get_unquoted(node, name)
00122             attr[name] = value
00123         obj_dic = node.__getattribute__("obj_dict")
00124         for name in obj_dic:
00125             if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
00126                 attr[name] = get_unquoted(obj_dic, name)
00127         node.attr = attr
00128 
00129         if 'style' in node.attr:
00130             if node.attr['style'] == 'invis':
00131                 return None
00132 
00133         color = QColor(node.attr['color']) if 'color' in node.attr else None
00134         name = None
00135         if 'label' in node.attr:
00136             name = node.attr['label']
00137         elif 'name' in node.attr:
00138             name = node.attr['name']
00139         else:
00140             print("Error, no label defined for node with attr: %s" % node.attr)
00141             return None
00142         if name is None:
00143             # happens on Lucid pygraphviz version
00144             print("Error, label is None for node %s, pygraphviz version may be too old." % node)
00145         else:
00146             name = name.decode('string_escape')
00147 
00148         # decrease rect by one so that edges do not reach inside
00149         bb_width = len(name) / 5
00150         if 'width' in node.attr:
00151             bb_width = node.attr['width']
00152         bb_height = 1.0
00153         if 'width' in node.attr:
00154             bb_height = node.attr['height']
00155         bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00156         pos = (0, 0)
00157         if 'pos' in node.attr:
00158             pos = node.attr['pos'].split(',')
00159         bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00160 
00161         node_item = NodeItem(highlight_level=highlight_level,
00162                              bounding_box=bounding_box,
00163                              label=name,
00164                              shape=node.attr.get('shape', 'ellipse'),
00165                              color=color,
00166                              #parent=None,
00167                              #label_pos=None
00168                              )
00169         #node_item.setToolTip(self._generate_tool_tip(node.attr.get('URL', None)))
00170         return node_item
00171 
00172     def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
00173         """
00174         adds EdgeItem by data in edge to edges
00175         :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00176         """
00177         # let pydot imitate pygraphviz api
00178         attr = {}
00179         for name in edge.get_attributes().iterkeys():
00180             value = get_unquoted(edge, name)
00181             attr[name] = value
00182         edge.attr = attr
00183 
00184         if 'style' in edge.attr:
00185             if edge.attr['style'] == 'invis':
00186                 return
00187         style = edge.attr.get('style', None)
00188 
00189         label = edge.attr.get('label', None)
00190         label_pos = edge.attr.get('lp', None)
00191         label_center = None
00192         if label_pos is not None:
00193             label_pos = label_pos.split(',')
00194             label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
00195 
00196         # try pydot, fallback for pygraphviz
00197         source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
00198         destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
00199 
00200         # create edge with from-node and to-node
00201         edge_pos = (0, 0)
00202         if 'pos' in edge.attr:
00203             edge_pos = edge.attr['pos']
00204         if label is not None:
00205             label = label.decode('string_escape')
00206 
00207         color = None
00208         if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
00209             r = edge.attr['colorR']
00210             g = edge.attr['colorG']
00211             b = edge.attr['colorB']
00212             color = QColor(float(r), float(g), float(b))
00213 
00214         edge_item = EdgeItem(highlight_level=highlight_level,
00215                              spline=edge_pos,
00216                              label_center=label_center,
00217                              label=label,
00218                              from_node=nodes[source_node],
00219                              to_node=nodes[destination_node],
00220                              penwidth=int(edge.attr['penwidth']),
00221                              edge_color=color,
00222                              style=style)
00223 
00224         if same_label_siblings:
00225             if label is None:
00226                 # for sibling detection
00227                 label = "%s_%s" % (source_node, destination_node)
00228             # symmetrically add all sibling edges with same label
00229             if label in edges:
00230                 for sibling in edges[label]:
00231                     edge_item.add_sibling_edge(sibling)
00232                     sibling.add_sibling_edge(edge_item)
00233 
00234         if label not in edges:
00235             edges[label] = []
00236         edges[label].append(edge_item)
00237 
00238     def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
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 
00249         #graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
00250         #graph.layout(prog='dot')
00251 
00252         # let pydot imitate pygraphviz api
00253         graph.nodes_iter = graph.get_node_list
00254         graph.edges_iter = graph.get_edge_list
00255 
00256         graph.subgraphs_iter = graph.get_subgraph_list
00257 
00258         nodes = {}
00259         for subgraph in graph.subgraphs_iter():
00260             subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
00261             # skip subgraphs with empty bounding boxes
00262             if subgraph_nodeitem is None:
00263                 continue
00264 
00265             subgraph.nodes_iter = subgraph.get_node_list
00266             nodes[subgraph.get_name()] = subgraph_nodeitem
00267             for node in subgraph.nodes_iter():
00268                 # hack required by pydot
00269                 if node.get_name() in ('graph', 'node', 'empty'):
00270                     continue
00271                 nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
00272         for node in graph.nodes_iter():
00273             # hack required by pydot
00274             if node.get_name() in ('graph', 'node', 'empty'):
00275                 continue
00276             nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
00277 
00278         edges = {}
00279 
00280         for subgraph in graph.subgraphs_iter():
00281             subgraph.edges_iter = subgraph.get_edge_list
00282             for edge in subgraph.edges_iter():
00283                 self.addEdgeItem(edge, nodes, edges,
00284                                  highlight_level=highlight_level,
00285                                  same_label_siblings=same_label_siblings)
00286 
00287         for edge in graph.edges_iter():
00288             self.addEdgeItem(edge, nodes, edges,
00289                              highlight_level=highlight_level,
00290                              same_label_siblings=same_label_siblings)
00291 
00292         return nodes, edges


qt_dotgraph
Author(s): Thibault Kruse
autogenerated on Fri Aug 28 2015 12:15:47