dot – Order of nodes in graphviz

I am working on this graph:

digraph "example.gsn.yaml" {

  ## Elements
  "G1" [shape="box", label=<<B>G1</B><BR align="left"/>Goal 1>];
  "G2" [shape="box", label=<<B>G2</B><BR align="left"/>Goal 2>];
  "G4" [shape="box", label=<<B>G4</B><BR align="left"/>Goal 4>];
  "G3" [shape="box", label=<<B>G3</B><BR align="left"/>Goal 3>];
  "C1" [shape="box", style="rounded", label=<<B>C1</B><BR align="left"/>Context 1>];
  "S1" [shape="parallelogram", label=<<B>S1</B><BR align="left"/>Strategy 1>];
  "A1" [shape="oval", label=<<B>A1</B><BR align="left"/>Argument 1>];
  "A1":e -> "A1":e [headlabel=< <B>A</B> >, labeldistance=2.5, penwidth=0, arrowhead=none];
  "Sn1" [shape="circle", label=<<B>Sn1</B><BR align="left"/>Solution 1>];
  "Sn2" [shape="circle", label=<<B>Sn2</B><BR align="left"/>Solution 2>];
  "J1" [shape="oval", label=<<B>J1</B><BR align="left"/>Justification 1>];
  "J1":e -> "J1":e [headlabel=< <B>J</B> >, labeldistance=2.5, penwidth=0, arrowhead=none];

  ## Relations
  "G1" -> "G2";
  "G1" -> "G3";
  "G1" -> "G4";
  "G2" -> "S1";
  "G4" -> "Sn2";
  "G3" -> "Sn1";
  "G3" -> "C1" [arrowhead=empty];
  {rank = same; "G3"; "C1"; }
  "S1" -> "Sn1";
  "S1" -> "J1" [arrowhead=empty];
  "S1" -> "A1" [arrowhead=empty];
  {rank = same; "A1"; "S1"; }
  {rank = same; "S1"; "J1"; }
}


This is my current graph.
I would like to minimize crossings.
My preferred layout would be that A1, S1 and J1 are on the same rank in a left-to-right order.
However I cannot find out how to do this.

Can anybody help, please? Thanks in advance.

maintainability – Best practices for maintainable Graphviz / PlantUML code

I’ve been using Graphviz a little and just found out about PlantUML which is quite similar.

I make diagrams but later the processes or systems depicted by the diagrams might change so I need to make changes to the diagrams, hence it is important that code used to generate the diagrams is maintainable.

I was wondering if there are any best practices, conventions or guides to writing maintainable code in Graphviz and/or PlantUML. What approaches you have, and if you can share tips from your experiences.

Graphviz and PlantUML have support for comments, do these improve maintainability, and if so, what is important to comment?

How to structure the document? For example, I can declare all the nodes first and the declare the edges so all the nodes are in one place and all the edges in another place, or I can declare multiple groups where the nodes and edges are defined together. But the edges go to multiple nodes so it can be difficult to group them logically together.

Add padding between nodes from command line when using Terraform and Graphviz

I’m generating *.dot files representing my infrastructure using Terraform graph command. I’m then using Graphviz to convert the dot graph to a png image. Only thing is the node graph isn’t very readable and I’d like to uniformly spread out the nodes.

I’ve tried playing with overriding node attributes with the -G and -N graphviz options but doesn’t do anything.

How can I uniformly spread out nodes when rendering dot data with Graphviz from the command line?

strace – apt install graphviz failing with segmentation fault

Trying to install the graphviz library and am seeing this:

mtaylor@galaxy:~/tmp/apt-strace-output$ sudo apt install -y graphviz
Reading package lists... Done
Building dependency tree       
Reading state information... Done
graphviz is already the newest version (2.40.1-2).
The following packages were automatically installed and are no longer required:
  acl colord-data emboss-data libcolorhug2 libexif12 libgphoto2-l10n libgphoto2-port12 libgusb2
  libhpdf-2.3.0 libieee1284-3 libsane-common
Use 'sudo apt autoremove' to remove them.
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
2 not fully installed or removed.
After this operation, 0 B of additional disk space will be used.
Setting up libgvc6 (2.40.1-2) ...
Error: /usr/lib/x86_64-linux-gnu/graphviz/config6a is zero sized, or other read error.
Warning: Could not load "/usr/lib/x86_64-linux-gnu/graphviz/libgvplugin_pango.so.6" - file not found
Segmentation fault (core dumped)
dpkg: error processing package libgvc6 (--configure):
 installed libgvc6 package post-installation script subprocess returned error exit status 139
dpkg: dependency problems prevent configuration of graphviz:
 graphviz depends on libgvc6; however:
  Package libgvc6 is not configured yet.

dpkg: error processing package graphviz (--configure):
 dependency problems - leaving unconfigured
No apport report written because the error message indicates its a followup error from a previous failure.
  Processing triggers for libc-bin (2.27-3ubuntu1.2) ...
Errors were encountered while processing:
 libgvc6
 graphviz
E: Sub-process /usr/bin/dpkg returned an error code (1)

config6a is indeed a 0 byte long file located in /usr/lib/x86_64-linux-gnu/graphviz.
I’ve no idea what the use or relevance of that file is.

Re-ran the above using
sudo strace -o outputfile.txt -ff -s 80 apt install graphviz
which output one output file for the parent process and 41 child processes.
One of the children executes execve(“/usr/sbin/libgvc6-config-update”) and
eventually segfaults. The last 18 lines of the file read:

access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libdatrie.so.1", O_RDONLY|O_CLOEXEC) = 4
read(4, "177ELF2113>130020@000a@0087@323115"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=26544, ...}) = 0
mmap(NULL, 2121744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7f9676801000
mprotect(0x7f9676807000, 2093056, PROT_NONE) = 0
mmap(0x7f9676a06000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x5000) = 0x7f9676a06000
close(4)                                = 0
mprotect(0x7f9676a06000, 4096, PROT_READ) = 0
mprotect(0x7f9676c32000, 8192, PROT_READ) = 0
mprotect(0x7f9676e3c000, 4096, PROT_READ) = 0
mprotect(0x7f96770da000, 4096, PROT_READ) = 0
mprotect(0x7f9677325000, 12288, PROT_READ) = 0
mprotect(0x7f967753d000, 4096, PROT_READ) = 0
mprotect(0x7f9677c53000, 4096, PROT_READ) = 0
mprotect(0x7f9677e5d000, 4096, PROT_READ) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xaf26} ---
+++ killed by SIGSEGV (core dumped) +++

I have read that generally one does not want to have a /etc/ld.so.nohwcap file.
I’d love to know what to try next to fix this!

Thanks in advance,

Marshall

System configuration
Arch: AMD 64 
OS: Ubuntu 18.04.4 LTS 
Kernel: 4.15.0-111-generic #112-Ubuntu SMP (x86_64)

C ++ wrapper for the graphviz library

background

The following code is (apparently minimal) torn from a current live project. The project offers data extraction, cleansing, analysis, clustering and visualization in a reasonable size (with a small budget, and for this reason we do not use graphics or the like for the visualization part).

So we use Graphviz & # 39; s very mature for our sins neato Motor that implements the Kamada Kawai algorithm, which has been shown to work well for our purpose (after many algorithms have been explored), although it doesn't scale very well. We opted for a graphical user interface instead of using the Boost graphics library. (possibly a mistake).

For this code review, I'm going to focus on one thin piece, that is C++ Wrapper class of graphviz lib. And specifically on one aspect. How to reasonably and safely get in touch with the many, many char* Parameters that the C-API expects.

Your friend who char*

I pasted the (slimmed down) wrapper class along with an improvised class main() To show usage. The wrapper only executes RAII and "method => function shoveling".

Most graphviz API applications char*, They are const (ie will they be changed when we call their API)? Who knows. They don't seem to be modified, but without reading all the sources, we can't know for sure.

We want const std::string& or std::string_view or at worst, even const char* APIs? Yes, we do.

We put in a bunch of string (sorry char*) Constants for attributes and color names etc., small example below.

The code shown works perfectly. It's messy, I don't like it because it uses a bunch of C-style casts to discard those constness, Yes, I could use it static_cast or reinterpret_cast or const_cast for some of these cases. Very painful syntax. In this encapsulated API, I choose the C representations for brevity.

Undefined behavior?

What's worse is that I think it causes undefined behavior. I have decided std::string_view as my C ++ end API type for all of these mini strings. There are a number of options, I've tried a few, but this seems reasonable because I need to store C ++ end tables of color constants, for example (see short code excerpt). – std::string seems like heavy overkill here.

But std::string_view should not be passed on to a char* because it is not guaranteed to quit '', – Maybe that's not UB, but it's bad!

As I said, it works perfectly because I know that all strings end with '', but it doesn't make me happy.

Feedback wanted.

  • General information about the older C-API encapsulation class
  • Specifically on this option and alternatives for that char* API – is my best option to deal with it (const) char* also in C ++ instead std::string_view?
#include  // these 2 includes are the graphiz cgraph lib
#include 

#include 

using size_t = std::size_t;

class Graph {
public:
  Graph() {
    gvc_ = gvContext();

    static const char* fargv() = {"neato", "-Tsvg"}; // NOLINT
    gvParseArgs(gvc_, 2, (char**)fargv);             // NOLINT

    graph_ = agopen((char*)"g", Agundirected, nullptr); // NOLINT

    // clang-format off
    set_graph_attr_def("splines",   "none");
    set_graph_attr_def("ratio",     "1.25");

    set_node_attr_def("tooltip",    "");
    set_node_attr_def("fillcolor",  "grey");
    set_node_attr_def("shape",      "point");
    set_node_attr_def("width",      "0.05");
    set_node_attr_def("penwidth",   "0");

    set_edge_attr_def("weight",     "1");
    // clang-format on
  }

  Graph(const Graph& other) = delete;
  Graph& operator=(const Graph& other) = delete;

  Graph(Graph&& other) = delete;
  Graph& operator=(Graph&& other) = delete;

  ~Graph() {
    if (graph_ != nullptr) {
      if (gvc_ != nullptr) gvFreeLayout(gvc_, graph_);
      agclose(graph_);
    }
    if (gvc_ != nullptr) gvFreeContext(gvc_);
  }

  void set_graph_attr_def(std::string_view name, std::string_view value) {
    agattr(graph_, AGRAPH, (char*)name.data(), (char*)value.data()); // NOLINT
  }

  void set_node_attr_def(std::string_view name, std::string_view value) {
    agattr(graph_, AGNODE, (char*)name.data(), (char*)value.data()); // NOLINT
  }

  void set_edge_attr_def(std::string_view name, std::string_view value) {
    agattr(graph_, AGEDGE, (char*)name.data(), (char*)value.data()); // NOLINT
  }

  void set_node_attr(Agnode_t* node, std::string_view name, std::string_view value) { // NOLINT
    agset(node, (char*)name.data(), (char*)value.data());                             // NOLINT
  }

  void set_edge_attr(Agedge_t* edge, std::string_view name, std::string_view value) { // NOLINT
    agset(edge, (char*)name.data(), (char*)value.data());                             // NOLIN
  }

  Agedge_t* add_edge(Agnode_t* src, Agnode_t* dest, std::string_view weight_str) {
    auto edge = agedge(graph_, src, dest, nullptr, 1);
    set_edge_attr(edge, "weight", weight_str);
    return edge;
  }

  Agnode_t* add_node(std::string_view node_name) {
    auto node = agnode(graph_, (char*)node_name.data(), 1); // NOLINT
    set_node_attr(node, "tooltip", node_name);
    return node;
  }

  void layout() {
    gvLayoutJobs(gvc_, graph_);
  }

  void render() {
    gvRenderJobs(gvc_, graph_);
  }

private:
  Agraph_t* graph_ = nullptr;
  GVC_t*    gvc_   = nullptr;
};

static constexpr const size_t max_colours = 30;

static constexpr const std::array colours = {
    "blue",           "green",         "red",        "gold",
    "black",          "magenta",       "brown",      "pink",
    "khaki",          "cyan",          "tan",        "blueviolet",
    "burlywood",      "cadetblue",     "chartreuse", "chocolate",
    "coral",          "darkgoldenrod", "darkgreen",  "darkkhaki",
    "darkolivegreen", "darkorange",    "darkorchid", "darksalmon",
    "darkseagreen",   "dodgerblue",    "lavender",   "mediumpurple",
    "plum",           "yellow"};

int main() {
  auto graph = Graph{}; // initializes instace of a graphviz graph

  // build node list by loading data from a mongo database

  auto node1 = graph.add_node("1");
  auto node2 = graph.add_node("2");
  // ...  10,000 + nodes  (that's all neato can handle, we would like more)

  // 2.3 is the "weight" and it's a double in our code but graphiz wants a string
  // there is a reason that the Graph::add_edge API takes the string
  // the double -> string conversion is quite expensive (we use Ryu)
  // and we need it twice. Once for graphviz and once for the cluster
  // both as a string
  graph.add_edge(node1, node2, "2.3");
  //... 2 - 25 million edges

  // run clustering algorithm on separate thread

  graph.layout(); // graphviz neato: slowest part of whole program

  // clustering has finished by now, update the colours
  graph.set_node_attr(node1, "fillcolor", colours(0)); // NOLINT
  graph.set_node_attr(node1, "fillcolor", colours(1)); // NOLINT
  // ...

  graph.render(); // sends svg to stdout
}

object-oriented – Use Python, python-graphviz, and graphviz to draw a UML-like class hierarchy of Python's abstract base classes

motivation

Pythons abstract base classes in the collections.abc Modules work as mixins and also define abstract interfaces that call common functions in Python objects.

While reading the actual source is quite revealing, it would be useful for Python programmers to look at the hierarchy as a graph.

Unified Modeling Language (UML) is a method of visualizing system design.

libraries

Graphviz is a natural choice for this type of issue. There are many examples of people who generate such issues with Graphviz.

However, to ensure correctness and completeness, as well as a simple customization of the logic with which they were created, the Python classes must be inserted into Graphviz.

A natural choice is python-graphviz, but we need to put the python into the graphviz interface.

Our grammar of graphics

UML communicates functions that Python does not have. By using a smaller subset of the relevant functions, we get a diagram that is clearer and easier for UML familiar users to understand.

Our subgroup consists of class diagrams, in which:

  • Names, data elements and methods of abstract classes are printed in italics, concrete implementations are not.
  • Edges (lines) are dashed if the subclass is abstract, not if the relation is a specialization.
  • We only show is a Relationships (class hierarchy) are not has a Relationships (composition) because it does not really apply to our application and because the abc The module has no such relevant comments (if we had comments showing this relationship, we could support them, but I will not be here).

Avoid information overload in the output

We exclude most standard methods and members in object, We also hide other members related to class relationships and implementations that have no direct impact on users.

We do not display return types (eg. : type) because we miss the notes in the module.

We do not show + (Public), - (private) and # (protected) icons, because everything in Python can be accessed and it is just a convention indicating that users should avoid interfaces preceded by underscores (_).

The code

I have this environment with Anaconda, which uses a fresh installation and needed

$ conda install graphviz python-graphviz

And I made my design in a notebook with Jupyter Lab.

First we import the Digraph object python-graphviz, some functions from the standard library collections.abc Module for the abstract base classes we want to examine and some types we want to use to distinguish between objects.

We also put types or methods and members that we want to exclude into a tuple and put the names of methods that we intentionally hide in a group.

from graphviz import Digraph
from inspect import getclasstree, isabstract, classify_class_attrs, signature
import collections.abc
from html import escape
from abc import ABCMeta
from types import WrapperDescriptorType, MethodDescriptorType, BuiltinFunctionType

# usually want to hide default (usually unimplemented) implementations:
# ClassMethodDescriptorType includes dict.fromkeys - interesting/rare- enough?
EXCLUDED_TYPES = ( 
    WrapperDescriptorType, # e.g. __repr__ and __lt__
    MethodDescriptorType, # e.g. __dir__, __format__, __reduce__, __reduce_ex__
    BuiltinFunctionType, # e.g. __new__
                 ) 
HIDE_METHODS = {
    '__init_subclass__',             # error warning, can't get signature
    '_abc_impl', '__subclasshook__', # why see this?
    '__abstractmethods__',
               }

Now we need to create the table (labels) for the graphviz nodes. Graphviz uses a syntax that looks like HTML (but not).

Here we create a function that inherits the class and returns the table from the information we can derive from the class.

def node_label(cls, show_all=False, hide_override=set()):
    italic_format = '{}'.format
    name_format = italic_format if isabstract(cls) else format
    attributes = ()
    methods = ()
    abstractmethods = getattr(cls, "__abstractmethods__", ())
    for attr in classify_class_attrs(cls):
        if ((show_all or attr.name(0) != '_' or attr.name in abstractmethods)
            and not isinstance(attr.object, EXCLUDED_TYPES)
            and attr.name not in hide_override):
            if name in abstractmethods:
                name = italic_format(attr.name)
            else:
                name = attr.name
            if attr.kind in {'property', 'data'}: 
                attributes.append(name)
            else:
                try:
                    args = escape(str(signature(attr.object)))
                except (ValueError, TypeError) as e:
                    print(f'was not able to get signature for {attr}, {repr(e)}')
                    args = '()'
                methods.append(name + args)
    td_align = ''
    line_join = '
'.join attr_section = f"
{td_align}{line_join(attributes)}" method_section = f"
{td_align}{line_join(methods)}" return f"""< {attr_section} {method_section}
{name_format(cls.__name__)}
>"""

The above code gives us a way to create the tables for the nodes.

Now we define a function that takes a class tree (the kind that is returned by) inspect.getclasstree(classes)) and returns a fully instantiated Digraph Object suitable to display an image.

def generate_dot(
    classtree, show_all=False, 
    hide_override=HIDE_METHODS, show_object=False):
    """recurse through classtree structure 
    and return a Digraph object
    """
    dot = Digraph(
        name=None, comment=None,
        filename=None, directory=None, format='svg', 
        engine=None, encoding='utf-8', 
        graph_attr=None, 
        node_attr=dict(shape='none'),
        edge_attr=dict(
            arrowtail='onormal',
            dir='back'),
        body=None, strict=False)
    def recurse(classtree):
        for classobjs in classtree:
            if isinstance(classobjs, tuple):
                cls, bases = classobjs
                if show_object or cls is not object:
                    dot.node(
                        cls.__name__, 
                        label=node_label(cls, show_all=show_all, 
                                         hide_override=hide_override))
                for base in bases:
                    if show_object or base is not object:
                        dot.edge(base.__name__, cls.__name__, 
                                 style="dashed" if isabstract(base) else 'solid')
            if isinstance(classobjs, list):
                recurse(classobjs)
    recurse(classtree)
    return dot

And the use:

classes = (c for c in vars(collections.abc).values() if isinstance(c, ABCMeta))
classtree = getclasstree(classes, unique=True)

dot = generate_dot(classtree, show_all=True, show_object=True)
#print(dot.source) # far too verbose...
dot

We do show_object=True here, but since everything is an object in Python, it's a bit redundant and will be at the top of every class hierarchy, so I think it's safe to use the default show_object=False for regular use.

dot.render('abcs', format='png')

Gives us the following PNG file (because the stack overflow does not display SVGs):

ABC class hierarchy

Suggestions for further work

An expert in Python or Graphviz could find important things that we have missed here.

We could Pull out some features and write some unit tests.

We could also search and edit annotations for data items that are usually included in it __dict__,

We may also not treat all types of Python object creation as we should.

I've mostly used the following example code to model the classes for creating the node table:

from abc import abstractmethod, ABCMeta
from inspect import isabstract, isfunction, classify_class_attrs, signature

class NodeBase(metaclass=ABCMeta):
    __slots__ = 'slota', 'slotb'
    @abstractmethod
    def abstract_method(self, bar, baz=True):
        raise NotImplementedError        
    def implemented_method(self, bar, baz=True):
        return True
    @property
    @abstractmethod
    def abstract_property(self):
        raise NotImplementedError
    @property
    def property(self):
        return False

class NodeExample(NodeBase):
    __slots__ = 'slotc'

NE = NodeExample

bash – gegl does not forward anything to graphviz

So I've read about a parameter in the man and info pages, called --dot, for GeGL. Allegedly, GV data are printed out, which can be read by graphviz. However, I can not recognize the actual program for recognizing the --dot Parameters and the inline statements at http://gegl.org/development.html, namely this line:

gegl --file gaussian-blur.xml --output out.png | dot -Tpng > gb.png

Does not create an actual diagram. I have confirmed that dot works with very simple GV files. However, GeGL does not seem to want to output any actual data, and typing –dot will only get the help message for an unrecognized parameter.

I realize that this is more of an error in the man page than in the actual program, as it was written some time ago, but does the piping from GeGL to Dot still work in some way? Could someone give me a sample line with which it can be made? An automatic graphics feature would be really handy.

If it helps, I'll try doing this on Linux Mint 19.1.

graphviz – I have a problem creating a PNG image from Java using Grahviz's point command

Java will issue the following error when it reaches the line where it should generate the file. I use the following line of code to generate the image:

Runtime.getRuntime (). Exec ("dot -Tpng" + System.getProperty ("user.dir") + "\ Tree" + Correlated + ". Dot -o" + System.getProperty ("user.dir") + "\ Tree" + correlative + ". png");

However, I get the following error:
java.io.IOException: Can not execute program "dot": CreateProcess error = 2, The system can not find the specified file
at java.lang.ProcessBuilder.start (unknown source)

Look for the error, but the answer I found was that I had to create an environment variable for Windows to detect the "dot" command. It's really the first time I've had this error with the Graphviz tool.

graph – Vertical and horizontal headings with Graphviz

I have to use Graphviz to create a vertical and horizontal header, but I do not know how the two subgraphs should have a different orientation. I tried rankdir, but apparently it does not work with subgraphs. I have no idea how to proceed. I am happy about any help.

Digrap table {
node[shape=box style=filled fillcolor=olivedrab2]
    // rankdir = TB
Subgraph lines {
rankdir = TB
ON[label="A"]
     B[label="B"]
     C[label="C"]
     D[label="D"]
     e[label="E"]
     F[label="F"]       
     A-> B
B-> A
B-> C
C-> B
C-> D
D-> C
D-> E
E-> D
E-> F
F-> E
{rank = equal to A B C D E F}
}
Subgraph column {
rankdir = LR
M[label="M"]
    N[label="N"]
    O[label="O"]
    P[label="P"]
    Q[label="Q"]
    R[label="R"]        
    M-> N
// N-> M
N → O
// O-> N
O-> P
// P-> O
P-> Q
// Q-> P
Q-> R
// R-> Q
{rank = equal to M N O P Q R}
}

}

Something like that