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:

If your goal is a very close match to the old doc maybe you’d be better served by an SVG editor than Graphviz? Graphviz is great if you have data and don’t know how to lay it out and want a computer to lay it out for you. It’s a bit worse in the other direction, where you already have a layout you want to copy: in that case a vector image editor may be a better fit? Depends on your goals

What I’m doing is converting old scans to html web pages (e.g. Notes on IMP Programming ) and I’ld like to embed the flowcharts from those documents using a textual description, and have them render on demand (as in that example, though I did code those ones up to fall back to pngs if the rendering isn’t available). Most of the flowchart constructs used were easy to replicate but when I do hit a tough one they tend to be really tough :slight_smile: But graphical editing and svg are not a good fit since the common constructs can’t be easily reused. graphviz was designed specifically for this sort of application, seems a shame to cop out and go GUI. (I don’t like GUIs, if you haven’t guessed :slight_smile: )

Thank you! I will study your example until I understand it :slight_smile: That’s definitely a pointer in the right direction that I should be able to adapt to the rest of my document. Appreciate the help.

As you are trying to match an existing layout and not asking Graphviz to start from scratch, you might also look at pikchr - a nice recreation of the PIC language.

Success :slight_smile: Possibly messier than one of you guys would do it but it renders the way I want. (just not in this forum where it looks quite different :-/ ) Actually I take that back, it renders OK in dreampuf but not in GraphVizFiddle. Haven’t tried it embedded on my own web page yet. Maybe I’m not quite there after all, but for now just happy to be a little closer (at least when using dreampuf to test in).

I had to use splines=false to force the bendy vector to be straight. Didn’t really understand that one.

digraph AA_Operating_System {
    rankdir=TB;
    layout=dot;
    splines=false;
    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=2.0,     label=""];
    
    eopleft                [shape=none,    group = left,   height=0, width=1.7,     label=""];
    
    //{rank=same; eopleft;eopmid;eopright};
    faultyleft             [shape=none,    group = left,   height=0, width=0,     label=""];
    bottomleft             [shape=none,    group = left,   height=0, width=0,     label=""];

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

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

    subgraph cluster_compiler {
        label="COMPILER";
        style=dashed;
        labelloc=t; labeljust=r;
        {rank=same; eopmid; eopright};
        compile ; dummy2 ; eopmid; movebuf;
    }
//
    subgraph cluster_entry {
        label="ENTRY SEQUENCE\nIN PERM";
        style=dashed;
        labelloc=t; labeljust=r;
        faulty; dummy3; loadprog;
    }

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

    // left:
    afterstartleft     -> afterstartmid [headclip=false, tailclip=false];
    //  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, headclip=false, tailclip=false] ;
    afterstartmid:s      -> loadperm:n [headclip=false, tailclip=true];
    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:w [label="NO", arrowhead=none];
    //eopright:w           -> eopmid:e [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,headclip=false, tailclip=false];
    eopleft            -> faultyleft [arrowhead=none];
    faultyleft         -> bottomleft [arrowhead=none, headclip=false, tailclip=false];

}

I remember pic! Didn’t know it was still around. I’ll keep it in mind if the graphviz route turns out to be too awkward in the long term. But I’m kind of hoping to build some sort of semi-custom flowchart tool to make these easier and once I’ve worked out all the different components I will need to reproduce this old style of flowchart it ought to be easier to churn them out faster by generating graphviz code and not having to worry about the graphics positioning etc. Unfortunately I have a lot of diagrams that I’ld like to update.