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 import pydot
00034 
00035 import roslib
00036 roslib.load_manifest('qt_dotgraph')
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['bb'].strip('"').split(',')
00084         if len(bb) < 4:
00085             raise ValueError('bounding box has too few elements %s' % subgraph.attr)
00086         bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
00087         if 'lp' in subgraph.attr:
00088             label_pos = subgraph.attr['lp'].strip('"').split(',')
00089         else:
00090             label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2)
00091         bounding_box.moveCenter(QPointF(float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
00092         name = subgraph.attr['label']
00093         color = QColor(subgraph.attr['color']) if 'color' in subgraph.attr else None
00094         subgraph_nodeitem = NodeItem(highlight_level,
00095                                      bounding_box,
00096                                      label=name,
00097                                      shape='box',
00098                                      color=color,
00099                                      label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
00100         bounding_box = QRectF(bounding_box)
00101         # With clusters we have the problem that mouse hovers cannot
00102         # decide whether to be over the cluster or a subnode. Using
00103         # just the "title area" solves this. TODO: Maybe using a
00104         # border region would be even better (multiple RectF)
00105         bounding_box.setHeight(LABEL_HEIGHT)
00106         subgraph_nodeitem.set_hovershape(bounding_box)
00107         return subgraph_nodeitem
00108 
00109     def getNodeItemForNode(self, node, highlight_level):
00110         """
00111         returns a pyqt NodeItem object, or None in case of error or invisible style
00112         """
00113         # let pydot imitate pygraphviz api
00114         attr = {}
00115         for name in node.get_attributes().iterkeys():
00116             value = get_unquoted(node, name)
00117             attr[name] = value
00118         obj_dic = node.__getattribute__("obj_dict")
00119         for name in obj_dic:
00120             if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
00121                 attr[name] = get_unquoted(obj_dic, name)
00122         node.attr = attr
00123 
00124         if 'style' in node.attr:
00125             if node.attr['style'] == 'invis':
00126                 return None
00127 
00128         color = QColor(node.attr['color']) if 'color' in node.attr else None
00129         name = None
00130         if 'label' in node.attr:
00131             name = node.attr['label']
00132         elif 'name' in node.attr:
00133             name = node.attr['name']
00134         else:
00135             print("Error, no label defined for node with attr: %s" % node.attr)
00136             return None
00137         if name is None:
00138             # happens on Lucid pygraphviz version
00139             print("Error, label is None for node %s, pygraphviz version may be too old." % node)
00140         else:
00141             name = name.decode('string_escape')
00142 
00143         # decrease rect by one so that edges do not reach inside
00144         bb_width = len(name) / 5
00145         if 'width' in node.attr:
00146             bb_width = node.attr['width']
00147         bb_height = 1.0
00148         if 'width' in node.attr:
00149             bb_height = node.attr['height']
00150         bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00151         pos = (0, 0)
00152         if 'pos' in node.attr:
00153             pos = node.attr['pos'].split(',')
00154         bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00155 
00156         node_item = NodeItem(highlight_level=highlight_level,
00157                              bounding_box=bounding_box,
00158                              label=name,
00159                              shape=node.attr.get('shape', 'ellipse'),
00160                              color=color,
00161                              #parent=None,
00162                              #label_pos=None
00163                              )
00164         #node_item.setToolTip(self._generate_tool_tip(node.attr.get('URL', None)))
00165         return node_item
00166 
00167     def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
00168         """
00169         adds EdgeItem by data in edge to edges
00170         :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00171         """
00172         # let pydot imitate pygraphviz api
00173         attr = {}
00174         for name in edge.get_attributes().iterkeys():
00175             value = get_unquoted(edge, name)
00176             attr[name] = value
00177         edge.attr = attr
00178 
00179         if 'style' in edge.attr:
00180             if edge.attr['style'] == 'invis':
00181                 return
00182 
00183         label = edge.attr.get('label', None)
00184         label_pos = edge.attr.get('lp', None)
00185         label_center = None
00186         if label_pos is not None:
00187             label_pos = label_pos.split(',')
00188             label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
00189 
00190         # try pydot, fallback for pygraphviz
00191         source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
00192         destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
00193 
00194         # create edge with from-node and to-node
00195         edge_pos = (0, 0)
00196         if 'pos' in edge.attr:
00197             edge_pos = edge.attr['pos']
00198         if label is not None:
00199             label = label.decode('string_escape')
00200         edge_item = EdgeItem(highlight_level=highlight_level,
00201                              spline=edge_pos,
00202                              label_center=label_center,
00203                              label=label,
00204                              from_node=nodes[source_node],
00205                              to_node=nodes[destination_node])
00206 
00207         if same_label_siblings:
00208             if label is None:
00209                 # for sibling detection
00210                 label = "%s_%s" % (source_node, destination_node)
00211             # symmetrically add all sibling edges with same label
00212             if label in edges:
00213                 for sibling in edges[label]:
00214                     edge_item.add_sibling_edge(sibling)
00215                     sibling.add_sibling_edge(edge_item)
00216 
00217         if label not in edges:
00218             edges[label] = []
00219         edges[label].append(edge_item)
00220 
00221     def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
00222         """
00223         takes dotcode, runs layout, and creates qt items based on the dot layout.
00224         returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
00225         :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00226         """
00227         # layout graph
00228         if dotcode is None:
00229             return {}, {}
00230         graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
00231 
00232         #graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
00233         #graph.layout(prog='dot')
00234 
00235         # let pydot imitate pygraphviz api
00236         graph.nodes_iter = graph.get_node_list
00237         graph.edges_iter = graph.get_edge_list
00238 
00239         graph.subgraphs_iter = graph.get_subgraph_list
00240 
00241         nodes = {}
00242         for subgraph in graph.subgraphs_iter():
00243             subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
00244 
00245             subgraph.nodes_iter = subgraph.get_node_list
00246             nodes[subgraph.get_name()] = subgraph_nodeitem
00247             for node in subgraph.nodes_iter():
00248                 # hack required by pydot
00249                 if node.get_name() in ('graph', 'node', 'empty'):
00250                     continue
00251                 nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
00252         for node in graph.nodes_iter():
00253             # hack required by pydot
00254             if node.get_name() in ('graph', 'node', 'empty'):
00255                 continue
00256             nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
00257 
00258         edges = {}
00259 
00260         for subgraph in graph.subgraphs_iter():
00261             subgraph.edges_iter = subgraph.get_edge_list
00262             for edge in subgraph.edges_iter():
00263                 self.addEdgeItem(edge, nodes, edges,
00264                                  highlight_level=highlight_level,
00265                                  same_label_siblings=same_label_siblings)
00266 
00267         for edge in graph.edges_iter():
00268             self.addEdgeItem(edge, nodes, edges,
00269                              highlight_level=highlight_level,
00270                              same_label_siblings=same_label_siblings)
00271 
00272         return nodes, edges


qt_dotgraph
Author(s): Thibault Kruse
autogenerated on Fri Jan 3 2014 11:44:08