Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/prov/dot.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
| author | shellac |
|---|---|
| date | Sat, 02 May 2020 07:14:21 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:26e78fe6e8c4 |
|---|---|
| 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 |
