Trying to replicate old flowchart

Below is a flowchart from an old document that I want to recreate as closely as possible. I’ve worked out how to do most of it (it took me some time to realise that the ordering of statements was significant!) but I’m having real problems drawing a dotted box around multiple nodes - I’ve not been able to enclose all the nodes that are supposed to be in each group. I did discover that I had to put ‘rank=same’ inside the subgraph when there were nodes in the same rank, but every attempt at fixing this that way has made it worse.

This is what I’m trying to reproduce:

This is as good as I can get. You’ll notice the dotted boxes don’t include the decision nodes:

digraph AA_Operating_System { rankdir=TB; layout=dot; node [fontname="Courier New", fontsize=11]; edge [fontname="Courier New", fontsize=10]; graph [fontname="Courier New", fontsize=12, label="OUTLINE OF AA OPERATING SYSTEM", labelloc=t, labeljust=r]; // left: afterstartleft [shape=none, group = left, height=0, width=0, label=""]; eopleft [shape=none, group = left, height=0, width=0, label=""]; faultyleft [shape=none, group = left, height=0, width=0, label=""]; bottomleft [shape=none, group = left, width=0, height=0, label=""]; // mid: entrypoint [shape=none, group = middle, height=0, width=0, label="" ]; start [shape=box, group = middle, label="load call program\nunder director\ncontrol" ]; afterstartmid [shape=none, group = middle, height=0, width=0, label="" ]; loadperm [shape=box, group = middle, label="load perm\nand compiler\nfrom compiler\ntape" ]; loadpermaftermid [shape=none, group = middle, width=0, height=0, label="" ]; compile [shape=box, group = middle, label="compile one\nline of source\nprogram into\noutput buffers.\nmove buffers onto\nwork tape if\nnecessary" ]; eopmid [shape=hexagon, group = middle, height=1.1, width=2.0, label="line was\nend of program" ]; movebuf [shape=box, group = middle, label="move remaining\nbuffers onto work\ntape unless\nprogram faulty"]; faulty [shape=hexagon, group = middle, height=1.1, width=1.8, label="program is\nfaulty"]; loadprog [shape=box, group = middle, label="load program\nfrom work tape"]; users [shape=box, group = middle, label="USERS PROGRAM"]; bottommid [shape=none, group = middle, width=0, height=0, label=""]; // right: loadpermafterright [shape=none, group = right, width=0, height=0, label=""]; eopright [shape=none, group = right, width=0, height=0, label=""]; { rank=same; afterstartleft; afterstartmid; } { rank=same; loadpermaftermid; loadpermafterright; } { rank=same; eopleft; eopmid; eopright; } { rank=same; faultyleft; faulty; } { rank=same; bottomleft; bottommid; } // left: afterstartleft -> afterstartmid []; faultyleft -> faulty [dir=back, arrowtail=normal, label="YES"]; bottomleft -> bottommid [arrowhead=none]; // mid: entrypoint:s -> start:n; start:s -> afterstartmid:n [arrowhead=none] ; afterstartmid:s -> loadperm:n []; loadperm:s -> loadpermaftermid:n [arrowhead=none]; // right: loadpermaftermid -> loadpermafterright [dir=back, arrowtail=normal]; loadpermafterright -> eopright [arrowhead=none]; // mid: loadpermaftermid:s -> compile:n []; compile:s -> eopmid:n []; eopmid:s -> movebuf:n [label="YES"]; // right: eopmid -> eopright [label="NO", arrowhead=none]; // mid: movebuf:s -> faulty:n []; faulty:s -> loadprog:n [label="NO"]; loadprog:s -> users:n []; users:s -> bottommid:n [arrowhead=none]; // left: afterstartleft -> eopleft [arrowhead=inv]; eopleft -> faultyleft [arrowhead=none]; faultyleft -> bottomleft [arrowhead=none]; subgraph cluster_call { label="CALL PROGRAM"; style=dashed; labelloc=t; labeljust=r; loadperm; } subgraph cluster_compiler { label="COMPILER"; style=dashed; labelloc=t; labeljust=r; //compile ; eopmid; movebuf; movebuf //{ rank=same; eopleft; eopmid; eopright; } } subgraph cluster_entry { label="ENTRY SEQUENCE\nIN PERM"; style=dashed; labelloc=t; labeljust=r; faulty; loadprog; //{ rank=same; faultyleft; faulty; } } }

I’ve not been able to embed the graph+source using the [dot] tag as per the forum’s intro message so I’ll just insert the code again manually:

digraph AA_Operating_System {
    rankdir=TB;
    layout=dot;
    node [fontname="Courier New", fontsize=11];
    edge [fontname="Courier New", fontsize=10];
    graph [fontname="Courier New", fontsize=12, label="OUTLINE OF AA OPERATING SYSTEM", labelloc=t, labeljust=r];

    // left:
    afterstartleft         [shape=none,    group = left,   height=0, width=0,     label=""];
    eopleft                [shape=none,    group = left,   height=0, width=0,     label=""];
    faultyleft             [shape=none,    group = left,   height=0, width=0,     label=""];
    bottomleft             [shape=none,    group = left,   width=0, height=0,     label=""];

    // mid:
    entrypoint             [shape=none,    group = middle, height=0, width=0,     label="" ];
    start                  [shape=box,     group = middle,                        label="load call program\nunder director\ncontrol" ];
    afterstartmid          [shape=none,    group = middle, height=0, width=0,     label="" ];
    loadperm               [shape=box,     group = middle,                        label="load perm\nand compiler\nfrom compiler\ntape" ];
    loadpermaftermid       [shape=none,    group = middle, width=0, height=0,     label="" ];
    compile                [shape=box,     group = middle,                        label="compile one\nline of source\nprogram into\noutput buffers.\nmove buffers onto\nwork tape if\nnecessary" ];
    eopmid                 [shape=hexagon, group = middle, height=1.1, width=2.0, label="line was\nend of program" ];
    movebuf                [shape=box,     group = middle,                        label="move remaining\nbuffers onto work\ntape unless\nprogram faulty"];
    faulty                 [shape=hexagon, group = middle, height=1.1, width=1.8, label="program is\nfaulty"];
    loadprog               [shape=box,     group = middle,                        label="load program\nfrom work tape"];
    users                  [shape=box,     group = middle,                        label="USERS PROGRAM"];
    bottommid              [shape=none,    group = middle, width=0, height=0,     label=""];
    // right:
    loadpermafterright     [shape=none,    group = right,  width=0, height=0,     label=""];
    eopright               [shape=none,    group = right,  width=0, height=0,     label=""];


    { rank=same; afterstartleft; afterstartmid; }
    { rank=same; loadpermaftermid; loadpermafterright; }
    { rank=same; eopleft; eopmid; eopright; }
    { rank=same; faultyleft; faulty; }
    { rank=same; bottomleft; bottommid; }

    // left:
    afterstartleft     -> afterstartmid [];
    faultyleft         -> faulty [dir=back, arrowtail=normal, label="YES"];
    bottomleft         -> bottommid [arrowhead=none];

    // mid:
    entrypoint:s         -> start:n;
    start:s              -> afterstartmid:n [arrowhead=none] ;
    afterstartmid:s      -> loadperm:n [];
    loadperm:s           -> loadpermaftermid:n [arrowhead=none];     

    // right:
    loadpermaftermid   -> loadpermafterright [dir=back, arrowtail=normal];
    loadpermafterright -> eopright [arrowhead=none];

    // mid:
    loadpermaftermid:s   -> compile:n [];
    compile:s            -> eopmid:n [];
    eopmid:s             -> movebuf:n  [label="YES"];

    // right:                
    eopmid             -> eopright [label="NO", arrowhead=none];

    // mid:
    movebuf:s            -> faulty:n [];                                                  
    faulty:s             -> loadprog:n [label="NO"];
    loadprog:s           -> users:n [];
    users:s              -> bottommid:n [arrowhead=none];                              

    // left:
    afterstartleft     -> eopleft [arrowhead=inv];
    eopleft            -> faultyleft [arrowhead=none];
    faultyleft         -> bottomleft [arrowhead=none];

    subgraph cluster_call {
        label="CALL PROGRAM";
        style=dashed;
        labelloc=t; labeljust=r;
        loadperm;
    }

    subgraph cluster_compiler {
        label="COMPILER";
        style=dashed;
        labelloc=t; labeljust=r;
        //compile ; eopmid; movebuf;
        movebuf
        //{ rank=same; eopleft; eopmid; eopright; }
    }

    subgraph cluster_entry {
        label="ENTRY SEQUENCE\nIN PERM";
        style=dashed;
        labelloc=t; labeljust=r;
        faulty; loadprog;
        //{ rank=same; faultyleft; faulty; }
    }

}

I’m not asking you to write it for me, just point me in the right direction please, because I’ve tried everything I can think of, and there’s clearly some concept I’m still missing (unless the problem is that it’s just not possible?).

thanks,

Graham

Below is a “pretty close” version. One edge to fix.
The biggest change was to move the clusters up in the text. (The Graphviz language does add attributes as it parses, it is first-definition-wins.
Then removed two rank=same entries that conflicted with the cluster definitions.
Finally added one minlen to replace (kindof) one of the rank=same entries


// from https://forum.graphviz.org/t/trying-to-replicate-old-flowchart/3377

digraph AA_Operating_System {
    rankdir=TB;
    layout=dot;
    node [fontname="Courier New", fontsize=11];
    edge [fontname="Courier New", fontsize=10];
    graph [fontname="Courier New", fontsize=12, label="OUTLINE OF AA OPERATING SYSTEM", labelloc=t, labeljust=r];

    // left:
    afterstartleft         [shape=none,    group = left,   height=0, width=0,     label=""];
    eopleft                [shape=none,    group = left,   height=0, width=0,     label=""];
    faultyleft             [shape=none,    group = left,   height=0, width=0,     label=""];
    bottomleft             [shape=none,    group = left,   width=0, height=0,     label=""];

    // mid:
    entrypoint             [shape=none,    group = middle, height=0, width=0,     label="" ];
    start                  [shape=box,     group = middle,                        label="load call program\nunder director\ncontrol" ];
    afterstartmid          [shape=none,    group = middle, height=0, width=0,     label="" ];
    loadperm               [shape=box,     group = middle,                        label="load perm\nand compiler\nfrom compiler\ntape" ];
    loadpermaftermid       [shape=none,    group = middle, width=0, height=0,     label="" ];
    compile                [shape=box,     group = middle,                        label="compile one\nline of source\nprogram into\noutput buffers.\nmove buffers onto\nwork tape if\nnecessary" ];
    eopmid                 [shape=hexagon, group = middle, height=1.1, width=2.0, label="line was\nend of program" ];
    movebuf                [shape=box,     group = middle,                        label="move remaining\nbuffers onto work\ntape unless\nprogram faulty"];
    faulty                 [shape=hexagon, group = middle, height=1.1, width=1.8, label="program is\nfaulty"];
    loadprog               [shape=box,     group = middle,                        label="load program\nfrom work tape"];
    users                  [shape=box,     group = middle,                        label="USERS PROGRAM"];
    bottommid              [shape=none,    group = middle, width=0, height=0,     label=""];
    // right:
    loadpermafterright     [shape=none,    group = right,  width=0, height=0,     label=""];
    eopright               [shape=none,    group = right,  width=0, height=0,     label=""];

   subgraph cluster_call {
        label="CALL PROGRAM";
        style=dashed;
        labelloc=t; labeljust=r;
        loadperm;
    }

    subgraph cluster_compiler {
        label="COMPILER";
        style=dashed;
        labelloc=t; labeljust=r;
        compile ; eopmid; movebuf;
    }

    subgraph cluster_entry {
        label="ENTRY SEQUENCE\nIN PERM";
        style=dashed;
        labelloc=t; labeljust=r;
        faulty; loadprog;
    }

    { rank=same; afterstartleft; afterstartmid; }
    { rank=same; loadpermaftermid; loadpermafterright; }
//    { rank=same; eopleft; eopmid; eopright; }
//    { rank=same; faultyleft; faulty; }
    { rank=same; bottomleft; bottommid; }

    // left:
    afterstartleft     -> afterstartmid [];
    //  removed rank=same, added minlen
    faultyleft         -> faulty [dir=back, arrowtail=normal, label="YES" minlen=0];
    bottomleft         -> bottommid [arrowhead=none];

    // mid:
    entrypoint:s         -> start:n;
    start:s              -> afterstartmid:n [arrowhead=none] ;
    afterstartmid:s      -> loadperm:n [];
    loadperm:s           -> loadpermaftermid:n [arrowhead=none];     

    // right:
    loadpermaftermid   -> loadpermafterright [dir=back, arrowtail=normal];
    loadpermafterright -> eopright [arrowhead=none];

    // mid:
    loadpermaftermid:s   -> compile:n [];
    compile:s            -> eopmid:n [];
    eopmid:s             -> movebuf:n  [label="YES"];

    // right:                
    eopmid:e             -> eopright [label="NO", arrowhead=none];

    // mid:
    movebuf:s            -> faulty:n [];                                                  
    faulty:s             -> loadprog:n [label="NO"];
    loadprog:s           -> users:n [];
    users:s              -> bottommid:n [arrowhead=none];                              

    // left:
    afterstartleft     -> eopleft [arrowhead=inv];
    eopleft            -> faultyleft [arrowhead=none];
    faultyleft         -> bottomleft [arrowhead=none];

}

Giving: