dot_to_qt.py
Go to the documentation of this file.
1 #
2 # License: Yujin
3 #
4 ##############################################################################
5 # Description
6 ##############################################################################
7 
8 """
9 .. module:: dot_to_qt
10  :platform: Unix
11  :synopsis: Repackaging of the limiting ROS qt_dotgraph.dot_to_qt module.
12 
13 Oh my spaghettified magnificence,
14 Bless my noggin with a tickle from your noodly appendages!
15 
16 """
17 
18 ##############################################################################
19 # Imports
20 ##############################################################################
21 
22 # import pydot
23 import pygraphviz
24 import pyparsing
25 
26 # work around for https://bugs.launchpad.net/ubuntu/+source/pydot/+bug/1321135
27 pyparsing._noncomma = "".join([c for c in pyparsing.printables if c != ","])
28 
29 
30 from python_qt_binding.QtCore import QPointF, QRectF
31 from python_qt_binding.QtGui import QColor
32 
33 from .edge_item import EdgeItem
34 from .node_item import NodeItem
35 
36 
37 POINTS_PER_INCH = 72
38 
39 ##############################################################################
40 # Support
41 ##############################################################################
42 
43 
44 # hack required by pydot
45 def get_unquoted(item, name):
46  value = item.get(name)
47  if value is None:
48  return None
49  try:
50  return value.strip('"\n"')
51  except AttributeError:
52  # not part of the string family
53  return value
54 
55 # approximately, for workarounds (TODO: get this from dotfile somehow)
56 LABEL_HEIGHT = 30
57 
58 ##############################################################################
59 # Classes
60 ##############################################################################
61 
62 
63 # Class generating Qt Elements from doctcode
65 
66  def __init__(self):
67  pass
68 
69  def getNodeItemForSubgraph(self, subgraph, highlight_level):
70  # let pydot imitate pygraphviz api
71  attr = {}
72  for name in subgraph.get_attributes().iterkeys():
73  value = get_unquoted(subgraph, name)
74  attr[name] = value
75  obj_dic = subgraph.__getattribute__("obj_dict")
76  for name in obj_dic:
77  if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
78  attr[name] = get_unquoted(obj_dic, name)
79  elif name == 'nodes':
80  for key in obj_dic['nodes']['graph'][0]['attributes']:
81  attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
82  subgraph.attr = attr
83 
84  bb = subgraph.attr.get('bb', None)
85  if bb is None:
86  # no bounding box
87  return None
88  bb = bb.strip('"').split(',')
89  if len(bb) < 4:
90  # bounding box is empty
91  return None
92  bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
93  if 'lp' in subgraph.attr:
94  label_pos = subgraph.attr['lp'].strip('"').split(',')
95  else:
96  label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, 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, -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
98  name = subgraph.attr.get('label', '')
99  color = QColor(subgraph.attr['color']) if 'color' in subgraph.attr else None
100  subgraph_nodeitem = NodeItem(highlight_level,
101  bounding_box,
102  label=name,
103  shape='box',
104  color=color,
105  label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
106  bounding_box = QRectF(bounding_box)
107  # With clusters we have the problem that mouse hovers cannot
108  # decide whether to be over the cluster or a subnode. Using
109  # just the "title area" solves this. TODO: Maybe using a
110  # border region would be even better (multiple RectF)
111  bounding_box.setHeight(LABEL_HEIGHT)
112  subgraph_nodeitem.set_hovershape(bounding_box)
113  return subgraph_nodeitem
114 
115  def getNodeItemForNode(self, node, highlight_level):
116  """
117  returns a pyqt NodeItem object, or None in case of error or invisible style
118  """
119  # let pydot imitate pygraphviz api
120 # attr = {}
121 # for name in node.get_attributes().iterkeys():
122 # value = get_unquoted(node, name)
123 # attr[name] = value
124 # obj_dic = node.__getattribute__("obj_dict")
125 # for name in obj_dic:
126 # if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
127 # attr[name] = get_unquoted(obj_dic, name)
128 # node.attr = attr
129 
130  if 'style' in node.attr:
131  if node.attr['style'] == 'invis':
132  return None
133 
134  color = QColor(node.attr['color']) if 'color' in node.attr else None
135  name = None
136  if 'label' in node.attr:
137  name = node.attr['label']
138  elif 'name' in node.attr:
139  name = node.attr['name']
140  else:
141  print("Error, no label defined for node with attr: %s" % node.attr)
142  return None
143  if name is None:
144  # happens on Lucid pygraphviz version
145  print("Error, label is None for node %s, pygraphviz version may be too old." % node)
146  else:
147  name = name.decode('string_escape')
148 
149  # decrease rect by one so that edges do not reach inside
150  bb_width = len(name) / 5
151  if 'width' in node.attr:
152  bb_width = node.attr['width']
153  bb_height = 1.0
154  if 'width' in node.attr:
155  bb_height = node.attr['height']
156  bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
157  pos = (0, 0)
158  if 'pos' in node.attr:
159  pos = node.attr['pos'].split(',')
160  bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
161 
162  node_item = NodeItem(highlight_level=highlight_level,
163  bounding_box=bounding_box,
164  label=name,
165  shape=node.attr.get('shape', 'ellipse'),
166  color=color,
167  tooltip=node.attr.get('tooltip', None)
168  # parent=None,
169  # label_pos=None
170  )
171  # node_item.setToolTip(self._generate_tool_tip(node.attr.get('URL', None)))
172  return node_item
173 
174  def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
175  """
176  adds EdgeItem by data in edge to edges
177  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
178  """
179  # let pydot imitate pygraphviz api
180 # attr = {}
181 # for name in edge.get_attributes().iterkeys():
182 # value = get_unquoted(edge, name)
183 # attr[name] = value
184 # edge.attr = attr
185 
186  if 'style' in edge.attr:
187  if edge.attr['style'] == 'invis':
188  return
189  style = edge.attr.get('style', None)
190 
191  label = edge.attr.get('label', None)
192  label_pos = edge.attr.get('lp', None)
193  label_center = None
194  if label_pos is not None:
195  label_pos = label_pos.split(',')
196  label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
197 
198  # try pydot, fallback for pygraphviz
199  source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
200  destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
201 
202  # create edge with from-node and to-node
203  edge_pos = "0,0"
204  if 'pos' in edge.attr:
205  edge_pos = edge.attr['pos']
206  if label is not None:
207  label = label.decode('string_escape')
208 
209  color = None
210  if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
211  r = edge.attr['colorR']
212  g = edge.attr['colorG']
213  b = edge.attr['colorB']
214  color = QColor(float(r), float(g), float(b))
215 
216  edge_item = EdgeItem(highlight_level=highlight_level,
217  spline=edge_pos,
218  label_center=label_center,
219  label=label,
220  from_node=nodes[source_node],
221  to_node=nodes[destination_node],
222  penwidth=int(edge.attr['penwidth']),
223  edge_color=color,
224  style=style)
225 
226  if same_label_siblings:
227  if label is None:
228  # for sibling detection
229  label = "%s_%s" % (source_node, destination_node)
230  # symmetrically add all sibling edges with same label
231  if label in edges:
232  for sibling in edges[label]:
233  edge_item.add_sibling_edge(sibling)
234  sibling.add_sibling_edge(edge_item)
235 
236  if label not in edges:
237  edges[label] = []
238  edges[label].append(edge_item)
239 
240  def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
241  """
242  takes dotcode, runs layout, and creates qt items based on the dot layout.
243  returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
244  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
245  """
246  # layout graph
247  if dotcode is None:
248  return {}, {}
249 
250  # pydot - this function is very buggy and expensive, quickly > 1s!
251  # graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
252  # let pydot imitate pygraphviz api
253  # graph.nodes_iter = graph.get_node_list
254  # graph.edges_iter = graph.get_edge_list
255  # graph.subgraphs_iter = graph.get_subgraph_list
256 
257  # pygraphviz
258  graph = pygraphviz.AGraph(string=dotcode.encode("ascii", "ignore"), strict=False, directed=True)
259  graph.layout(prog='dot')
260 
261  nodes = {}
262  for subgraph in graph.subgraphs_iter():
263  subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
264  # skip subgraphs with empty bounding boxes
265  if subgraph_nodeitem is None:
266  continue
267 
268  subgraph.nodes_iter = subgraph.get_node_list
269  nodes[subgraph.get_name()] = subgraph_nodeitem
270  for node in subgraph.nodes_iter():
271  # hack required by pydot
272  if node.get_name() in ('graph', 'node', 'empty'):
273  continue
274  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
275  for node in graph.nodes_iter():
276  # hack required by pydot
277  if node.get_name() in ('graph', 'node', 'empty'):
278  continue
279  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
280 
281  edges = {}
282 
283  for subgraph in graph.subgraphs_iter():
284  subgraph.edges_iter = subgraph.get_edge_list
285  for edge in subgraph.edges_iter():
286  self.addEdgeItem(edge, nodes, edges,
287  highlight_level=highlight_level,
288  same_label_siblings=same_label_siblings)
289 
290  for edge in graph.edges_iter():
291  self.addEdgeItem(edge, nodes, edges,
292  highlight_level=highlight_level,
293  same_label_siblings=same_label_siblings)
294 
295  return nodes, edges
296 
297  def graph_to_qt_items(self, graph, highlight_level, same_label_siblings=False):
298  """
299  takes a pydot/pygraphviz graph and creates qt items based on the dot layout.
300  returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
301  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
302  """
303  if graph is None:
304  return {}, {}
305 
306  # if pydot, let pydot imitate pygraphviz api
307  # graph.nodes_iter = graph.get_node_list
308  # graph.edges_iter = graph.get_edge_list
309  # graph.subgraphs_iter = graph.get_subgraph_list
310 
311  nodes = {}
312  for subgraph in graph.subgraphs_iter():
313  subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
314  # skip subgraphs with empty bounding boxes
315  if subgraph_nodeitem is None:
316  continue
317 
318  subgraph.nodes_iter = subgraph.get_node_list
319  nodes[subgraph.get_name()] = subgraph_nodeitem
320  for node in subgraph.nodes_iter():
321  # hack required by pydot
322  if node.get_name() in ('graph', 'node', 'empty'):
323  continue
324  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
325  for node in graph.nodes_iter():
326  # hack required by pydot
327  if node.get_name() in ('graph', 'node', 'empty'):
328  continue
329  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
330 
331  edges = {}
332 
333  for subgraph in graph.subgraphs_iter():
334  subgraph.edges_iter = subgraph.get_edge_list
335  for edge in subgraph.edges_iter():
336  self.addEdgeItem(edge, nodes, edges,
337  highlight_level=highlight_level,
338  same_label_siblings=same_label_siblings)
339 
340  for edge in graph.edges_iter():
341  self.addEdgeItem(edge, nodes, edges,
342  highlight_level=highlight_level,
343  same_label_siblings=same_label_siblings)
344 
345  return nodes, edges
def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:174
def graph_to_qt_items(self, graph, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:297
def getNodeItemForNode(self, node, highlight_level)
Definition: dot_to_qt.py:115
def get_unquoted(item, name)
Support.
Definition: dot_to_qt.py:45
def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:240
def getNodeItemForSubgraph(self, subgraph, highlight_level)
Definition: dot_to_qt.py:69


rqt_py_trees
Author(s): Thibault Kruse, Michal Staniaszek, Daniel Stonier, Naveed Usmani
autogenerated on Mon Jun 10 2019 14:55:56