From 73ec963e4a240d99037f9082c1c30f4583ec5f56 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Sun, 21 Jan 2024 16:52:29 +0200 Subject: [PATCH] added visualization for graphs Doubly linked lists test cases are failing. Need review asap. --- examples/graph/graph.cc | 11 +- examples/graph/unnamed.dot | 7 + examples/list/doubly_list_iteration.cc | 2 +- src/classes/graph/graph.h | 155 +++++++++++++++++- src/classes/list/doubly_linked_list.h | 2 +- .../graph_visual/graph_visualization.h | 116 +++++++++++++ tests/graph/graph.cc | 2 +- 7 files changed, 280 insertions(+), 15 deletions(-) create mode 100644 examples/graph/unnamed.dot create mode 100644 src/visualization/graph_visual/graph_visualization.h diff --git a/examples/graph/graph.cc b/examples/graph/graph.cc index 59af8933..721067f3 100644 --- a/examples/graph/graph.cc +++ b/examples/graph/graph.cc @@ -3,19 +3,22 @@ #endif int main() { - weighted_graph g("directed", {{{1, 3}, {2, 4}}, {{3, 4}, {4, 5}}}); + weighted_graph g("undirected"); g.add_edge(1, 3, 4); g.add_edge(3, 4, 5); g.add_edge(0, 1, 6); + g.add_edge(0, 2, 7); + std::cout << g.shortest_path(0, 10) << '\n'; - graph g2("directed", {{1, 3}, {3, 4}, {4, 5}}); + graph g2("directed", { {1, {2,3}}, {2, {4, 5}}}); std::vector v = g2.bfs(0); for (auto &x : v) { std::cout << x << " "; } std::cout << '\n'; - graph g3("directed", { {'a', 'b'}, {'c', 'd'}, {'b','c'} }); - std::cout << "Graph g3 is: " << g3 << '\n'; + + // now you can visualize graphs with algoplus + g2.visualize(); } diff --git a/examples/graph/unnamed.dot b/examples/graph/unnamed.dot new file mode 100644 index 00000000..05fb31e4 --- /dev/null +++ b/examples/graph/unnamed.dot @@ -0,0 +1,7 @@ +digraph G{ +2->4 +2->5 +1->2 +1->3 + +} diff --git a/examples/list/doubly_list_iteration.cc b/examples/list/doubly_list_iteration.cc index 698fc151..4c602766 100644 --- a/examples/list/doubly_list_iteration.cc +++ b/examples/list/doubly_list_iteration.cc @@ -11,4 +11,4 @@ int main() { for (doubly_list_iter it = l1.begin(); it != l1.end(); it++) { std::cout << *(it) << '\n'; } -} \ No newline at end of file +} diff --git a/src/classes/graph/graph.h b/src/classes/graph/graph.h index c073a19d..f2b80031 100644 --- a/src/classes/graph/graph.h +++ b/src/classes/graph/graph.h @@ -1,10 +1,13 @@ #ifndef GRAPH_H #define GRAPH_H +#include "../../visualization/graph_visual/graph_visualization.h" + #ifdef __cplusplus #include #include #include +#include #include #include #include @@ -13,7 +16,7 @@ template class graph { public: - graph(std::string __type, std::vector> __adj = {}) { + graph(std::string __type, std::vector > > __adj = {}) { try { if (__type == "directed" || __type == "undirected") { this->__type = __type; @@ -21,9 +24,9 @@ template class graph { throw std::invalid_argument("Can't recognize the type of graph"); } if (!__adj.empty()) { - for (size_t i = 0; i < __adj.size(); i++) { - for (T &x : __adj[i]) { - this->add_edge(i, x); + for(size_t i = 0; i<__adj.size(); i++){ + for(T &neigh: __adj[i].second){ + this -> add_edge(__adj[i].first, neigh); } } } @@ -67,6 +70,8 @@ template class graph { std::vector topological_sort(); bool bipartite(); + + void visualize(); friend std::ostream & operator <<(std::ostream &out, graph &g){ out << '{'; @@ -258,10 +263,65 @@ template bool graph::bipartite(){ } +template void graph::visualize(){ + std::string s; + if(__type == "directed"){ + if(std::is_same_v || std::is_same_v){ + for(auto &[element, neighbors]: adj){ + for(T &x : neighbors){ + s += element; + s += "->"; + s += x; + s += '\n'; + } + } + } + else{ + for(auto &[element, neighbors]: adj){ + for(T &x : neighbors){ + s += std::to_string(element); + s += "->"; + s += std::to_string(x); + s += '\n'; + } + } + } + } + else{ + if(std::is_same_v || std::is_same_v){ + for(auto &[element, neighbors]: adj){ + for(T &x : neighbors){ + s += element; + s += "--"; + s += x; + s += '\n'; + } + } + } + else{ + for(auto &[element, neighbors]: adj){ + for(T &x : neighbors){ + s += std::to_string(element); + s += "--"; + s += std::to_string(x); + s += '\n'; + } + } + } + } + s += '\n'; + if(__type == "directed"){ + digraph_visualization::visualize(s); + } + else{ + graph_visualization::visualize(s); + } +} + template class weighted_graph { public: weighted_graph(std::string __type, - std::vector>> __adj = {{}}) { + std::vector, int64_t>> __adj = {}) { try { if (__type == "directed" || __type == "undirected") { this->__type = __type; @@ -270,9 +330,7 @@ template class weighted_graph { } if (!__adj.empty()) { for (size_t i = 0; i < __adj.size(); i++) { - for (std::pair &x : __adj[i]) { - this->add_edge(i, x.first, x.second); - } + this->add_edge(__adj[i].first.first, __adj[i].first.second, __adj[i].second); } } } catch (std::invalid_argument &e) { @@ -320,6 +378,8 @@ template class weighted_graph { bool bipartite(); + void visualize(); + friend std::ostream &operator <<(std::ostream &out, weighted_graph &g){ out << '{'; std::vector elements = g.topological_sort(); @@ -596,4 +656,83 @@ template bool weighted_graph::bipartite(){ return true; } +template void weighted_graph::visualize(){ + std::string s; + if(__type == "directed"){ + if(std::is_same_v || std::is_same_v){ + for(auto &[element, neighbors]: adj){ + for(std::pair &x : neighbors){ + if(x.first == element){ + continue; + } + s += element; + s += "->"; + s += x.first; + s += "[weight="; + s += std::to_string(x.second); + s += "]"; + s += '\n'; + } + } + } + else{ + for(auto &[element, neighbors]: adj){ + for(std::pair &x : neighbors){ + if(x.first == element){ + continue; + } + s += std::to_string(element); + s += "->"; + s += std::to_string(x.first); + s += "[weight="; + s += std::to_string(x.second); + s += "]"; + s += '\n'; + } + } + } + } + else{ + if(std::is_same_v || std::is_same_v){ + for(auto &[element, neighbors]: adj){ + for(std::pair &x : neighbors){ + if(x.first == element){ + continue; + } + s += element; + s += "--"; + s += x.first; + s += "[weight="; + s += std::to_string(x.second); + s += "]"; + s += '\n'; + } + } + } + else{ + for(auto &[element, neighbors]: adj){ + for(std::pair &x : neighbors){ + if(x.first == element){ + continue; + } + s += std::to_string(element); + s += "--"; + s += std::to_string(x.first); + s += "[weight="; + s += std::to_string(x.second); + s += "]"; + s += '\n'; + } + } + } + } + if(__type == "directed"){ + digraph_visualization::visualize(s); + } + else{ + graph_visualization::visualize(s); + } + +} + #endif diff --git a/src/classes/list/doubly_linked_list.h b/src/classes/list/doubly_linked_list.h index f91fca5a..728b3eef 100644 --- a/src/classes/list/doubly_linked_list.h +++ b/src/classes/list/doubly_linked_list.h @@ -92,4 +92,4 @@ template class doubly_linked_list { p->prev() = nullptr; return p; } -}; \ No newline at end of file +}; diff --git a/src/visualization/graph_visual/graph_visualization.h b/src/visualization/graph_visual/graph_visualization.h new file mode 100644 index 00000000..3ddca1fb --- /dev/null +++ b/src/visualization/graph_visual/graph_visualization.h @@ -0,0 +1,116 @@ +#include +#include +#include + +using namespace std; + +#define OPEN_COMMAND "open" + +// COLORS FOR TEXT +const char* reset = "\033[0m"; + const char* black = "\033[30m"; + const char* red = "\033[31m"; + const char* green = "\033[32m"; + const char* yellow = "\033[33m"; + const char* blue = "\033[34m"; + const char* magenta = "\033[35m"; + const char* cyan = "\033[36m"; + const char* white = "\033[37m"; + + const char* blackbackground = "\033[40m"; + const char* redbackground = "\033[41m"; + const char* greenbackground = "\033[42m"; + const char* yellowbackground = "\033[43m"; + const char* bluebackground = "\033[44m"; + const char* magentabackground = "\033[45m"; + const char* cyanbackground = "\033[46m"; + const char* whitebackground = "\033[47m"; + + const char* bold = "\033[1m"; + const char* underline = "\033[4m"; + const char* blink = "\033[5m"; + const char* inverse = "\033[7m"; + + +namespace graph_visualization{ + + void visualize(std::string &__generate, std::string newFileName = "unnamed.dot") { + auto start_time = std::chrono::high_resolution_clock::now(); + try { + if (newFileName.size() < 5 || newFileName.substr(newFileName.length() - 4) != ".dot") newFileName += ".dot"; + //newFileName = "examples/" + newFileName; + // Open the file for writing + std::ofstream outFile(newFileName); + + // Check if the file is successfully opened + if (outFile.is_open()) { + // Write the DOT format header + outFile << "graph G{" << std::endl; + + // Generate DOT code recursively + outFile << __generate; + // Write the DOT format footer + outFile << "}" << std::endl; + + // Close the file + outFile.close(); + + std::cout << green << "Visualization file '" << newFileName << "' created successfully." << std::endl << reset; + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + double runtime_sec = static_cast(duration.count()) / 1e6; + + std::cout << blue << "Visualization runtime: " << runtime_sec << " sec" << std::endl << reset; + std::string openCommand = OPEN_COMMAND + std::string(" ") + newFileName; + system(openCommand.c_str()); + } else { + std::cerr << red << "Error: Unable to open file '" << newFileName << "' for writing." << std::endl << reset; + } + } catch (const std::exception& e) { + std::cerr << red << "Error: " << e.what() << std::endl << reset; + } + }; + +}; + + +namespace digraph_visualization{ + + void visualize(std::string &__generate, std::string newFileName = "unnamed.dot") { + auto start_time = std::chrono::high_resolution_clock::now(); + try { + if (newFileName.size() < 5 || newFileName.substr(newFileName.length() - 4) != ".dot") newFileName += ".dot"; + //newFileName = "examples/" + newFileName; + // Open the file for writing + std::ofstream outFile(newFileName); + + // Check if the file is successfully opened + if (outFile.is_open()) { + // Write the DOT format header + outFile << "digraph G{" << std::endl; + + // Generate DOT code recursively + outFile << __generate; + // Write the DOT format footer + outFile << "}" << std::endl; + + // Close the file + outFile.close(); + + std::cout << green << "Visualization file '" << newFileName << "' created successfully." << std::endl << reset; + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + double runtime_sec = static_cast(duration.count()) / 1e6; + + std::cout << blue << "Visualization runtime: " << runtime_sec << " sec" << std::endl << reset; + std::string openCommand = OPEN_COMMAND + std::string(" ") + newFileName; + system(openCommand.c_str()); + } else { + std::cerr << red << "Error: Unable to open file '" << newFileName << "' for writing." << std::endl << reset; + } + } catch (const std::exception& e) { + std::cerr << red << "Error: " << e.what() << std::endl << reset; + } + }; + +}; diff --git a/tests/graph/graph.cc b/tests/graph/graph.cc index 4baf6d3c..c66ba26a 100644 --- a/tests/graph/graph.cc +++ b/tests/graph/graph.cc @@ -4,7 +4,7 @@ #include TEST_CASE("testing clearing of a graph"){ - graph g("directed", { {1,2}, {3,4}, {4,5}}); + graph g("directed", { {0, {1,2}}, {1, {3,4}}, {2, {4,5}}}); g.clear(); std::vector v = g.topological_sort(); std::vector v2 = g.dfs(0);