11#include "../graph.hpp"
37 bool show_node_labels =
true;
38 bool show_edge_labels =
true;
39 bool show_weights =
true;
40 bool show_edge_ids =
false;
41 bool show_user_attrs =
false;
42 std::map<std::string, std::string> graph_attrs;
43 std::optional<DotLayout> layout = std::nullopt;
44 std::string graph_name =
"G";
49inline std::string quote_dot_string(
const std::string& value) {
50 std::string out =
"\"";
51 for (
char c : value) {
52 if (c ==
'\\' || c ==
'"') {
61inline bool is_plain_dot_id(
const std::string& value) {
65 const auto first =
static_cast<unsigned char>(value.front());
66 if (!(std::isalpha(first) || value.front() ==
'_')) {
69 for (
char c : value) {
70 const auto ch =
static_cast<unsigned char>(c);
71 if (!(std::isalnum(ch) || c ==
'_')) {
78inline bool is_dot_number(
const std::string& value) {
79 bool has_digit =
false;
80 bool has_exponent =
false;
83 for (std::size_t i = 0; i < value.size(); ++i) {
84 const char c = value[i];
85 if (std::isdigit(
static_cast<unsigned char>(c))) {
89 if (c ==
'-' && i == 0) {
92 if ((c ==
'+' || c ==
'-') && i > 0 && (value[i - 1] ==
'e' || value[i - 1] ==
'E')) {
95 if (c ==
'.' && !has_dot && !has_exponent) {
99 if ((c ==
'e' || c ==
'E') && has_digit && !has_exponent) {
110inline std::string graph_id(
const std::string& value) {
111 return is_plain_dot_id(value) ? value : quote_dot_string(value);
114inline std::string attr_value(
const std::string& value) {
115 return is_plain_dot_id(value) || is_dot_number(value) ? value : quote_dot_string(value);
118inline const char* dot_layout_name(DotLayout layout) {
122 case DotLayout::Neato:
126 case DotLayout::Sfdp:
128 case DotLayout::Circo:
134using DotAttrList = std::vector<std::pair<std::string, std::string>>;
136inline bool has_dot_attr(
const DotAttrList& attrs,
const std::string& key) {
140 [&](
const auto& attr) { return attr.first == key; }
144inline void append_dot_attr(DotAttrList& attrs,
const std::string& key,
const std::string& value) {
145 if (!has_dot_attr(attrs, key)) {
146 attrs.emplace_back(key, value);
150inline std::string format_dot_attrs(
const DotAttrList& attrs) {
152 for (
const auto& [key, value] : attrs) {
156 out += graph_id(key) +
"=" + attr_value(value);
162std::string to_string(
const T& value) {
163 std::ostringstream out;
169std::optional<std::string> any_attr_as_string(
const std::any& value) {
170 if (
const auto* typed = std::any_cast<T>(&value)) {
171 return to_string(*typed);
176inline std::string any_attr_value(
const std::any& value) {
177 if (
const auto* typed = std::any_cast<std::string>(&value)) {
180 if (
const auto* typed = std::any_cast<bool>(&value)) {
181 return *typed ?
"true" :
"false";
184 if (
auto converted = any_attr_as_string<short>(value))
return *converted;
185 if (
auto converted = any_attr_as_string<unsigned short>(value))
return *converted;
186 if (
auto converted = any_attr_as_string<int>(value))
return *converted;
187 if (
auto converted = any_attr_as_string<unsigned int>(value))
return *converted;
188 if (
auto converted = any_attr_as_string<long>(value))
return *converted;
189 if (
auto converted = any_attr_as_string<unsigned long>(value))
return *converted;
190 if (
auto converted = any_attr_as_string<long long>(value))
return *converted;
191 if (
auto converted = any_attr_as_string<unsigned long long>(value))
return *converted;
192 if (
auto converted = any_attr_as_string<float>(value))
return *converted;
193 if (
auto converted = any_attr_as_string<double>(value))
return *converted;
194 if (
auto converted = any_attr_as_string<long double>(value))
return *converted;
196 throw std::runtime_error(
"DOT export failed: unsupported user attribute type.");
215 typename OutEdgeSelector,
216 typename VertexSelector
222 const char* graph_keyword = Directed ?
"digraph" :
"graph";
223 const char* edge_operator = Directed ?
" -> " :
" -- ";
225 std::ostringstream out;
226 out << graph_keyword <<
" " << detail::graph_id(options.graph_name) <<
" {\n";
227 detail::DotAttrList graph_attrs;
228 if (options.layout.has_value()) {
229 detail::append_dot_attr(graph_attrs,
"layout", detail::dot_layout_name(*options.layout));
231 for (
const auto& [key, value] : options.graph_attrs) {
232 detail::append_dot_attr(graph_attrs, key, value);
234 for (
const auto& [key, value] : graph_attrs) {
235 out <<
" " << detail::graph_id(key) <<
"=" << detail::attr_value(value) <<
";\n";
238 for (
const auto& node : graph.nodes()) {
239 const std::string node_text = detail::to_string(node);
240 out <<
" " << detail::quote_dot_string(node_text);
242 if (options.show_node_labels) {
243 attrs +=
"label=" + detail::quote_dot_string(node_text);
245 if (options.show_user_attrs) {
246 for (
const auto& [key, value] : graph.node_attrs(node)) {
247 if (options.show_node_labels && key ==
"label") {
250 if (!attrs.empty()) {
253 attrs += detail::graph_id(key) +
"=" + detail::attr_value(detail::any_attr_value(value));
256 if (!attrs.empty()) {
257 out <<
" [" << attrs <<
"]";
262 for (
const auto edge_id : graph.edge_ids()) {
264 out <<
" " << detail::quote_dot_string(detail::to_string(source))
266 << detail::quote_dot_string(detail::to_string(target));
268 detail::DotAttrList attrs;
269 if constexpr (Weighted) {
270 if (options.show_weights) {
271 const std::string weight = detail::to_string(graph.get_edge_weight(edge_id));
272 detail::append_dot_attr(attrs,
"weight", weight);
273 if (options.show_edge_labels) {
274 detail::append_dot_attr(attrs,
"label", weight);
279 if (options.show_edge_ids) {
280 detail::append_dot_attr(attrs,
"edge_id", std::to_string(edge_id));
281 if constexpr (!Weighted) {
282 if (options.show_edge_labels) {
283 detail::append_dot_attr(attrs,
"label",
"edge_id=" + std::to_string(edge_id));
288 if (options.show_user_attrs) {
289 for (
const auto& [key, value] : graph.edge_attrs(edge_id)) {
290 detail::append_dot_attr(attrs, key, detail::any_attr_value(value));
294 if (!attrs.empty()) {
295 out <<
" [" << detail::format_dot_attrs(attrs) <<
"]";
311 typename OutEdgeSelector,
312 typename VertexSelector
316 const std::filesystem::path& output_path,
319 std::ofstream out(output_path);
321 throw std::runtime_error(
"DOT export failed: could not open output file.");
323 out << to_dot(graph, options);
Graph wrapper around Boost Graph Library with Python-inspired helpers.
Definition graph.hpp:288
std::pair< NodeID, NodeID > get_edge_endpoints(std::size_t edge_id) const
Returns the endpoints of an edge identified by its wrapper-managed ID.
Definition multigraph.hpp:40
DotLayout
Graphviz layout engines commonly used to render DOT output.
Definition dot.hpp:27
void write_dot(const Graph< NodeID, EdgeWeight, Directed, Multi, Weighted, OutEdgeSelector, VertexSelector > &graph, const std::filesystem::path &output_path, const DotOptions &options={})
Writes Graphviz DOT output to a filesystem path.
Definition dot.hpp:314
std::string to_dot(const Graph< NodeID, EdgeWeight, Directed, Multi, Weighted, OutEdgeSelector, VertexSelector > &graph, const DotOptions &options={})
Serializes a graph to Graphviz DOT.
Definition dot.hpp:218
Controls the lightweight DOT export surface.
Definition dot.hpp:36