CREATE_TEST(AGraph_construction)
CREATE_TEST(clusters)
CREATE_TEST(edge_color)
+CREATE_TEST(edge_fillcolor)
CREATE_TEST(edge_penwidth)
CREATE_TEST(engines)
CREATE_TEST(GVContext_construction)
#include <string>
#include "graphviz_edge.h"
+#include "svg_element.h"
GraphvizEdge::GraphvizEdge(SVG::SVGElement &svg_g_element)
: m_edgeop(svg_g_element.graphviz_id), m_svg_g_element(svg_g_element) {}
std::string_view GraphvizEdge::edgeop() const { return m_edgeop; }
+std::string GraphvizEdge::fillcolor() const {
+ const auto fill = m_svg_g_element.attribute_from_subtree<std::string>(
+ &SVG::SVGAttributes::fill, &SVG::SVGElement::is_closed_shape_element, "");
+ const auto fill_opacity = m_svg_g_element.attribute_from_subtree<double>(
+ &SVG::SVGAttributes::fill_opacity,
+ &SVG::SVGElement::is_closed_shape_element, 1);
+ if (fill.empty() && fill_opacity == 1) {
+ return "";
+ }
+ return SVG::to_dot_color(fill, fill_opacity);
+}
+
const SVG::SVGElement &GraphvizEdge::svg_g_element() const {
return m_svg_g_element;
}
/// Return the 'edgeop' according to the DOT language specification. Note that
/// this is not the same as the 'id' attribute of an edge.
std::string_view edgeop() const;
+ /// Return the edge's `fillcolor` attribute in RGB hex format if the opacity
+ /// is 100 % or in RGBA hex format otherwise.
+ std::string fillcolor() const;
/// Return the edge's `penwidth` attribute
double penwidth() const;
/// Return a non-mutable reference to the SVG `g` element corresponding to the
return px * 3 / 4;
}
+bool SVG::SVGElement::is_closed_shape_element() const {
+ switch (type) {
+ case SVG::SVGElementType::Circle:
+ case SVG::SVGElementType::Ellipse:
+ case SVG::SVGElementType::Polygon:
+ case SVG::SVGElementType::Rect:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool SVG::SVGElement::is_shape_element() const {
switch (type) {
case SVG::SVGElementType::Circle:
/// throw an exception unless the `throw_if_bbox_not_defined` parameter is
/// `false`.
SVG::SVGRect bbox(bool throw_if_bbox_not_defined = true);
+ bool is_closed_shape_element() const;
bool is_shape_element() const;
std::string to_string(std::size_t indent_size) const;
--- /dev/null
+#include <string_view>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include "test_utilities.h"
+
+TEST_CASE("Edge color",
+ "Test that the Graphviz `color` attribute is used to set the "
+ "`stroke` and `stroke-opacity` attributes correctly for edges in the "
+ "generated SVG") {
+
+ const auto primitive_arrow_shape =
+ GENERATE(from_range(all_primitive_arrow_shapes));
+ INFO(fmt::format("Primitive arrow shape: {}", primitive_arrow_shape));
+
+ const auto edge_rgb_fillcolor = GENERATE("#000000", "#ffffff", "#5580aa");
+ const auto opacity = GENERATE(0, 100, 255);
+ const auto edge_rgba_fillcolor =
+ fmt::format("{}{:02x}", edge_rgb_fillcolor, opacity);
+ INFO(fmt::format("Edge fillcolor: {}", edge_rgba_fillcolor));
+
+ auto dot =
+ fmt::format("digraph g1 {{edge [arrowhead={} fillcolor=\"{}\"]; a -> b}}",
+ primitive_arrow_shape, edge_rgba_fillcolor);
+
+ const auto engine = "dot";
+ auto svg_analyzer = SVGAnalyzer::make_from_dot(dot, engine);
+
+ const auto expected_edge_fillcolor = [&]() -> const std::string {
+ if (primitive_arrow_shapes_without_closed_svg_shape.contains(
+ primitive_arrow_shape)) {
+ return "";
+ }
+ if (opacity == 255) {
+ return edge_rgb_fillcolor;
+ }
+ if (opacity == 0) {
+ return "#00000000"; // transparent/none
+ }
+ return edge_rgba_fillcolor;
+ }();
+
+ for (const auto &graph : svg_analyzer.graphs()) {
+ for (const auto &edge : graph.edges()) {
+ CHECK(edge.fillcolor() == expected_edge_fillcolor);
+ }
+ }
+}
"box", "crow", "curve", "diamond", "dot", "icurve",
"inv", "none", "normal", "tee", "vee"};
+const std::unordered_set<std::string_view>
+ primitive_arrow_shapes_without_closed_svg_shape = {"curve", "icurve",
+ "none"};
+
const std::unordered_set<std::string_view> all_rank_directions = {"TB", "BT",
"LR", "RL"};
extern const std::unordered_set<std::string_view>
primitive_polygon_and_polyline_arrow_shapes;
extern const std::unordered_set<std::string_view> all_primitive_arrow_shapes;
+extern const std::unordered_set<std::string_view>
+ primitive_arrow_shapes_without_closed_svg_shape;
/// rank directions
extern const std::unordered_set<std::string_view> all_rank_directions;