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


qt_dotgraph
Author(s): Thibault Kruse
autogenerated on Mon Oct 6 2014 03:57:59