00001 """
00002 Copyright (c) 2013, Cogniteam
00003 All rights reserved.
00004
00005 Redistribution and use in source and binary forms, with or without
00006 modification, are permitted provided that the following conditions are met:
00007
00008 * Redistributions of source code must retain the above copyright
00009 notice, this list of conditions and the following disclaimer.
00010
00011 * Redistributions in binary form must reproduce the above copyright
00012 notice, this list of conditions and the following disclaimer in the
00013 documentation and/or other materials provided with the distribution.
00014
00015 * Neither the name of the Cogniteam nor the
00016 names of its contributors may be used to endorse or promote products
00017 derived from this software without specific prior written permission.
00018
00019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
00023 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029 """
00030
00031 from __future__ import division
00032 from python_qt_binding.QtGui import QGraphicsTextItem
00033 from pydot import *
00034
00035 POINTS_PER_INCH = 72
00036
00037
00038 class DotProcessor(object):
00039 def __init__(self, dot_to_qt):
00040 super(DotProcessor, self).__init__()
00041
00042 self._dot_to_qt = dot_to_qt
00043
00044 def process(self, dot_data):
00045 graphs = []
00046 graphs_nodes_and_edges = {}
00047 cluster_nodes = []
00048
00049 self._map_dot_graph(dot_data, graphs, graphs_nodes_and_edges, cluster_nodes)
00050
00051 return self._get_qt_elements(graphs, graphs_nodes_and_edges, cluster_nodes)
00052
00053 def _is_cluster(self, node_name, cluster_nodes):
00054 return next((True for node in cluster_nodes if node.get_name() == node_name), False)
00055
00056 def _find_parent_graph_node(self, graph_name, graphs_nodes_and_edges):
00057 for graph in graphs_nodes_and_edges:
00058 graph_nodes = graphs_nodes_and_edges[graph][0]
00059
00060 graph_node = next((node for node in graph_nodes if node.get_name() == graph_name), None)
00061
00062 if graph_node is not None:
00063 return graph_node
00064
00065 return None
00066
00067 def _extract_positon(self, graph_object):
00068 return [float(value) for value in graph_object.get_pos().strip('\"').split(',')]
00069
00070 def _extract_node_size(self, node):
00071 width = node.get_width().strip('\"')
00072 height = node.get_height().strip('\"')
00073
00074 return float(width), float(height)
00075
00076 def _align_edge_positions(self, edge, horizontal_offset, vertical_offset):
00077 position = edge.get_pos()[3:].strip('\"').split(' ')
00078
00079 result = '\"e,'
00080 for pair in position:
00081 x, y = [float(value) for value in pair.replace('\\', '').split(',')]
00082 result += '{0},{1} '.format(horizontal_offset + x, vertical_offset + y)
00083 result += '\"'
00084
00085 edge.set_pos(result)
00086
00087 def _align_edge_label(self, edge, horizontal_offset, vertical_offset):
00088 label_pos_x, label_pos_y = [float(value) for value in edge.get_lp().strip('\"').split(',')]
00089
00090 edge.set_lp('\"{0},{1}\"'.format(horizontal_offset + label_pos_x, vertical_offset + label_pos_y))
00091
00092 def _align_edge_to_parent(self, edge, horizontal_offset, vertical_offset):
00093 self._align_edge_positions(edge, horizontal_offset, vertical_offset)
00094
00095 if edge.get_label() is not None:
00096 self._align_edge_label(edge, horizontal_offset, vertical_offset)
00097
00098 def _align_node_to_parent(self, node, horizontal_offset, vertical_offset):
00099
00100 x, y = self._extract_positon(node)
00101 node.set_pos('\"{0},{1}\"'.format(horizontal_offset + x, vertical_offset + y))
00102
00103 def _get_offsets(self, target_node):
00104 parent_x, parent_y = self._extract_positon(target_node)
00105 parent_width, parent_height = self._extract_node_size(target_node)
00106
00107
00108 horizontal_offset = parent_x - parent_width * POINTS_PER_INCH / 2 + 8
00109 vertical_offset = parent_y - parent_height * POINTS_PER_INCH / 2 + 8
00110
00111 return horizontal_offset, vertical_offset
00112
00113 def _get_qt_nodes(self, graphs, graphs_nodes_and_edges, cluster_nodes, nodes):
00114 for graph in graphs:
00115 if self._is_cluster(graph, cluster_nodes):
00116 parent_node = self._find_parent_graph_node(graph, graphs_nodes_and_edges)
00117 horizontal_offset, vertical_offset = self._get_offsets(parent_node)
00118
00119 cluster_node_nodes = graphs_nodes_and_edges[graph][0]
00120
00121 for node in cluster_node_nodes:
00122 self._align_node_to_parent(node, horizontal_offset, vertical_offset)
00123
00124 if self._is_cluster(node.get_name(), cluster_nodes):
00125 nodes[node.get_name()] = self._dot_to_qt.get_cluster_node(node)
00126 else:
00127 nodes[node.get_name()] = self._dot_to_qt.get_node_item_for_node(node)
00128 else:
00129 graph_nodes = graphs_nodes_and_edges[graph][0]
00130
00131 for node in graph_nodes:
00132 if self._is_cluster(node.get_name(), cluster_nodes):
00133 nodes[node.get_name()] = self._dot_to_qt.get_cluster_node(node)
00134 else:
00135 nodes[node.get_name()] = self._dot_to_qt.get_node_item_for_node(node)
00136
00137 def _get_qt_edges(self, graphs, graphs_nodes_and_edges, cluster_nodes, nodes, edges):
00138 for graph in graphs:
00139 if self._is_cluster(graph, cluster_nodes):
00140 parent_node = self._find_parent_graph_node(graph, graphs_nodes_and_edges)
00141 horizontal_offset, vertical_offset = self._get_offsets(parent_node)
00142
00143 cluster_node_edges = graphs_nodes_and_edges[graph][1]
00144
00145 for edge in cluster_node_edges:
00146 self._align_edge_to_parent(edge, horizontal_offset, vertical_offset)
00147 self._dot_to_qt.add_edge_item_for_edge(edge, nodes, edges)
00148 else:
00149 cluster_node_edges = graphs_nodes_and_edges[graph][1]
00150 for edge in cluster_node_edges:
00151 self._dot_to_qt.add_edge_item_for_edge(edge, nodes, edges)
00152
00153 def _get_qt_elements(self, graphs, graphs_nodes_and_edges, cluster_nodes):
00154 nodes, edges = {}, {}
00155
00156 self._get_qt_nodes(graphs, graphs_nodes_and_edges, cluster_nodes, nodes)
00157 self._get_qt_edges(graphs, graphs_nodes_and_edges, cluster_nodes, nodes, edges)
00158
00159 return nodes, edges
00160
00161 def _map_dot_graph(self, dot_data, graphs, graphs_nodes_and_edges, cluster_nodes):
00162
00163 graph = graph_from_dot_data(dot_data)
00164 graphs.append(graph.get_name())
00165
00166 for sub_graph in graph.get_subgraph_list():
00167 if not sub_graph.get_name():
00168 continue
00169
00170 digraph, digraph_dot, self_edge = self._create_digraph(sub_graph)
00171
00172 if self_edge:
00173 graph.add_edge(self_edge)
00174
00175 bounding_box = self._map_dot_graph(digraph_dot, graphs, graphs_nodes_and_edges, cluster_nodes)
00176 width, height = self._calculate_size(bounding_box)
00177
00178 width += 0.2
00179 height += 0.2
00180
00181 node = self._create_fixed_size_node(digraph.get_name(), digraph.get_label(), str(width), str(height),
00182 digraph.get_URL())
00183
00184 cluster_nodes.append(node)
00185 graph.add_node(node)
00186
00187 if self._has_html_nodes(graph):
00188 graph = self._adjust_html_nodes_size(graph)
00189
00190 processed_graph, processed_graph_dot, _ = self._create_digraph(graph,
00191 sub_graphs=False,
00192 translate_edge=True)
00193
00194 self._map_nodes_and_edges_to_graph(graphs_nodes_and_edges,
00195 processed_graph,
00196 processed_graph.get_node_list(),
00197 processed_graph.get_edge_list())
00198
00199 return processed_graph.get_bb()
00200
00201 def _map_nodes_and_edges_to_graph(self, graphs_nodes_and_edges, graph, nodes, edges, include_nameless=True):
00202 if graph.get_name() not in graphs_nodes_and_edges:
00203 graphs_nodes_and_edges[graph.get_name()] = ([], [])
00204
00205 graph_name = graph.get_name()
00206 graph_nodes = graphs_nodes_and_edges[graph_name][0]
00207 graph_edges = graphs_nodes_and_edges[graph_name][1]
00208
00209 for node in nodes:
00210 if node.get_name() in ('graph', 'node', 'empty'):
00211 continue
00212
00213 graph_nodes.append(node)
00214
00215 for edge in edges:
00216 graph_edges.append(edge)
00217
00218 if not include_nameless:
00219 return
00220
00221 for sub_graph in graph.get_subgraph_list():
00222 if sub_graph.get_name():
00223 continue
00224
00225 self._map_nodes_and_edges_to_graph(graphs_nodes_and_edges,
00226 graph,
00227 sub_graph.get_node_list(),
00228 sub_graph.get_edge_list(),
00229 include_nameless=False)
00230
00231 def _calculate_size(self, bounding_box):
00232 return [float(value) / POINTS_PER_INCH for value in bounding_box.strip('\"').split(',')[-2:]]
00233
00234 def _create_fixed_size_node(self, name, label, width, height, url):
00235 return Node(name=name, label=label, shape='box', width=width, height=height, fixedsize='true', URL=url)
00236
00237 def _is_html_node(self, node):
00238 if node.get_name() in ('graph', 'node', 'empty'):
00239 return False
00240
00241 label = node.get_label()
00242
00243 if not label:
00244 return False
00245
00246 label = label.strip('\"')
00247
00248 if label.startswith('<') and label.endswith('>'):
00249 return True
00250
00251 return True
00252
00253 def _has_html_nodes(self, graph):
00254 for node in graph.get_node_list():
00255 if self._is_html_node(node):
00256 return True
00257
00258 for sub_graph in graph.get_subgraph_list():
00259 if self._is_html_node(sub_graph):
00260 return True
00261
00262 return False
00263
00264 def _adjust_html_node_size(self, node):
00265 if node.get_name() in ('graph', 'node', 'empty'):
00266 return node
00267
00268 label = node.get_label()
00269
00270 if not label:
00271 return node
00272
00273 label = label.strip('\"')
00274
00275 if not label.startswith('<') and not label.endswith('>'):
00276 return node
00277
00278 text = QGraphicsTextItem()
00279 text.setHtml(label)
00280
00281 bounding_box = text.boundingRect()
00282 width = bounding_box.width() / POINTS_PER_INCH
00283 height = bounding_box.height() / POINTS_PER_INCH
00284
00285 url = node.get_attributes().get('URL', None)
00286
00287 return self._create_fixed_size_node(node.get_name(), label, str(width), str(height), url)
00288
00289 def _adjust_html_nodes_size(self, graph):
00290 digraph = Dot(graph_name=graph.get_name(), graph_type='digraph')
00291
00292 for node in graph.get_node_list():
00293 digraph.add_node(self._adjust_html_node_size(node))
00294
00295 for edge in graph.get_edge_list():
00296 digraph.add_edge(edge)
00297
00298 for sub_graph in graph.get_subgraph_list():
00299 if sub_graph.get_name() == '':
00300 fixed_sub_graph = Subgraph()
00301 for node in sub_graph.get_node_list():
00302 fixed_sub_graph.add_node(self._adjust_html_node_size(node))
00303
00304 digraph.add_subgraph(fixed_sub_graph)
00305 else:
00306 digraph.add_subgraph(sub_graph)
00307
00308 return graph_from_dot_data(digraph.create_dot())
00309
00310 def _translate_edge_source(self, edge):
00311 source = edge.get_ltail()
00312
00313 if not source:
00314 source = edge.get_source()
00315
00316 return source
00317
00318 def _translate_edge_destination(self, edge):
00319 destination = edge.get_lhead()
00320
00321 if not destination:
00322 destination = edge.get_destination()
00323
00324 return destination
00325
00326 def _is_parent_graph_node(self, source, destination, parent_name):
00327 return source == destination and destination == parent_name
00328
00329 def _create_digraph(self, graph, sub_graphs=True, translate_edge=False):
00330 parent_edge = None
00331 digraph = Dot(graph_name=graph.get_name(), graph_type='digraph')
00332 digraph.set_node_defaults(shape='box')
00333 label = None
00334 url = None
00335 for node in graph.get_node_list():
00336 if node.get_name() in ('node', 'empty'):
00337 continue
00338 elif node.get_name() == 'graph':
00339 label = node.get_attributes().get('label', ' ')
00340 url = node.get_attributes().get('URL', None)
00341 else:
00342 digraph.add_node(node)
00343
00344 for edge in graph.get_edge_list():
00345 source = self._translate_edge_source(edge)
00346 destination = self._translate_edge_destination(edge)
00347 edge_label = edge.get_label()
00348
00349 if translate_edge:
00350 if self._is_parent_graph_node(source, destination, graph.get_name()):
00351 continue
00352
00353 translated_edge = Edge(source, destination)
00354 if edge_label:
00355 translated_edge.set_label(edge_label)
00356
00357 digraph.add_edge(translated_edge)
00358
00359 else:
00360 if self._is_parent_graph_node(source, destination, graph.get_name()):
00361 parent_edge = Edge(source, destination)
00362 if edge_label:
00363 parent_edge.set_label(edge_label)
00364 continue
00365 digraph.add_edge(edge)
00366
00367 for sub_graph in graph.get_subgraph_list():
00368 if sub_graphs or sub_graph.get_name() == '':
00369 digraph.add_subgraph(sub_graph)
00370
00371 if label:
00372 digraph.set_label(label)
00373 digraph.set_labelloc('t')
00374
00375 if url:
00376 digraph.set_URL(url)
00377
00378 digraph_dot = digraph.create_dot()
00379
00380 return graph_from_dot_data(digraph_dot), digraph_dot, parent_edge
00381