dot_to_qt.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2008, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 import codecs
34 
35 import pydot
36 
37 from python_qt_binding.QtCore import QPointF, QRectF
38 from python_qt_binding.QtGui import QColor
39 
40 from .edge_item import EdgeItem
41 from .node_item import NodeItem
42 
43 POINTS_PER_INCH = 72
44 
45 
46 # hack required by pydot
47 def get_unquoted(item, name):
48  value = item.get(name)
49  if value is None:
50  return None
51  try:
52  return value.strip('"\n"')
53  except AttributeError:
54  # not part of the string family
55  return value
56 
57 
58 # approximately, for workarounds (TODO: get this from dotfile somehow)
59 LABEL_HEIGHT = 30
60 
61 
62 # Class generating Qt Elements from doctcode
64 
65  def __init__(self):
66  pass
67 
68  def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None):
69  # let pydot imitate pygraphviz api
70  attr = {}
71  for name in subgraph.get_attributes().keys():
72  value = get_unquoted(subgraph, name)
73  attr[name] = value
74  obj_dic = subgraph.__getattribute__('obj_dict')
75  for name in obj_dic:
76  if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
77  attr[name] = get_unquoted(obj_dic, name)
78  elif name == 'nodes':
79  for key in obj_dic['nodes']['graph'][0]['attributes']:
80  attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
81  subgraph.attr = attr
82 
83  bb = subgraph.attr.get('bb', None)
84  if bb is None:
85  # no bounding box
86  return None
87  bb = bb.strip('"').split(',')
88  if len(bb) < 4:
89  # bounding box is empty
90  return None
91  bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
92  if 'lp' in subgraph.attr:
93  label_pos = subgraph.attr['lp'].strip('"').split(',')
94  else:
95  label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2,
96  float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2)
97  bounding_box.moveCenter(QPointF(float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2,
98  -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
99  name = subgraph.attr.get('label', '')
100  color = QColor(subgraph.attr['color']) if 'color' in subgraph.attr else None
101  subgraph_nodeitem = NodeItem(highlight_level,
102  bounding_box,
103  label=name,
104  shape='box',
105  color=color,
106  parent=scene.activePanel() if scene is not None else None,
107  label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
108  bounding_box = QRectF(bounding_box)
109  # With clusters we have the problem that mouse hovers cannot
110  # decide whether to be over the cluster or a subnode. Using
111  # just the "title area" solves this. TODO: Maybe using a
112  # border region would be even better (multiple RectF)
113  bounding_box.setHeight(LABEL_HEIGHT)
114  subgraph_nodeitem.set_hovershape(bounding_box)
115 
116  if scene is not None:
117  scene.addItem(subgraph_nodeitem)
118  return subgraph_nodeitem
119 
120  def getNodeItemForNode(self, node, highlight_level, scene=None):
121  """Return a pyqt NodeItem object, or None in case of error or invisible style."""
122  # let pydot imitate pygraphviz api
123  attr = {}
124  for name in node.get_attributes().keys():
125  value = get_unquoted(node, name)
126  attr[name] = value
127  obj_dic = node.__getattribute__('obj_dict')
128  for name in obj_dic:
129  if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
130  attr[name] = get_unquoted(obj_dic, name)
131  node.attr = attr
132 
133  if node.attr.get('style') == 'invis':
134  return None
135 
136  color = QColor(node.attr['color']) if 'color' in node.attr else None
137 
138  name = node.attr.get('label', node.attr.get('name'))
139  if name is None:
140  print('Error, no label defined for node with attr: %s' % node.attr)
141  return None
142 
143  name = codecs.escape_decode(name)[0].decode('utf-8')
144 
145  # decrease rect by one so that edges do not reach inside
146  bb_width = node.attr.get('width', len(name) / 5)
147  bb_height = node.attr.get('height', 1.0)
148  bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(
149  bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
150  pos = node.attr.get('pos', '0,0').split(',')
151  bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
152 
153  node_item = NodeItem(highlight_level=highlight_level,
154  bounding_box=bounding_box,
155  label=name,
156  shape=node.attr.get('shape', 'ellipse'),
157  color=color,
158  tooltip=node.attr.get('tooltip'),
159  parent=scene.activePanel() if scene is not None else None
160  # label_pos=None
161  )
162  if scene is not None:
163  scene.addItem(node_item)
164  return node_item
165 
166  def addEdgeItem(
167  self, edge, nodes, edges, highlight_level, same_label_siblings=False, scene=None):
168  """
169  Add EdgeItem by data in edge to edges.
170 
171  :param same_label_siblings:
172  if true, edges with same label will be considered siblings (collective highlighting)
173  """
174  # let pydot imitate pygraphviz api
175  attr = {}
176  for name in edge.get_attributes().keys():
177  value = get_unquoted(edge, name)
178  attr[name] = value
179  edge.attr = attr
180 
181  style = edge.attr.get('style')
182  if style == 'invis':
183  return
184 
185  label = edge.attr.get('label', None)
186  label_pos = edge.attr.get('lp', None)
187  label_center = None
188  if label_pos is not None:
189  label_pos = label_pos.split(',')
190  label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
191 
192  # try pydot, fallback for pygraphviz
193  source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
194  destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
195 
196  # create edge with from-node and to-node
197  edge_pos = edge.attr.get('pos')
198  if edge_pos is None:
199  return
200  if label is not None:
201  label = codecs.escape_decode(label)[0].decode('utf-8')
202 
203  penwidth = int(edge.attr.get('penwidth', 1))
204 
205  color = None
206  if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
207  r = edge.attr['colorR']
208  g = edge.attr['colorG']
209  b = edge.attr['colorB']
210  color = QColor(float(r), float(g), float(b))
211 
212  edge_item = EdgeItem(highlight_level=highlight_level,
213  spline=edge_pos,
214  label_center=label_center,
215  label=label,
216  from_node=nodes[source_node],
217  to_node=nodes[destination_node],
218  penwidth=penwidth,
219  parent=scene.activePanel() if scene is not None else None,
220  edge_color=color,
221  style=style)
222 
223  if same_label_siblings:
224  if label is None:
225  # for sibling detection
226  label = '%s_%s' % (source_node, destination_node)
227  # symmetrically add all sibling edges with same label
228  if label in edges:
229  for sibling in edges[label]:
230  edge_item.add_sibling_edge(sibling)
231  sibling.add_sibling_edge(edge_item)
232 
233  edge_name = source_node.strip('"\n"') + '_TO_' + destination_node.strip('"\n"')
234  if label is not None:
235  edge_name = edge_name + '_' + label
236 
237  if edge_name not in edges:
238  edges[edge_name] = []
239  edges[edge_name].append(edge_item)
240  if scene is not None:
241  edge_item.add_to_scene(scene)
242 
243  def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False, scene=None):
244  """
245  Take dotcode, run layout, and creates qt items based on the dot layout.
246 
247  Returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of
248  Edge_Item.
249  :param same_label_siblings:
250  if true, edges with same label will be considered siblings (collective highlighting)
251  """
252  # layout graph
253  if dotcode is None:
254  return {}, {}
255  graph = pydot.graph_from_dot_data(dotcode)
256  if isinstance(graph, list):
257  graph = graph[0]
258 
259  # graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
260  # graph.layout(prog='dot')
261 
262  nodes = self.parse_nodes(graph, highlight_level, scene=scene)
263  edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings, scene=scene)
264  return nodes, edges
265 
266  def parse_nodes(self, graph, highlight_level, scene=None):
267  """Recursively search all nodes inside the graph and all subgraphs."""
268  # let pydot imitate pygraphviz api
269  graph.nodes_iter = graph.get_node_list
270  graph.subgraphs_iter = graph.get_subgraph_list
271 
272  nodes = {}
273  for subgraph in graph.subgraphs_iter():
274  subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level, scene=scene)
275  nodes.update(self.parse_nodes(subgraph, highlight_level, scene=scene))
276  # skip subgraphs with empty bounding boxes
277  if subgraph_nodeitem is None:
278  continue
279 
280  subgraph.nodes_iter = subgraph.get_node_list
281  nodes[subgraph.get_name()] = subgraph_nodeitem
282  for node in subgraph.nodes_iter():
283  # hack required by pydot
284  if node.get_name() in ('graph', 'node', 'empty'):
285  continue
286  nodes[node.get_name()] = \
287  self.getNodeItemForNode(node, highlight_level, scene=scene)
288  for node in graph.nodes_iter():
289  # hack required by pydot
290  if node.get_name() in ('graph', 'node', 'empty'):
291  continue
292  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene)
293  return nodes
294 
295  def parse_edges(self, graph, nodes, highlight_level, same_label_siblings, scene=None):
296  """Recursively search all edges inside the graph and all subgraphs."""
297  # let pydot imitate pygraphviz api
298  graph.subgraphs_iter = graph.get_subgraph_list
299  graph.edges_iter = graph.get_edge_list
300 
301  edges = {}
302  for subgraph in graph.subgraphs_iter():
303  subgraph.edges_iter = subgraph.get_edge_list
304  edges.update(
305  self.parse_edges(
306  subgraph, nodes, highlight_level, same_label_siblings, scene=scene))
307  for edge in subgraph.edges_iter():
308  self.addEdgeItem(edge, nodes, edges,
309  highlight_level=highlight_level,
310  same_label_siblings=same_label_siblings,
311  scene=scene)
312 
313  for edge in graph.edges_iter():
314  self.addEdgeItem(edge, nodes, edges,
315  highlight_level=highlight_level,
316  same_label_siblings=same_label_siblings,
317  scene=scene)
318 
319  return edges
def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False, scene=None)
Definition: dot_to_qt.py:167
def get_unquoted(item, name)
Definition: dot_to_qt.py:47
def getNodeItemForNode(self, node, highlight_level, scene=None)
Definition: dot_to_qt.py:120
def parse_edges(self, graph, nodes, highlight_level, same_label_siblings, scene=None)
Definition: dot_to_qt.py:295
def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None)
Definition: dot_to_qt.py:68
def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False, scene=None)
Definition: dot_to_qt.py:243
def parse_nodes(self, graph, highlight_level, scene=None)
Definition: dot_to_qt.py:266


qt_dotgraph
Author(s): Thibault Kruse, Dirk Thomas
autogenerated on Fri Jun 24 2022 02:42:36