• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberZHG / SVGDiagram / 20449459503

23 Dec 2025 02:20AM UTC coverage: 98.685% (-0.07%) from 98.755%
20449459503

Pull #37

github

web-flow
Merge c3395b0d5 into 97aba17bf
Pull Request #37: Add dashed and dotted styles

917 of 932 new or added lines in 6 files covered. (98.39%)

1801 of 1825 relevant lines covered (98.68%)

1328.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.61
/src/svg_graph.cpp
1
#include "svg_nodes.h"
2

3
#include <format>
4
#include <cmath>
5

6
#include "attribute_utils.h"
7
#include "svg_text_size.h"
8
#include "geometry_utils.h"
9
using namespace std;
10
using namespace svg_diagram;
11

12

13
void SVGGraph::addNode(shared_ptr<SVGNode>& node) {
218✔
14
    node->setParent(this);
218✔
15
    _nodes.emplace_back(node);
218✔
16
}
218✔
17

18
void SVGGraph::addEdge(shared_ptr<SVGEdge>& edge) {
181✔
19
    edge->setParent(this);
181✔
20
    _edges.emplace_back(edge);
181✔
21
}
181✔
22

23
void SVGGraph::addSubgraph(shared_ptr<SVGGraph>& subgraph) {
15✔
24
    subgraph->setParent(this);
15✔
25
    _graphs.emplace_back(subgraph);
15✔
26
}
15✔
27

28
void SVGGraph::removeNode(const SVGNode* node) {
9✔
29
    for (int i = 0; i < static_cast<int>(_nodes.size()); ++i) {
9✔
30
        if (_nodes[i].get() == node) {
4✔
31
            _nodes.erase(_nodes.begin() + i);  // Need to keep the original order
4✔
32
            break;
4✔
33
        }
34
    }
35
}
9✔
36

37
void SVGGraph::removeEdge(const SVGEdge* edge) {
9✔
38
    for (int i = 0; i < static_cast<int>(_edges.size()); ++i) {
15✔
39
        if (_edges[i].get() == edge) {
9✔
40
            _edges.erase(_edges.begin() + i);
3✔
41
            break;
3✔
42
        }
43
    }
44
}
9✔
45

46
void SVGGraph::removeSubgraph(const SVGGraph* subgraph) {
9✔
47
    for (int i = 0; i < static_cast<int>(_graphs.size()); ++i) {
16✔
48
        if (_graphs[i].get() == subgraph) {
9✔
49
            _graphs.erase(_graphs.begin() + i);
2✔
50
            break;
2✔
51
        }
52
    }
53
}
9✔
54

55
void SVGGraph::removeChild(const SVGItem* item) {
9✔
56
    removeNode(dynamic_cast<const SVGNode*>(item));
9✔
57
    removeEdge(dynamic_cast<const SVGEdge*>(item));
9✔
58
    removeSubgraph(dynamic_cast<const SVGGraph*>(item));
9✔
59
}
9✔
60

61
SVGNode& SVGGraph::defaultNodeAttributes() {
20✔
62
    return _defaultNode;
20✔
63
}
64

65
SVGEdge& SVGGraph::defaultEdgeAttributes() {
10✔
66
    return _defaultEdge;
10✔
67
}
68

69
optional<reference_wrapper<const string>> SVGGraph::defaultNodeAttribute(const string_view& key) const {
3,331✔
70
    if (const auto it = _defaultNode.attributes().find(key); it != _defaultNode.attributes().end()) {
3,331✔
71
        return std::ref(it->second);
252✔
72
    }
73
    if (parent() != nullptr) {
3,079✔
74
        return parent()->defaultNodeAttribute(key);
239✔
75
    }
76
    return {};
2,840✔
77
}
78

79
optional<reference_wrapper<const string>> SVGGraph::defaultEdgeAttribute(const string_view& key) const {
2,477✔
80
    if (const auto it = _defaultEdge.attributes().find(key); it != _defaultEdge.attributes().end()) {
2,477✔
81
        return it->second;
121✔
82
    }
83
    if (parent() != nullptr) {
2,356✔
84
        return parent()->defaultEdgeAttribute(key);
182✔
85
    }
86
    return {};
2,174✔
87
}
88

89
pair<double, double> SVGGraph::center() const {
16✔
90
    return {_cx, _cy};
16✔
91
}
92

93
void SVGGraph::adjustNodeSizes() {
163✔
94
    setAttributeIfNotExist(ATTR_KEY_MARGIN, string(ATTR_DEF_MARGIN_GRAPH));
326✔
95
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
326✔
96
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
326✔
97
    double minX = 0.0, minY = 0.0, maxX = 0.0, maxY = 0.0;
163✔
98
    bool first = true;
163✔
99
    const auto updateGraphSize = [&](const double cx, const double cy, const double width, const double height) {
301✔
100
        const double x1 = cx - width / 2.0;
301✔
101
        const double y1 = cy - height / 2.0;
301✔
102
        const double x2 = cx + width / 2.0;
301✔
103
        const double y2 = cy + height / 2.0;
301✔
104
        if (first) {
301✔
105
            first = false;
134✔
106
            minX = x1; minY = y1;
134✔
107
            maxX = x2; maxY = y2;
134✔
108
        } else {
109
            minX = min(minX, x1); minY = min(minY, y1);
167✔
110
            maxX = max(maxX, x2); maxY = max(maxY, y2);
167✔
111
        }
112
    };
464✔
113
    for (const auto& node : _nodes) {
448✔
114
        node->adjustNodeSize();
285✔
115
        const auto [cx, cy] = node->center();
285✔
116
        const auto strokeWidth = node->penWidth();
285✔
117
        const auto width = node->width() + strokeWidth;
285✔
118
        const auto height = node->height() + strokeWidth;
285✔
119
        updateGraphSize(cx, cy, width, height);
285✔
120
    }
121
    for (const auto& graph : _graphs) {
179✔
122
        graph->adjustNodeSizes();
16✔
123
        const auto [cx, cy] = graph->center();
16✔
124
        const auto strokeWidth = graph->penWidth();
16✔
125
        const auto width = graph->width() + strokeWidth;
16✔
126
        const auto height = graph->height() + strokeWidth;
16✔
127
        updateGraphSize(cx, cy, width, height);
16✔
128
    }
129
    if (first) {
163✔
130
        setSize(-1, -1);
29✔
131
    } else {
132
        const auto [marginWidth, marginHeight] = margin();
134✔
133
        if (const auto label = getAttribute(ATTR_KEY_LABEL); !label.empty()) {
134✔
134
            auto [textWidth, textHeight] = computeTextSize();
10✔
135
            textWidth += marginWidth * 2;
10✔
136
            if (textWidth > maxX - minX) {
10✔
137
                const double cx = (minX + maxX) / 2.0;
3✔
138
                minX = cx - textWidth / 2.0;
3✔
139
                maxX = cx + textWidth / 2.0;
3✔
140
            }
141
            minY -= textHeight + marginHeight;
10✔
142
            _textY = minY + textHeight / 2.0;
10✔
143
        }
134✔
144
        minX -= marginWidth; minY -= marginHeight;
134✔
145
        maxX += marginWidth; maxY += marginHeight;
134✔
146
        const auto width = maxX - minX;
134✔
147
        const auto height = maxY - minY;
134✔
148
        _cx = (minX + maxX) / 2.0;
134✔
149
        _cy = (minY + maxY) / 2.0;
134✔
150
        setSize(width, height);
134✔
151
    }
152
}
163✔
153

154
vector<unique_ptr<SVGDraw>> SVGGraph::produceSVGDraws(const NodesMapping &nodes) {
147✔
155
    adjustNodeSizes();
147✔
156
    vector<unique_ptr<SVGDraw>> svgDraws;
147✔
157
    for (auto& draw : produceClusterSVGDraws()) {
177✔
158
        svgDraws.emplace_back(std::move(draw));
30✔
159
    }
147✔
160
    for (auto& draw : produceNodeSVGDraws()) {
717✔
161
        svgDraws.emplace_back(std::move(draw));
570✔
162
    }
147✔
163
    for (auto& draw : produceEdgeSVGDraws(nodes)) {
565✔
164
        svgDraws.emplace_back(std::move(draw));
418✔
165
    }
147✔
166
    return svgDraws;
147✔
NEW
167
}
×
168

169
vector<shared_ptr<SVGNode>> SVGGraph::findNodes() const {
163✔
170
    vector<shared_ptr<SVGNode>> nodes = _nodes;
163✔
171
    for (auto& graph : _graphs) {
179✔
172
        for (auto& node : graph->findNodes()) {
41✔
173
            nodes.emplace_back(std::move(node));
25✔
174
        }
16✔
175
    }
176
    return nodes;
163✔
NEW
177
}
×
178

179
vector<unique_ptr<SVGDraw>> SVGGraph::produceNodeSVGDraws() const {
163✔
180
    vector<unique_ptr<SVGDraw>> svgDraws;
163✔
181
    for (const auto& node : _nodes) {
448✔
182
        if (enabledDebug()) {
285✔
183
            node->enableDebug();
61✔
184
        }
185
        const auto& id = node->id();
285✔
186
        svgDraws.emplace_back(make_unique<SVGDrawComment>(format("Node: {}", id)));
285✔
187
        auto group = make_unique<SVGDrawGroup>();
285✔
188
        group->setAttribute("id", id);
285✔
189
        group->setAttribute("class", "node");
570✔
190
        group->addChild(make_unique<SVGDrawTitle>(id));
285✔
191
        auto subDraws = node->produceSVGDraws();
285✔
192
        group->addChildren(subDraws);
285✔
193
        svgDraws.emplace_back(std::move(group));
285✔
194
    }
285✔
195
    for (const auto& graph : _graphs) {
179✔
196
        for (auto subDraws = graph->produceNodeSVGDraws(); auto& subDraw : subDraws) {
66✔
197
            svgDraws.emplace_back(std::move(subDraw));
50✔
198
        }
16✔
199
    }
200
    return svgDraws;
163✔
NEW
201
}
×
202

203
vector<unique_ptr<SVGDraw>> SVGGraph::produceEdgeSVGDraws(const NodesMapping& nodes) const {
163✔
204
    vector<unique_ptr<SVGDraw>> svgDraws;
163✔
205
    for (auto& edge : _edges) {
372✔
206
        if (enabledDebug()) {
209✔
207
            edge->enableDebug();
12✔
208
        }
209
        const auto& id = edge->id();
209✔
210
        svgDraws.emplace_back(make_unique<SVGDrawComment>(format("Edge: {} ({} -> {})", id, edge->nodeFrom(), edge->nodeTo())));
209✔
211
        auto group = make_unique<SVGDrawGroup>();
209✔
212
        group->setAttribute("id", id);
209✔
213
        group->setAttribute("class", "edge");
418✔
214
        group->addChild(make_unique<SVGDrawTitle>(format("{}->{}", edge->nodeFrom(), edge->nodeTo())));
209✔
215
        auto subDraws = edge->produceSVGDraws(nodes);
209✔
216
        group->addChildren(subDraws);
209✔
217
        svgDraws.emplace_back(std::move(group));
209✔
218
    }
209✔
219
    for (auto& graph : _graphs) {
179✔
220
        for (auto subDraws = graph->produceEdgeSVGDraws(nodes); auto& subDraw : subDraws) {
44✔
221
            svgDraws.emplace_back(std::move(subDraw));
28✔
222
        }
16✔
223
    }
224
    return svgDraws;
163✔
NEW
225
}
×
226

227
std::vector<std::unique_ptr<SVGDraw>> SVGGraph::produceClusterSVGDraws() {
163✔
228
    vector<unique_ptr<SVGDraw>> svgDraws;
163✔
229
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR_GRAPH));
326✔
230
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
326✔
231
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
326✔
232
    setAttributeIfNotExist(ATTR_KEY_PEN_WIDTH, string(ATTR_DEF_PEN_WIDTH));
326✔
233
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
326✔
234
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
326✔
235
    if (const auto _width = width(), _height = height(); _width > 0 && _height > 0) {
163✔
236
        const auto& _color = color();
134✔
237
        const auto& _fillColor = fillColor();
134✔
238
        if (_color != "none" || _fillColor != "none") {
134✔
239
            auto group = make_unique<SVGDrawGroup>();
12✔
240
            group->setAttribute("id", id());
12✔
241
            group->setAttribute("class", "cluster");
24✔
242
            auto rect = make_unique<SVGDrawRect>(_cx, _cy, _width, _height);
12✔
243
            setStrokeStyles(rect.get());
12✔
244
            rect->setFill(fillColor());
12✔
245
            group->addChild(std::move(rect));
12✔
246
            if (enabledDebug()) {
12✔
247
                const auto [marginX, marginY] = margin();
6✔
248
                auto childrenRect = make_unique<SVGDrawRect>(_cx, _cy, _width - marginX * 2.0, _height - marginY * 2.0);
6✔
249
                childrenRect->setFill("none");
12✔
250
                childrenRect->setStroke("green");
12✔
251
                group->addChild(std::move(childrenRect));
6✔
252
            }
6✔
253
            svgDraws.emplace_back(std::move(group));
12✔
254
        }
12✔
255
        if (const auto& _label = getAttribute(ATTR_KEY_LABEL); !_label.empty()) {
134✔
256
            appendSVGDrawsLabelWithCenter(svgDraws, _cx, _textY);
10✔
257
        }
258
    }
134✔
259
    for (const auto& graph : _graphs) {
179✔
260
        if (enabledDebug()) {
16✔
261
            graph->enableDebug();
8✔
262
        }
263
        for (auto subDraws = graph->produceClusterSVGDraws(); auto& subDraw : subDraws) {
52✔
264
            svgDraws.emplace_back(std::move(subDraw));
36✔
265
        }
16✔
266
    }
267
    return svgDraws;
163✔
NEW
268
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc