how to write GraphViz subgraphs with boost::write_graphviz
Asked Answered
P

1

8

Is it possible to generate a DOT subgraph using ::boost::write_graphviz?

For instance, if I create a subgraph G0 in a graph G, can I get something like the following in the DOT output:

graph G {
  subgraph G0 {
    ...
  }
  ...
}
Political answered 28/3, 2015 at 1:0 Comment(4)
How would you "add a subgraph to a graph" in BGL?Carmelacarmelia
@Carmelacarmelia with create_subgraph?Political
I mean, perhaps you can honour us with a simple self contained example? I've spent over 45 minutes trying to figure this out. It's frustrating that I continuously have to include the possibility that I might be using subgraphs in the wrong/a different way. Questions without specific context and "this is where I'm stuck" are generally too broad. (Me asking that is not a dumb question, for the record. Even though I have some experience (considerable, maybe even), I happen to not have used subgraphs.)Carmelacarmelia
Indeed I didn't understand subgraphs. But now I do :/ See my answerCarmelacarmelia
C
21

I finally figured out both how subgraphs work and how to use boost::write_graphviz to actually print these.

The first requirement is "semi-documented" in a comment in the boost library source code: requires graph_name property.

The most surprising requirement however seemed to be that detail::write_graphviz_subgraph assumes the presence of

  • vertex_attribute
  • edge_attribute
  • graph_vertex_attribute, graph_edge_attribute, graph_graph_attribute graph properties

properties. I'd think these requirements can be quite restrictive, because your graph type would look at least like this:

using Graph =
    adjacency_list<vecS, vecS, directedS, 
      property<vertex_attribute_t, GraphvizAttributes>,
      property<edge_index_t, int, property<edge_attribute_t, GraphvizAttributes> >,
      property<graph_name_t, std::string,
        property<graph_graph_attribute_t,  GraphvizAttributes,
        property<graph_vertex_attribute_t, GraphvizAttributes,
        property<graph_edge_attribute_t,   GraphvizAttributes>
      > > >
    >;

Anyways, it's nice to demonstrate how to use those to actually supply edge/node/(sub)graph attributes to Graphviz, of course:

Live On Coliru

template <typename SubGraph> SubGraph create_data()
{
    enum { A,B,C,D,E,F,N }; // main edges
    SubGraph main(N);

    SubGraph& sub1 = main.create_subgraph();
    SubGraph& sub2 = main.create_subgraph();

    auto A1 = add_vertex(A, sub1);
    auto B1 = add_vertex(B, sub1);

    auto E2 = add_vertex(E, sub2);
    auto C2 = add_vertex(C, sub2);
    auto F2 = add_vertex(F, sub2);

    add_edge(A1, B1, sub1);
    add_edge(E2, F2, sub2);
    add_edge(C2, F2, sub2);

    add_edge(E, B, main);
    add_edge(B, C, main);
    add_edge(B, D, main);
    add_edge(F, D, main);

    // setting some graph viz attributes
    get_property(main, graph_name) = "G0";
    get_property(sub1, graph_name) = "clusterG1";
    get_property(sub2, graph_name) = "clusterG2";

    get_property(sub1, graph_graph_attribute)["label"]              = "G1";
    /*extra*/get_property(sub1, graph_vertex_attribute)["shape"]    = "Mrecord";

    get_property(sub2, graph_graph_attribute)["label"]              = "G2";
    /*extra*/get_property(sub1, graph_vertex_attribute)["color"]    = "red";
    /*extra*/get_property(sub2, graph_graph_attribute)["fillcolor"] = "lightgray";
    /*extra*/get_property(sub2, graph_graph_attribute)["style"]     = "filled";
    /*extra*/get_property(sub2, graph_vertex_attribute)["shape"]    = "circle";

    return main;
}

enter image description here

That's with

int main() {
#ifdef GENERATE_RANDOM_GRAPHS
    auto g = generate_random<subgraph<Graph> >();
#else
    auto g = create_data<subgraph<Graph> >();
#endif

    for (auto vd : make_iterator_range(vertices(g))) {
        put(get(vertex_attribute, g), vd, 
                GraphvizAttributes{
                    {"label", name_for_index(vd)}
                });
    }

    write_graphviz(std::cout, g);
}

I've also implemented generate_random for my own testing and understanding, and it generates graphs like:

enter image description here

Full Program

Live On Coliru

#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/subgraph.hpp>
#include <iostream>

using namespace boost;

//#define GENERATE_RANDOM_GRAPHS
#ifdef GENERATE_RANDOM_GRAPHS
#include <boost/graph/random.hpp> // in case you comment out the random graph creation code
#include <random>

template <typename SubGraph> SubGraph generate_random()
{
    std::mt19937 prng(std::random_device{}());
    SubGraph randomized(uniform_int<int>(10,20)(prng));

    auto subs = uniform_int<int>(1,5)(prng);
    while (subs--) randomized.create_subgraph();
    subs = boost::size(randomized.children());

    int offset = 0;
    for (auto& sub : make_iterator_range(randomized.children()))
    {
        for (size_t i = offset; i < num_vertices(randomized); i += subs) 
            add_vertex(i, sub);
        ++offset;
    }

    auto random_edges = [&](SubGraph& g) {
        uniform_int<typename SubGraph::vertex_descriptor> v(0, num_vertices(g) -1);
        for (size_t i = 1; i < 4; ++i)
            add_edge(v(prng), v(prng), g);
    };

    for (auto& sub : make_iterator_range(randomized.children()))
        random_edges(sub);
    random_edges(randomized);

    // setting some graph viz attributes
    get_property(randomized, graph_name) = "G0";

    offset = 0;
    for (auto& sub : make_iterator_range(randomized.children())) {
        ++offset;
        get_property(sub, graph_name) = "cluster" + std::to_string(offset);
        get_property(sub, graph_graph_attribute)["label"]    = "G" + std::to_string(offset);
    }

    return randomized;
}
#else

template <typename SubGraph> SubGraph create_data()
{
    enum { A,B,C,D,E,F,N }; // main edges
    SubGraph main(N);

    SubGraph& sub1 = main.create_subgraph();
    SubGraph& sub2 = main.create_subgraph();

    auto A1 = add_vertex(A, sub1);
    auto B1 = add_vertex(B, sub1);

    auto E2 = add_vertex(E, sub2);
    auto C2 = add_vertex(C, sub2);
    auto F2 = add_vertex(F, sub2);

    add_edge(A1, B1, sub1);
    add_edge(E2, F2, sub2);
    add_edge(C2, F2, sub2);

    add_edge(E, B, main);
    add_edge(B, C, main);
    add_edge(B, D, main);
    add_edge(F, D, main);

    // setting some graph viz attributes
    get_property(main, graph_name) = "G0";
    get_property(sub1, graph_name) = "clusterG1";
    get_property(sub2, graph_name) = "clusterG2";

    get_property(sub1, graph_graph_attribute)["label"]              = "G1";
    /*extra*/get_property(sub1, graph_vertex_attribute)["shape"]    = "Mrecord";

    get_property(sub2, graph_graph_attribute)["label"]              = "G2";
    /*extra*/get_property(sub1, graph_vertex_attribute)["color"]    = "red";
    /*extra*/get_property(sub2, graph_graph_attribute)["fillcolor"] = "lightgray";
    /*extra*/get_property(sub2, graph_graph_attribute)["style"]     = "filled";
    /*extra*/get_property(sub2, graph_vertex_attribute)["shape"]    = "circle";

    return main;
}
#endif

using GraphvizAttributes = 
    std::map<std::string, std::string>;

using Graph =
    adjacency_list<vecS, vecS, directedS, 
        property<vertex_attribute_t, GraphvizAttributes>,
        property<edge_index_t, int, property<edge_attribute_t, GraphvizAttributes> >,
        property<graph_name_t, std::string,
        property<graph_graph_attribute_t,  GraphvizAttributes,
        property<graph_vertex_attribute_t, GraphvizAttributes,
        property<graph_edge_attribute_t,   GraphvizAttributes>
        > > >
    >;

static std::string name_for_index(intmax_t index) {
    std::string name;

    do {
        name += 'A' + (index%26);
        index /= 26;
    } while (index);

    return name;
}

int main() {
#ifdef GENERATE_RANDOM_GRAPHS
    auto g = generate_random<subgraph<Graph> >();
#else
    auto g = create_data<subgraph<Graph> >();
#endif

    for (auto vd : make_iterator_range(vertices(g))) {
        put(get(vertex_attribute, g), vd, 
                GraphvizAttributes{
                    {"label", name_for_index(vd)}
                });
    }

    write_graphviz(std::cout, g);
}
Carmelacarmelia answered 29/3, 2015 at 23:48 Comment(2)
I believe we cannot use make_iterator_vertex_map(names) and subgraph<Graph> as you have defined it, at least not in 1.68.0. Please see https://mcmap.net/q/1322938/-boost-graph-and-graphviz-nested-subgraphs.Cisneros
@Cisneros very good spot. UBSan/ASan agreed. In the linked question I explain where I got my wrong assumption (which unfortunately appeared to work). I fixed this answer too, now. Cheers!Carmelacarmelia

© 2022 - 2024 — McMap. All rights reserved.