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

CyberZHG / SVGDiagram / 20417213331

21 Dec 2025 11:10PM UTC coverage: 98.755% (+0.05%) from 98.705%
20417213331

push

github

web-flow
Add docs for background color (#33)

1745 of 1767 relevant lines covered (98.75%)

1242.51 hits per line

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

98.48
/src/svg_nodes.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
const unordered_map<string_view, string>& SVGItem::attributes() const {
34,480✔
13
    return _attributes;
34,480✔
14
}
15

16
void SVGItem::setAttribute(const string_view& key, const string& value) {
3,207✔
17
    _attributes[key] = value;
3,207✔
18
}
3,207✔
19

20
void SVGItem::setAttribute(const string_view& key, const double value) {
829✔
21
    _attributes[key] = format("{}", value);
829✔
22
}
829✔
23

24
void SVGItem::setAttributeIfNotExist(const string_view& key, const string& value) {
1,345✔
25
    if (!_attributes.contains(key)) {
1,345✔
26
        _attributes[key] = value;
646✔
27
    }
28
}
1,345✔
29

30
void SVGItem::setDoubleAttributeIfNotExist(const string_view& key, const double value) {
40✔
31
    setAttributeIfNotExist(key, format("{}", value));
40✔
32
}
40✔
33

34
const string& SVGItem::getAttribute(const string_view& key) const {
1,075✔
35
    static const string EMPTY_STRING;
1,075✔
36
    if (const auto it = _attributes.find(key); it != _attributes.end()) {
1,075✔
37
        return it->second;
841✔
38
    }
39
    return EMPTY_STRING;
234✔
40
}
41

42
void SVGItem::setPrecomputedTextSize(const double width, const double height) {
60✔
43
    _precomputedTextWidth = width;
60✔
44
    _precomputedTextHeight = height;
60✔
45
}
60✔
46

47
pair<double, double> SVGItem::precomputedTextSize() const {
450✔
48
    return {_precomputedTextWidth, _precomputedTextHeight};
450✔
49
}
50

51
const string& SVGItem::id() const {
734✔
52
    const auto it = _attributes.find(ATTRIBUTE_KEY_ID);
734✔
53
    if (it == _attributes.end()) {
734✔
54
        throw runtime_error("Attribute 'ID' not found");
1✔
55
    }
56
    return it->second;
1,466✔
57
}
58

59
void SVGItem::setID(const string& id) {
380✔
60
    setAttribute(ATTRIBUTE_KEY_ID, id);
380✔
61
}
380✔
62

63
void SVGItem::setLabel(const string& label) {
293✔
64
    setAttribute(ATTRIBUTE_KEY_LABEL, label);
293✔
65
}
293✔
66

67
double SVGItem::width() const {
1,070✔
68
    return AttributeUtils::inchToPoint(stod(getAttribute(ATTRIBUTE_KEY_WIDTH)));
1,070✔
69
}
70

71
void SVGItem::setWidth(const double width) {
396✔
72
    setAttribute(ATTRIBUTE_KEY_WIDTH, AttributeUtils::pointToInch(width));
396✔
73
}
396✔
74

75
double SVGItem::height() const {
958✔
76
    return AttributeUtils::inchToPoint(stod(getAttribute(ATTRIBUTE_KEY_HEIGHT)));
958✔
77
}
78

79
void SVGItem::setHeight(const double height) {
396✔
80
    setAttribute(ATTRIBUTE_KEY_HEIGHT, AttributeUtils::pointToInch(height));
396✔
81
}
396✔
82

83
void SVGItem::setSize(const double width, const double height) {
160✔
84
    setWidth(width);
160✔
85
    setHeight(height);
160✔
86
}
160✔
87

88
void SVGItem::setFixedSize(const double width, const double height) {
8✔
89
    setSize(width, height);
8✔
90
    setAttribute(ATTRIBUTE_KEY_FIXED_SIZE, "ON");
8✔
91
}
8✔
92

93
pair<double, double> SVGItem::margin() const {
566✔
94
    return AttributeUtils::parseMargin(getAttribute(ATTRIBUTE_KEY_MARGIN));
566✔
95
}
96

97
void SVGItem::setMargin(const string& value) {
67✔
98
    setAttribute(ATTRIBUTE_KEY_MARGIN, value);
67✔
99
}
67✔
100

101
void SVGItem::setMargin(const double margin) {
14✔
102
    setMargin(format("{}", AttributeUtils::pointToInch(margin)));
14✔
103
}
14✔
104

105
void SVGItem::setMargin(const double marginX, const double marginY) {
53✔
106
    setMargin(format("{},{}", AttributeUtils::pointToInch(marginX), AttributeUtils::pointToInch(marginY)));
53✔
107
}
53✔
108

109
string SVGItem::color() const {
2,391✔
110
    return getAttribute(ATTRIBUTE_KEY_COLOR);
2,391✔
111
}
112

113
void SVGItem::setColor(const string& color) {
54✔
114
    setAttribute(ATTRIBUTE_KEY_COLOR, color);
54✔
115
}
54✔
116

117
string SVGItem::fillColor() const {
385✔
118
    return getAttribute(ATTRIBUTE_KEY_FILL_COLOR);
385✔
119
}
120

121
void SVGItem::setFillColor(const string& color) {
28✔
122
    setAttribute(ATTRIBUTE_KEY_FILL_COLOR, color);
28✔
123
}
28✔
124

125
string SVGItem::fontColor() const {
347✔
126
    return getAttribute(ATTRIBUTE_KEY_FONT_COLOR);
347✔
127
}
128

129
void SVGItem::setFontColor(const string& color) {
49✔
130
    setAttribute(ATTRIBUTE_KEY_FONT_COLOR, color);
49✔
131
}
49✔
132

133
void SVGItem::setPenWidth(const double width) {
28✔
134
    setAttribute(ATTRIBUTE_KEY_PEN_WIDTH, width);
28✔
135
}
28✔
136

137
double SVGItem::penWidth() const {
1,459✔
138
    if (color() == "none") {
1,459✔
139
        return 0.0;
7✔
140
    }
141
    if (const auto value = getAttribute(ATTRIBUTE_KEY_PEN_WIDTH); !value.empty()) {
1,452✔
142
        const auto width = stod(value);
132✔
143
        if (fabs(width - 1.0) < GeometryUtils::EPSILON) {
132✔
144
            return 1.0;
16✔
145
        }
146
        return width;
116✔
147
    }
1,452✔
148
    return 1.0;
1,320✔
149
}
150

151
void SVGItem::setFontName(const string& fontName) {
9✔
152
    setAttribute(ATTRIBUTE_KEY_FONT_NAME, fontName);
9✔
153
}
9✔
154

155
void SVGItem::setFontSize(const double fontSize) {
9✔
156
    setAttribute(ATTRIBUTE_KEY_FONT_SIZE, fontSize);
9✔
157
}
9✔
158

159
void SVGItem::setFont(const string& fontName, const double fontSize) {
5✔
160
    setFontName(fontName);
5✔
161
    setFontSize(fontSize);
5✔
162
}
5✔
163

164
void SVGItem::appendSVGDrawsLabelWithCenter(vector<unique_ptr<SVGDraw>>& svgDraws, const double cx, const double cy) {
377✔
165
    if (enabledDebug()) {
377✔
166
        const auto [textWidth, textHeight] = computeTextSize();
73✔
167
        const auto [marginX, marginY] = computeMargin();
73✔
168
        auto textRect = make_unique<SVGDrawRect>(cx, cy, textWidth, textHeight);
73✔
169
        textRect->setFill("none");
146✔
170
        textRect->setStroke("blue");
146✔
171
        svgDraws.emplace_back(std::move(textRect));
73✔
172
        auto marginRect = make_unique<SVGDrawRect>(cx, cy, textWidth + marginX * 2, textHeight + marginY * 2);
73✔
173
        marginRect->setFill("none");
146✔
174
        marginRect->setStroke("red");
146✔
175
        svgDraws.emplace_back(std::move(marginRect));
73✔
176
    }
73✔
177
    if (const auto label = getAttribute(ATTRIBUTE_KEY_LABEL); !label.empty()) {
377✔
178
        auto draw = make_unique<SVGDrawText>(cx, cy, label);
347✔
179
        if (const auto& color = fontColor(); color != "black") {
347✔
180
            draw->setFill(color);
136✔
181
        }
347✔
182
        const string fontFamily = getAttribute(ATTRIBUTE_KEY_FONT_NAME);
347✔
183
        const double fontSize = stod(getAttribute(ATTRIBUTE_KEY_FONT_SIZE));
347✔
184
        draw->setFont(fontFamily, fontSize);
347✔
185
        svgDraws.emplace_back(std::move(draw));
347✔
186
    }
724✔
187
}
377✔
188

189
pair<double, double> SVGItem::computeTextSize() {
450✔
190
    if (const auto [precomputedTextWidth, precomputedTextHeight] = precomputedTextSize();
450✔
191
        precomputedTextWidth > 0 && precomputedTextHeight > 0) {
450✔
192
        return {precomputedTextWidth, precomputedTextHeight};
159✔
193
    }
194
    const SVGTextSize textSize;
291✔
195
    const auto label = getAttribute(ATTRIBUTE_KEY_LABEL);
291✔
196
    const double fontSize = stod(getAttribute(ATTRIBUTE_KEY_FONT_SIZE));
291✔
197
    const string fontFamily = getAttribute(ATTRIBUTE_KEY_FONT_NAME);
291✔
198
    auto [width, height] = textSize.computeTextSize(label, fontSize, fontFamily);
291✔
199
    if (width == 0.0) {
291✔
200
        width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
30✔
201
    }
202
    if (height == 0.0) {
291✔
203
        height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
30✔
204
    }
205
    return {width, height};
291✔
206
}
291✔
207

208
pair<double, double> SVGItem::computeMargin() {
440✔
209
    setAttributeIfNotExist(ATTRIBUTE_KEY_MARGIN, "0.1111111111111111,0.05555555555555555");
880✔
210
    return margin();
440✔
211
}
212

213
std::pair<double, double> SVGItem::computeTextSizeWithMargin() {
274✔
214
    const auto [width, height] = computeTextSize();
274✔
215
    const auto [marginX, marginY] = computeMargin();
274✔
216
    return {width + marginX * 2, height + marginY * 2};
274✔
217
}
218

219
SVGItem::SVGItem(const string& id) {
8✔
220
    setID(id);
8✔
221
}
8✔
222

223
void SVGItem::enableDebug() {
116✔
224
    _enabledDebug = true;
116✔
225
}
116✔
226

227
bool SVGItem::enabledDebug() const {
952✔
228
    return _enabledDebug;
952✔
229
}
230

231
void SVGItem::setParent(SVGGraph* parent) {
388✔
232
    if (_parent != nullptr) {
388✔
233
        _parent->removeChild(this);
9✔
234
    }
235
    _parent = parent;
388✔
236
}
388✔
237

238
SVGGraph * SVGItem::parent() const {
13,444✔
239
    return _parent;
13,444✔
240
}
241

242
SVGNode::SVGNode(const double cx, const double cy) {
50✔
243
    _cx = cx;
50✔
244
    _cy = cy;
50✔
245
}
50✔
246

247
void SVGNode::setAttributeIfNotExist(const string_view &key, const string &value) {
3,053✔
248
    if (attributes().contains(key)) {
3,053✔
249
        return;
1,870✔
250
    }
251
    if (parent() != nullptr) {
1,183✔
252
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
1,183✔
253
            return;
106✔
254
        }
255
    }
256
    setAttribute(key, value);
1,077✔
257
}
258

259
const string& SVGNode::getAttribute(const std::string_view& key) const {
6,966✔
260
    static const string EMPTY_STRING;
6,966✔
261
    if (const auto it = attributes().find(key); it != attributes().end()) {
6,966✔
262
        return it->second;
5,528✔
263
    }
264
    if (parent() != nullptr) {
1,438✔
265
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
1,438✔
266
            return ret.value();
146✔
267
        }
268
    }
269
    return EMPTY_STRING;
1,292✔
270
}
271

272
void SVGNode::setShape(const string& shape) {
117✔
273
    setAttribute(ATTRIBUTE_KEY_SHAPE, shape);
117✔
274
}
117✔
275

276
void SVGNode::setShape(const string_view& shape) {
117✔
277
    setShape(string(shape));
117✔
278
}
117✔
279

280
void SVGNode::setCenter(const double cx, const double cy) {
127✔
281
    _cx = cx;
127✔
282
    _cy = cy;
127✔
283
}
127✔
284

285
pair<double, double> SVGNode::center() const {
434✔
286
    return {_cx, _cy};
434✔
287
}
288

289
void SVGNode::adjustNodeSize() {
256✔
290
    setAttributeIfNotExist(ATTRIBUTE_KEY_SHAPE, string(SHAPE_DEFAULT));
512✔
291
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_NAME, "Times,serif");
512✔
292
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_SIZE, "14");
256✔
293
    const auto shape = getAttribute(ATTRIBUTE_KEY_SHAPE);
256✔
294
    if (shape == SHAPE_CIRCLE) {
256✔
295
        adjustNodeSizeCircle();
107✔
296
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
149✔
297
        adjustNodeSizeDoubleCircle();
11✔
298
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
138✔
299
        adjustNodeSizeRect();
45✔
300
    } else if (shape == SHAPE_ELLIPSE) {
93✔
301
        adjustNodeSizeEllipse();
93✔
302
    }
303
}
256✔
304

305
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
256✔
306
    setAttributeIfNotExist(ATTRIBUTE_KEY_SHAPE, string(SHAPE_DEFAULT));
512✔
307
    setAttributeIfNotExist(ATTRIBUTE_KEY_COLOR, "black");
512✔
308
    setAttributeIfNotExist(ATTRIBUTE_KEY_FILL_COLOR, "none");
512✔
309
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_COLOR, "black");
512✔
310
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_NAME, "Times,serif");
512✔
311
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_SIZE, "14");
256✔
312
    const auto shape = getAttribute(ATTRIBUTE_KEY_SHAPE);
256✔
313
    if (shape == SHAPE_CIRCLE) {
256✔
314
        return produceSVGDrawsCircle();
107✔
315
    }
316
    if (shape == SHAPE_DOUBLE_CIRCLE) {
149✔
317
        return produceSVGDrawsDoubleCircle();
11✔
318
    }
319
    if (shape == SHAPE_RECT) {
138✔
320
        return produceSVGDrawsRect();
42✔
321
    }
322
    if (shape == SHAPE_ELLIPSE) {
96✔
323
        return produceSVGDrawsEllipse();
93✔
324
    }
325
    return produceSVGDrawsNone();
3✔
326
}
256✔
327

328
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
396✔
329
    setAttributeIfNotExist(ATTRIBUTE_KEY_SHAPE, string(SHAPE_DEFAULT));
396✔
330
    const auto shape = getAttribute(ATTRIBUTE_KEY_SHAPE);
396✔
331
    if (shape == SHAPE_CIRCLE) {
396✔
332
        return computeConnectionPointCircle(angle);
106✔
333
    }
334
    if (shape == SHAPE_DOUBLE_CIRCLE) {
290✔
335
        return computeConnectionPointDoubleCircle(angle);
6✔
336
    }
337
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
284✔
338
        return computeConnectionPointRect(angle);
128✔
339
    }
340
    if (shape == SHAPE_ELLIPSE) {
156✔
341
        return computeConnectionPointEllipse(angle);
156✔
342
    }
343
    return {0.0, 0.0};
×
344
}
396✔
345

346
double SVGNode::computeAngle(const double x, const double y) const {
396✔
347
    return atan2(y - _cy, x - _cx);
396✔
348
}
349

350
double SVGNode::computeAngle(const pair<double, double>& p) const {
396✔
351
    return computeAngle(p.first, p.second);
396✔
352
}
353

354
bool SVGNode::isFixedSize() const {
256✔
355
    return AttributeUtils::parseBool(getAttribute(ATTRIBUTE_KEY_FIXED_SIZE));
256✔
356
}
357

358
void SVGNode::updateNodeSize(const double width, const double height) {
256✔
359
    if (isFixedSize()) {
256✔
360
        setDoubleAttributeIfNotExist(ATTRIBUTE_KEY_WIDTH, AttributeUtils::pointToInch(width));
20✔
361
        setDoubleAttributeIfNotExist(ATTRIBUTE_KEY_HEIGHT, AttributeUtils::pointToInch(height));
20✔
362
    } else {
363
        setWidth(width);
236✔
364
        setHeight(height);
236✔
365
    }
366
}
256✔
367

368
void SVGNode::updateNodeSize(const pair<double, double>& size) {
45✔
369
    updateNodeSize(size.first, size.second);
45✔
370
}
45✔
371

372
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
256✔
373
    appendSVGDrawsLabelWithCenter(svgDraws, _cx, _cy);
256✔
374
}
256✔
375

376
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
377
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
378
    appendSVGDrawsLabel(svgDraws);
3✔
379
    return svgDraws;
3✔
380
}
×
381

382
void SVGNode::adjustNodeSizeCircle() {
118✔
383
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
118✔
384
    updateNodeSize(diameter, diameter);
118✔
385
}
118✔
386

387
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
107✔
388
    vector<unique_ptr<SVGDraw>> svgDraws;
107✔
389
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width(), height()) / 2.0);
107✔
390
    circle->setStroke(color());
107✔
391
    circle->setFill(fillColor());
107✔
392
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
107✔
393
        circle->setStrokeWidth(strokeWidth);
3✔
394
    }
395
    svgDraws.emplace_back(std::move(circle));
107✔
396
    appendSVGDrawsLabel(svgDraws);
107✔
397
    return svgDraws;
214✔
398
}
107✔
399

400
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
106✔
401
    const double radius = (width() + penWidth()) / 2.0;
106✔
402
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
106✔
403
}
404

405
void SVGNode::adjustNodeSizeDoubleCircle() {
11✔
406
    adjustNodeSizeCircle();
11✔
407
}
11✔
408

409
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsDoubleCircle() {
11✔
410
    vector<unique_ptr<SVGDraw>> svgDraws;
11✔
411
    const double radius = max(width(), height()) / 2.0;
11✔
412
    const auto strokeWidth = penWidth();
11✔
413
    auto circleInner = make_unique<SVGDrawCircle>(_cx, _cy, radius);
11✔
414
    circleInner->setStroke(color());
11✔
415
    circleInner->setFill(fillColor());
11✔
416
    if (strokeWidth != 1.0) {
11✔
417
        circleInner->setStrokeWidth(strokeWidth);
4✔
418
    }
419
    svgDraws.emplace_back(std::move(circleInner));
11✔
420
    auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN);
11✔
421
    circleOuter->setStroke(color());
11✔
422
    circleOuter->setFill("none");
22✔
423
    if (strokeWidth != 1.0) {
11✔
424
        circleOuter->setStrokeWidth(strokeWidth);
4✔
425
    }
426
    svgDraws.emplace_back(std::move(circleOuter));
11✔
427
    appendSVGDrawsLabel(svgDraws);
11✔
428
    return svgDraws;
22✔
429
}
11✔
430

431
std::pair<double, double> SVGNode::computeConnectionPointDoubleCircle(const double angle) const {
6✔
432
    const double radius = (width() + penWidth()) / 2.0 + DOUBLE_BORDER_MARGIN;
6✔
433
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
6✔
434
}
435

436
void SVGNode::adjustNodeSizeRect() {
45✔
437
    updateNodeSize(computeTextSizeWithMargin());
45✔
438
}
45✔
439

440
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
42✔
441
    vector<unique_ptr<SVGDraw>> svgDraws;
42✔
442
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
42✔
443
    rect->setStroke(color());
42✔
444
    rect->setFill(fillColor());
42✔
445
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
42✔
446
        rect->setStrokeWidth(strokeWidth);
6✔
447
    }
448
    svgDraws.emplace_back(std::move(rect));
42✔
449
    appendSVGDrawsLabel(svgDraws);
42✔
450
    return svgDraws;
84✔
451
}
42✔
452

453
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
128✔
454
    const double strokeWidth = penWidth();
128✔
455
    const double totalWidth = width() + strokeWidth;
128✔
456
    const double totalHeight = height() + strokeWidth;
128✔
457
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
128✔
458
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
128✔
459
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
256✔
460
    for (const auto& [x, y] : vertices) {
628✔
461
        if (GeometryUtils::isSameAngle(angle, x, y)) {
504✔
462
            return {_cx + x, _cy + y};
4✔
463
        }
464
    }
465
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
282✔
466
        x1 = vertices[i].first;
282✔
467
        y1 = vertices[i].second;
282✔
468
        x2 = vertices[(i + 1) % vertices.size()].first;
282✔
469
        y2 = vertices[(i + 1) % vertices.size()].second;
282✔
470
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
282✔
471
            return {_cx + intersect.value().first, _cy + intersect.value().second};
124✔
472
        }
473
    }
474
    return {_cx, _cy};
×
475
}
128✔
476

477
void SVGNode::adjustNodeSizeEllipse() {
93✔
478
    const auto [textWidth, textHeight] = computeTextSize();
93✔
479
    const auto [marginX, marginY] = computeMargin();
93✔
480
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
93✔
481
}
93✔
482

483
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
93✔
484
    vector<unique_ptr<SVGDraw>> svgDraws;
93✔
485
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
93✔
486
    ellipse->setStroke(color());
93✔
487
    ellipse->setFill(fillColor());
93✔
488
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
93✔
489
        ellipse->setStrokeWidth(strokeWidth);
10✔
490
    }
491
    svgDraws.emplace_back(std::move(ellipse));
93✔
492
    appendSVGDrawsLabel(svgDraws);
93✔
493
    return svgDraws;
186✔
494
}
93✔
495

496
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
156✔
497
    const double strokeWidth = penWidth();
156✔
498
    const double totalWidth = width() + strokeWidth;
156✔
499
    const double totalHeight = height() + strokeWidth;
156✔
500
    const double rx = totalWidth / 2, ry = totalHeight / 2;
156✔
501
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
156✔
502
    const double x = rx * ry * cos(angle) / base;
156✔
503
    const double y = rx * ry * sin(angle) / base;
156✔
504
    return {_cx + x, _cy + y};
156✔
505
}
506

507
SVGEdge::SVGEdge(const string& idFrom, const string& idTo) {
23✔
508
    _nodeFrom = idFrom;
23✔
509
    _nodeTo = idTo;
23✔
510
}
23✔
511

512
void SVGEdge::setAttributeIfNotExist(const std::string_view &key, const std::string &value) {
1,509✔
513
    if (attributes().contains(key)) {
1,509✔
514
        return;
481✔
515
    }
516
    if (parent() != nullptr) {
1,028✔
517
        if (const auto ret = parent()->defaultEdgeAttribute(key); ret.has_value()) {
1,028✔
518
            return;
63✔
519
        }
520
    }
521
    setAttribute(key, value);
965✔
522
}
523

524
const string& SVGEdge::getAttribute(const string_view& key) const {
3,268✔
525
    static const string EMPTY_STRING;
3,268✔
526
    if (const auto it = attributes().find(key); it != attributes().end()) {
3,268✔
527
        return it->second;
2,554✔
528
    }
529
    if (parent() != nullptr) {
714✔
530
        if (const auto ret = parent()->defaultEdgeAttribute(key); ret.has_value()) {
714✔
531
            return ret.value();
54✔
532
        }
533
    }
534
    return EMPTY_STRING;
660✔
535
}
536

537
void SVGEdge::setNodeFrom(const string& id) {
7✔
538
    _nodeFrom = id;
7✔
539
}
7✔
540

541
const string& SVGEdge::nodeFrom() const {
396✔
542
    return _nodeFrom;
396✔
543
}
544

545
void SVGEdge::setNodeTo(const string& id) {
7✔
546
    _nodeTo = id;
7✔
547
}
7✔
548

549
const string& SVGEdge::nodeTo() const {
396✔
550
    return _nodeTo;
396✔
551
}
552

553
void SVGEdge::setConnection(const string& idFrom, const string& idTo) {
140✔
554
    _nodeFrom = idFrom;
140✔
555
    _nodeTo = idTo;
140✔
556
}
140✔
557

558
void SVGEdge::setSplines(const string& value) {
28✔
559
    setAttribute(ATTRIBUTE_KEY_SPLINES, value);
28✔
560
}
28✔
561

562
void SVGEdge::setSplines(const string_view& value) {
28✔
563
    setSplines(string(value));
28✔
564
}
28✔
565

566
void SVGEdge::addConnectionPoint(const pair<double, double>& point) {
141✔
567
    _connectionPoints.emplace_back(point);
141✔
568
}
141✔
569

570
void SVGEdge::addConnectionPoint(const double x, const double y) {
141✔
571
    addConnectionPoint({x, y});
141✔
572
}
141✔
573

574
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDraws(const NodesMapping& nodes) {
198✔
575
    setAttributeIfNotExist(ATTRIBUTE_KEY_SPLINES, string(SPLINES_DEFAULT));
396✔
576
    setAttributeIfNotExist(ATTRIBUTE_KEY_COLOR, "black");
396✔
577
    setAttributeIfNotExist(ATTRIBUTE_KEY_ARROW_HEAD, "none");
396✔
578
    setAttributeIfNotExist(ATTRIBUTE_KEY_ARROW_TAIL, "none");
396✔
579
    setAttributeIfNotExist(ATTRIBUTE_KEY_MARGIN, "0,0");
396✔
580
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_NAME, "Times,serif");
396✔
581
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_SIZE, "14");
198✔
582
    const auto splines = getAttribute(ATTRIBUTE_KEY_SPLINES);
198✔
583
    if (splines == SPLINES_LINE) {
198✔
584
        return produceSVGDrawsLine(nodes);
26✔
585
    }
586
    if (splines == SPLINES_SPLINE) {
172✔
587
        return produceSVGDrawsSpline(nodes);
172✔
588
    }
589
    return {};
×
590
}
198✔
591

592
void SVGEdge::setArrowHead() {
80✔
593
    setArrowHead(ARROW_DEFAULT);
80✔
594
}
80✔
595

596
void SVGEdge::setArrowHead(const string_view& shape) {
93✔
597
    setArrowHead(string(shape));
93✔
598
}
93✔
599

600
void SVGEdge::setArrowHead(const string& shape) {
96✔
601
    setAttribute(ATTRIBUTE_KEY_ARROW_HEAD, shape);
96✔
602
}
96✔
603

604
void SVGEdge::setArrowTail() {
18✔
605
    setArrowTail(ARROW_DEFAULT);
18✔
606
}
18✔
607

608
void SVGEdge::setArrowTail(const string_view& shape) {
33✔
609
    setArrowTail(string(shape));
33✔
610
}
33✔
611

612
void SVGEdge::setArrowTail(const string& shape) {
33✔
613
    setAttribute(ATTRIBUTE_KEY_ARROW_TAIL, shape);
33✔
614
}
33✔
615

616
std::pair<double, double> SVGEdge::computeTextCenter(const double cx, const double cy, double dx, double dy) {
111✔
617
    const auto [width, height] = computeTextSizeWithMargin();
111✔
618
    const auto points = vector<pair<double, double>>{
619
        {cx - width / 2, cy - height / 2},
×
620
        {cx - width / 2, cy + height / 2},
×
621
        {cx + width / 2, cy + height / 2},
×
622
        {cx + width / 2, cy - height / 2},
×
623
    };
222✔
624
    const auto d = GeometryUtils::normalize(dx, dy);
111✔
625
    dx = d.first, dy = d.second;
111✔
626
    const double ux = -dy, uy = dx;
111✔
627
    double maxShift = 0.0;
111✔
628
    for (const auto& [x, y] : points) {
555✔
629
        const double totalArea = GeometryUtils::cross(x - cx, y - cy, dx, dy);
444✔
630
        const double unitArea = GeometryUtils::cross(dx, dy, ux, uy);
444✔
631
        const double shift = totalArea / unitArea;
444✔
632
        maxShift = max(maxShift, shift);
444✔
633
    }
634
    return {cx + ux * maxShift, cy + uy * maxShift};
222✔
635
}
111✔
636

637
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDrawsLine(const NodesMapping& nodes) {
105✔
638
    const auto& nodeFrom = nodes.at(_nodeFrom);
105✔
639
    const auto& nodeTo = nodes.at(_nodeTo);
105✔
640
    const auto arrowHeadShape = getAttribute(ATTRIBUTE_KEY_ARROW_HEAD);
105✔
641
    const auto arrowTailShape = getAttribute(ATTRIBUTE_KEY_ARROW_TAIL);
105✔
642
    vector<unique_ptr<SVGDraw>> svgDraws;
105✔
643
    vector<unique_ptr<SVGDraw>> svgDrawArrows;
105✔
644
    vector<pair<double, double>> points;
105✔
645
    if (_connectionPoints.empty()) {
105✔
646
        const double angleFrom = nodeFrom->computeAngle(nodeTo->center());
89✔
647
        const double angleTo = nodeTo->computeAngle(nodeFrom->center());
89✔
648
        points.emplace_back(addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom));
89✔
649
        points.emplace_back(addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo));
89✔
650
    } else {
651
        const double angleFrom = nodeFrom->computeAngle(_connectionPoints[0]);
16✔
652
        points.emplace_back(addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom));
16✔
653
        for (const auto& [x, y] : _connectionPoints) {
42✔
654
            points.emplace_back(x, y);
26✔
655
        }
656
        const size_t n = _connectionPoints.size();
16✔
657
        const double angleTo = nodeTo->computeAngle(_connectionPoints[n - 1]);
16✔
658
        points.emplace_back(addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo));
16✔
659
    }
660
    for (size_t i = 0; i + 1 < points.size(); ++i) {
236✔
661
        const auto& [x1, y1] = points[i];
131✔
662
        const auto& [x2, y2] = points[i + 1];
131✔
663
        svgDraws.emplace_back(make_unique<SVGDrawLine>(x1, y1, x2, y2));
131✔
664
    }
665
    const auto strokeWidth = penWidth();
105✔
666
    for (const auto& line : svgDraws) {
236✔
667
        const auto& draw = dynamic_cast<SVGDrawLine*>(line.get());
131✔
668
        draw->setStroke(color());
131✔
669
        if (strokeWidth != 1.0) {
131✔
670
            draw->setStrokeWidth(strokeWidth);
7✔
671
        }
672
    }
673
    for (auto& arrow : svgDrawArrows) {
167✔
674
        svgDraws.emplace_back(std::move(arrow));
62✔
675
    }
676
    if (const auto label = getAttribute(ATTRIBUTE_KEY_LABEL); !label.empty()) {
105✔
677
        double totalLength = 0.0;
30✔
678
        for (size_t i = 0; i + 1 < points.size(); ++i) {
66✔
679
            const auto& [x1, y1] = points[i];
36✔
680
            const auto& [x2, y2] = points[i + 1];
36✔
681
            totalLength += GeometryUtils::distance(x1, y1, x2, y2);
36✔
682
        }
683
        const double halfLength = totalLength / 2.0;
30✔
684
        double sumLength = 0.0;
30✔
685
        size_t index = 0;
30✔
686
        double lineX = 0.0, lineY = 0.0;
30✔
687
        for (size_t i = 0; i + 1 < points.size(); ++i) {
34✔
688
            const auto& [x1, y1] = points[i];
34✔
689
            const auto& [x2, y2] = points[i + 1];
34✔
690
            const double length = GeometryUtils::distance(x1, y1, x2, y2);
34✔
691
            const double nextSum = sumLength + length;
34✔
692
            if (nextSum > halfLength) {
34✔
693
                index = i;
30✔
694
                const double ratio = (halfLength - sumLength) / length;
30✔
695
                lineX = x1 + ratio * (x2 - x1);
30✔
696
                lineY = y1 + ratio * (y2 - y1);
30✔
697
                break;
30✔
698
            }
699
            sumLength = nextSum;
4✔
700
        }
701
        const double dx = points[index + 1].first - points[index].first;
30✔
702
        const double dy = points[index + 1].second - points[index].second;
30✔
703
        const auto [cx, cy] = computeTextCenter(lineX, lineY, dx, dy);
30✔
704
        appendSVGDrawsLabelWithCenter(svgDraws, cx, cy);
30✔
705
    }
105✔
706
    return svgDraws;
210✔
707
}
105✔
708

709
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDrawsSpline(const NodesMapping& nodes) {
172✔
710
    static constexpr double NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS = 100;
711
    constexpr double SPLINE_LENGTH_APPROXIMATION_STEP = 1.0 / NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS;
172✔
712
    if (_connectionPoints.empty()) {
172✔
713
        return produceSVGDrawsLine(nodes);
79✔
714
    }
715
    const auto& nodeFrom = nodes.at(_nodeFrom);
93✔
716
    const auto& nodeTo = nodes.at(_nodeTo);
93✔
717
    const auto arrowHeadShape = getAttribute(ATTRIBUTE_KEY_ARROW_HEAD);
93✔
718
    const auto arrowTailShape = getAttribute(ATTRIBUTE_KEY_ARROW_TAIL);
93✔
719
    vector<unique_ptr<SVGDraw>> svgDraws;
93✔
720
    vector<unique_ptr<SVGDraw>> svgDrawArrows;
93✔
721
    const auto strokeWidth = penWidth();
93✔
722
    vector<pair<double, double>> points;
93✔
723
    const double angleFrom = nodeFrom->computeAngle(_connectionPoints[0]);
93✔
724
    auto [sx, sy] = addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom);
93✔
725
    points.emplace_back(sx, sy);
93✔
726
    points.emplace_back(sx, sy);
93✔
727
    for (const auto& [x, y] : _connectionPoints) {
230✔
728
        points.emplace_back(x, y);
137✔
729
    }
730
    const double angleTo = nodeTo->computeAngle(_connectionPoints[_connectionPoints.size() - 1]);
93✔
731
    auto [ex, ey] = addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo);
93✔
732
    points.emplace_back(ex, ey);
93✔
733
    points.emplace_back(ex, ey);
93✔
734
    auto d = format("M {} {}", points[1].first, points[1].second);
93✔
735
    vector<vector<pair<double, double>>> splines;
93✔
736
    for (int i = 1; i + 2 < static_cast<int>(points.size()); ++i) {
323✔
737
        const auto [x0, y0] = points[i - 1];
230✔
738
        const auto [x1, y1] = points[i];
230✔
739
        const auto [x2, y2] = points[i + 1];
230✔
740
        const auto [x3, y3] = points[i + 2];
230✔
741
        const double c1x = x1 + (x2 - x0) / 6.0;
230✔
742
        const double c1y = y1 + (y2 - y0) / 6.0;
230✔
743
        const double c2x = x2 - (x3 - x1) / 6.0;
230✔
744
        const double c2y = y2 - (y3 - y1) / 6.0;
230✔
745
        d += format(" C {} {} {} {} {} {}", c1x, c1y, c2x, c2y, x2, y2);
230✔
746
        splines.emplace_back(vector{points[i], {c1x, c1y}, {c2x, c2y}, points[i + 1]});
690✔
747
    }
748
    if (enabledDebug()) {
93✔
749
        for (const auto& spline : splines) {
16✔
750
            auto line1 = make_unique<SVGDrawLine>(spline[0].first, spline[0].second, spline[1].first, spline[1].second);
11✔
751
            auto line2 = make_unique<SVGDrawLine>(spline[2].first, spline[2].second, spline[3].first, spline[3].second);
11✔
752
            line1->setStroke("blue");
22✔
753
            line2->setStroke("blue");
22✔
754
            svgDraws.emplace_back(std::move(line1));
11✔
755
            svgDraws.emplace_back(std::move(line2));
11✔
756
        }
11✔
757
    }
758
    auto path = make_unique<SVGDrawPath>(d);
93✔
759
    path->setStroke(color());
93✔
760
    path->setFill("none");
186✔
761
    if (strokeWidth != 1.0) {
93✔
762
        path->setStrokeWidth(strokeWidth);
4✔
763
    }
764
    svgDraws.emplace_back(std::move(path));
93✔
765
    for (auto& arrow : svgDrawArrows) {
195✔
766
        svgDraws.emplace_back(std::move(arrow));
102✔
767
    }
768
    if (const auto label = getAttribute(ATTRIBUTE_KEY_LABEL); !label.empty()) {
93✔
769
        double totalLength = 0.0;
81✔
770
        vector<double> lengths(splines.size());
81✔
771
        for (size_t i = 0; i < splines.size(); ++i) {
279✔
772
            lengths[i] = GeometryUtils::computeBezierLength(splines[i][0], splines[i][1], splines[i][2], splines[i][3]);
198✔
773
            totalLength += lengths[i];
198✔
774
        }
775
        const double halfLength = totalLength / 2.0;
81✔
776
        double sumLength = 0.0;
81✔
777
        double splineX = 0.0, splineY = 0.0;
81✔
778
        double dx = 0.0, dy = 0.0;
81✔
779
        for (size_t i = 0; i < splines.size(); ++i) {
146✔
780
            const double nextSum = sumLength + lengths[i];
146✔
781
            if (nextSum > halfLength) {
146✔
782
                const double targetLength = halfLength - sumLength;
81✔
783
                auto [x1, y1] = splines[i][0];
81✔
784
                double totalSegmentLength = 0.0;
81✔
785
                for (int j = 1; j < NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS; ++j) {
3,496✔
786
                    const double t = j * SPLINE_LENGTH_APPROXIMATION_STEP;
3,496✔
787
                    const auto [x2, y2] = GeometryUtils::computeBezierAt(splines[i], t);
3,496✔
788
                    totalSegmentLength += GeometryUtils::distance(x1, y1, x2, y2);
3,496✔
789
                    if (j + 1 == NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS || totalSegmentLength > targetLength - GeometryUtils::EPSILON) {
3,496✔
790
                        const double tMid = t - SPLINE_LENGTH_APPROXIMATION_STEP * 0.5;
81✔
791
                        auto point = GeometryUtils::computeBezierAt(splines[i], tMid);
81✔
792
                        splineX = point.first, splineY = point.second;
81✔
793
                        point = GeometryUtils::computeBezierDerivative(splines[i], tMid);
81✔
794
                        dx = point.first, dy = point.second;
81✔
795
                        break;
81✔
796
                    }
797
                    x1 = x2;
3,415✔
798
                    y1 = y2;
3,415✔
799
                }
800
                break;
81✔
801
            }
802
            sumLength = nextSum;
65✔
803
        }
804
        const auto [cx, cy] = computeTextCenter(splineX, splineY, dx, dy);
81✔
805
        appendSVGDrawsLabelWithCenter(svgDraws, cx, cy);
81✔
806
    }
174✔
807
    return svgDraws;
93✔
808
}
93✔
809

810
double SVGEdge::computeArrowTipMargin(const string_view& shape) const {
396✔
811
    if (shape == ARROW_NORMAL || shape == ARROW_EMPTY) {
396✔
812
        return computeArrowTipMarginNormal();
164✔
813
    }
814
    return 0.0;
232✔
815
}
816

817
double SVGEdge::computeArrowTipMarginNormal() const {
164✔
818
    const double angle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
164✔
819
    const double strokeWidth = penWidth();
164✔
820
    const double margin = strokeWidth / 2.0 / sin(angle);
164✔
821
    return margin;
164✔
822
}
823

824
pair<double, double> SVGEdge::addArrow(const string_view& shape, vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle) const {
396✔
825
    const double arrowTipMargin = computeArrowTipMargin(shape);
396✔
826
    const pair arrowTip = {connectionPoint.first + arrowTipMargin * cos(angle), connectionPoint.second + arrowTipMargin * sin(angle)};
396✔
827
    if (shape == ARROW_NORMAL) {
396✔
828
        return addArrowNormal(svgDraws, arrowTip, angle, true);
148✔
829
    }
830
    if (shape == ARROW_EMPTY) {
248✔
831
        return addArrowNormal(svgDraws, arrowTip, angle, false);
16✔
832
    }
833
    return {connectionPoint.first - 0.2 * cos(angle), connectionPoint.second - 0.2 * sin(angle)};
232✔
834
}
835

836
pair<double, double> SVGEdge::addArrowNormal(vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle, const bool solid) const {
164✔
837
    const double x0 = connectionPoint.first;
164✔
838
    const double y0 = connectionPoint.second;
164✔
839
    const double sideLen = GeometryUtils::distance(ARROW_WIDTH, ARROW_HALF_HEIGHT);
164✔
840
    const double halfAngle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
164✔
841
    const double x1 = x0 + sideLen * cos(angle - halfAngle);
164✔
842
    const double y1 = y0 + sideLen * sin(angle - halfAngle);
164✔
843
    const double x2 = x0 + sideLen * cos(angle + halfAngle);
164✔
844
    const double y2 = y0 + sideLen * sin(angle + halfAngle);
164✔
845
    auto polygon = make_unique<SVGDrawPolygon>(vector<pair<double, double>>{{x0, y0}, {x1, y1}, {x2, y2}, {x0, y0}});
328✔
846
    polygon->setStroke(color());
164✔
847
    if (solid) {
164✔
848
        polygon->setFill(color());
148✔
849
    } else {
850
        polygon->setFill("none");
48✔
851
    }
852
    if (const double strokeWidth = penWidth(); strokeWidth != 1.0) {
164✔
853
        polygon->setStrokeWidth(strokeWidth);
14✔
854
    }
855
    svgDraws.emplace_back(std::move(polygon));
164✔
856
    return {x0 + ARROW_WIDTH * cos(angle), y0 + ARROW_WIDTH * sin(angle)};
328✔
857
}
164✔
858

859
void SVGGraph::addNode(shared_ptr<SVGNode>& node) {
199✔
860
    node->setParent(this);
199✔
861
    _nodes.emplace_back(node);
199✔
862
}
199✔
863

864
void SVGGraph::addEdge(shared_ptr<SVGEdge>& edge) {
174✔
865
    edge->setParent(this);
174✔
866
    _edges.emplace_back(edge);
174✔
867
}
174✔
868

869
void SVGGraph::addSubgraph(shared_ptr<SVGGraph>& subgraph) {
15✔
870
    subgraph->setParent(this);
15✔
871
    _graphs.emplace_back(subgraph);
15✔
872
}
15✔
873

874
void SVGGraph::removeNode(const SVGNode* node) {
9✔
875
    for (int i = 0; i < static_cast<int>(_nodes.size()); ++i) {
9✔
876
        if (_nodes[i].get() == node) {
4✔
877
            _nodes.erase(_nodes.begin() + i);  // Need to keep the original order
4✔
878
            break;
4✔
879
        }
880
    }
881
}
9✔
882

883
void SVGGraph::removeEdge(const SVGEdge* edge) {
9✔
884
    for (int i = 0; i < static_cast<int>(_edges.size()); ++i) {
15✔
885
        if (_edges[i].get() == edge) {
9✔
886
            _edges.erase(_edges.begin() + i);
3✔
887
            break;
3✔
888
        }
889
    }
890
}
9✔
891

892
void SVGGraph::removeSubgraph(const SVGGraph* subgraph) {
9✔
893
    for (int i = 0; i < static_cast<int>(_graphs.size()); ++i) {
16✔
894
        if (_graphs[i].get() == subgraph) {
9✔
895
            _graphs.erase(_graphs.begin() + i);
2✔
896
            break;
2✔
897
        }
898
    }
899
}
9✔
900

901
void SVGGraph::removeChild(const SVGItem* item) {
9✔
902
    removeNode(dynamic_cast<const SVGNode*>(item));
9✔
903
    removeEdge(dynamic_cast<const SVGEdge*>(item));
9✔
904
    removeSubgraph(dynamic_cast<const SVGGraph*>(item));
9✔
905
}
9✔
906

907
SVGNode& SVGGraph::defaultNodeAttributes() {
20✔
908
    return _defaultNode;
20✔
909
}
910

911
SVGEdge& SVGGraph::defaultEdgeAttributes() {
10✔
912
    return _defaultEdge;
10✔
913
}
914

915
optional<reference_wrapper<const string>> SVGGraph::defaultNodeAttribute(const string_view& key) const {
2,838✔
916
    if (const auto it = _defaultNode.attributes().find(key); it != _defaultNode.attributes().end()) {
2,838✔
917
        return std::ref(it->second);
252✔
918
    }
919
    if (parent() != nullptr) {
2,586✔
920
        return parent()->defaultNodeAttribute(key);
217✔
921
    }
922
    return {};
2,369✔
923
}
924

925
optional<reference_wrapper<const string>> SVGGraph::defaultEdgeAttribute(const string_view& key) const {
1,887✔
926
    if (const auto it = _defaultEdge.attributes().find(key); it != _defaultEdge.attributes().end()) {
1,887✔
927
        return it->second;
117✔
928
    }
929
    if (parent() != nullptr) {
1,770✔
930
        return parent()->defaultEdgeAttribute(key);
145✔
931
    }
932
    return {};
1,625✔
933
}
934

935
pair<double, double> SVGGraph::center() const {
16✔
936
    return {_cx, _cy};
16✔
937
}
938

939
void SVGGraph::adjustNodeSizes() {
149✔
940
    setAttributeIfNotExist(ATTRIBUTE_KEY_MARGIN, format("{}", 8.0 / 72.0));
149✔
941
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_NAME, "Times,serif");
298✔
942
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_SIZE, "14");
298✔
943
    double minX = 0.0, minY = 0.0, maxX = 0.0, maxY = 0.0;
149✔
944
    bool first = true;
149✔
945
    const auto updateGraphSize = [&](const double cx, const double cy, const double width, const double height) {
272✔
946
        const double x1 = cx - width / 2.0;
272✔
947
        const double y1 = cy - height / 2.0;
272✔
948
        const double x2 = cx + width / 2.0;
272✔
949
        const double y2 = cy + height / 2.0;
272✔
950
        if (first) {
272✔
951
            first = false;
120✔
952
            minX = x1; minY = y1;
120✔
953
            maxX = x2; maxY = y2;
120✔
954
        } else {
955
            minX = min(minX, x1); minY = min(minY, y1);
152✔
956
            maxX = max(maxX, x2); maxY = max(maxY, y2);
152✔
957
        }
958
    };
421✔
959
    for (const auto& node : _nodes) {
405✔
960
        node->adjustNodeSize();
256✔
961
        const auto [cx, cy] = node->center();
256✔
962
        const auto strokeWidth = node->penWidth();
256✔
963
        const auto width = node->width() + strokeWidth;
256✔
964
        const auto height = node->height() + strokeWidth;
256✔
965
        updateGraphSize(cx, cy, width, height);
256✔
966
    }
967
    for (const auto& graph : _graphs) {
165✔
968
        graph->adjustNodeSizes();
16✔
969
        const auto [cx, cy] = graph->center();
16✔
970
        const auto strokeWidth = graph->penWidth();
16✔
971
        const auto width = graph->width() + strokeWidth;
16✔
972
        const auto height = graph->height() + strokeWidth;
16✔
973
        updateGraphSize(cx, cy, width, height);
16✔
974
    }
975
    if (first) {
149✔
976
        setSize(-1, -1);
29✔
977
    } else {
978
        const auto [marginWidth, marginHeight] = margin();
120✔
979
        if (const auto label = getAttribute(ATTRIBUTE_KEY_LABEL); !label.empty()) {
120✔
980
            auto [textWidth, textHeight] = computeTextSize();
10✔
981
            textWidth += marginWidth * 2;
10✔
982
            if (textWidth > maxX - minX) {
10✔
983
                const double cx = (minX + maxX) / 2.0;
3✔
984
                minX = cx - textWidth / 2.0;
3✔
985
                maxX = cx + textWidth / 2.0;
3✔
986
            }
987
            minY -= textHeight + marginHeight;
10✔
988
            _textY = minY + textHeight / 2.0;
10✔
989
        }
120✔
990
        minX -= marginWidth; minY -= marginHeight;
120✔
991
        maxX += marginWidth; maxY += marginHeight;
120✔
992
        const auto width = maxX - minX;
120✔
993
        const auto height = maxY - minY;
120✔
994
        _cx = (minX + maxX) / 2.0;
120✔
995
        _cy = (minY + maxY) / 2.0;
120✔
996
        setSize(width, height);
120✔
997
    }
998
}
149✔
999

1000
vector<unique_ptr<SVGDraw>> SVGGraph::produceSVGDraws(const NodesMapping &nodes) {
133✔
1001
    adjustNodeSizes();
133✔
1002
    vector<unique_ptr<SVGDraw>> svgDraws;
133✔
1003
    for (auto& draw : produceClusterSVGDraws()) {
163✔
1004
        svgDraws.emplace_back(std::move(draw));
30✔
1005
    }
133✔
1006
    for (auto& draw : produceNodeSVGDraws()) {
645✔
1007
        svgDraws.emplace_back(std::move(draw));
512✔
1008
    }
133✔
1009
    for (auto& draw : produceEdgeSVGDraws(nodes)) {
529✔
1010
        svgDraws.emplace_back(std::move(draw));
396✔
1011
    }
133✔
1012
    return svgDraws;
133✔
1013
}
×
1014

1015
vector<shared_ptr<SVGNode>> SVGGraph::findNodes() const {
149✔
1016
    vector<shared_ptr<SVGNode>> nodes = _nodes;
149✔
1017
    for (auto& graph : _graphs) {
165✔
1018
        for (auto& node : graph->findNodes()) {
41✔
1019
            nodes.emplace_back(std::move(node));
25✔
1020
        }
16✔
1021
    }
1022
    return nodes;
149✔
1023
}
×
1024

1025
vector<unique_ptr<SVGDraw>> SVGGraph::produceNodeSVGDraws() const {
149✔
1026
    vector<unique_ptr<SVGDraw>> svgDraws;
149✔
1027
    for (const auto& node : _nodes) {
405✔
1028
        if (enabledDebug()) {
256✔
1029
            node->enableDebug();
57✔
1030
        }
1031
        const auto& id = node->id();
256✔
1032
        svgDraws.emplace_back(make_unique<SVGDrawComment>(format("Node: {}", id)));
256✔
1033
        auto group = make_unique<SVGDrawGroup>();
256✔
1034
        group->setAttribute("id", id);
256✔
1035
        group->setAttribute("class", "node");
512✔
1036
        group->addChild(make_unique<SVGDrawTitle>(id));
256✔
1037
        auto subDraws = node->produceSVGDraws();
256✔
1038
        group->addChildren(subDraws);
256✔
1039
        svgDraws.emplace_back(std::move(group));
256✔
1040
    }
256✔
1041
    for (const auto& graph : _graphs) {
165✔
1042
        for (auto subDraws = graph->produceNodeSVGDraws(); auto& subDraw : subDraws) {
66✔
1043
            svgDraws.emplace_back(std::move(subDraw));
50✔
1044
        }
16✔
1045
    }
1046
    return svgDraws;
149✔
1047
}
×
1048

1049
vector<unique_ptr<SVGDraw>> SVGGraph::produceEdgeSVGDraws(const NodesMapping& nodes) const {
149✔
1050
    vector<unique_ptr<SVGDraw>> svgDraws;
149✔
1051
    for (auto& edge : _edges) {
347✔
1052
        if (enabledDebug()) {
198✔
1053
            edge->enableDebug();
12✔
1054
        }
1055
        const auto& id = edge->id();
198✔
1056
        svgDraws.emplace_back(make_unique<SVGDrawComment>(format("Edge: {} ({} -> {})", id, edge->nodeFrom(), edge->nodeTo())));
198✔
1057
        auto group = make_unique<SVGDrawGroup>();
198✔
1058
        group->setAttribute("id", id);
198✔
1059
        group->setAttribute("class", "edge");
396✔
1060
        group->addChild(make_unique<SVGDrawTitle>(format("{}->{}", edge->nodeFrom(), edge->nodeTo())));
198✔
1061
        auto subDraws = edge->produceSVGDraws(nodes);
198✔
1062
        group->addChildren(subDraws);
198✔
1063
        svgDraws.emplace_back(std::move(group));
198✔
1064
    }
198✔
1065
    for (auto& graph : _graphs) {
165✔
1066
        for (auto subDraws = graph->produceEdgeSVGDraws(nodes); auto& subDraw : subDraws) {
44✔
1067
            svgDraws.emplace_back(std::move(subDraw));
28✔
1068
        }
16✔
1069
    }
1070
    return svgDraws;
149✔
1071
}
×
1072

1073
std::vector<std::unique_ptr<SVGDraw>> SVGGraph::produceClusterSVGDraws() {
149✔
1074
    vector<unique_ptr<SVGDraw>> svgDraws;
149✔
1075
    setAttributeIfNotExist(ATTRIBUTE_KEY_COLOR, "none");
298✔
1076
    setAttributeIfNotExist(ATTRIBUTE_KEY_FILL_COLOR, "none");
298✔
1077
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_COLOR, "black");
298✔
1078
    setAttributeIfNotExist(ATTRIBUTE_KEY_PEN_WIDTH, "1.0");
298✔
1079
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_NAME, "Times,serif");
298✔
1080
    setAttributeIfNotExist(ATTRIBUTE_KEY_FONT_SIZE, "14");
298✔
1081
    if (const auto _width = width(), _height = height(); _width > 0 && _height > 0) {
149✔
1082
        const auto& _color = color();
120✔
1083
        const auto& _fillColor = fillColor();
120✔
1084
        if (_color != "none" || _fillColor != "none") {
120✔
1085
            auto group = make_unique<SVGDrawGroup>();
12✔
1086
            group->setAttribute("id", id());
12✔
1087
            group->setAttribute("class", "cluster");
24✔
1088
            auto rect = make_unique<SVGDrawRect>(_cx, _cy, _width, _height);
12✔
1089
            rect->setStroke(color());
12✔
1090
            rect->setFill(fillColor());
12✔
1091
            if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
12✔
1092
                rect->setStrokeWidth(strokeWidth);
6✔
1093
            }
1094
            group->addChild(std::move(rect));
12✔
1095
            if (enabledDebug()) {
12✔
1096
                const auto [marginX, marginY] = margin();
6✔
1097
                auto childrenRect = make_unique<SVGDrawRect>(_cx, _cy, _width - marginX * 2.0, _height - marginY * 2.0);
6✔
1098
                childrenRect->setFill("none");
12✔
1099
                childrenRect->setStroke("green");
12✔
1100
                group->addChild(std::move(childrenRect));
6✔
1101
            }
6✔
1102
            svgDraws.emplace_back(std::move(group));
12✔
1103
        }
12✔
1104
        if (const auto& _label = getAttribute(ATTRIBUTE_KEY_LABEL); !_label.empty()) {
120✔
1105
            appendSVGDrawsLabelWithCenter(svgDraws, _cx, _textY);
10✔
1106
        }
1107
    }
120✔
1108
    for (const auto& graph : _graphs) {
165✔
1109
        if (enabledDebug()) {
16✔
1110
            graph->enableDebug();
8✔
1111
        }
1112
        for (auto subDraws = graph->produceClusterSVGDraws(); auto& subDraw : subDraws) {
52✔
1113
            svgDraws.emplace_back(std::move(subDraw));
36✔
1114
        }
16✔
1115
    }
1116
    return svgDraws;
149✔
1117
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc