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

CyberZHG / SVGDiagram / 20017347916

08 Dec 2025 05:07AM UTC coverage: 96.9% (+0.8%) from 96.062%
20017347916

Pull #1

github

web-flow
Merge 640c6ea88 into 678dd23b1
Pull Request #1: Add text label for lines

113 of 117 new or added lines in 2 files covered. (96.58%)

1313 of 1355 relevant lines covered (96.9%)

979.05 hits per line

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

96.24
/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
void SVGItem::setAttribute(const string_view& key, const string& value) {
759✔
13
    _attributes[key] = value;
759✔
14
}
759✔
15

16
void SVGItem::setAttributeIfNotExist(const string_view& key, const string& value) {
2,468✔
17
    if (!_attributes.contains(key)) {
2,468✔
18
        _attributes[key] = value;
1,207✔
19
    }
20
}
2,468✔
21

22
const string& SVGItem::getAttribute(const string_view& key) const {
4,168✔
23
    static const string EMPTY_STRING;
4,168✔
24
    if (const auto it = _attributes.find(key); it != _attributes.end()) {
4,168✔
25
        return it->second;
3,952✔
26
    }
27
    return EMPTY_STRING;
216✔
28
}
29

30
void SVGItem::setPrecomputedTextSize(const double width, const double height) {
46✔
31
    _precomputedTextWidth = width;
46✔
32
    _precomputedTextHeight = height;
46✔
33
}
46✔
34

35
std::pair<double, double> SVGItem::precomputedTextSize() const {
180✔
36
    return {_precomputedTextWidth, _precomputedTextHeight};
180✔
37
}
38

39
void SVGItem::setLabel(const string& label) {
110✔
40
    setAttribute(DOT_ATTR_KEY_LABEL, label);
110✔
41
}
110✔
42

43
void SVGItem::setMargin(const string& value) {
61✔
44
    setAttribute(DOT_ATTR_KEY_MARGIN, value);
61✔
45
}
61✔
46

47
void SVGItem::setMargin(const double margin) {
10✔
48
    setMargin(format("{}", margin));
10✔
49
}
10✔
50

51
void SVGItem::setMargin(const double marginX, const double marginY) {
51✔
52
    setMargin(format("{},{}", marginX, marginY));
51✔
53
}
51✔
54

55
void SVGItem::setMarginInPoints(const double margin) {
10✔
56
    setMargin(margin / SVG_DEFAULT_DPI);
10✔
57
}
10✔
58

59
void SVGItem::setMarginInPoints(const double marginX, const double marginY) {
51✔
60
    setMargin(marginX / SVG_DEFAULT_DPI, marginY / SVG_DEFAULT_DPI);
51✔
61
}
51✔
62

63
void SVGItem::setColor(const string& color) {
31✔
64
    setAttribute(DOT_ATTR_KEY_COLOR, color);
31✔
65
}
31✔
66

67
void SVGItem::setFillColor(const string& color) {
11✔
68
    setAttribute(DOT_ATTR_KEY_FILL_COLOR, color);
11✔
69
}
11✔
70

71
void SVGItem::setFontColor(const string& color) {
27✔
72
    setAttribute(DOT_ATTR_KEY_FONT_COLOR, color);
27✔
73
}
27✔
74

75
void SVGItem::setPenWidth(const double width) {
11✔
76
    setAttribute(DOT_ATTR_KEY_PEN_WIDTH, format("{}", width));
11✔
77
}
11✔
78

79
double SVGItem::penWidth() const {
534✔
80
    if (const auto it = _attributes.find(DOT_ATTR_KEY_PEN_WIDTH); it != _attributes.end()) {
534✔
81
        const auto width = AttributeUtils::pointToSVGPixel(AttributeUtils::parseLengthToInch(it->second));
534✔
82
        if (fabs(width - 1.0) < GeometryUtils::EPSILON) {
534✔
83
            return 1.0;
534✔
84
        }
85
        return width;
397✔
86
    }
87
    return 1.0;
×
88
}
89

90
void SVGItem::appendSVGDrawsLabelWithCenter(vector<unique_ptr<SVGDraw>>& svgDraws, const double cx, const double cy) {
158✔
91
    if (enabledDebug()) {
158✔
92
        const auto [textWidth, textHeight] = computeTextSize();
22✔
93
        const auto [marginX, marginY] = computeMarginInPoints();
22✔
94
        auto textRect = make_unique<SVGDrawRect>(cx, cy, textWidth, textHeight);
22✔
95
        textRect->setFill("none");
44✔
96
        textRect->setStroke("blue");
44✔
97
        svgDraws.emplace_back(std::move(textRect));
22✔
98
        auto marginRect = make_unique<SVGDrawRect>(cx, cy, textWidth + marginX * 2, textHeight + marginY * 2);
22✔
99
        marginRect->setFill("none");
44✔
100
        marginRect->setStroke("red");
44✔
101
        svgDraws.emplace_back(std::move(marginRect));
22✔
102
    }
22✔
103
    if (const auto label = getAttribute(DOT_ATTR_KEY_LABEL); !label.empty()) {
158✔
104
        auto draw = make_unique<SVGDrawText>(cx, cy, label);
156✔
105
        if (const auto& fontColor = getAttribute(DOT_ATTR_KEY_FONT_COLOR); fontColor != "black") {
156✔
106
            draw->setFill(fontColor);
30✔
107
        }
108
        svgDraws.emplace_back(std::move(draw));
156✔
109
    }
314✔
110
}
158✔
111

112
pair<double, double> SVGItem::computeTextSize() {
180✔
113
    if (const auto [precomputedTextWidth, precomputedTextHeight] = precomputedTextSize();
180✔
114
        precomputedTextWidth > 0 && precomputedTextHeight > 0) {
180✔
115
        return {precomputedTextWidth, precomputedTextHeight};
112✔
116
        }
117
    const SVGTextSize textSize;
68✔
118
    const auto label = getAttribute(DOT_ATTR_KEY_LABEL);
68✔
119
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_NAME, "Times,serif");
136✔
120
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_SIZE, "14");
68✔
121
    const double fontSize = stod(getAttribute(DOT_ATTR_KEY_FONT_SIZE));
68✔
122
    const string fontFamily = getAttribute(DOT_ATTR_KEY_FONT_NAME);
68✔
123
    auto [width, height] = textSize.computeTextSize(label, fontSize, fontFamily);
68✔
124
    if (width == 0.0) {
68✔
125
        width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
4✔
126
    }
127
    if (height == 0.0) {
68✔
128
        height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
4✔
129
    }
130
    return {width, height};
68✔
131
}
68✔
132

133
pair<double, double> SVGItem::computeMarginInPoints() {
180✔
134
    setAttributeIfNotExist(DOT_ATTR_KEY_MARGIN, "0.11,0.055");
180✔
135
    const auto margin = getAttribute(DOT_ATTR_KEY_MARGIN);
180✔
136
    return AttributeUtils::parseMarginToPixels(margin);
360✔
137
}
180✔
138

139
std::pair<double, double> SVGItem::computeTextSizeWithMargin() {
134✔
140
    const auto [width, height] = computeTextSize();
134✔
141
    const auto [marginX, marginY] = computeMarginInPoints();
134✔
142
    return {width + marginX * 2, height + marginY * 2};
134✔
143
}
144

145
void SVGItem::enableDebug() {
22✔
146
    _enabledDebug = true;
22✔
147
}
22✔
148

149
bool SVGItem::enabledDebug() const {
158✔
150
    return _enabledDebug;
158✔
151
}
152

153

154
SVGNode::SVGNode(const double cx, const double cy) {
40✔
155
    _cx = cx;
40✔
156
    _cy = cy;
40✔
157
}
40✔
158

159
void SVGNode::setShape(const string& shape) {
99✔
160
    setAttribute(DOT_ATTR_KEY_SHAPE, shape);
99✔
161
}
99✔
162

163
void SVGNode::setShape(const string_view& shape) {
99✔
164
    setShape(string(shape));
99✔
165
}
99✔
166

167
void SVGNode::setCenterInPoints(const double cx, const double cy) {
54✔
168
    _cx = cx;
54✔
169
    _cy = cy;
54✔
170
}
54✔
171

172
pair<double, double> SVGNode::centerInPoints() const {
124✔
173
    return {_cx, _cy};
124✔
174
}
175

176
void SVGNode::adjustNodeSize() {
144✔
177
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
288✔
178
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
144✔
179
    if (shape == NODE_SHAPE_CIRCLE) {
144✔
180
        adjustNodeSizeCircle();
95✔
181
    } else if (shape == NODE_SHAPE_NONE || shape == NODE_SHAPE_RECT) {
49✔
182
        adjustNodeSizeRect();
25✔
183
    } else if (shape == NODE_SHAPE_ELLIPSE) {
24✔
184
        adjustNodeSizeEllipse();
24✔
185
    }
186
}
144✔
187

188
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
144✔
189
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
288✔
190
    setAttributeIfNotExist(DOT_ATTR_KEY_COLOR, "black");
288✔
191
    setAttributeIfNotExist(DOT_ATTR_KEY_FILL_COLOR, "none");
288✔
192
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_COLOR, "black");
288✔
193
    setAttributeIfNotExist(DOT_ATTR_KEY_PEN_WIDTH, format("{}", AttributeUtils::svgPixelToPoint(1.0)));
144✔
194
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
144✔
195
    if (shape == NODE_SHAPE_NONE) {
144✔
196
        return produceSVGDrawsNone();
1✔
197
    }
198
    if (shape == NODE_SHAPE_CIRCLE) {
143✔
199
        return produceSVGDrawsCircle();
95✔
200
    }
201
    if (shape == NODE_SHAPE_RECT) {
48✔
202
        return produceSVGDrawsRect();
24✔
203
    }
204
    if (shape == NODE_SHAPE_ELLIPSE) {
24✔
205
        return produceSVGDrawsEllipse();
24✔
206
    }
207
    return {};
×
208
}
144✔
209

210
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
322✔
211
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
644✔
212
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
322✔
213
    if (shape == NODE_SHAPE_CIRCLE) {
322✔
214
        return computeConnectionPointCircle(angle);
98✔
215
    }
216
    if (shape == NODE_SHAPE_RECT) {
224✔
217
        return computeConnectionPointRect(angle);
112✔
218
    }
219
    if (shape == NODE_SHAPE_ELLIPSE) {
112✔
220
        return computeConnectionPointEllipse(angle);
112✔
221
    }
222
    return {0.0, 0.0};
×
223
}
322✔
224

225
/*
226
pair<double, double> SVGNode::computeConnectionPoint(const double x1, const double y1, const double x2, const double y2) {
227
    return computeConnectionPoint(atan2(y2 - y1, x2 - x1));
228
}
229

230
pair<double, double> SVGNode::computeConnectionPoint(const double x, const double y) {
231
    return computeConnectionPoint(_cx, _cy, x, y);
232
}
233

234
pair<double, double> SVGNode::computeConnectionPoint(const pair<double, double>& p) {
235
    return computeConnectionPoint(p.first, p.second);
236
}
237
*/
238

239
double SVGNode::computeAngle(const double x, const double y) const {
322✔
240
    return atan2(y - _cy, x - _cx);
322✔
241
}
242

243
double SVGNode::computeAngle(const pair<double, double>& p) const {
322✔
244
    return computeAngle(p.first, p.second);
322✔
245
}
246

247
bool SVGNode::isFixedSize() const {
144✔
248
    return AttributeUtils::parseBool(getAttribute(DOT_ATTR_KEY_FIXED_SIZE));
144✔
249
}
250

251
void SVGNode::updateNodeSize(const double width, const double height) {
144✔
252
    const auto widthString = format("{}", width);
144✔
253
    const auto heightString = format("{}", height);
144✔
254
    if (isFixedSize()) {
144✔
255
        setAttributeIfNotExist(DOT_ATTR_KEY_WIDTH, widthString);
×
256
        setAttributeIfNotExist(DOT_ATTR_KEY_HEIGHT, heightString);
×
257
    } else {
258
        setAttribute(DOT_ATTR_KEY_WIDTH, widthString);
144✔
259
        setAttribute(DOT_ATTR_KEY_HEIGHT, heightString);
144✔
260
    }
261
}
144✔
262

263
void SVGNode::updateNodeSize(const pair<double, double>& size) {
25✔
264
    updateNodeSize(size.first, size.second);
25✔
265
}
25✔
266

267
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
144✔
268
    appendSVGDrawsLabelWithCenter(svgDraws, _cx, _cy);
144✔
269
}
144✔
270

271
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
1✔
272
    vector<unique_ptr<SVGDraw>> svgDraws;
1✔
273
    appendSVGDrawsLabel(svgDraws);
1✔
274
    return svgDraws;
1✔
275
}
×
276

277
void SVGNode::adjustNodeSizeCircle() {
95✔
278
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
95✔
279
    updateNodeSize(diameter, diameter);
95✔
280
}
95✔
281

282
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
95✔
283
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
95✔
284
    const double height = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
95✔
285
    vector<unique_ptr<SVGDraw>> svgDraws;
95✔
286
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width, height) / 2.0);
95✔
287
    circle->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
95✔
288
    circle->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
95✔
289
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
95✔
290
        circle->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
×
291
    }
292
    svgDraws.emplace_back(std::move(circle));
95✔
293
    appendSVGDrawsLabel(svgDraws);
95✔
294
    return svgDraws;
190✔
295
}
95✔
296

297
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
98✔
298
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
98✔
299
    const double penWidth = stod(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
98✔
300
    const double radius = (width + penWidth) / 2.0;
98✔
301
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
98✔
302
}
303

304
void SVGNode::adjustNodeSizeRect() {
25✔
305
    updateNodeSize(computeTextSizeWithMargin());
25✔
306
}
25✔
307

308
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
24✔
309
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
24✔
310
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT));
24✔
311
    vector<unique_ptr<SVGDraw>> svgDraws;
24✔
312
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width, height);
24✔
313
    rect->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
24✔
314
    rect->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
24✔
315
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
24✔
316
        rect->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
×
317
    }
318
    svgDraws.emplace_back(std::move(rect));
24✔
319
    appendSVGDrawsLabel(svgDraws);
24✔
320
    return svgDraws;
48✔
321
}
24✔
322

323
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
112✔
324
    const double strokeWidth = stod(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
112✔
325
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH)) + strokeWidth;
112✔
326
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT)) + strokeWidth;
112✔
327
    double x1 = -width / 2, y1 = -height / 2;
112✔
328
    double x2 = width / 2, y2 = height / 2;
112✔
329
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
224✔
330
    for (const auto& [x, y] : vertices) {
560✔
331
        if (GeometryUtils::isSameAngle(angle, x, y)) {
448✔
332
            return {_cx + x, _cy + y};
×
333
        }
334
    }
335
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
262✔
336
        x1 = vertices[i].first;
262✔
337
        y1 = vertices[i].second;
262✔
338
        x2 = vertices[(i + 1) % vertices.size()].first;
262✔
339
        y2 = vertices[(i + 1) % vertices.size()].second;
262✔
340
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
262✔
341
            return {_cx + intersect.value().first, _cy + intersect.value().second};
112✔
342
        }
343
    }
344
    return {_cx, _cy};
×
345
}
112✔
346

347
void SVGNode::adjustNodeSizeEllipse() {
24✔
348
    const auto [textWidth, textHeight] = computeTextSize();
24✔
349
    const auto [marginX, marginY] = computeMarginInPoints();
24✔
350
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
24✔
351
}
24✔
352

353
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
24✔
354
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
24✔
355
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT));
24✔
356
    vector<unique_ptr<SVGDraw>> svgDraws;
24✔
357
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width, height);
24✔
358
    ellipse->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
24✔
359
    ellipse->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
24✔
360
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
24✔
361
        ellipse->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
6✔
362
    }
363
    svgDraws.emplace_back(std::move(ellipse));
24✔
364
    appendSVGDrawsLabel(svgDraws);
24✔
365
    return svgDraws;
48✔
366
}
24✔
367

368
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
112✔
369
    const double strokeWidth = stod(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
112✔
370
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH)) + strokeWidth;
112✔
371
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT)) + strokeWidth;
112✔
372
    const double rx = width / 2, ry = height / 2;
112✔
373
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
112✔
374
    const double x = rx * ry * cos(angle) / base;
112✔
375
    const double y = rx * ry * sin(angle) / base;
112✔
376
    return {_cx + x, _cy + y};
112✔
377
}
378

379
SVGEdge::SVGEdge(const string& idFrom, const string& idTo) {
21✔
380
    _nodeFrom = idFrom;
21✔
381
    _nodeTo = idTo;
21✔
382
}
21✔
383

384
void SVGEdge::setNodeFrom(const string& id) {
1✔
385
    _nodeFrom = id;
1✔
386
}
1✔
387

388
const string& SVGEdge::nodeFrom() const {
322✔
389
    return _nodeFrom;
322✔
390
}
391

392
void SVGEdge::setNodeTo(const string& id) {
1✔
393
    _nodeTo = id;
1✔
394
}
1✔
395

396
const string& SVGEdge::nodeTo() const {
322✔
397
    return _nodeTo;
322✔
398
}
399

400
void SVGEdge::setConnection(const string& idFrom, const string& idTo) {
117✔
401
    _nodeFrom = idFrom;
117✔
402
    _nodeTo = idTo;
117✔
403
}
117✔
404

405
void SVGEdge::setSplines(const string& value) {
22✔
406
    setAttribute(DOT_ATTR_KEY_SPLINES, value);
22✔
407
}
22✔
408

409
void SVGEdge::setSplines(const string_view& value) {
22✔
410
    setSplines(string(value));
22✔
411
}
22✔
412

413
void SVGEdge::addConnectionPoint(const pair<double, double>& point) {
128✔
414
    _connectionPoints.emplace_back(point);
128✔
415
}
128✔
416

417
void SVGEdge::addConnectionPoint(const double x, const double y) {
128✔
418
    addConnectionPoint({x, y});
128✔
419
}
128✔
420

421
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDraws(const NodesMapping& nodes) {
161✔
422
    setAttributeIfNotExist(DOT_ATTR_KEY_SPLINES, string(EDGE_SPLINES_DEFAULT));
322✔
423
    setAttributeIfNotExist(DOT_ATTR_KEY_COLOR, "black");
322✔
424
    setAttributeIfNotExist(DOT_ATTR_KEY_PEN_WIDTH, "1");
322✔
425
    setAttributeIfNotExist(DOT_ATTR_KEY_ARROW_HEAD, "none");
322✔
426
    setAttributeIfNotExist(DOT_ATTR_KEY_ARROW_TAIL, "none");
322✔
427
    setAttributeIfNotExist(DOT_ATTR_KEY_MARGIN, "0,0");
322✔
428
    const auto splines = getAttribute(DOT_ATTR_KEY_SPLINES);
161✔
429
    if (splines == EDGE_SPLINES_LINE) {
161✔
430
        return produceSVGDrawsLine(nodes);
24✔
431
    }
432
    if (splines == EDGE_SPLINES_SPLINE) {
137✔
433
        return produceSVGDrawsSpline(nodes);
137✔
434
    }
435
    return {};
×
436
}
161✔
437

438
void SVGEdge::setArrowHead() {
83✔
439
    setArrowHead(ARROW_SHAPE_DEFAULT);
83✔
440
}
83✔
441

442
void SVGEdge::setArrowHead(const string_view& shape) {
91✔
443
    setAttribute(DOT_ATTR_KEY_ARROW_HEAD, string(shape));
182✔
444
}
91✔
445

446
void SVGEdge::setArrowTail() {
×
447
    setArrowTail(ARROW_SHAPE_DEFAULT);
×
448
}
×
449

450
void SVGEdge::setArrowTail(const string_view& shape) {
8✔
451
    setAttribute(DOT_ATTR_KEY_ARROW_TAIL, string(shape));
16✔
452
}
8✔
453

454
std::pair<double, double> SVGEdge::computeTextCenter(const double cx, const double cy, double dx, double dy) {
14✔
455
    const auto [width, height] = computeTextSizeWithMargin();
14✔
456
    const auto points = vector<pair<double, double>>{
NEW
457
        {cx - width / 2, cy - height / 2},
×
NEW
458
        {cx - width / 2, cy + height / 2},
×
NEW
459
        {cx + width / 2, cy + height / 2},
×
NEW
460
        {cx + width / 2, cy - height / 2},
×
461
    };
28✔
462
    const auto d = GeometryUtils::normalize(dx, dy);
14✔
463
    dx = d.first, dy = d.second;
14✔
464
    const double ux = -dy, uy = dx;
14✔
465
    double maxShift = 0.0;
14✔
466
    for (const auto& [x, y] : points) {
70✔
467
        const double totalArea = GeometryUtils::cross(x - cx, y - cy, dx, dy);
56✔
468
        const double unitArea = GeometryUtils::cross(dx, dy, ux, uy);
56✔
469
        const double shift = totalArea / unitArea;
56✔
470
        maxShift = max(maxShift, shift);
56✔
471
    }
472
    return {cx + ux * maxShift, cy + uy * maxShift};
28✔
473
}
14✔
474

475
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDrawsLine(const NodesMapping& nodes) {
76✔
476
    const auto& nodeFrom = nodes.at(_nodeFrom);
76✔
477
    const auto& nodeTo = nodes.at(_nodeTo);
76✔
478
    const auto arrowHeadShape = getAttribute(DOT_ATTR_KEY_ARROW_HEAD);
76✔
479
    const auto arrowTailShape = getAttribute(DOT_ATTR_KEY_ARROW_TAIL);
76✔
480
    vector<unique_ptr<SVGDraw>> svgDraws;
76✔
481
    vector<unique_ptr<SVGDraw>> svgDrawArrows;
76✔
482
    vector<pair<double, double>> points;
76✔
483
    if (_connectionPoints.empty()) {
76✔
484
        const double angleFrom = nodeFrom->computeAngle(nodeTo->centerInPoints());
62✔
485
        const double angleTo = nodeTo->computeAngle(nodeFrom->centerInPoints());
62✔
486
        points.emplace_back(addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom));
62✔
487
        points.emplace_back(addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo));
62✔
488
    } else {
489
        const double angleFrom = nodeFrom->computeAngle(_connectionPoints[0]);
14✔
490
        points.emplace_back(addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom));
14✔
491
        for (const auto& [x, y] : _connectionPoints) {
36✔
492
            points.emplace_back(x, y);
22✔
493
        }
494
        const size_t n = _connectionPoints.size();
14✔
495
        const double angleTo = nodeTo->computeAngle(_connectionPoints[n - 1]);
14✔
496
        points.emplace_back(addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo));
14✔
497
    }
498
    for (size_t i = 0; i + 1 < points.size(); ++i) {
174✔
499
        const auto& [x1, y1] = points[i];
98✔
500
        const auto& [x2, y2] = points[i + 1];
98✔
501
        svgDraws.emplace_back(make_unique<SVGDrawLine>(x1, y1, x2, y2));
98✔
502
    }
503
    const auto strokeWidth = penWidth();
76✔
504
    for (const auto& line : svgDraws) {
174✔
505
        const auto& draw = dynamic_cast<SVGDrawLine*>(line.get());
98✔
506
        draw->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
98✔
507
        if (strokeWidth != 1.0) {
98✔
508
            draw->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
98✔
509
        }
510
    }
511
    for (auto& arrow : svgDrawArrows) {
110✔
512
        svgDraws.emplace_back(std::move(arrow));
34✔
513
    }
514
    if (const auto label = getAttribute(DOT_ATTR_KEY_LABEL); !label.empty()) {
76✔
515
        double totalLength = 0.0;
14✔
516
        for (size_t i = 0; i + 1 < points.size(); ++i) {
30✔
517
            const auto& [x1, y1] = points[i];
16✔
518
            const auto& [x2, y2] = points[i + 1];
16✔
519
            totalLength += GeometryUtils::distance(x1, y1, x2, y2);
16✔
520
        }
521
        const double halfLength = totalLength / 2.0;
14✔
522
        double sumLength = 0.0;
14✔
523
        size_t index = 0;
14✔
524
        double lineX = 0.0, lineY = 0.0;
14✔
525
        for (size_t i = 0; i + 1 < points.size(); ++i) {
16✔
526
            const auto& [x1, y1] = points[i];
16✔
527
            const auto& [x2, y2] = points[i + 1];
16✔
528
            const double length = GeometryUtils::distance(x1, y1, x2, y2);
16✔
529
            const double nextSum = sumLength + length;
16✔
530
            if (nextSum > halfLength) {
16✔
531
                index = i;
14✔
532
                const double ratio = (halfLength - sumLength) / length;
14✔
533
                lineX = x1 + ratio * (x2 - x1);
14✔
534
                lineY = y1 + ratio * (y2 - y1);
14✔
535
                break;
14✔
536
            }
537
            sumLength = nextSum;
2✔
538
        }
539
        const double dx = points[index + 1].first - points[index].first;
14✔
540
        const double dy = points[index + 1].second - points[index].second;
14✔
541
        const auto [cx, cy] = computeTextCenter(lineX, lineY, dx, dy);
14✔
542
        appendSVGDrawsLabelWithCenter(svgDraws, cx, cy);
14✔
543
    }
76✔
544
    return svgDraws;
152✔
545
}
76✔
546

547
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDrawsSpline(const NodesMapping& nodes) {
137✔
548
    if (_connectionPoints.empty()) {
137✔
549
        return produceSVGDrawsLine(nodes);
52✔
550
    }
551
    const auto& nodeFrom = nodes.at(_nodeFrom);
85✔
552
    const auto& nodeTo = nodes.at(_nodeTo);
85✔
553
    const auto arrowHeadShape = getAttribute(DOT_ATTR_KEY_ARROW_HEAD);
85✔
554
    const auto arrowTailShape = getAttribute(DOT_ATTR_KEY_ARROW_TAIL);
85✔
555
    vector<unique_ptr<SVGDraw>> svgDraws;
85✔
556
    vector<unique_ptr<SVGDraw>> svgDrawArrows;
85✔
557
    const auto strokeWidth = penWidth();
85✔
558
    vector<pair<double, double>> points;
85✔
559
    const double angleFrom = nodeFrom->computeAngle(_connectionPoints[0]);
85✔
560
    auto [sx, sy] = addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom);
85✔
561
    points.emplace_back(sx, sy);
85✔
562
    points.emplace_back(sx, sy);
85✔
563
    for (const auto& [x, y] : _connectionPoints) {
212✔
564
        points.emplace_back(x, y);
127✔
565
    }
566
    const double angleTo = nodeTo->computeAngle(_connectionPoints[_connectionPoints.size() - 1]);
85✔
567
    auto [ex, ey] = addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo);
85✔
568
    points.emplace_back(ex, ey);
85✔
569
    points.emplace_back(ex, ey);
85✔
570
    auto d = format("M {} {}", points[1].first, points[1].second);
85✔
571
    for (int i = 1; i + 2 < static_cast<int>(points.size()); ++i) {
297✔
572
        const auto [x0, y0] = points[i - 1];
212✔
573
        const auto [x1, y1] = points[i];
212✔
574
        const auto [x2, y2] = points[i + 1];
212✔
575
        const auto [x3, y3] = points[i + 2];
212✔
576
        const double c1x = x1 + (x2 - x0) / 6.0;
212✔
577
        const double c1y = y1 + (y2 - y0) / 6.0;
212✔
578
        const double c2x = x2 - (x3 - x1) / 6.0;
212✔
579
        const double c2y = y2 - (y3 - y1) / 6.0;
212✔
580
        d += format(" C {} {} {} {} {} {}", c1x, c1y, c2x, c2y, x2, y2);
212✔
581
    }
582
    auto path = make_unique<SVGDrawPath>(d);
85✔
583
    path->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
85✔
584
    path->setFill("none");
170✔
585
    if (strokeWidth != 1.0) {
85✔
586
        path->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
85✔
587
    }
588
    svgDraws.emplace_back(std::move(path));
85✔
589
    for (auto& arrow : svgDrawArrows) {
166✔
590
        svgDraws.emplace_back(std::move(arrow));
81✔
591
    }
592
    return svgDraws;
85✔
593
}
85✔
594

595
double SVGEdge::computeArrowTipMargin(const string_view& shape) const {
322✔
596
    if (shape == ARROW_SHAPE_NORMAL) {
322✔
597
        return computeArrowTipMarginNormal();
115✔
598
    }
599
    return 0.0;
207✔
600
}
601

602
double SVGEdge::computeArrowTipMarginNormal() const {
115✔
603
    const double angle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
115✔
604
    const double strokeWidth = penWidth();
115✔
605
    const double margin = strokeWidth / 2.0 / sin(angle);
115✔
606
    return margin;
115✔
607
}
608

609
pair<double, double> SVGEdge::addArrow(const string_view& shape, vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle) const {
322✔
610
    const double arrowTipMargin = computeArrowTipMargin(shape);
322✔
611
    const pair arrowTip = {connectionPoint.first + arrowTipMargin * cos(angle), connectionPoint.second + arrowTipMargin * sin(angle)};
322✔
612
    if (shape == ARROW_SHAPE_NORMAL) {
322✔
613
        return addArrowNormal(svgDraws, arrowTip, angle);
115✔
614
    }
615
    return {connectionPoint.first - 0.2 * cos(angle), connectionPoint.second - 0.2 * sin(angle)};
207✔
616
}
617

618
pair<double, double> SVGEdge::addArrowNormal(vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle) const {
115✔
619
    const double x0 = connectionPoint.first;
115✔
620
    const double y0 = connectionPoint.second;
115✔
621
    const double sideLen = GeometryUtils::distance(ARROW_WIDTH, ARROW_HALF_HEIGHT);
115✔
622
    const double halfAngle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
115✔
623
    const double x1 = x0 + sideLen * cos(angle - halfAngle);
115✔
624
    const double y1 = y0 + sideLen * sin(angle - halfAngle);
115✔
625
    const double x2 = x0 + sideLen * cos(angle + halfAngle);
115✔
626
    const double y2 = y0 + sideLen * sin(angle + halfAngle);
115✔
627
    auto polygon = make_unique<SVGDrawPolygon>(vector<pair<double, double>>{{x0, y0}, {x1, y1}, {x2, y2}, {x0, y0}});
230✔
628
    polygon->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
115✔
629
    polygon->setFill(getAttribute(DOT_ATTR_KEY_COLOR));
115✔
630
    if (const double strokeWidth = penWidth(); strokeWidth != 1.0) {
115✔
631
        polygon->setStrokeWidth(getAttribute(DOT_ATTR_KEY_PEN_WIDTH));
115✔
632
    }
633
    svgDraws.emplace_back(std::move(polygon));
115✔
634
    return {x0 + ARROW_WIDTH * cos(angle), y0 + ARROW_WIDTH * sin(angle)};
230✔
635
}
115✔
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

© 2026 Coveralls, Inc