Edd Mann Developer

Using Graphviz with SVG Output in PHP

Since adding the functionality to process syntax highlighting through Pygments I had been on the look out for similar external tools I could integrate. One area that I felt was lacking in my posts was accompanying visual aids, which would be useful when explaining a new concept or algorithm. Like most developers I feel at home in an editor, so delving into another software package did not appeal to me. It was highly desirable to maintain my current work-flow and store all content related to a post (unless necessary) within the single markdown file. Graphviz was the tool for job, enabling graphs to be described using a small DSL and results outputted in a variety of formats. Along with the ability to describe graphs in plain-text, the option to output results in SVG allowed me keep post dependencies down to a minimum. SVG (Scalable Vector Graphics) is an XML-based vector image format, providing lossless-scaling and small file sizes. This allowed me to embedded outputted graphs within the rendered post, similar in process to syntax highlighting with Pygments.

Implementation

Below is a simple example implementation to output SVG variants of each defined graph within a supplied string (post). I made the assumption that the ‘dot’ command (provided by Graphviz) is present in the user’s path (this can be easily altered). For consistency I decided to declare graph definitions using Markdown Extra’s fenced blocks notation, discussed further in the Pygments post. Providing the code block with a unique ‘.dot-show’ language type-hint allowed me to be sure of no conflicting pre-processes.

function graphviz($post)
{
    return preg_replace_callback('/~~~[\s]*\.dot-show\n(.*?)\n~~~/is', function($match)
    {
        list($orig, $dot) = $match;

        $proc = proc_open(
            'dot -Tsvg',
            [ [ 'pipe', 'r' ], [ 'pipe', 'w' ] /* ignore stderr */ ],
            $pipes
        );

        fwrite($pipes[0], $dot);
        fclose($pipes[0]);

        $output = stream_get_contents($pipes[1]);
        fclose($pipes[1]);

        if ( ! proc_close($proc)) {
            $output = preg_replace(
                '/.*<svg width="[0-9]+pt" height="([0-9]+pt)"/s',
                '<svg style="max-height:$1;" ',
                $output
            );
            $output = preg_replace('/<!--(.*)-->/Uis', '', $output);
            $output = preg_replace('/id="(.*?)"/s', 'id="$1_' . rand() . '"', $output);
        } else {
            $output = $orig;
        }

        return $output;
    }, $post);
}

Looking at the example above you will notice the use of the same regular expression replacement and process command calls found in the Pygments implementation. All that has been altered is the processing occurred on the outputted result and of course the command itself. Comments and unnecessary headers are removed from the resulting output, along with the inclusion of random ‘id’ element names (as multiple graphs may use the same names). The ‘max-height’ style replaces the defined width and height of the SVG element to fix an issue I found in maintaining height ratio when resizing the graph.

Examples

Now that we have an example implementation to work with, lets see some of the impressive results we can achieve when using Graphviz and SVG output.

UML Class Diagram

Inspired by the great article found here, I was able to describe a simple UML Class Diagram in .dot notation and output the results in SVG.

digraph G
{
    node [
        shape = "record"
    ]
    Animal [
        label = "{Animal|+ name : string\l+ age : int\l|+ walk() : void\l}"
    ]
    Dog [
        label = "{Dog||+ bark() : void\l}"
    ]
    Cat [
        label = "{Cat||+ meow() : void\l}"
    ]
    edge [
        arrowhead = "empty"
    ]
    Dog -> Animal
    Cat -> Animal
    edge [
        arrowhead = "none"
        headlabel = "0..*"
        taillabel = "0..*"
    ]
}

Binary Tree

digraph G
{
    graph[ordering="out"];
    null[shape=point];
    5 -> 3;
    5 -> 8;
    3 -> 1;
    3 -> 4;
    8 -> 6;
    8 -> null;
}

Circularly Doubly Linked-List

digraph G
{
    n1 [label="Linked List|{size: 3}",
    shape=record];
    n1 -> n2 [label="sentinel"];
    n2 [label="Entry|{null}",shape=record];
    n2 -> n3 [label="next"];
    n3 [label="Entry|{A}",shape=record];
    n3 -> n4 [label="next"];
    n4 [label="Entry|{B}",shape=record];
    n4 -> n5 [label="next"];
    n5 [label="Entry|{C}",shape=record];
    n5 -> n2 [label="next"];
    n5 -> n4 [label="previous"];
    n4 -> n3 [label="previous"];
    n3 -> n2 [label="previous"];
    n2 -> n5 [label="previous"];
}

Huffman Coding Tree

Using this generator I was able to visually represent the Huffman Tree for a given string.

digraph G
{
    edge [label=0];
    graph [ranksep=0];
    O [shape=record, label="{{O|2}|000}"];
    W [shape=record, label="{{W|2}|001}"];
    D [shape=record, label="{{D|1}|0100}"];
    H [shape=record, label="{{H|1}|0101}"];
    SPACE [shape=record, label="{{SPACE|2}|011}"];
    DHSPACE [label=4];
    T [shape=record, label="{{T|1}|1000}"];
    COMA [shape=record, label="{{COMA|1}|1001}"];
    TCOMA [label=2];
    S [shape=record, label="{{S|1}|1010}"];
    R [shape=record, label="{{R|1}|1011}"];
    SR [label=2];
    TCOMASR [label=4];
    E [shape=record, label="{{E|3}|110}"];
    L [shape=record, label="{{L|3}|111}"];
    18 -> 8 -> 4 -> O;
    DHSPACE -> 2 -> D;
    10 -> TCOMASR -> TCOMA -> T;
    SR -> S;
    6 -> E;4 -> W [label=1];
    2 -> H [label=1];
    8 -> DHSPACE -> SPACE [label=1];
    TCOMA -> COMA [label=1];
    TCOMASR -> SR -> R [label=1];
    18 -> 10 -> 6 -> L [label=1];
}

Resources