I'm studying BGL now and I found a tutorial for it. Everything worked fine from it until I reached function to add_named_vertex. Here is a piece of code I have, that does not work as I (and tutorial) expect:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <string>
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
boost::property<boost::vertex_name_t, std::string>
>
create_empty_graph() { return {}; }
template<typename graph, typename name_type>
typename boost::graph_traits<graph>::vertex_descriptor
add_named_vertex(const name_type& vertex_name, graph& g) noexcept {
const auto vd = boost::add_vertex(g);
auto vertex_name_map = get(boost::vertex_name, g);
put(vertex_name_map, vd, vertex_name);
return vd;
}
int main()
{
auto g = create_empty_graph();
const auto va = add_named_vertex("One", g);
const auto vb = add_named_vertex("Two", g);
boost::add_edge(va,vb, g);
std::stringstream f;
boost::write_graphviz(f, g);
std::cout << f.str() << std::endl;
return 0;
}
I expect:
digraph G {
0[label=One];
1[label=Two];
0->1;
}
But here is what I got:
digraph G {
0;
1;
0->1;
}
As you can see, there are no labels in the output of this code. Could you please tell me, what am I missing? Is it expected behavior?
Tried both clang++ and gcc and for range of Boost version (1.69 - 1.71).
Yes it is expectd behavior. To print the labels, add a property writer:
auto vlw = boost::make_label_writer(boost::get(boost::vertex_name, g));
boost::write_graphviz(f, g, vlw);
See it Live on Coliru
Or, as I prefer, use write_graphviz_dp to use dynamic_properties:
boost::dynamic_properties dp;
dp.property("node_id", boost::get(boost::vertex_index, g));
dp.property("label", boost::get(boost::vertex_name, g));
boost::write_graphviz_dp(f, g, dp);
See it Live on Coliru
It may seem like more work, but it is easy and flexible with many vertex/edge properties. You can search my answers for good examples of this.
Both the above solutions print
digraph G {
0[label=One];
1[label=Two];
0->1 ;
}
BONUS
You don't need the add_named_vertex function. You can initialize the property directly with boost::add_vertex:
const auto va = add_vertex({"One"}, g);
const auto vb = add_vertex({"Two"}, g);
add_edge(va, vb, g);
Related
I have to write code that can parse a file like this:
digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
}
Each vertex has label necessarily, but also it may have an unknown number of properties. Moreover, these properties can be own for each vertex. With edges everything is simple - only one label. I would like not to write my own parser, but to use read_graphviz. Tell me, please, how can this be done?
It would be perfect if the code worked with such bundled property:
struct Vertex {
std::string label;
std::map<std::string, std::string> attributes;
};
I wrote a code that parses this:
digraph G {
0[label="person" attributes="name=James_Cameron"];
1[label="film" attributes="title=Avatar_2;year=2022"];
0->1 [label="directed"];
}
But this format looks creepy.
My code:
#include <iostream>
#include <vector>
#include <map>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
struct Vertex {
std::string raw_attrs;
std::string label;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string label;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, Vertex, Edge> Graph;
int main(int argc, char** argv) {
Graph graph;
boost::dynamic_properties dp(boost::ignore_other_properties);
dp.property("label", boost::get(&Vertex::label, graph));
dp.property("attributes", boost::get(&Vertex::raw_attrs, graph));
dp.property("label", boost::get(&Edge::label, graph));
std::ifstream f("file.txt");
boost::read_graphviz(f, graph, dp);
// transform raw_attrs to attributes
}
I promised to find the time to demonstrate the support for dynamic attributes in BGL read_graphviz.
I'll do that by implementing a roundtripping
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Note that it's a logical roundtrip. It doesn't promise to preserve formatting or node id numbering.
0. Preliminaries
Some useful definitions:
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
1. Writing The Graph
The simpler part first. We use the overload that takes a writer interface. We can hack it in few lines:
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
We supply the vertex writer as the attributes writer, and write the one edge property (relation) as the label.
2. Reading The Graph
You can pass a generator function to the constructor of dynamic_properties. This will be invoked when a new attribute is found.
G do_read(std::istream& is) {
G g;
// ...
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
Let's use a lambda to define the newattr generator function:
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
Where make_dyn is a simple helper to wrap a concrete property map (like our function property map) in a shared pointer to a dynamic property map:
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
In production code you would probably make this a free function, which would be more re-usable and slightly more readable by getting rid of the decltype expression.
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
G do_read(std::istream& is) {
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
G g;
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Prints
g++ -std=c++20 -O2 -pedantic -pthread main.cpp -lboost_graph && ./a.out
digraph G {
0[label=person, name="James Cameron"];
1[label=film, title="Avatar 2", year=2022];
0->1 [label=directed];
}
Writing a parser is not difficult. Here is the code for an application that parses the vertices. I hope you do not think it is creepy.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
class cVertex
{
int userIndex;
std::string label;
std::map<std::string, std::string> attributes;
static std::vector<cVertex> myVertex;
public:
void read(const std::string &fname);
std::string textVertexList();
private:
void parseVertexLine(const std::string &line);
};
std::vector<cVertex> cVertex::myVertex;
void cVertex::read(const std::string &fname)
{
std::ifstream ifs(fname);
if (!ifs.is_open())
{
std::cout << "Cannot open file\n";
exit(1);
}
std::string line;
getline(ifs, line);
while (getline(ifs, line))
{
if (line.find("->") != -1)
break;
parseVertexLine(line);
}
}
std::string cVertex::textVertexList()
{
std::stringstream ss;
for (auto &v : myVertex)
{
ss << "\nvertex " << v.userIndex
<< " label is " << v.label << "\n";
for (auto &a : v.attributes)
{
ss << a.first << " | " << a.second << "\n";
}
}
return ss.str();
}
void cVertex::parseVertexLine(const std::string &line)
{
cVertex v;
v.userIndex = atoi(line.c_str());
int p = line.find("label");
int q = line.find("\"", p + 7);
v.label = line.substr(p + 7, q - p - 7);
while (1)
{
std::pair< std::string,std::string > ap;
p = line.find("=", q);
if (p == -1)
break;
ap.first = line.substr(q + 2, p - q - 2);
p = line.find("\"", p);
q = line.find("\"", p + 1);
ap.second = line.substr(p + 1, q - p - 1);
v.attributes.insert(ap);
int dbg = 0;
}
myVertex.push_back(v);
}
main()
{
cVertex theVertex;
theVertex.read("data.txt");
std::cout << theVertex.textVertexList();
return 0;
}
I'm successfully using boost graph's component finder to assign color, i.e. the component's index to every vertex in my graph like so:
#include <boost/graph/connected_components.hpp>
#include <boost/graph/adjacency_list.hpp>
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> g;
std::vector<int> compon_map(boost::num_vertices(g));
int number_of_components = boost::connected_components(g, &compon_map[0]);
This will then yield a different number_of_components after each iteration in my simulation (not shown) because I do
boost::clear_vertex(v, g);
in between to erase some edges based on some condition.
The thing is that in my simulation I want to write out some property (say weight) of all edges, and the length of the edge iterator needs to stay constant (dataset restrictions).
My question is therefore: Is there a way to pass some edge property like a
int L = boost::num_edges(g);
std::vector<bool> is_still_existent(L); // or
std::vector<double> edge_weights(L);
to the boost::connected_components (that then counts edges only based on that property) or is there another way to trick the edge iterator to stay at the initial length even after having removed edges?
Thanks in advance for any hints :)
Yes. You can use a filtered graph adaptor with an edge filter. I have several answers up on SO showing how to use it, but will see whether I can usefully create an example based on on your snippet.
So I made a self-contained sample¹: Live On Coliru,
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
int main(int argc, char** argv) {
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
int n = boost::connected_components(g, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, g));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("label", boost::make_assoc_property_map(weights));
boost::write_graphviz_dp(std::cout, g, dp);
}
which generates a pseudo-random graph:
Let's add some filtering to it:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
struct NonRemoved {
std::set<E> const* _ref;
bool operator()(E e) const { return not _ref->contains(e); }
};
int main(int argc, char** argv)
{
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
auto random_edges = [&g] {
auto [f,l] = edges(g);
std::deque<E> re(f,l);
std::random_shuffle(begin(re), end(re));
return re;
}();
std::set<E> removed;
NonRemoved predicate{&removed};
boost::filtered_graph<G, NonRemoved, boost::keep_all> f(g, predicate, {});
do {
int n = boost::connected_components(f, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, f));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("color",
boost::make_function_property_map<E>([&removed](E e) {
return removed.contains(e) ? "red" : "blue";
}));
dp.property("label",
boost::make_function_property_map<E>([&removed, &weights](E e) {
if (removed.contains(e))
return std::string("REMOVED");
return std::to_string(weights.at(e));
}));
std::ofstream ofs("graph_" + std::to_string(random_edges.size()) + ".dot");
boost::write_graphviz_dp(ofs, f, dp);
removed.insert(random_edges.front());
random_edges.pop_front();
} while (not random_edges.empty());
}
Now writes a series of graph_XXX.dot graphs that display as:
¹ (changing the vertex container selector to vecS for simplicity)
Suppose I have the following undirected graph
A("")--2.31--B("Hello")--1.036--C("")
Where A and C are empty strings and the real numbers are weights. Now I want to get from B to A. I know in boost, that can be done with
auto vp = boost::vertices(g);
for(auto vit = vp.first; vit != vp.second; ++vit)
{
std::cout << *vit << " " << std::endl;
}
When I get to vertex A, I want to be able to randomize the string from B, based on some math on the weight of the edge from B to A. So I want to be able to do something like g[A].name = newString(weight from B to A);.
The problem is I don't really know how to do that. I have edge weights and they are imported properly from a file that I am reading in. And I know how to get the weights if I iterate through this way:
auto es = boost::edges(g);
for(auto eit = es.first; eit != es.second; ++eit)
{
std::cout << get(weight,*eit) << std::endl;
}
The issue with this is that this will iterate through the nodes of the graph and not necessarily caring about any of the edges.
Is there something in boost that will do what I want? I have tried looking at this implementation from Stack Overflow, and I understand how to start at a specific vertex. However I am not sure if there is a way to tweak this algorithm to use my edge weights to achieve what I want, and to be honest the documentation for DFS is hard to understand. I believe DFS is what I want, but I am not sure how to implement this easily without breaking down and writing my own DFS which I don't really want to do.
Now I want to get from B to A. I know in boost, that can be done with [...]
No, the code just enumerates vertices, even those without edges and in no particular order.
When I get to vertex A
What does it mean to "get to vertex A"? Do you need have a path there, or are you simply going to enumerate all vertices and traverse edges from there?
I want to be able to randomize the string from B, based on some math on the weight of the edge from B to A
I read this as "I want to calculate a string based on the weight of edges discovered and update the label string for the destination vertex.
That's at least slightly confusing, because it suggests a direction on edges of an undirected graph. I'm assuming you indeed want to use a direction (the direction of traversal during edge discovery).
And I know how to get the weights if I iterate through this way: [...snip...] The issue with this is that this will iterate through the nodes of the graph and not necessarily caring about any of the edges.
Huh. That's completely flipped around. that loop does iterate edges, specifically. Did you swap the code samples?
Is there something in boost that will do what I want?
That's unanswerable until you know what you want.
I believe DFS is what I want
Okay... Let's get you started a little bit:
The Sample Graph
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
Let's define a graph that can store labels (name) and weights:
struct Vertex { std::string name; };
struct Edge { double weight; };
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vertex, Edge>;
Now, let's print the sample graph in Graphviz format:
Graph make_sample();
void dump(Graph&);
int main() {
Graph g = make_sample();
dump(g);
}
That's a bit like cheating, but it generally helps to start out with the big picture and hide the details, so let's implement make_sample:
Graph make_sample() {
Graph g;
auto A = add_vertex({""}, g);
auto B = add_vertex({"Hello"}, g);
auto C = add_vertex({""}, g);
add_edge(A, B, {2.31}, g);
add_edge(B, C, {1.036}, g);
return g;
}
And dump:
#include <boost/graph/graphviz.hpp>
void dump(Graph& g) {
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, g));
dp.property("label", get(&Vertex::name, g));
dp.property("label", get(&Edge::weight, g));
write_graphviz_dp(std::cout, g, dp);
}
The graph looks like this:
Adding DFS
Search is simple:
#include <boost/graph/depth_first_search.hpp>
struct Visitor : boost::default_dfs_visitor {
};
void operate_on(Graph& g) {
Visitor vis;
std::vector<boost::default_color_type> color(num_vertices(g));
boost::depth_first_search(g, vis, color.data());
}
But you wanted to do the magic:
struct Visitor : boost::default_dfs_visitor {
void examine_edge(Graph::edge_descriptor e, const Graph& g) const {
std::cout << "Examining edge " << e << "\n";
}
};
Now already, we print:
Examining edge (0,1)
Examining edge (1,0)
Examining edge (1,2)
Examining edge (2,1)
Now, let's check the properties of the related vertices:
Vertex const& s = g[source(e, g)];
Vertex const& t = g[target(e, g)];
Now you can do some logic:
if (s.name.empty() && !t.name.empty()) { // use your own logic here
std::cout << "Updating label for '" << t.name << "' in edge " << e << "\n";
}
Which already prints:
Updating label for 'Hello' in edge (0,1)
Updating label for 'Hello' in edge (2,1)
Write - Access
For safety reasons, Graph is received by const& in the visitor. To be able to mutate, we need a writable reference. One way is to store a Graph& inside the visitor:
struct Visitor : boost::default_dfs_visitor {
Graph& _g;
Visitor(Graph& g) : _g(g) {}
void examine_edge(Graph::edge_descriptor e, const Graph&) const {
// get vertex bundles
Vertex& s = _g[source(e, _g)];
Vertex& t = _g[target(e, _g)];
if (s.name.empty() && !t.name.empty()) { // use your own logic here
t.name += '(' + std::to_string(_g[e].weight) + ')';
}
return;
}
};
Perhaps more idiomatic would be to use Property Maps - which have similar effect:
struct Visitor : boost::default_dfs_visitor {
boost::property_map<Graph, std::string Vertex::*>::type _name;
boost::property_map<Graph, double Edge::*>::const_type _weight;
Visitor(Graph& g)
: _name(get(&Vertex::name, g)),
_weight(get(&Edge::weight, static_cast<Graph const&>(g)))
{ }
void examine_edge(Graph::edge_descriptor e, const Graph& g) const {
auto& sname = _name[source(e, g)];
auto& tname = _name[target(e, g)];
if (sname.empty() && !tname.empty()) { // use your own logic here
tname += '(' + std::to_string(_weight[e]) + ')';
}
return;
}
};
Caveat: write access during algorithm is dangerous. Be careful not to violate the pre-conditions/invariants of the algorithm
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
struct Vertex { std::string name; };
struct Edge { double weight; };
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vertex, Edge>;
Graph make_sample();
void dump(Graph&);
void operate_on(Graph&);
int main() {
Graph g = make_sample();
operate_on(g);
dump(g);
}
Graph make_sample() {
Graph g;
auto A = add_vertex({""}, g);
auto B = add_vertex({"Hello"}, g);
auto C = add_vertex({""}, g);
add_edge(A, B, {2.31}, g);
add_edge(B, C, {1.036}, g);
return g;
}
#include <boost/graph/graphviz.hpp>
void dump(Graph& g) {
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, g));
dp.property("label", get(&Vertex::name, g));
dp.property("label", get(&Edge::weight, g));
write_graphviz_dp(std::cout, g, dp);
}
#include <boost/graph/depth_first_search.hpp>
struct Visitor : boost::default_dfs_visitor {
boost::property_map<Graph, std::string Vertex::*>::type _name;
boost::property_map<Graph, double Edge::*>::const_type _weight;
Visitor(Graph& g)
: _name(get(&Vertex::name, g)),
_weight(get(&Edge::weight, static_cast<Graph const&>(g)))
{ }
void examine_edge(Graph::edge_descriptor e, const Graph& g) const {
auto& sname = _name[source(e, g)];
auto& tname = _name[target(e, g)];
if (sname.empty() && !tname.empty()) { // use your own logic here
tname += '(' + std::to_string(_weight[e]) + ')';
}
}
};
void operate_on(Graph& g) {
Visitor vis { g };
std::vector<boost::default_color_type> color(num_vertices(g));
boost::depth_first_search(g, vis, color.data());
}
Prints:
graph G {
0 [label=""];
1 [label="Hello(2.310000)(1.036000)"];
2 [label=""];
0--1 [label=2.31];
1--2 [label=1.036];
}
Which looks like:
I'm trying to generate a .dot which displays an horizontal graph with Boost Graph Library.
My code when creating my graph looks like this:
struct VertexP {
std::string tag;
};
struct EdgeP {
std::string symbol;
};
struct GraphP{
std::string orientation;
};
typedef boost::adjacency_list<boost::vecS,
boost::vecS, boost::directedS,
VertexP, EdgeP, GraphP> Graph;
GraphP property;
property.orientation = "LR";
Graph graph(property);
// Then fill the graph
The code I'm using to generate the .dot file is something like this:
Graph g = creator.AutomatonToGraph(&automaton);
ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
write_graphviz_dp(dot_file, g, dp);
This writes the .dot file perfectly with nodes and edges labels, but my problem is that I want to add the rankdir=LR graph property to the output file. I tried with:
Graph g = creator.AutomatonToGraph(&automaton);
ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
dp.property("rankdir", get(&GraphP::orientation, g));
write_graphviz_dp(dot_file, g, dp);
But I get a big chunk of errors starting by this:
/src/lab2.cc:48:55: required from here
/usr/include/boost/graph/detail/adjacency_list.hpp:2585:29: error: forming reference to void
typedef value_type& reference;
^~~~~~~~~
I'm really new using BGL, what I'm doing wrong?
Reading into the implementation of dynamic_graph_properties_writer, I figured out you should probably just do
dp.property("rankdir", boost::make_constant_property<Graph*>(std::string("LR")));
For dynamic retrieval you can use a function property map: (map set/get requests into C++ class/structure changes):
#include <boost/property_map/function_property_map.hpp>
dp.property("rankdir", boost::make_function_property_map<Graph*>([](Graph const* g) { return g->m_property->orientation; }));
Live Demo
See it Live On Wandbox
#include <boost/graph/adj_list_serialize.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <fstream>
using namespace boost;
struct VertexP { std::string tag; };
struct EdgeP { std::string symbol; };
struct GraphP { std::string orientation; };
typedef adjacency_list<vecS, vecS, directedS, VertexP, EdgeP, GraphP> Graph;
int main() {
Graph g(GraphP{"LR"});
// Then fill the graph
add_edge(
add_vertex(VertexP{ "tag1" }, g),
add_vertex(VertexP{ "tag2" }, g),
EdgeP{ "symbol" }, g
);
{
std::ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
dp.property("rankdir", boost::make_constant_property<Graph*>(std::string("LR")));
dp.property("dummy", boost::make_function_property_map<Graph*>([](Graph const* g) { return g->m_property->orientation; }));
write_graphviz_dp(dot_file, g, dp);
}
}
Which writes
digraph G {
dummy=LR;
rankdir=LR;
tag2 [label=tag2];
tag1 [label=tag1];
tag1->tag2 [label=symbol];
}
I'm struggling trying to manually colouring graph's vertices using boost. I wrote the code below but I can't figure it out why the generated file does not have any colour.
int main(int,char*[]) {
typedef property<edge_name_t, string> EdgeProperties;
typedef property<vertex_name_t, string, property<vertex_color_t, default_color_type>> VertexProperties;
typedef adjacency_list<vecS, vecS, directedS, VertexProperties, EdgeProperties> Graph;
typedef graph_traits<Graph>::vertex_descriptor Vertex;
typedef graph_traits<Graph>::edge_descriptor Edge;
Graph g;
property_map<Graph, vertex_name_t>::type vertex_label = get(vertex_name, g);
property_map<Graph, vertex_color_t>::type color_map = get(vertex_color, g);
property_map<Graph, edge_name_t>::type edge_label = get(edge_name, g);
Vertex v1 = add_vertex(g);
vertex_label[v1] = "v1";
put(color_map, v1, boost::red_color);
std::ofstream outf("example.gv");
write_graphviz(outf, g,
make_label_writer(vertex_label),
make_label_writer(edge_label)
);
return 0;
}
Vertex colourings are really an algorithmic detail. There is no "automatic" provision to translate it to graphviz, as far as I know.
You can add custom attributes though.
I'd usually change it around to use dynamic properties:
std::ofstream outf("example.gv");
dynamic_properties dp;
dp.property("node_id", get(vertex_index, g));
dp.property("label", vertex_label);
dp.property("label", edge_label);
write_graphviz_dp(outf, g, dp);
Now it's as simple as adding a new vertex attribute to the dynamic set:
dp.property("color", make_transform_value_property_map(&color_to_string, color_map));
The only complication is that you need to supply the conversion from default_color_type to text. This is another symptom of the fact that the color maps aren't usually used for representational purposes. Here's a fully working sample:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <iostream>
using namespace boost;
inline char const* color_to_string(default_color_type c) {
switch(c) {
case default_color_type::red_color: return "red";
case default_color_type::green_color: return "green";
case default_color_type::gray_color: return "gray";
case default_color_type::white_color: return "white";
case default_color_type::black_color: return "black";
}
return ""; // not known
}
int main() {
typedef property<edge_name_t, std::string> EdgeProperties;
typedef property<vertex_name_t, std::string, property<vertex_color_t, default_color_type>> VertexProperties;
typedef adjacency_list<vecS, vecS, directedS, VertexProperties, EdgeProperties> Graph;
typedef graph_traits<Graph>::vertex_descriptor Vertex;
Graph g;
property_map<Graph, vertex_name_t>::type vertex_label = get(vertex_name, g);
property_map<Graph, vertex_color_t>::type color_map = get(vertex_color, g);
property_map<Graph, edge_name_t>::type edge_label = get(edge_name, g);
Vertex v1 = add_vertex(g);
vertex_label[v1] = "v1";
put(color_map, v1, boost::red_color);
dynamic_properties dp;
dp.property("node_id", get(vertex_index, g));
dp.property("label", vertex_label);
dp.property("label", edge_label);
dp.property("color", make_transform_value_property_map(&color_to_string, color_map));
write_graphviz_dp(std::cout, g, dp);
}
Prints:
digraph G {
0 [color=red, label=v1];
}