Connecting node centers with labels outside nodes

Is something like the below diagram possible with Graphviz?

In particular, I’ve been unable to figure out how to:

  1. Place node labels outside of the node itself
  2. Connect node centers
  3. Display nodes as graphics (e.g. with center pin)

Thank you.

On a bet, I suppose it could be done, but no guarantees. (The edge routing is probably the hardest part):

  • Explicitly (programmatically) position all nodes (pos | Graphviz), labels (lp | Graphviz), and clusters (bb | Graphviz)
  • Explicitly position “center pin” node on-top-of circular node (again pos | Graphviz)
  • Use neato -n (pos | Graphviz) to have neato just route edges. (use splines=ortho and edge [dir=both arrowhead=none arrowtail=none headclip=false tailclip=false] to go center-to-center)
  • Either:
    • Invoke religious icons in hopes that splines=ortho provides “nice” edge routing. (splines=ortho can be disappointing)
    • Explicitly route all edges yourself (poorly described here: Fun with edges!). Doable (maybe) but a bit of a pain.

Before doing the above, look at the PIC language (PIC (markup language) - Wikipedia). I like the pikchr & dpic variants.

I came up with the diagram below that meets some of your requirements, using HTML-like labels and basic DOT processing.

graph All Styles

A cluster is used to depict each Instrument.

Inside each cluster is a single node using the "plaintext" shape. The label of the shape uses HTML-like labels to create a two-row table.

  • Each table cell in the first row contains the label text for the cell below it in the second row.
  • Each table cell in the second row has a port name assigned within the <td> element (e.g. <td port="out2">) and uses a single character to depict the circle-enclosed dot, using the “Wingdings” font (on Windows) with a 30px pixel size.

Now you can add edges which connect via the ports using a statement such as:

a:out1 -- c:a;

I was not able to replicate everything in your example. Unfortunately splines="ortho" routing scrambles the result, and the HTML-like labels don’t support the headclip, headport, tailclip, or tailport attributes you would normally use to bring an edge to the center of a node.

That said, I hope this gets you closer to what you are trying to accomplish.

Here is the DOT source code used to create the graph above:

strict graph "main"
{
    layout="dot";
    rankdir="LR";

    graph[ fontname="Arial Bold" labeljust="l" ];
    node[ fontname="Wingdings" ];
    subgraph "cluster_1" {  label="Instrument A"
        "a"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_2" {  label="Instrument B"
        "b"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_3" {  label="Instrument C"
        "c"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">A</td><td fixedsize="true" width="50">B</td><td fixedsize="true" width="50">C</td><td fixedsize="true" width="50">D</td></tr><tr><td port="a"><font point-size="30">⦿</font></td><td port="b"><font point-size="30">⦿</font></td><td port="c"><font point-size="30">⦿</font></td><td port="d"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    "a":"out1" -- "c":"a";
    "b":"out1" -- "c":"b";
}

Another thing I just thought of is to use the ranksep and nodesep attributes to increase the spacing between the nodes.

The revised DOT source is as follows:

strict graph "main"
{
    layout="dot";
    rankdir="LR";

    graph[ fontname="Arial Bold" labeljust="l" ];
    node[ fontname="Wingdings" ];
    nodesep="2" ranksep="2"
    subgraph "cluster_1" {  label="Instrument A"
        "a"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_2" {  label="Instrument B"
        "b"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_3" {  label="Instrument C"
        "c"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">A</td><td fixedsize="true" width="50">B</td><td fixedsize="true" width="50">C</td><td fixedsize="true" width="50">D</td></tr><tr><td port="a"><font point-size="30">⦿</font></td><td port="b"><font point-size="30">⦿</font></td><td port="c"><font point-size="30">⦿</font></td><td port="d"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    "a":"out1" -- "c":"a";
    "b":"out1" -- "c":"b";
}

Another variation, using Top to Bottom (instead of Left to Right) layout:

Source:

strict graph "main"
{
    layout="dot";
    rankdir="TB";

    graph[ fontname="Arial Bold" labeljust="l" ];
    node[ fontname="Wingdings" ];
    nodesep="1.5" ranksep="1.5"
    subgraph "cluster_1" {  label="Instrument A"
        "a"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_2" {  label="Instrument B"
        "b"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">Out 1</td><td fixedsize="true" width="50">Out 2</td><td fixedsize="true" width="50">In 1</td></tr><tr><td port="out1"><font point-size="30">⦿</font></td><td port="out2"><font point-size="30">⦿</font></td><td port="in"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    subgraph "cluster_3" {  label="Instrument C"
        "c"[ shape="plaintext"  label=<<table border="0" cellborder="0" cellspacing="0"><tr><td fixedsize="true" width="50">A</td><td fixedsize="true" width="50">B</td><td fixedsize="true" width="50">C</td><td fixedsize="true" width="50">D</td></tr><tr><td port="a"><font point-size="30">⦿</font></td><td port="b"><font point-size="30">⦿</font></td><td port="c"><font point-size="30">⦿</font></td><td port="d"><font point-size="30">⦿</font></td></tr></table>> ];
    }
    "a":"out1" -- "c":"a";
    "c":"b" -- "b":"out1";
}