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 node.attr.get('style') == 'invis':
00130             return None
00131 
00132         color = QColor(node.attr['color']) if 'color' in node.attr else None
00133 
00134         name = node.attr.get('label', node.attr.get('name'))
00135         if name is None:
00136             print("Error, no label defined for node with attr: %s" % node.attr)
00137             return None
00138         name = name.decode('string_escape')
00139 
00140         # decrease rect by one so that edges do not reach inside
00141         bb_width = node.attr.get('width', len(name) / 5)
00142         bb_height = node.attr.get('height', 1.0)
00143         bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00144         pos = node.attr.get('pos', '0,0').split(',')
00145         bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00146 
00147         node_item = NodeItem(highlight_level=highlight_level,
00148                              bounding_box=bounding_box,
00149                              label=name,
00150                              shape=node.attr.get('shape', 'ellipse'),
00151                              color=color,
00152                              tooltip=node.attr.get('tooltip')
00153                              #parent=None,
00154                              #label_pos=None
00155                              )
00156         return node_item
00157 
00158     def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
00159         """
00160         adds EdgeItem by data in edge to edges
00161         :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00162         """
00163         # let pydot imitate pygraphviz api
00164         attr = {}
00165         for name in edge.get_attributes().iterkeys():
00166             value = get_unquoted(edge, name)
00167             attr[name] = value
00168         edge.attr = attr
00169 
00170         style = edge.attr.get('style')
00171         if style == 'invis':
00172             return
00173 
00174         label = edge.attr.get('label', None)
00175         label_pos = edge.attr.get('lp', None)
00176         label_center = None
00177         if label_pos is not None:
00178             label_pos = label_pos.split(',')
00179             label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
00180 
00181         # try pydot, fallback for pygraphviz
00182         source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
00183         destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
00184 
00185         # create edge with from-node and to-node
00186         edge_pos = edge.attr.get('pos')
00187         if edge_pos is None:
00188             return
00189         if label is not None:
00190             label = label.decode('string_escape')
00191 
00192         penwidth = int(edge.attr.get('penwidth', 1))
00193 
00194         color = None
00195         if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
00196             r = edge.attr['colorR']
00197             g = edge.attr['colorG']
00198             b = edge.attr['colorB']
00199             color = QColor(float(r), float(g), float(b))
00200 
00201         edge_item = EdgeItem(highlight_level=highlight_level,
00202                              spline=edge_pos,
00203                              label_center=label_center,
00204                              label=label,
00205                              from_node=nodes[source_node],
00206                              to_node=nodes[destination_node],
00207                              penwidth=penwidth,
00208                              edge_color=color,
00209                              style=style)
00210 
00211         if same_label_siblings:
00212             if label is None:
00213                 # for sibling detection
00214                 label = "%s_%s" % (source_node, destination_node)
00215             # symmetrically add all sibling edges with same label
00216             if label in edges:
00217                 for sibling in edges[label]:
00218                     edge_item.add_sibling_edge(sibling)
00219                     sibling.add_sibling_edge(edge_item)
00220 
00221         if label not in edges:
00222             edges[label] = []
00223         edges[label].append(edge_item)
00224 
00225     def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
00226         """
00227         takes dotcode, runs layout, and creates qt items based on the dot layout.
00228         returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
00229         :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00230         """
00231         # layout graph
00232         if dotcode is None:
00233             return {}, {}
00234         graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
00235         if isinstance(graph, list):
00236             graph = graph[0]
00237 
00238         #graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
00239         #graph.layout(prog='dot')
00240 
00241         # let pydot imitate pygraphviz api
00242         graph.nodes_iter = graph.get_node_list
00243         graph.edges_iter = graph.get_edge_list
00244 
00245         graph.subgraphs_iter = graph.get_subgraph_list
00246 
00247         nodes = {}
00248         for subgraph in graph.subgraphs_iter():
00249             subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
00250             # skip subgraphs with empty bounding boxes
00251             if subgraph_nodeitem is None:
00252                 continue
00253 
00254             subgraph.nodes_iter = subgraph.get_node_list
00255             nodes[subgraph.get_name()] = subgraph_nodeitem
00256             for node in subgraph.nodes_iter():
00257                 # hack required by pydot
00258                 if node.get_name() in ('graph', 'node', 'empty'):
00259                     continue
00260                 nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
00261         for node in graph.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 
00267         edges = {}
00268 
00269         for subgraph in graph.subgraphs_iter():
00270             subgraph.edges_iter = subgraph.get_edge_list
00271             for edge in subgraph.edges_iter():
00272                 self.addEdgeItem(edge, nodes, edges,
00273                                  highlight_level=highlight_level,
00274                                  same_label_siblings=same_label_siblings)
00275 
00276         for edge in graph.edges_iter():
00277             self.addEdgeItem(edge, nodes, edges,
00278                              highlight_level=highlight_level,
00279                              same_label_siblings=same_label_siblings)
00280 
00281         return nodes, edges


qt_dotgraph
Author(s): Thibault Kruse
autogenerated on Fri Feb 3 2017 03:42:09