Program Listing for File graphviz.cpp

Return to documentation for file (lib/graphviz.cpp)

#include <AnalysisGraph.hpp>
#include <Node.hpp>
#include <graphviz_interface.hpp>
#include <range/v3/all.hpp>

using namespace std;

/*
 ============================================================================
 Private: Graph Visualization (in graphviz.cpp)
 ============================================================================
*/

pair<Agraph_t*, GVC_t*> AnalysisGraph::to_agraph(bool simplified_labels,
                                                 int label_depth,
                                                 string node_to_highlight,
                                                 string rankdir) {

  using delphi::gv::set_property, delphi::gv::add_node;
  using namespace ranges::views;
  using ranges::end, ranges::to;
  using ranges::views::slice, ranges::views::replace, ranges::max,
      ranges::views::transform;

  Agraph_t* G = agopen(const_cast<char*>("G"), Agdirected, NULL);
  GVC_t* gvc;
  gvc = gvContext();

  // Set global properties
  set_property(G, AGNODE, "shape", "rectangle");
  set_property(G, AGNODE, "style", "rounded");
  set_property(G, AGNODE, "color", "maroon");
  set_property(G, AGRAPH, "dpi", "150");
  set_property(G, AGRAPH, "overlap", "scale");
  set_property(G, AGRAPH, "splines", "true");
  set_property(G, AGRAPH, "rankdir", rankdir);

#if defined __APPLE__
  set_property(G, AGNODE, "fontname", "Gill Sans");
#else
  set_property(G, AGNODE, "fontname", "Helvetica");
#endif

  Agnode_t* src;
  Agnode_t* trgt;
  Agedge_t* edge;

  string source_label;
  string target_label;

  // Add CAG links
  for (auto e : this->edges()) {
    string source_name = this->source(e).name;
    string target_name = this->target(e).name;

    // TODO Implement a refined version of this that checks for set size
    // equality, a la the Python implementation (i.e. check if the length of
    // the nodeset is the same as the length of the set of simplified labels).

    string source_label, target_label;

    if (simplified_labels == true) {
      source_label = source_name | split('/') | slice(end - label_depth, end) |
                     join('/') | replace('_', ' ') | to<string>();
      target_label = target_name | split('/') | slice(end - label_depth, end) |
                     join('/') | replace('_', ' ') | to<string>();
    }
    else {
      source_label = source_name;
      target_label = target_name;
    }

    src = add_node(G, source_name);
    set_property(src, "label", source_label);

    trgt = add_node(G, target_name);
    set_property(trgt, "label", target_label);

    edge = agedge(G, src, trgt, 0, true);
  }

  if (node_to_highlight != "") {
    Agnode_t* node;
    node = add_node(G, node_to_highlight);
    set_property(node, "color", "blue");
  }

  // Add concepts, indicators, and link them.
  for (Node& node : this->nodes()) {
    string concept_name = node.name;
    for (auto indicator : node.indicators) {
      src = add_node(G, concept_name);
      trgt = add_node(G, indicator.name);
      set_property(trgt,
                   "label",
                   indicator.name + "\nSource: " + indicator.source +
                       "\nDBN Initialization Value: " +
                       to_string(indicator.mean) + "\nUnit: " + indicator.unit);
      set_property(trgt, "style", "rounded,filled");
      set_property(trgt, "fillcolor", "lightblue");

      edge = agedge(G, src, trgt, 0, true);
    }
  }
  gvLayout(gvc, G, "dot");
  return make_pair(G, gvc);
}

/*
 ============================================================================
 Public: Graph Visualization
 ============================================================================
*/

void AnalysisGraph::to_png(string filename,
                           bool simplified_labels,
                           int label_depth,
                           string node_to_highlight,
                           string rankdir) {
  auto [G, gvc] = this->to_agraph(
      simplified_labels, label_depth, node_to_highlight, rankdir);
  gvRenderFilename(gvc, G, "png", const_cast<char*>(filename.c_str()));
  gvFreeLayout(gvc, G);
  agclose(G);
  gvFreeContext(gvc);
}

string AnalysisGraph::to_dot() {
  auto [G, gvc] = this->to_agraph();

  stringstream sstream;
  stringbuf* sstream_buffer;
  streambuf* original_cout_buffer;

  // Back up original cout buffer
  original_cout_buffer = cout.rdbuf();
  sstream_buffer = sstream.rdbuf();

  // Redirect cout to sstream
  cout.rdbuf(sstream_buffer);

  gvRender(gvc, G, "dot", stdout);
  agclose(G);
  gvFreeContext(gvc);

  // Restore cout's original buffer
  cout.rdbuf(original_cout_buffer);

  // Return the string with the graph in DOT format
  return sstream.str();
}