Graphviz Example

An "UML-like" Graph (from Stackoverflow )

Introduction

As part of my experiments to see how far I can coerce Graphviz into making graphs people have drawn with other tools, I decided to recreate a graph the original author claimed was an idiosyncratic UML class diagram.

The original

What the original author wanted, but failed to create in Graphviz was this:

In the original author's attempts, he gave nodes a record shape so he could utilize the eability to connect edges to certain ports. This was a good idea, unfortunately for him he ran into various problems that stem from not knowing how Graphviz works and the various quirks this entails.

My Versions

Luckily I have a bit more experience in Graphviz and with relatively little effort, this was what I came up with, utilizing records:

The source code I used can be found at the end of this article.

To achieve the desired effect (removing the horizontal lines), I hid the records border by setting its color to none. I then placed a cluster around the node and used that to draw a border instead.

Other than than, I added some whitespace, aligned the text, tweaked the edge label a bit and used lhead (which requires compound = true set on the graph to work) to have the edge's head arrow point to the cluster's border instead of to the node with the port.

Can we do better?

The end result is pretty close to the desired look, although having to use a clusters for every node is a bit annoying and the edge doesn't quite start at the node's text. I though an attempt using an HTML shape for the nodes might fix this, so I gave it a go.

The end result is not much different except that the line of the arrow starts even further away from the node's text, so the previous version is the one to go with.

Final Results

Original Version

This licensed of this work is Unknown

My Version using record Shape

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License

My Version using HTML Shape

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License

Source Code

In case you read through the code and wonder what the \N is about, it is a special escape string that is replaced by the name of the node.


digraph G {

    graph [
        compound = true     // To clip the head at the cluster border
        dpi = 200
        penwidth = 2        // Make the cluster's borders a bit thicker
        rankdir = "LR"
        ranksep = 1         // Add a bit more space inbetween nodes
    ]

    node [
        color = none        // Hide the node's border
        fontname = "Bitstream Vera Sans"
        height = 0          // Make the node as small as possible (it will grow if it needs more space)
        margin = 0          // Remove unneeded whitespace
        shape = "record"
    ]

    edge [
        arrowhead = "open"
        labelangle = -5     // Place the asteriks closer to the line
        labeldistance = 2.5 // Place the asteriks further away from the arrow head
        penwidth = 2        // Make the line a bit thicker
    ]

    /* @NOTE: escaping spaces in the label using '\' doesn't work so use '\' or '&nbsp' instead. */
    subgraph cluster_Person {
        Person [
            label = "\N\l | &nbsp;&nbsp;&nbsp; age : int\l | <livesIn> &nbsp;&nbsp;&nbsp; livesIn : City\l | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sinceYear : int\l"
        ]
    }

    subgraph cluster_City {
        City [
            label = "<city> \N\l | &nbsp;&nbsp;&nbsp; name : string\l"
        ]
    }

    Person:livesIn -> City:city [headlabel = "*", lhead = "cluster_City"] // lhead allows us to point to the cluster's border instead of the node, as long as we add `compound = true` to the graph
}        

digraph G {

    graph [
        rankdir = "LR"
        ranksep = 1        // Add a bit more space inbetween nodes
    ]

    node [
        fontname = "Bitstream Vera Sans"
        height = 0          // Make the node as small as possible (it will grow if it needs more space)
        margin = 0          // Remove unneeded whitespace
        shape = "plaintext" // Make sure our HTML is not placed inside a node
    ]

    edge [
        arrowhead = "open"
        labelangle = -5     // Place the asteriks closer to the line
        labeldistance = 1.5 // Place the asteriks further away from the arrow head
        penwidth = 2        // Make the line a bit thicker
    ]

        
    Person [
        label = <
            <table CELLBORDER="0" CELLSPACING="1" BORDER="2">
                <tr><td align="left">Person</td></tr>
                <tr><td align="left">    age : int</td></tr>
                <tr><td align="left" port="livesIn">    livesIn : City</td></tr>
                <tr><td align="left">        sinceYear : int</td></tr>                
            </table>
        >
    ]
    
    City [
        label = <
            <table CELLBORDER="0" CELLSPACING="1" BORDER="2">
                <tr><td align="left" port="city">City</td></tr>
                <tr><td align="left" cellpadding="1">    name : string</td></tr>
            </table>
        >
    ]
    
    Person:livesIn -> City:city [headlabel = "*"]
}