I expected the code
#include
#include
#include
#include
The trouble was caused by supplying make_iterator_vertex_map(names)
, which returns char
s from names
('A'
, 'B'
). This is incompatible with the expected (and default) vertex_index
properties (0
, 1
). After the fix, I get:
digraph G0 {
subgraph clusterG1 {
subgraph clusterG2 {
1;
}
0;
}
0 -> 1;
}
vertex_marker
, which is std::vector<bool>
of 2
elements in my case, not 67
and more was expected to be indexed by vertex indices. I was experiencing undefined behavior,
and if ( vertex_marker[pos] )
was never fulfilled.
The responsible portion of boost::detail::write_graphviz_subgraph
:
// Print out vertices and edges not in the subgraphs.
typename graph_traits<Graph>::vertex_iterator i, end;
typename graph_traits<Graph>::edge_iterator ei, edge_end;
for(boost::tie(i,end) = vertices(g); i != end; ++i) {
Vertex v = g.local_to_global(*i);
int pos = get(vertex_id, v); // 66, 65, should be 1, 0
if ( vertex_marker[pos] ) { // out-of-bounds access
vertex_marker[pos] = false;
out << escape_dot_string(pos);
make_vertex_attributes_writer(g.root())(out, v);
out << ";" << std::endl;
}
}
A static_assert
/SFINAE of the vertex_id
parameter type against Graph
's vertex_id
type (which can be done immediately in boost::write_graphviz
) would be greatly welcome. On a side note, the hardcoded int pos = ...
does not look too professional...
And if we want to keep the custom names, we have to provide them via label
attributes instead.
Good spot. The linked answer actually has UB, as your own answer explains. The map passed in to the write_graphviz
function isn't actually supposed to have the node_id for the graphviz output. Instead, that map was assumed to be a vertex_index_t
propertymap.
That was an assumption I probably held from boost::print_graph
(graph_utility.hpp) that does that such property-map.
To make it work safely I'd modify the example to employ write_graphviz_dp
- using dynamic properties:
int main() {
boost::dynamic_properties dp;
dp.property("node_id", boost::make_transform_value_property_map<std::string>(&name_for_index, boost::identity_property_map{}));
write_graphviz_dp(std::cout, create_data<subgraph<Graph> >(), dp);
}
I opted to use a transform function to get a name for any vertex descriptor, not wanting to assume anything about the number of vertices anymore either, I wrote the more general function to generate names like "A",...,"Z","AA",...,"ZZ" etc.:
static std::string name_for_index(intmax_t index) {
std::string name;
do {
name += 'A' + (index%26);
index /= 26;
} while (index);
return name;
}
Live On Coliru
The above overload doesn't have the subgraph support. So, instead let's fix the vertex_attribute
map to have the expected vertex labels:
int main() {
auto g = create_data<subgraph<Graph> >();
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);
}
Now it does print:
Live On Coliru
digraph G0 {
subgraph clusterG1 {
graph [
label=G1];
node [
color=red, shape=Mrecord];
0[label="Vertex A"];
1[label="Vertex B"];
0 -> 1;
}
subgraph clusterG2 {
graph [
fillcolor=lightgray, label=G2, style=filled];
node [
shape=circle];
4[label="Vertex E"];
2[label="Vertex C"];
5[label="Vertex F"];
4 -> 5;
2 -> 5;
}
3[label="Vertex D"];
1 -> 2;
1 -> 3;
4 -> 1;
5 -> 3;
}
Which renders as
Preserving for posterity
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/subgraph.hpp>
#include <iostream>
using namespace boost;
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;
}
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 = "Vertex ";
do {
name += 'A' + (index%26);
index /= 26;
} while (index);
return name;
}
int main() {
auto g = create_data<subgraph<Graph> >();
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);
}