diff --git a/.gitignore b/.gitignore index eb2c885..7bb5cfe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __history/ *.cbp *.png .vscode +.clangd build tests/bin diff --git a/README.md b/README.md index ea62691..e09e669 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ int main() tree.insert({19, 20}); tree.deoverlap(); - + for (auto const& i : tree) { std::cout << "[" << i.low() << ", " << i.high() << "]\n"; @@ -61,10 +61,80 @@ Create a build folder, navigate there, run cmake and build the tree-tests target You might have to adapt the linker line for gtest, if you built it yourself and didn't install it into your system. If you want to generate the pretty drawings, install cairo, pull the submodule and pass INT_TREE_DRAW_EXAMPLES=on to the cmake command line to generate a drawings/make_drawings executeable. +## Draw Dot Graph +This draws a dot graph of the tree: +```c++ +#include +#include + +int main() +{ + using namespace lib_interval_tree; + interval_tree_t tree; + + tree.insert(make_safe_interval(21, 16)); // make_safe_interval swaps low and high if not in right order. + tree.insert({8, 9}); + tree.insert({25, 30}); + tree.insert({5, 8}); + tree.insert({15, 23}); + tree.insert({17, 19}); + tree.insert({26, 26}); + tree.insert({0, 3}); + tree.insert({6, 10}); + tree.insert({19, 20}); + + draw_dot_graph( + std::cout, + tree, + { + // digraph or graph? + .digraph = true, + + // graph name + .name = "G", + + // extra node attributes + .extra_node_attributes = {"color=red"}, + + // extra graph statements + .extra_statements = {"rankdir=LR"}, + + // put space after comma of interval label? (a,b) vs (a, b) + .space_after_comma = false, + + // left brace override enabled if not 0, otherwise determined from interval kind + .left_brace = '\0', + + // right brace override enabled if not 0, otherwise determined from interval kind + .right_brace = '\0', + + // edge attributes + .edge_attributes = {"color=blue"}, + + // indent characters + .indent = "\t", + } + ); +} +``` + ## Free Functions ### interval make_safe_interval(NumericT border1, NumericT border2) Creates an interval where the borders are sorted so the lower border is the first one. +### draw_dot_graph(std::ostream& os, interval_tree_t const& tree, DrawOptions const& options) +Draws a dot graph of the interval tree to the output stream. +Options are: +- digraph: bool +- name: std::string +- extra_node_attributes: std::vector +- extra_statements: std::vector +- space_after_comma: bool +- left_brace: std::optional +- right_brace: std::optional +- edge_attributes: std::vector +- indent: std::string + ## Members of IntervalTree - [Members of IntervalTree](#members-of-intervaltreeinterval) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 8af7d72..72fd136 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(from_readme) \ No newline at end of file +add_subdirectory(from_readme) +add_subdirectory(dot_graph) \ No newline at end of file diff --git a/example/dot_graph/CMakeLists.txt b/example/dot_graph/CMakeLists.txt new file mode 100644 index 0000000..fa19a1e --- /dev/null +++ b/example/dot_graph/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(dotgraph main.cpp) +target_link_libraries(dotgraph interval-tree) + +set_target_properties(dotgraph + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/interval_tree/examples" +) \ No newline at end of file diff --git a/example/dot_graph/main.cpp b/example/dot_graph/main.cpp new file mode 100644 index 0000000..c4350be --- /dev/null +++ b/example/dot_graph/main.cpp @@ -0,0 +1,52 @@ +#include +#include + +int main() +{ + using namespace lib_interval_tree; + interval_tree_t tree; + + tree.insert(make_safe_interval(21, 16)); // make_safe_interval swaps low and high if not in right order. + tree.insert({8, 9}); + tree.insert({25, 30}); + tree.insert({5, 8}); + tree.insert({15, 23}); + tree.insert({17, 19}); + tree.insert({26, 26}); + tree.insert({0, 3}); + tree.insert({6, 10}); + tree.insert({19, 20}); + + draw_dot_graph( + std::cout, + tree, + { + // digraph or graph? + .digraph = true, + + // graph name + .name = "G", + + // extra node attributes + .extra_node_attributes = {"color=red"}, + + // extra graph statements + .extra_statements = {"rankdir=LR"}, + + // put space after comma of interval label? (a,b) vs (a, b) + .space_after_comma = false, + + // left brace override, otherwise determined from interval kind + .left_brace = '\0', + + // right brace override, otherwise determined from interval kind + .right_brace = '\0', + + // edge attributes + .edge_attributes = {"color=blue"}, + + // indent characters + .indent = "\t", + } + ); +} \ No newline at end of file diff --git a/example/from_readme/main.cpp b/example/from_readme/main.cpp index 17d5659..063f364 100644 --- a/example/from_readme/main.cpp +++ b/example/from_readme/main.cpp @@ -1,6 +1,8 @@ // #include // to draw tree. this is not header only anymore. #include +#include + int main() { using namespace lib_interval_tree; diff --git a/include/interval-tree/dot_graph.hpp b/include/interval-tree/dot_graph.hpp new file mode 100644 index 0000000..5533fac --- /dev/null +++ b/include/interval-tree/dot_graph.hpp @@ -0,0 +1,217 @@ +#pragma once + +#include "interval_types.hpp" + +#include +#include +#include +#include + +namespace lib_interval_tree +{ + struct dot_graph_draw_settings + { + bool digraph = true; + std::string name = "G"; + std::vector extra_node_attributes = {}; + std::vector extra_statements = {}; + bool space_after_comma = false; + char left_brace = '\0'; + char right_brace = '\0'; + std::vector edge_attributes = {}; + std::string indent = "\t"; + }; + + namespace detail + { + template + class graph_painter + { + public: + graph_painter(std::ostream& stream, TreeT const& tree, dot_graph_draw_settings settings) + : stream_{stream} + , tree_{tree} + , settings_{std::move(settings)} + , node_id_{"a"} + , left_brace_{} + , right_brace_{} + { + using ival_type = typename TreeT::interval_type; + + const auto determine_brace = []() { + if (std::is_same::value) + return "[]"; + else if (std::is_same::value) + return "(]"; + else if (std::is_same::value) + return "[)"; + else if (std::is_same::value) + return "()"; + else if (std::is_same::value) + return "[]"; + else + return "[]"; + }; + + if (settings_.left_brace != '\0') + left_brace_ = settings_.left_brace; + else + left_brace_ = determine_brace()[0]; + + if (settings_.right_brace != '\0') + right_brace_ = settings_.right_brace; + else + right_brace_ = determine_brace()[1]; + } + + void make_header() + { + stream_ << (settings_.digraph ? "digraph" : "graph") << " " << settings_.name << " {\n"; + for (auto const& statement : settings_.extra_statements) + { + stream_ << settings_.indent << statement << ";\n"; + } + } + + template + void make_label(T const& ival) + { +#if __cplusplus >= 201703L + if constexpr (std::is_same::value) + { + stream_ << (ival.left_border() == interval_border::open ? '(' : '[') << ival.low() + << (settings_.space_after_comma ? ", " : ",") << ival.high() + << (ival.right_border() == interval_border::open ? ')' : ']'); + } + else + { + stream_ << left_brace_ << ival.low() << (settings_.space_after_comma ? ", " : ",") << ival.high() + << right_brace_; + } +#else + stream_ << left_brace_ << ival.low() << (settings_.space_after_comma ? ", " : ",") << ival.high() + << right_brace_; +#endif + } + + template + void specify_node(interval_type const& ival) + { + stream_ << settings_.indent << node_id_ << " [label=\""; + increment_node_id(); + make_label(ival); + stream_ << "\""; + if (!settings_.extra_node_attributes.empty()) + { + for (auto const& attr : settings_.extra_node_attributes) + { + stream_ << ", " << attr; + } + } + stream_ << "];\n"; + } + + template + void specify_all_nodes(iterator_type const& node) + { + specify_node(*node); + if (node.left() != tree_.end()) + specify_all_nodes(node.left()); + if (node.right() != tree_.end()) + specify_all_nodes(node.right()); + } + + void specify_edge(std::string const& from, std::string const& to) + { + stream_ << settings_.indent << from << (settings_.digraph ? " -> " : " -- ") << to; + if (!settings_.edge_attributes.empty()) + { + stream_ << " ["; + for (auto iter = settings_.edge_attributes.begin(); iter != settings_.edge_attributes.end(); ++iter) + { + stream_ << *iter; + if (iter + 1 != settings_.edge_attributes.end()) + stream_ << ", "; + } + stream_ << "]"; + } + stream_ << ";\n"; + } + + template + void specify_all_edges(iterator_type const& node) + { + auto previous_id = node_id_; + if (node.left() != tree_.end()) + { + increment_node_id(); + specify_edge(previous_id, node_id_); + specify_all_edges(node.left()); + } + if (node.right() != tree_.end()) + { + increment_node_id(); + specify_edge(previous_id, node_id_); + specify_all_edges(node.right()); + } + } + + void close() + { + stream_ << "}"; + } + + void reset_node_id() + { + node_id_ = "a"; + } + + private: + void increment_node_id() + { + const auto character = node_id_.begin(); + for (auto iter = character; iter != node_id_.end(); ++iter) + { + if (*iter == 'z') + { + *iter = 'a'; + if (iter + 1 == node_id_.end()) + { + node_id_ += 'a'; + break; + } + } + else + { + ++*iter; + break; + } + } + } + + private: + std::ostream& stream_; + TreeT const& tree_; + dot_graph_draw_settings settings_; + std::string node_id_; + char left_brace_; + char right_brace_; + }; + } + + template + void draw_dot_graph(std::ostream& stream, TreeT const& tree, dot_graph_draw_settings const& settings = {}) + { + detail::graph_painter painter{stream, tree, settings}; + painter.make_header(); + if (tree.empty()) + { + painter.close(); + return; + } + painter.specify_all_nodes(tree.root()); + painter.reset_node_id(); + painter.specify_all_edges(tree.root()); + painter.close(); + } +} \ No newline at end of file diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 2b283c5..c239d77 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -6,14 +6,10 @@ #include "feature_test.hpp" #include -#include -#include #include #include #include -#include - namespace lib_interval_tree { // ############################################################################################################ diff --git a/tests/dot_draw_tests.hpp b/tests/dot_draw_tests.hpp new file mode 100644 index 0000000..a7bdc39 --- /dev/null +++ b/tests/dot_draw_tests.hpp @@ -0,0 +1,240 @@ +#pragma once + +#include +#include + +class DotDrawTests : public ::testing::Test +{ + public: + + protected: +}; + +TEST_F(DotDrawTests, WillDrawEmptyTree) +{ + lib_interval_tree::interval_tree_t tree; + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n}"); +} + +TEST_F(DotDrawTests, DrawsSingleNode) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10]\"];\n}"); +} + +TEST_F(DotDrawTests, DrawsNodesWithEdge) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10]\"];\n\tb [label=\"[10,20]\"];\n\ta -> b;\n}"); +} + +TEST_F(DotDrawTests, SpaceAfterCommaIsSetIfEnabled) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.space_after_comma = true; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0, 10]\"];\n}"); +} + +TEST_F(DotDrawTests, GraphIsUndirectedIfSpecifiedAsSuch) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.digraph = false; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "graph G {\n\ta [label=\"[0,10]\"];\n\tb [label=\"[10,20]\"];\n\ta -- b;\n}"); +} + +TEST_F(DotDrawTests, CanSpecifyExtraNodeAttributes) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.extra_node_attributes = {"color=red"}; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10]\", color=red];\n}"); +} + +TEST_F(DotDrawTests, GraphNameIsSet) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.name = "MyGraph"; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph MyGraph {\n\ta [label=\"[0,10]\"];\n}"); +} + +TEST_F(DotDrawTests, CanOverrideLeftBrace) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.left_brace = '('; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"(0,10]\"];\n}"); +} + +TEST_F(DotDrawTests, CanOverrideRightBrace) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.right_brace = ')'; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10)\"];\n}"); +} + +TEST_F(DotDrawTests, CanSpecifyExtraStatements) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.extra_statements = {"rankdir=LR"}; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\trankdir=LR;\n\ta [label=\"[0,10]\"];\n}"); +} + +TEST_F(DotDrawTests, CanSpecifyExtraEdgeAttributes) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.edge_attributes = {"color=blue"}; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10]\"];\n\tb [label=\"[10,20]\"];\n\ta -> b [color=blue];\n}"); +} + +TEST_F(DotDrawTests, IndentIsSet) +{ + lib_interval_tree::interval_tree_t tree; + tree.insert({0, 10}); + std::stringstream ss; + lib_interval_tree::dot_graph_draw_settings settings; + settings.indent = "XXX"; + lib_interval_tree::draw_dot_graph(ss, tree, settings); + EXPECT_EQ(ss.str(), "digraph G {\nXXXa [label=\"[0,10]\"];\n}"); +} + +TEST_F(DotDrawTests, CanDrawTreeWithOpenIntervalType) +{ + lib_interval_tree::interval_tree> tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"(0,10)\"];\n\tb [label=\"(10,20)\"];\n\ta -> b;\n}"); +} + +TEST_F(DotDrawTests, CanDrawTreeWithLeftOpenIntervalType) +{ + lib_interval_tree::interval_tree> tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"(0,10]\"];\n\tb [label=\"(10,20]\"];\n\ta -> b;\n}"); +} + +TEST_F(DotDrawTests, CanDrawTreeWithRightOpenIntervalType) +{ + lib_interval_tree::interval_tree> tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10)\"];\n\tb [label=\"[10,20)\"];\n\ta -> b;\n}"); +} + +TEST_F(DotDrawTests, CanDrawTreeWithClosedAdjacentIntervalType) +{ + lib_interval_tree::interval_tree> tree; + tree.insert({0, 10}); + tree.insert({10, 20}); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10]\"];\n\tb [label=\"[10,20]\"];\n\ta -> b;\n}"); +} + +#if __cplusplus >= 201703L +TEST_F(DotDrawTests, CanDrawTreeWithDynamicIntervalType) +{ + lib_interval_tree::interval_tree> tree; + tree.insert({0, 10, lib_interval_tree::interval_border::closed, lib_interval_tree::interval_border::open}); + tree.insert({10, 20, lib_interval_tree::interval_border::open, lib_interval_tree::interval_border::closed_adjacent} + ); + std::stringstream ss; + lib_interval_tree::draw_dot_graph(ss, tree); + EXPECT_EQ(ss.str(), "digraph G {\n\ta [label=\"[0,10)\"];\n\tb [label=\"(10,20]\"];\n\ta -> b;\n}"); +} +#endif + +TEST_F(DotDrawTests, CanDrawLargerTree) +{ + using namespace lib_interval_tree; + interval_tree_t tree; + + tree.insert(make_safe_interval(21, 16)); // make_safe_interval swaps low and high if not in right order. + tree.insert({8, 9}); + tree.insert({25, 30}); + tree.insert({5, 8}); + tree.insert({15, 23}); + tree.insert({17, 19}); + tree.insert({26, 26}); + tree.insert({0, 3}); + tree.insert({6, 10}); + tree.insert({19, 20}); + + std::stringstream ss; + + lib_interval_tree::dot_graph_draw_settings settings; + settings.indent = ""; + + draw_dot_graph(ss, tree, settings); + EXPECT_EQ( + ss.str(), + R"(digraph G { +a [label="[16,21]"]; +b [label="[8,9]"]; +c [label="[5,8]"]; +d [label="[0,3]"]; +e [label="[6,10]"]; +f [label="[15,23]"]; +g [label="[25,30]"]; +h [label="[17,19]"]; +i [label="[19,20]"]; +j [label="[26,26]"]; +a -> b; +b -> c; +c -> d; +c -> e; +b -> f; +a -> g; +g -> h; +h -> i; +g -> j; +})" + ); +} \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp index de6ab1a..93a925c 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -15,6 +15,7 @@ #include "iteration_tests.hpp" #include "hook_tests.hpp" #include "custom_interval_tests.hpp" +#include "dot_draw_tests.hpp" int main(int argc, char** argv) {