dot_writer.py 3.71 KB
Newer Older
1 2 3 4 5
# encoding: utf8
"""Routines manipulating the dot.

"""

6
import itertools
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
import pydot
from .asp_to_dot import VisualConfig


DEFAULT_DOT_FILE = 'out/out.dot'
DEFAULT_PROG_FILE = 'dot'


def _dot_from_properties(properties:dict or None, prefix:str=' ') -> str:
    """Return a dot '[]' expression where given properties are represented.

    If given properties are None, empty string will be output.

    """
    if properties:
        content = ' '.join(
            '{}="{}"'.format(field, value.replace('"', r'\"'))
            for field, value in properties.items()
        )
        return prefix + '[' + content + ']'
    else:
        return ''


31 32
def multiple_graphs_from_configs(visual_configs:[VisualConfig]) -> [[str]]:
    """Yield generators of lines of dot describing the given VisualConfig instances.
33 34 35 36 37 38

    Produce one graph per VisualConfig.
    See function counterpart, one_graph_from_configs.

    """
    for visual_config in visual_configs:
39 40 41 42 43
        yield itertools.chain(
            ['Digraph biseau_graph {\n'],
            _from_config(visual_config),
            ['}\n\n\n'],
        )
44 45


46
def one_graph_from_configs(visual_configs:[VisualConfig]) -> [str]:
47 48 49 50 51 52 53 54 55 56 57 58
    """Yield lines of dot describing the given VisualConfig instances.

    Produce only one graph,
    using the union of visual_configs to implement the view.

    """
    yield 'Digraph biseau_graph {\n'
    for visual_config in visual_configs:
        yield from _from_config(visual_config)
    yield '}'


59
def _from_config(visual_config:VisualConfig) -> [str]:
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    """Yield lines of dot's graph describing the given VisualConfig instance.

    """
    arcs, properties, upper_annotations, lower_annotations, globals_props, ranks = visual_config
    for object, props in globals_props.items():
        yield '\t{}{};\n'.format(object, _dot_from_properties(props))
    if 'node' not in globals_props:
        yield '\tnode [shape=ellipse style=filled width=.25]\n'
    if 'edge' not in globals_props:
        yield '\tedge [arrowhead=none labeldistance=1.5 minlen=2]\n'
    treated_nodes = set()  # contains nodes already treated
    for source, target in arcs:
        for node in source, target:
            if node not in treated_nodes:
                treated_nodes.add(node)
                node_dot_props = _dot_from_properties(properties.get(node))
                if node_dot_props:
                    yield '\t{n}{d}\n'.format(n=node, d=node_dot_props)
                if lower_annotations.get(node):
                    yield '\t{n} -> {n} {d}\n'.format(
                        n=node, d=_dot_from_properties(lower_annotations[node]))
                if upper_annotations.get(node):
                    yield '\t{n} -> {n} {d}\n'.format(
                        n=node, d=_dot_from_properties(upper_annotations[node]))
        yield '\t{}->{}{}\n'.format(source, target, _dot_from_properties(properties.get((source, target))))
    # build ranks
    for ranktype, nodes in ranks.items():
        yield '\t{{rank={}; {}}}\n'.format(ranktype, ';'.join(nodes))


def dot_to_png(dot_lines:iter, outfile:str, dotfile:str=DEFAULT_DOT_FILE,
               prog:str=DEFAULT_PROG_FILE):
    """Write in outfile a png render of the graph described in given dot lines"""
    dot_lines = ''.join(dot_lines)
    if dotfile:
        with open(dotfile, 'w') as fd:
            fd.write(dot_lines)
    graphs = iter(pydot.graph_from_dot_data(dot_lines))
    for graph in graphs:
        with open(outfile, 'wb') as fd:
100
            fd.write(graph.create(prog=prog or DEFAULT_PROG_FILE, format='png'))
101 102 103 104 105 106 107
        break

    # TODO: handle multiple graphs (see #10)
    for graph in graphs:
        print('WARNING: an additionnal graph as been found in the dot file.'
              'It will be ignored.')
        print(graph)