Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/prov/dot.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
1 """Graphical visualisation support for prov.model. | |
2 | |
3 This module produces graphical visualisation for provenanve graphs. | |
4 Requires pydot module and Graphviz. | |
5 | |
6 References: | |
7 | |
8 * pydot homepage: https://github.com/erocarrera/pydot | |
9 * Graphviz: http://www.graphviz.org/ | |
10 * DOT Language: http://www.graphviz.org/doc/info/lang.html | |
11 | |
12 .. moduleauthor:: Trung Dong Huynh <trungdong@donggiang.com> | |
13 """ | |
14 from __future__ import (absolute_import, division, print_function, | |
15 unicode_literals) | |
16 | |
17 try: | |
18 from html import escape | |
19 except ImportError: | |
20 from cgi import escape | |
21 from datetime import datetime | |
22 import pydot | |
23 import six | |
24 | |
25 from prov.model import ( | |
26 PROV_ACTIVITY, PROV_AGENT, PROV_ALTERNATE, PROV_ASSOCIATION, | |
27 PROV_ATTRIBUTION, PROV_BUNDLE, PROV_COMMUNICATION, PROV_DERIVATION, | |
28 PROV_DELEGATION, PROV_ENTITY, PROV_GENERATION, PROV_INFLUENCE, | |
29 PROV_INVALIDATION, PROV_END, PROV_MEMBERSHIP, PROV_MENTION, | |
30 PROV_SPECIALIZATION, PROV_START, PROV_USAGE, Identifier, | |
31 PROV_ATTRIBUTE_QNAMES, sorted_attributes, ProvException | |
32 ) | |
33 | |
34 __author__ = 'Trung Dong Huynh' | |
35 __email__ = 'trungdong@donggiang.com' | |
36 | |
37 | |
38 # Visual styles for various elements (nodes) and relations (edges) | |
39 # see http://graphviz.org/content/attrs | |
40 DOT_PROV_STYLE = { | |
41 # Generic node | |
42 0: { | |
43 'shape': 'oval', 'style': 'filled', | |
44 'fillcolor': 'lightgray', 'color': 'dimgray' | |
45 }, | |
46 # Elements | |
47 PROV_ENTITY: { | |
48 'shape': 'oval', 'style': 'filled', | |
49 'fillcolor': '#FFFC87', 'color': '#808080' | |
50 }, | |
51 PROV_ACTIVITY: { | |
52 'shape': 'box', 'style': 'filled', | |
53 'fillcolor': '#9FB1FC', 'color': '#0000FF' | |
54 }, | |
55 PROV_AGENT: { | |
56 'shape': 'house', 'style': 'filled', | |
57 'fillcolor': '#FED37F' | |
58 }, | |
59 PROV_BUNDLE: { | |
60 'shape': 'folder', 'style': 'filled', | |
61 'fillcolor': 'aliceblue' | |
62 }, | |
63 # Relations | |
64 PROV_GENERATION: { | |
65 'label': 'wasGeneratedBy', 'fontsize': '10.0', | |
66 'color': 'darkgreen', 'fontcolor': 'darkgreen' | |
67 }, | |
68 PROV_USAGE: { | |
69 'label': 'used', 'fontsize': '10.0', | |
70 'color': 'red4', 'fontcolor': 'red' | |
71 }, | |
72 PROV_COMMUNICATION: { | |
73 'label': 'wasInformedBy', 'fontsize': '10.0' | |
74 }, | |
75 PROV_START: { | |
76 'label': 'wasStartedBy', 'fontsize': '10.0' | |
77 }, | |
78 PROV_END: { | |
79 'label': 'wasEndedBy', 'fontsize': '10.0' | |
80 }, | |
81 PROV_INVALIDATION: { | |
82 'label': 'wasInvalidatedBy', 'fontsize': '10.0' | |
83 }, | |
84 PROV_DERIVATION: { | |
85 'label': 'wasDerivedFrom', 'fontsize': '10.0' | |
86 }, | |
87 PROV_ATTRIBUTION: { | |
88 'label': 'wasAttributedTo', 'fontsize': '10.0', | |
89 'color': '#FED37F' | |
90 }, | |
91 PROV_ASSOCIATION: { | |
92 'label': 'wasAssociatedWith', 'fontsize': '10.0', | |
93 'color': '#FED37F' | |
94 }, | |
95 PROV_DELEGATION: { | |
96 'label': 'actedOnBehalfOf', 'fontsize': '10.0', | |
97 'color': '#FED37F' | |
98 }, | |
99 PROV_INFLUENCE: { | |
100 'label': 'wasInfluencedBy', 'fontsize': '10.0', | |
101 'color': 'grey' | |
102 }, | |
103 PROV_ALTERNATE: { | |
104 'label': 'alternateOf', 'fontsize': '10.0' | |
105 }, | |
106 PROV_SPECIALIZATION: { | |
107 'label': 'specializationOf', 'fontsize': '10.0' | |
108 }, | |
109 PROV_MENTION: { | |
110 'label': 'mentionOf', 'fontsize': '10.0' | |
111 }, | |
112 PROV_MEMBERSHIP: { | |
113 'label': 'hadMember', 'fontsize': '10.0' | |
114 }, | |
115 } | |
116 | |
117 ANNOTATION_STYLE = { | |
118 'shape': 'note', 'color': 'gray', | |
119 'fontcolor': 'black', 'fontsize': '10' | |
120 } | |
121 ANNOTATION_LINK_STYLE = { | |
122 'arrowhead': 'none', 'style': 'dashed', | |
123 'color': 'gray' | |
124 } | |
125 ANNOTATION_START_ROW = '<<TABLE cellpadding=\"0\" border=\"0\">' | |
126 ANNOTATION_ROW_TEMPLATE = """ <TR> | |
127 <TD align=\"left\" href=\"%s\">%s</TD> | |
128 <TD align=\"left\"%s>%s</TD> | |
129 </TR>""" | |
130 ANNOTATION_END_ROW = ' </TABLE>>' | |
131 | |
132 | |
133 def htlm_link_if_uri(value): | |
134 try: | |
135 uri = value.uri | |
136 return '<a href="%s">%s</a>' % (uri, six.text_type(value)) | |
137 except AttributeError: | |
138 return six.text_type(value) | |
139 | |
140 | |
141 def prov_to_dot(bundle, show_nary=True, use_labels=False, | |
142 direction='BT', | |
143 show_element_attributes=True, show_relation_attributes=True): | |
144 """ | |
145 Convert a provenance bundle/document into a DOT graphical representation. | |
146 | |
147 :param bundle: The provenance bundle/document to be converted. | |
148 :type bundle: :class:`ProvBundle` | |
149 :param show_nary: shows all elements in n-ary relations. | |
150 :type show_nary: bool | |
151 :param use_labels: uses the prov:label property of an element as its name (instead of its identifier). | |
152 :type use_labels: bool | |
153 :param direction: specifies the direction of the graph. Valid values are "BT" (default), "TB", "LR", "RL". | |
154 :param show_element_attributes: shows attributes of elements. | |
155 :type show_element_attributes: bool | |
156 :param show_relation_attributes: shows attributes of relations. | |
157 :type show_relation_attributes: bool | |
158 :returns: :class:`pydot.Dot` -- the Dot object. | |
159 """ | |
160 if direction not in {'BT', 'TB', 'LR', 'RL'}: | |
161 # Invalid direction is provided | |
162 direction = 'BT' # reset it to the default value | |
163 maindot = pydot.Dot(graph_type='digraph', rankdir=direction, charset='utf-8') | |
164 | |
165 node_map = {} | |
166 count = [0, 0, 0, 0] # counters for node ids | |
167 | |
168 def _bundle_to_dot(dot, bundle): | |
169 def _attach_attribute_annotation(node, record): | |
170 # Adding a node to show all attributes | |
171 attributes = list( | |
172 (attr_name, value) for attr_name, value in record.attributes | |
173 if attr_name not in PROV_ATTRIBUTE_QNAMES | |
174 ) | |
175 | |
176 if not attributes: | |
177 return # No attribute to display | |
178 | |
179 # Sort the attributes. | |
180 attributes = sorted_attributes(record.get_type(), attributes) | |
181 | |
182 ann_rows = [ANNOTATION_START_ROW] | |
183 ann_rows.extend( | |
184 ANNOTATION_ROW_TEMPLATE % ( | |
185 attr.uri, escape(six.text_type(attr)), | |
186 ' href=\"%s\"' % value.uri if isinstance(value, Identifier) | |
187 else '', | |
188 escape(six.text_type(value) | |
189 if not isinstance(value, datetime) else | |
190 six.text_type(value.isoformat()))) | |
191 for attr, value in attributes | |
192 ) | |
193 ann_rows.append(ANNOTATION_END_ROW) | |
194 count[3] += 1 | |
195 annotations = pydot.Node( | |
196 'ann%d' % count[3], label='\n'.join(ann_rows), | |
197 **ANNOTATION_STYLE | |
198 ) | |
199 dot.add_node(annotations) | |
200 dot.add_edge(pydot.Edge(annotations, node, **ANNOTATION_LINK_STYLE)) | |
201 | |
202 def _add_bundle(bundle): | |
203 count[2] += 1 | |
204 subdot = pydot.Cluster( | |
205 graph_name='c%d' % count[2], URL='"%s"' % bundle.identifier.uri | |
206 ) | |
207 if use_labels: | |
208 if bundle.label == bundle.identifier: | |
209 bundle_label = '"%s"' % six.text_type(bundle.label) | |
210 else: | |
211 # Fancier label if both are different. The label will be | |
212 # the main node text, whereas the identifier will be a | |
213 # kind of subtitle. | |
214 bundle_label = ('<%s<br />' | |
215 '<font color="#333333" point-size="10">' | |
216 '%s</font>>') | |
217 bundle_label = bundle_label % ( | |
218 six.text_type(bundle.label), | |
219 six.text_type(bundle.identifier) | |
220 ) | |
221 subdot.set_label('"%s"' % six.text_type(bundle_label)) | |
222 else: | |
223 subdot.set_label('"%s"' % six.text_type(bundle.identifier)) | |
224 _bundle_to_dot(subdot, bundle) | |
225 dot.add_subgraph(subdot) | |
226 return subdot | |
227 | |
228 def _add_node(record): | |
229 count[0] += 1 | |
230 node_id = 'n%d' % count[0] | |
231 if use_labels: | |
232 if record.label == record.identifier: | |
233 node_label = '"%s"' % six.text_type(record.label) | |
234 else: | |
235 # Fancier label if both are different. The label will be | |
236 # the main node text, whereas the identifier will be a | |
237 # kind of subtitle. | |
238 node_label = ('<%s<br />' | |
239 '<font color="#333333" point-size="10">' | |
240 '%s</font>>') | |
241 node_label = node_label % (six.text_type(record.label), | |
242 six.text_type(record.identifier)) | |
243 else: | |
244 node_label = '"%s"' % six.text_type(record.identifier) | |
245 | |
246 uri = record.identifier.uri | |
247 style = DOT_PROV_STYLE[record.get_type()] | |
248 node = pydot.Node( | |
249 node_id, label=node_label, URL='"%s"' % uri, **style | |
250 ) | |
251 node_map[uri] = node | |
252 dot.add_node(node) | |
253 | |
254 if show_element_attributes: | |
255 _attach_attribute_annotation(node, rec) | |
256 return node | |
257 | |
258 def _add_generic_node(qname): | |
259 count[0] += 1 | |
260 node_id = 'n%d' % count[0] | |
261 node_label = '"%s"' % six.text_type(qname) | |
262 | |
263 uri = qname.uri | |
264 style = DOT_PROV_STYLE[0] | |
265 node = pydot.Node( | |
266 node_id, label=node_label, URL='"%s"' % uri, **style | |
267 ) | |
268 node_map[uri] = node | |
269 dot.add_node(node) | |
270 return node | |
271 | |
272 def _get_bnode(): | |
273 count[1] += 1 | |
274 bnode_id = 'b%d' % count[1] | |
275 bnode = pydot.Node( | |
276 bnode_id, label='""', shape='point', color='gray' | |
277 ) | |
278 dot.add_node(bnode) | |
279 return bnode | |
280 | |
281 def _get_node(qname): | |
282 if qname is None: | |
283 return _get_bnode() | |
284 uri = qname.uri | |
285 if uri not in node_map: | |
286 _add_generic_node(qname) | |
287 return node_map[uri] | |
288 | |
289 records = bundle.get_records() | |
290 relations = [] | |
291 for rec in records: | |
292 if rec.is_element(): | |
293 _add_node(rec) | |
294 else: | |
295 # Saving the relations for later processing | |
296 relations.append(rec) | |
297 | |
298 if not bundle.is_bundle(): | |
299 for bundle in bundle.bundles: | |
300 _add_bundle(bundle) | |
301 | |
302 for rec in relations: | |
303 args = rec.args | |
304 # skipping empty records | |
305 if not args: | |
306 continue | |
307 # picking element nodes | |
308 nodes = [ | |
309 value for attr_name, value in rec.formal_attributes | |
310 if attr_name in PROV_ATTRIBUTE_QNAMES | |
311 ] | |
312 other_attributes = [ | |
313 (attr_name, value) for attr_name, value in rec.attributes | |
314 if attr_name not in PROV_ATTRIBUTE_QNAMES | |
315 ] | |
316 add_attribute_annotation = ( | |
317 show_relation_attributes and other_attributes | |
318 ) | |
319 add_nary_elements = len(nodes) > 2 and show_nary | |
320 style = DOT_PROV_STYLE[rec.get_type()] | |
321 if len(nodes) < 2: # too few elements for a relation? | |
322 continue # cannot draw this | |
323 | |
324 if add_nary_elements or add_attribute_annotation: | |
325 # a blank node for n-ary relations or the attribute annotation | |
326 bnode = _get_bnode() | |
327 | |
328 # the first segment | |
329 dot.add_edge( | |
330 pydot.Edge( | |
331 _get_node(nodes[0]), bnode, arrowhead='none', **style | |
332 ) | |
333 ) | |
334 style = dict(style) # copy the style | |
335 del style['label'] # not showing label in the second segment | |
336 # the second segment | |
337 dot.add_edge(pydot.Edge(bnode, _get_node(nodes[1]), **style)) | |
338 if add_nary_elements: | |
339 style['color'] = 'gray' # all remaining segment to be gray | |
340 for node in nodes[2:]: | |
341 if node is not None: | |
342 dot.add_edge( | |
343 pydot.Edge(bnode, _get_node(node), **style) | |
344 ) | |
345 if add_attribute_annotation: | |
346 _attach_attribute_annotation(bnode, rec) | |
347 else: | |
348 # show a simple binary relations with no annotation | |
349 dot.add_edge( | |
350 pydot.Edge( | |
351 _get_node(nodes[0]), _get_node(nodes[1]), **style | |
352 ) | |
353 ) | |
354 | |
355 try: | |
356 unified = bundle.unified() | |
357 except ProvException: | |
358 # Could not unify this bundle | |
359 # try the original document anyway | |
360 unified = bundle | |
361 | |
362 _bundle_to_dot(maindot, unified) | |
363 return maindot |