diff --git a/docs/source/_static/images/graph_attr/rank_dir_bt.svg b/docs/source/_static/images/graph_attr/rank_dir_bt.svg index 5d51c80..ea70e9c 100644 --- a/docs/source/_static/images/graph_attr/rank_dir_bt.svg +++ b/docs/source/_static/images/graph_attr/rank_dir_bt.svg @@ -95,8 +95,9 @@ node0->node1 - - + + + @@ -149,8 +150,9 @@ node1->node0 - - + + + diff --git a/docs/source/_static/images/graph_attr/rank_dir_lr.svg b/docs/source/_static/images/graph_attr/rank_dir_lr.svg index 52c1c59..a369155 100644 --- a/docs/source/_static/images/graph_attr/rank_dir_lr.svg +++ b/docs/source/_static/images/graph_attr/rank_dir_lr.svg @@ -95,8 +95,9 @@ node0->node1 - - + + + @@ -149,8 +150,9 @@ node1->node0 - - + + + diff --git a/docs/source/_static/images/graph_attr/rank_dir_rl.svg b/docs/source/_static/images/graph_attr/rank_dir_rl.svg index 490343d..7f4020e 100644 --- a/docs/source/_static/images/graph_attr/rank_dir_rl.svg +++ b/docs/source/_static/images/graph_attr/rank_dir_rl.svg @@ -95,8 +95,9 @@ node0->node1 - - + + + @@ -149,8 +150,9 @@ node1->node0 - - + + + diff --git a/docs/source/_static/images/graph_attr/rank_dir_tb.svg b/docs/source/_static/images/graph_attr/rank_dir_tb.svg index 68fa4fa..af6a486 100644 --- a/docs/source/_static/images/graph_attr/rank_dir_tb.svg +++ b/docs/source/_static/images/graph_attr/rank_dir_tb.svg @@ -95,8 +95,9 @@ node0->node1 - - + + + @@ -149,8 +150,9 @@ node1->node0 - - + + + diff --git a/src/graph_layout.cpp b/src/graph_layout.cpp index 9c85aeb..565daca 100644 --- a/src/graph_layout.cpp +++ b/src/graph_layout.cpp @@ -11,6 +11,79 @@ using namespace std; using namespace svg_diagram; using namespace graph_layout; +namespace { + +using graph_layout::Attributes; +using graph_layout::ATTR_KEY_SHAPE; +using graph_layout::ATTR_KEY_LABEL; +using graph_layout::ATTR_KEY_FONT_NAME; +using graph_layout::ATTR_KEY_FONT_SIZE; +using graph_layout::ATTR_KEY_COLOR; +using graph_layout::ATTR_KEY_FILL_COLOR; +using graph_layout::ATTR_KEY_FONT_COLOR; +using graph_layout::ATTR_KEY_TAIL_LABEL; +using graph_layout::ATTR_KEY_HEAD_LABEL; +using graph_layout::ATTR_KEY_LABEL_DISTANCE; +using graph_layout::ATTR_KEY_SPLINES; +using graph_layout::ATTR_KEY_ARROW_HEAD; +using graph_layout::ATTR_KEY_ARROW_TAIL; +using graph_layout::AttributeRankDir; + +void applyNodeAttributes(SVGNode* node, const Attributes& attrs, const int u) { + node->setShape(attrs.vertexAttributes(u, ATTR_KEY_SHAPE)); + node->setLabel(attrs.vertexAttributes(u, ATTR_KEY_LABEL)); + node->setFont(attrs.vertexAttributes(u, ATTR_KEY_FONT_NAME), stod(attrs.vertexAttributes(u, ATTR_KEY_FONT_SIZE))); + node->setColor(attrs.vertexAttributes(u, ATTR_KEY_COLOR)); + node->setFillColor(attrs.vertexAttributes(u, ATTR_KEY_FILL_COLOR)); + node->setFontColor(attrs.vertexAttributes(u, ATTR_KEY_FONT_COLOR)); +} + +void applyEdgeAttributes(SVGEdge* edge, const Attributes& attrs, const int edgeId) { + if (const auto label = attrs.edgeAttributes(edgeId, ATTR_KEY_LABEL); !label.empty()) { + edge->setLabel(label); + } + if (const auto label = attrs.edgeAttributes(edgeId, ATTR_KEY_TAIL_LABEL); !label.empty()) { + edge->setTailLabel(label); + } + if (const auto label = attrs.edgeAttributes(edgeId, ATTR_KEY_HEAD_LABEL); !label.empty()) { + edge->setHeadLabel(label); + } + edge->setFont(attrs.edgeAttributes(edgeId, ATTR_KEY_FONT_NAME), stod(attrs.edgeAttributes(edgeId, ATTR_KEY_FONT_SIZE))); + edge->setLabelDistance(stod(attrs.edgeAttributes(edgeId, ATTR_KEY_LABEL_DISTANCE))); + edge->setSplines(attrs.edgeAttributes(edgeId, ATTR_KEY_SPLINES)); + edge->setArrowHead(attrs.edgeAttributes(edgeId, ATTR_KEY_ARROW_HEAD)); + edge->setArrowTail(attrs.edgeAttributes(edgeId, ATTR_KEY_ARROW_TAIL)); + edge->setMargin(2); + edge->setColor(attrs.edgeAttributes(edgeId, ATTR_KEY_COLOR)); + edge->setFontColor(attrs.edgeAttributes(edgeId, ATTR_KEY_FONT_COLOR)); +} + +pair computeReverseEdgeOffset(const double x1, const double y1, const double x2, const double y2) { + const double dx = x2 - x1; + const double dy = y2 - y1; + const double len = sqrt(dx * dx + dy * dy); + const double nx = -dy / len; + const double ny = dx / len; + const double midX = (x1 + x2) / 2; + const double midY = (y1 + y2) / 2; + return {midX + nx * 10.0, midY + ny * 10.0}; +} + +double getSelfLoopDirection(const string& rankDir) { + if (rankDir == AttributeRankDir::TOP_TO_BOTTOM) { + return 180; + } + if (rankDir == AttributeRankDir::BOTTOM_TO_TOP) { + return 0; + } + if (rankDir == AttributeRankDir::LEFT_TO_RIGHT) { + return -90; + } + return 90; +} + +} + DirectedGraphHierarchicalLayout::DirectedGraphHierarchicalLayout() = default; shared_ptr DirectedGraphHierarchicalLayout::createGraph(const size_t numVertices) { @@ -156,106 +229,54 @@ string DirectedGraphHierarchicalLayout::render() const { if (const auto bgColor = _attributes.graphAttributes(ATTR_KEY_BG_COLOR); !bgColor.empty()) { diagram.setBackgroundColor(bgColor); } - unordered_map> outEdges; - const auto rankDir = _attributes.rankDir(); - for (const auto& [id, u, v] : _graph->edges()) { - outEdges[u].insert(v); - } + vector nodeIds(n); for (int u = 0; u < n; ++u) { nodeIds[u] = format("node{}", u); const auto node = diagram.addNode(nodeIds[u]); node->setCenter(_xs[u], _ys[u]); if (u < _initialNumVertices) { - node->setShape(_attributes.vertexAttributes(u, ATTR_KEY_SHAPE)); - node->setLabel(_attributes.vertexAttributes(u, ATTR_KEY_LABEL)); - node->setFont(_attributes.vertexAttributes(u, ATTR_KEY_FONT_NAME), stod(_attributes.vertexAttributes(u, ATTR_KEY_FONT_SIZE))); - node->setColor(_attributes.vertexAttributes(u, ATTR_KEY_COLOR)); - node->setFillColor(_attributes.vertexAttributes(u, ATTR_KEY_FILL_COLOR)); - node->setFontColor(_attributes.vertexAttributes(u, ATTR_KEY_FONT_COLOR)); + applyNodeAttributes(node.get(), _attributes, u); } else { node->setShape(string("none")); node->setMargin(0, 0); } } - for (const auto& edge : _graph->edges()) { - if (isVirtualVertex(edge.u) || isVirtualVertex(edge.v)) { + + unordered_map> outEdges; + for (const auto& [id, u, v] : _graph->edges()) { + outEdges[u].insert(v); + } + + const auto rankDir = _attributes.rankDir(); + const double selfLoopDir = getSelfLoopDirection(rankDir); + + for (const auto& [id, u, v] : _graph->edges()) { + if (isVirtualVertex(u) || isVirtualVertex(v)) { continue; } - const auto e = diagram.addEdge(nodeIds[edge.u], nodeIds[edge.v]); - if (const auto label = _attributes.edgeAttributes(edge.id, ATTR_KEY_LABEL); !label.empty()) { - e->setLabel(label); - } - if (const auto label = _attributes.edgeAttributes(edge.id, ATTR_KEY_TAIL_LABEL); !label.empty()) { - e->setTailLabel(label); - } - if (const auto label = _attributes.edgeAttributes(edge.id, ATTR_KEY_HEAD_LABEL); !label.empty()) { - e->setHeadLabel(label); - } - e->setFont(_attributes.edgeAttributes(edge.id, ATTR_KEY_FONT_NAME), stod(_attributes.edgeAttributes(edge.id, ATTR_KEY_FONT_SIZE))); - e->setLabelDistance(stod(_attributes.edgeAttributes(edge.id, ATTR_KEY_LABEL_DISTANCE))); - e->setMargin(2); - e->setArrowHead(_attributes.edgeAttributes(edge.id, ATTR_KEY_ARROW_HEAD)); - e->setArrowTail(_attributes.edgeAttributes(edge.id, ATTR_KEY_ARROW_TAIL)); - e->setColor(_attributes.edgeAttributes(edge.id, ATTR_KEY_COLOR)); - e->setFontColor(_attributes.edgeAttributes(edge.id, ATTR_KEY_FONT_COLOR)); - if (edge.u != edge.v) { - if (outEdges[edge.v].contains(edge.u)) { - // There is a reverse edge - const auto x1 = _xs[edge.u]; - const auto y1 = _ys[edge.u]; - const auto x2 = _xs[edge.v]; - const auto y2 = _ys[edge.v]; - const double dx = x2 - x1; - const double dy = y2 - y1; - const double len = sqrt(dx * dx + dy * dy); - const double nx = -dy / len; - const double ny = dx / len; - const double midX = (x1 + x2) / 2; - const double midY = (y1 + y2) / 2; - const double x = midX + nx * 10.0; - const double y = midY + ny * 10.0; + const auto e = diagram.addEdge(nodeIds[u], nodeIds[v]); + applyEdgeAttributes(e.get(), _attributes, id); + if (u != v) { + if (outEdges[v].contains(u)) { + auto [x, y] = computeReverseEdgeOffset(_xs[u], _ys[u], _xs[v], _ys[v]); e->addConnectionPoint(x, y); } } else { - if (rankDir == AttributeRankDir::TOP_TO_BOTTOM) { - e->setSelfLoopAttributes(180, VertexPositioning::DEFAULT_VERTEX_MARGIN * 0.8, 30); - } else if (rankDir == AttributeRankDir::BOTTOM_TO_TOP) { - e->setSelfLoopAttributes(0, VertexPositioning::DEFAULT_VERTEX_MARGIN * 0.8, 30); - } else if (rankDir == AttributeRankDir::LEFT_TO_RIGHT) { - e->setSelfLoopAttributes(-90, VertexPositioning::DEFAULT_VERTEX_MARGIN * 0.8, 30); - } else { - e->setSelfLoopAttributes(90, VertexPositioning::DEFAULT_VERTEX_MARGIN * 0.8, 30); - } + e->setSelfLoopAttributes(selfLoopDir, VertexPositioning::DEFAULT_VERTEX_MARGIN * 0.8, 30); } } - for (const auto& virtualEdge : _virtualEdges) { - const auto& edgeId = virtualEdge.originalEdge.id; - const auto& originalEdge = virtualEdge.originalEdge; - const auto& edgeIds = virtualEdge.virtualEdgeIds; + + for (const auto&[originalEdge, edgeIds] : _virtualEdges) { + const auto& edgeId = originalEdge.id; const auto e = diagram.addEdge(nodeIds[originalEdge.u], nodeIds[originalEdge.v]); - if (const auto label = _attributes.edgeAttributes(edgeId, ATTR_KEY_LABEL); !label.empty()) { - e->setLabel(label); - } - if (const auto label = _attributes.edgeAttributes(edgeId, ATTR_KEY_TAIL_LABEL); !label.empty()) { - e->setTailLabel(label); - } - if (const auto label = _attributes.edgeAttributes(edgeId, ATTR_KEY_HEAD_LABEL); !label.empty()) { - e->setHeadLabel(label); - } - e->setFont(_attributes.edgeAttributes(edgeId, ATTR_KEY_FONT_NAME), stod(_attributes.edgeAttributes(edgeId, ATTR_KEY_FONT_SIZE))); - e->setLabelDistance(stod(_attributes.edgeAttributes(edgeId, ATTR_KEY_LABEL_DISTANCE))); - e->setSplines(_attributes.edgeAttributes(edgeId, ATTR_KEY_SPLINES)); - e->setArrowHead(_attributes.edgeAttributes(edgeId, ATTR_KEY_ARROW_HEAD)); - e->setArrowTail(_attributes.edgeAttributes(edgeId, ATTR_KEY_ARROW_TAIL)); - e->setMargin(2); - e->setColor(_attributes.edgeAttributes(edgeId, ATTR_KEY_COLOR)); - e->setFontColor(_attributes.edgeAttributes(edgeId, ATTR_KEY_FONT_COLOR)); + applyEdgeAttributes(e.get(), _attributes, edgeId); for (int i = 0; i + 1 < static_cast(edgeIds.size()); ++i) { const auto& edge = _graph->getEdge(edgeIds[i]); e->addConnectionPoint(_xs[edge.v], _ys[edge.v]); } } + return diagram.render(); }