graphviznetworkxpygraphvizpyprocessing

How to set the output size in GraphViz for the dot format?


I want to make sure, that all nodes are within some range (say [0,0,W,H]) after layout.

I figured bounding box would be the solution by using the bb attribute, but both dot and neato simply overwrite it.

For example my graph:

strict digraph {
    1,2,3;
    1 -> 3;
    3 -> 2;
}

Output of neato -Gbb="0,0,50,50" -T dot file.txt:

strict digraph {
        graph [bb="0,0,120.49,162.36"];
        node [label="\N"];
        1        [height=0.5,
                pos="27,18",
                width=0.75];
        3        [height=0.5,
                pos="70.233,75.918",
                width=0.75];
        1 -> 3   [pos="e,57.954,59.469 39.043,34.133 43.004,39.441 47.504,45.468 51.827,51.261"];
        2        [height=0.5,
                pos="93.485,144.36",
                width=0.75];
        3 -> 2   [pos="e,87.436,126.56 76.221,93.545 78.643,100.67 81.496,109.07 84.177,116.97"];
}

I get the same positions for any value of bb or any combination of size and dpi.

What I need, is to have all nodes within a given box.

Any suggestions how to do this?

Full story:

I'm using *graphviz_layout* from networkx to layout my graph. I draw the graph with pyprocessing. I want to avoid re-scaling the results from *graphviz_layout*, if there is a way to tell neato my bounds instead.


Solution

  • Here's the TL;DR version:

    To generate a 900 by 1500 pixel PNG image from the graph in foo.gv you can run:

    dot -Tpng -Gsize=9,15\! -Gdpi=100 -ofoo.png foo.gv
    

    to yield an image that is exactly 900 pixels wide or 1500 tall, but not necessarily both; and then:

    convert foo.png -gravity center -background white -extent 900x1500 final.png
    

    to pad the image with whitespace so that it is exactly 900 by 1500 pixels; where convert is part of the ImageMagick suite.

    Here's the detailed explanation:

    To clarify:

    1. The bb attribute is marked as "write-only", so none of the Graphviz layout engines are likely to respect whatever values you submit before processing.

    2. In the general case, Graphviz can generate both vector (scalable) and bitmap graphics, so DPI isn't always a meaningful metric.

      In particular, the dpi attribute is only valid for bitmap and SVG output formats.

    What you're trying to do is a little bit complicated as the pixel size of an image generated by Graphviz is driven by a couple of interacting attributes.

    Let's take an arbitrary graph as an example.

    The command gvgen‎ -dh3 will generate a directed hypercube of degree three. (If you'd like more detail on that, a PDF rendition of of gvgen's man page is here, but it doesn't matter, it's just an arbitrary graph for demonstration purposes.)

    A default dot-generated PNG rendering of that graph can be created by running the command:

    gvgen -dh3 | dot -Tpng -oexample.png
    

    When I do that, I get a 275 x 347 pixel PNG-format (bitmap) image. (As reported by file example.png.)

    The size attribute lets you recommend a maximum or desired height and width for the output image in inches. I.e., the attribute size=3,5 tells Graphviz to generate a 3 by 5 inch image at most. If the image is smaller than 3 by 5 to begin with, Graphviz will leave it alone. If the image is larger than 3 inches by 5 inches to begin with, Graphviz will scale it down until it fits within a 3 by 5 inch canvas (preserving the aspect ratio of the original image).

    Adding an exclamation point changes the size attribute from a maximum size to a desired size. Now if both dimensions are smaller than the specified size, the drawing is scaled up until at least one dimension equals the specified size.

    For example, running:

    gvgen -dh3 | dot -Tpng -Gsize=3,5\! -oexample.png
    

    (Note that \! escapes the exclamation point for bash. In a DOT file you would just write size=3,5!.)

    generates a 288 x 363 pixel image, slightly larger in both dimensions that the default.

    How did dot come up with those numbers? Well, Graphviz's default DPI value is 96 pixels per inch. At that resolution a 3 by 5 inch image is a 288 by 480 pixel image. The layout engine has simply scaled up the image until at least one of the dimensions matched the desired size.

    You can override the default DPI value using the dpi attribute, like so:

    gvgen -dh3 | dot -Tpng -Gsize=3,5\! -Gdpi=200 -oexample.png
    

    Now the generated image measures 600 by 757 pixels. Again, Graphviz simply scaled the image up until one of the dimensions matched the desired size (but at this resolution, a 3 by 5 inch image is a 600 by 1000 pixel image).

    Hence if you want to generate an image that contains an arbitrary number of pixels you need to set both size and dpi appropriately.

    I.e., suppose you want to create a 900 by 1500 pixel image. You could use:

    gvgen -dh3 | dot -Tpng -Gsize=9,15\! -Gdpi=100 -oexample.png
    

    or

    gvgen -dh3 | dot -Tpng -Gsize=3,5\! -Gdpi=300 -oexample.png
    

    or even:

    gvgen -dh3 | dot -Tpng -Gsize=900,1500\! -Gdpi=1 -oexample.png
    

    (But while that last one seemed to work for me in quick test, I'd recommend sticking with a more conventional DPI value just in case.)

    That solves most of your problem, but note that Graphviz is only scaling the image until one of the dimensions matches the specified size. Depending upon the aspect ratio of your original image you may or may not get the exact size you are looking for.

    This is where the ratio attribute comes into play.

    The command:

    gvgen -dh3 | dot -Tpng -Gsize=3,5\! -Gdpi=300 -Gratio=fill -oexample.png
    

    should give you an image with the precise dimension you are looking for, but with a couple of caveats:

    ratio also accepts a few different values. See the documentation and/or experiment a little to see what they do.

    For what it's worth, if you literally want an image of the precise pixel dimensions you're requesting, my suggestion would be to let Graphviz scale the image such that one of the two dimensions matches and then use something like ImageMagick to pad the image to get to the precise aspect ratio you're looking for. E.g., the command:

    convert in.png -gravity center -background white -extent 900x1500 out.png
    

    will center the image (from in.png) on a 900 by 1500 pixel canvas (saving it into out.png) by padding the horizontal or vertical dimension as necessary