diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.7/site-packages/prov/dot.py	Sat May 02 07:14:21 2020 -0400
@@ -0,0 +1,363 @@
+"""Graphical visualisation support for prov.model.
+
+This module produces graphical visualisation for provenanve graphs.
+Requires pydot module and Graphviz.
+
+References:
+
+* pydot homepage: https://github.com/erocarrera/pydot
+* Graphviz:       http://www.graphviz.org/
+* DOT Language:   http://www.graphviz.org/doc/info/lang.html
+
+.. moduleauthor:: Trung Dong Huynh <trungdong@donggiang.com>
+"""
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+try:
+    from html import escape
+except ImportError:
+    from cgi import escape
+from datetime import datetime
+import pydot
+import six
+
+from prov.model import (
+    PROV_ACTIVITY, PROV_AGENT, PROV_ALTERNATE, PROV_ASSOCIATION,
+    PROV_ATTRIBUTION, PROV_BUNDLE, PROV_COMMUNICATION, PROV_DERIVATION,
+    PROV_DELEGATION, PROV_ENTITY, PROV_GENERATION, PROV_INFLUENCE,
+    PROV_INVALIDATION, PROV_END, PROV_MEMBERSHIP, PROV_MENTION,
+    PROV_SPECIALIZATION, PROV_START, PROV_USAGE, Identifier,
+    PROV_ATTRIBUTE_QNAMES, sorted_attributes, ProvException
+)
+
+__author__ = 'Trung Dong Huynh'
+__email__ = 'trungdong@donggiang.com'
+
+
+# Visual styles for various elements (nodes) and relations (edges)
+# see http://graphviz.org/content/attrs
+DOT_PROV_STYLE = {
+    # Generic node
+    0: {
+        'shape': 'oval', 'style': 'filled',
+        'fillcolor': 'lightgray', 'color': 'dimgray'
+    },
+    # Elements
+    PROV_ENTITY: {
+        'shape': 'oval', 'style': 'filled',
+        'fillcolor': '#FFFC87', 'color': '#808080'
+    },
+    PROV_ACTIVITY: {
+        'shape': 'box', 'style': 'filled',
+        'fillcolor': '#9FB1FC', 'color': '#0000FF'
+    },
+    PROV_AGENT: {
+        'shape': 'house', 'style': 'filled',
+        'fillcolor': '#FED37F'
+    },
+    PROV_BUNDLE: {
+        'shape': 'folder', 'style': 'filled',
+        'fillcolor': 'aliceblue'
+    },
+    # Relations
+    PROV_GENERATION: {
+        'label': 'wasGeneratedBy', 'fontsize': '10.0',
+        'color': 'darkgreen', 'fontcolor': 'darkgreen'
+    },
+    PROV_USAGE: {
+        'label': 'used', 'fontsize': '10.0',
+        'color': 'red4', 'fontcolor': 'red'
+    },
+    PROV_COMMUNICATION: {
+        'label': 'wasInformedBy', 'fontsize': '10.0'
+    },
+    PROV_START: {
+        'label': 'wasStartedBy', 'fontsize': '10.0'
+    },
+    PROV_END: {
+        'label': 'wasEndedBy', 'fontsize': '10.0'
+    },
+    PROV_INVALIDATION: {
+        'label': 'wasInvalidatedBy', 'fontsize': '10.0'
+    },
+    PROV_DERIVATION: {
+        'label': 'wasDerivedFrom', 'fontsize': '10.0'
+    },
+    PROV_ATTRIBUTION: {
+        'label': 'wasAttributedTo', 'fontsize': '10.0',
+        'color': '#FED37F'
+    },
+    PROV_ASSOCIATION: {
+        'label': 'wasAssociatedWith', 'fontsize': '10.0',
+        'color': '#FED37F'
+    },
+    PROV_DELEGATION: {
+        'label': 'actedOnBehalfOf', 'fontsize': '10.0',
+        'color': '#FED37F'
+    },
+    PROV_INFLUENCE: {
+        'label': 'wasInfluencedBy', 'fontsize': '10.0',
+        'color': 'grey'
+    },
+    PROV_ALTERNATE: {
+        'label': 'alternateOf', 'fontsize': '10.0'
+    },
+    PROV_SPECIALIZATION: {
+        'label': 'specializationOf', 'fontsize': '10.0'
+    },
+    PROV_MENTION: {
+        'label': 'mentionOf', 'fontsize': '10.0'
+    },
+    PROV_MEMBERSHIP: {
+        'label': 'hadMember', 'fontsize': '10.0'
+    },
+    }
+
+ANNOTATION_STYLE = {
+    'shape': 'note', 'color': 'gray',
+    'fontcolor': 'black', 'fontsize': '10'
+}
+ANNOTATION_LINK_STYLE = {
+    'arrowhead': 'none', 'style': 'dashed',
+    'color': 'gray'
+}
+ANNOTATION_START_ROW = '<<TABLE cellpadding=\"0\" border=\"0\">'
+ANNOTATION_ROW_TEMPLATE = """    <TR>
+        <TD align=\"left\" href=\"%s\">%s</TD>
+        <TD align=\"left\"%s>%s</TD>
+    </TR>"""
+ANNOTATION_END_ROW = '    </TABLE>>'
+
+
+def htlm_link_if_uri(value):
+    try:
+        uri = value.uri
+        return '<a href="%s">%s</a>' % (uri, six.text_type(value))
+    except AttributeError:
+        return six.text_type(value)
+
+
+def prov_to_dot(bundle, show_nary=True, use_labels=False,
+                direction='BT',
+                show_element_attributes=True, show_relation_attributes=True):
+    """
+    Convert a provenance bundle/document into a DOT graphical representation.
+
+    :param bundle: The provenance bundle/document to be converted.
+    :type bundle: :class:`ProvBundle`
+    :param show_nary: shows all elements in n-ary relations.
+    :type show_nary: bool
+    :param use_labels: uses the prov:label property of an element as its name (instead of its identifier).
+    :type use_labels: bool
+    :param direction: specifies the direction of the graph. Valid values are "BT" (default), "TB", "LR", "RL".
+    :param show_element_attributes: shows attributes of elements.
+    :type show_element_attributes: bool
+    :param show_relation_attributes: shows attributes of relations.
+    :type show_relation_attributes: bool
+    :returns:  :class:`pydot.Dot` -- the Dot object.
+    """
+    if direction not in {'BT', 'TB', 'LR', 'RL'}:
+        # Invalid direction is provided
+        direction = 'BT'  # reset it to the default value
+    maindot = pydot.Dot(graph_type='digraph', rankdir=direction, charset='utf-8')
+
+    node_map = {}
+    count = [0, 0, 0, 0]  # counters for node ids
+
+    def _bundle_to_dot(dot, bundle):
+        def _attach_attribute_annotation(node, record):
+            # Adding a node to show all attributes
+            attributes = list(
+                (attr_name, value) for attr_name, value in record.attributes
+                if attr_name not in PROV_ATTRIBUTE_QNAMES
+            )
+
+            if not attributes:
+                return  # No attribute to display
+
+            # Sort the attributes.
+            attributes = sorted_attributes(record.get_type(), attributes)
+
+            ann_rows = [ANNOTATION_START_ROW]
+            ann_rows.extend(
+                ANNOTATION_ROW_TEMPLATE % (
+                    attr.uri, escape(six.text_type(attr)),
+                    ' href=\"%s\"' % value.uri if isinstance(value, Identifier)
+                    else '',
+                    escape(six.text_type(value)
+                           if not isinstance(value, datetime) else
+                           six.text_type(value.isoformat())))
+                for attr, value in attributes
+            )
+            ann_rows.append(ANNOTATION_END_ROW)
+            count[3] += 1
+            annotations = pydot.Node(
+                'ann%d' % count[3], label='\n'.join(ann_rows),
+                **ANNOTATION_STYLE
+            )
+            dot.add_node(annotations)
+            dot.add_edge(pydot.Edge(annotations, node, **ANNOTATION_LINK_STYLE))
+
+        def _add_bundle(bundle):
+            count[2] += 1
+            subdot = pydot.Cluster(
+                graph_name='c%d' % count[2], URL='"%s"' % bundle.identifier.uri
+            )
+            if use_labels:
+                if bundle.label == bundle.identifier:
+                    bundle_label = '"%s"' % six.text_type(bundle.label)
+                else:
+                    # Fancier label if both are different. The label will be
+                    # the main node text, whereas the identifier will be a
+                    # kind of subtitle.
+                    bundle_label = ('<%s<br />'
+                                    '<font color="#333333" point-size="10">'
+                                    '%s</font>>')
+                    bundle_label = bundle_label % (
+                        six.text_type(bundle.label),
+                        six.text_type(bundle.identifier)
+                    )
+                subdot.set_label('"%s"' % six.text_type(bundle_label))
+            else:
+                subdot.set_label('"%s"' % six.text_type(bundle.identifier))
+            _bundle_to_dot(subdot, bundle)
+            dot.add_subgraph(subdot)
+            return subdot
+
+        def _add_node(record):
+            count[0] += 1
+            node_id = 'n%d' % count[0]
+            if use_labels:
+                if record.label == record.identifier:
+                    node_label = '"%s"' % six.text_type(record.label)
+                else:
+                    # Fancier label if both are different. The label will be
+                    # the main node text, whereas the identifier will be a
+                    # kind of subtitle.
+                    node_label = ('<%s<br />'
+                                  '<font color="#333333" point-size="10">'
+                                  '%s</font>>')
+                    node_label = node_label % (six.text_type(record.label),
+                                               six.text_type(record.identifier))
+            else:
+                node_label = '"%s"' % six.text_type(record.identifier)
+
+            uri = record.identifier.uri
+            style = DOT_PROV_STYLE[record.get_type()]
+            node = pydot.Node(
+                node_id, label=node_label, URL='"%s"' % uri, **style
+            )
+            node_map[uri] = node
+            dot.add_node(node)
+
+            if show_element_attributes:
+                _attach_attribute_annotation(node, rec)
+            return node
+
+        def _add_generic_node(qname):
+            count[0] += 1
+            node_id = 'n%d' % count[0]
+            node_label = '"%s"' % six.text_type(qname)
+
+            uri = qname.uri
+            style = DOT_PROV_STYLE[0]
+            node = pydot.Node(
+                node_id, label=node_label, URL='"%s"' % uri, **style
+            )
+            node_map[uri] = node
+            dot.add_node(node)
+            return node
+
+        def _get_bnode():
+            count[1] += 1
+            bnode_id = 'b%d' % count[1]
+            bnode = pydot.Node(
+                bnode_id, label='""', shape='point', color='gray'
+            )
+            dot.add_node(bnode)
+            return bnode
+
+        def _get_node(qname):
+            if qname is None:
+                return _get_bnode()
+            uri = qname.uri
+            if uri not in node_map:
+                _add_generic_node(qname)
+            return node_map[uri]
+
+        records = bundle.get_records()
+        relations = []
+        for rec in records:
+            if rec.is_element():
+                _add_node(rec)
+            else:
+                # Saving the relations for later processing
+                relations.append(rec)
+
+        if not bundle.is_bundle():
+            for bundle in bundle.bundles:
+                _add_bundle(bundle)
+
+        for rec in relations:
+            args = rec.args
+            # skipping empty records
+            if not args:
+                continue
+            # picking element nodes
+            nodes = [
+                value for attr_name, value in rec.formal_attributes
+                if attr_name in PROV_ATTRIBUTE_QNAMES
+            ]
+            other_attributes = [
+                (attr_name, value) for attr_name, value in rec.attributes
+                if attr_name not in PROV_ATTRIBUTE_QNAMES
+            ]
+            add_attribute_annotation = (
+                show_relation_attributes and other_attributes
+            )
+            add_nary_elements = len(nodes) > 2 and show_nary
+            style = DOT_PROV_STYLE[rec.get_type()]
+            if len(nodes) < 2:  # too few elements for a relation?
+                continue  # cannot draw this
+
+            if add_nary_elements or add_attribute_annotation:
+                # a blank node for n-ary relations or the attribute annotation
+                bnode = _get_bnode()
+
+                # the first segment
+                dot.add_edge(
+                    pydot.Edge(
+                        _get_node(nodes[0]), bnode, arrowhead='none', **style
+                    )
+                )
+                style = dict(style)  # copy the style
+                del style['label']  # not showing label in the second segment
+                # the second segment
+                dot.add_edge(pydot.Edge(bnode, _get_node(nodes[1]), **style))
+                if add_nary_elements:
+                    style['color'] = 'gray'  # all remaining segment to be gray
+                    for node in nodes[2:]:
+                        if node is not None:
+                            dot.add_edge(
+                                pydot.Edge(bnode, _get_node(node), **style)
+                            )
+                if add_attribute_annotation:
+                    _attach_attribute_annotation(bnode, rec)
+            else:
+                # show a simple binary relations with no annotation
+                dot.add_edge(
+                    pydot.Edge(
+                        _get_node(nodes[0]), _get_node(nodes[1]), **style
+                    )
+                )
+
+    try:
+        unified = bundle.unified()
+    except ProvException:
+        # Could not unify this bundle
+        # try the original document anyway
+        unified = bundle
+
+    _bundle_to_dot(maindot, unified)
+    return maindot