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

CyberZHG / SVGDiagram / 20020810855

08 Dec 2025 07:58AM UTC coverage: 96.996% (-0.02%) from 97.017%
20020810855

push

github

web-flow
Use unit 'point' for all the functions (#3)

26 of 29 new or added lines in 3 files covered. (89.66%)

3 existing lines in 1 file now uncovered.

1356 of 1398 relevant lines covered (97.0%)

1110.52 hits per line

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

97.04
/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) {
895✔
13
    _attributes[key] = value;
895✔
14
}
895✔
15

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

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

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

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

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

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

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

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

55
void SVGItem::setColor(const string& color) {
31✔
56
    setAttribute(DOT_ATTR_KEY_COLOR, color);
31✔
57
}
31✔
58

59
void SVGItem::setFillColor(const string& color) {
11✔
60
    setAttribute(DOT_ATTR_KEY_FILL_COLOR, color);
11✔
61
}
11✔
62

63
void SVGItem::setFontColor(const string& color) {
32✔
64
    setAttribute(DOT_ATTR_KEY_FONT_COLOR, color);
32✔
65
}
32✔
66

67
void SVGItem::setPenWidth(const double width) {
11✔
68
    setAttribute(DOT_ATTR_KEY_PEN_WIDTH, format("{}", width));
11✔
69
}
11✔
70

71
double SVGItem::penWidth() const {
910✔
72
    if (const auto it = _attributes.find(DOT_ATTR_KEY_PEN_WIDTH); it != _attributes.end()) {
910✔
73
        const auto width = stod(it->second);
31✔
74
        if (fabs(width - 1.0) < GeometryUtils::EPSILON) {
31✔
75
            return 1.0;
31✔
76
        }
77
        return width;
26✔
78
    }
79
    return 1.0;
879✔
80
}
81

82
void SVGItem::appendSVGDrawsLabelWithCenter(vector<unique_ptr<SVGDraw>>& svgDraws, const double cx, const double cy) {
243✔
83
    if (enabledDebug()) {
243✔
84
        const auto [textWidth, textHeight] = computeTextSize();
34✔
85
        const auto [marginX, marginY] = computeMargin();
34✔
86
        auto textRect = make_unique<SVGDrawRect>(cx, cy, textWidth, textHeight);
34✔
87
        textRect->setFill("none");
68✔
88
        textRect->setStroke("blue");
68✔
89
        svgDraws.emplace_back(std::move(textRect));
34✔
90
        auto marginRect = make_unique<SVGDrawRect>(cx, cy, textWidth + marginX * 2, textHeight + marginY * 2);
34✔
91
        marginRect->setFill("none");
68✔
92
        marginRect->setStroke("red");
68✔
93
        svgDraws.emplace_back(std::move(marginRect));
34✔
94
    }
34✔
95
    if (const auto label = getAttribute(DOT_ATTR_KEY_LABEL); !label.empty()) {
243✔
96
        auto draw = make_unique<SVGDrawText>(cx, cy, label);
241✔
97
        if (const auto& fontColor = getAttribute(DOT_ATTR_KEY_FONT_COLOR); fontColor != "black") {
241✔
98
            draw->setFill(fontColor);
107✔
99
        }
100
        svgDraws.emplace_back(std::move(draw));
241✔
101
    }
484✔
102
}
243✔
103

104
pair<double, double> SVGItem::computeTextSize() {
277✔
105
    if (const auto [precomputedTextWidth, precomputedTextHeight] = precomputedTextSize();
277✔
106
        precomputedTextWidth > 0 && precomputedTextHeight > 0) {
277✔
107
        return {precomputedTextWidth, precomputedTextHeight};
136✔
108
        }
109
    const SVGTextSize textSize;
141✔
110
    const auto label = getAttribute(DOT_ATTR_KEY_LABEL);
141✔
111
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_NAME, "Times,serif");
282✔
112
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_SIZE, "14");
141✔
113
    const double fontSize = stod(getAttribute(DOT_ATTR_KEY_FONT_SIZE));
141✔
114
    const string fontFamily = getAttribute(DOT_ATTR_KEY_FONT_NAME);
141✔
115
    auto [width, height] = textSize.computeTextSize(label, fontSize, fontFamily);
141✔
116
    if (width == 0.0) {
141✔
117
        width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
4✔
118
    }
119
    if (height == 0.0) {
141✔
120
        height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
4✔
121
    }
122
    return {width, height};
141✔
123
}
141✔
124

125
pair<double, double> SVGItem::computeMargin() {
277✔
126
    setAttributeIfNotExist(DOT_ATTR_KEY_MARGIN, "0.1111111111111111,0.05555555555555555");
277✔
127
    const auto margin = getAttribute(DOT_ATTR_KEY_MARGIN);
277✔
128
    return AttributeUtils::parseMargin(margin);
554✔
129
}
277✔
130

131
std::pair<double, double> SVGItem::computeTextSizeWithMargin() {
219✔
132
    const auto [width, height] = computeTextSize();
219✔
133
    const auto [marginX, marginY] = computeMargin();
219✔
134
    return {width + marginX * 2, height + marginY * 2};
219✔
135
}
136

137
void SVGItem::enableDebug() {
34✔
138
    _enabledDebug = true;
34✔
139
}
34✔
140

141
bool SVGItem::enabledDebug() const {
243✔
142
    return _enabledDebug;
243✔
143
}
144

145

146
SVGNode::SVGNode(const double cx, const double cy) {
44✔
147
    _cx = cx;
44✔
148
    _cy = cy;
44✔
149
}
44✔
150

151
void SVGNode::setShape(const string& shape) {
103✔
152
    setAttribute(DOT_ATTR_KEY_SHAPE, shape);
103✔
153
}
103✔
154

155
void SVGNode::setShape(const string_view& shape) {
103✔
156
    setShape(string(shape));
103✔
157
}
103✔
158

159
void SVGNode::setCenter(const double cx, const double cy) {
54✔
160
    _cx = cx;
54✔
161
    _cy = cy;
54✔
162
}
54✔
163

164
pair<double, double> SVGNode::center() const {
128✔
165
    return {_cx, _cy};
128✔
166
}
167

168
void SVGNode::adjustNodeSize() {
152✔
169
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
304✔
170
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
152✔
171
    if (shape == NODE_SHAPE_CIRCLE) {
152✔
172
        adjustNodeSizeCircle();
103✔
173
    } else if (shape == NODE_SHAPE_NONE || shape == NODE_SHAPE_RECT) {
49✔
174
        adjustNodeSizeRect();
25✔
175
    } else if (shape == NODE_SHAPE_ELLIPSE) {
24✔
176
        adjustNodeSizeEllipse();
24✔
177
    }
178
}
152✔
179

180
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
152✔
181
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
304✔
182
    setAttributeIfNotExist(DOT_ATTR_KEY_COLOR, "black");
304✔
183
    setAttributeIfNotExist(DOT_ATTR_KEY_FILL_COLOR, "none");
304✔
184
    setAttributeIfNotExist(DOT_ATTR_KEY_FONT_COLOR, "black");
304✔
185
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
152✔
186
    if (shape == NODE_SHAPE_NONE) {
152✔
187
        return produceSVGDrawsNone();
1✔
188
    }
189
    if (shape == NODE_SHAPE_CIRCLE) {
151✔
190
        return produceSVGDrawsCircle();
103✔
191
    }
192
    if (shape == NODE_SHAPE_RECT) {
48✔
193
        return produceSVGDrawsRect();
24✔
194
    }
195
    if (shape == NODE_SHAPE_ELLIPSE) {
24✔
196
        return produceSVGDrawsEllipse();
24✔
197
    }
198
    return {};
×
199
}
152✔
200

201
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
330✔
202
    setAttributeIfNotExist(DOT_ATTR_KEY_SHAPE, string(NODE_SHAPE_DEFAULT));
660✔
203
    const auto shape = getAttribute(DOT_ATTR_KEY_SHAPE);
330✔
204
    if (shape == NODE_SHAPE_CIRCLE) {
330✔
205
        return computeConnectionPointCircle(angle);
106✔
206
    }
207
    if (shape == NODE_SHAPE_RECT) {
224✔
208
        return computeConnectionPointRect(angle);
112✔
209
    }
210
    if (shape == NODE_SHAPE_ELLIPSE) {
112✔
211
        return computeConnectionPointEllipse(angle);
112✔
212
    }
213
    return {0.0, 0.0};
×
214
}
330✔
215

216
/*
217
pair<double, double> SVGNode::computeConnectionPoint(const double x1, const double y1, const double x2, const double y2) {
218
    return computeConnectionPoint(atan2(y2 - y1, x2 - x1));
219
}
220

221
pair<double, double> SVGNode::computeConnectionPoint(const double x, const double y) {
222
    return computeConnectionPoint(_cx, _cy, x, y);
223
}
224

225
pair<double, double> SVGNode::computeConnectionPoint(const pair<double, double>& p) {
226
    return computeConnectionPoint(p.first, p.second);
227
}
228
*/
229

230
double SVGNode::computeAngle(const double x, const double y) const {
330✔
231
    return atan2(y - _cy, x - _cx);
330✔
232
}
233

234
double SVGNode::computeAngle(const pair<double, double>& p) const {
330✔
235
    return computeAngle(p.first, p.second);
330✔
236
}
237

238
bool SVGNode::isFixedSize() const {
152✔
239
    return AttributeUtils::parseBool(getAttribute(DOT_ATTR_KEY_FIXED_SIZE));
152✔
240
}
241

242
void SVGNode::updateNodeSize(const double width, const double height) {
152✔
243
    const auto widthString = format("{}", width);
152✔
244
    const auto heightString = format("{}", height);
152✔
245
    if (isFixedSize()) {
152✔
246
        setAttributeIfNotExist(DOT_ATTR_KEY_WIDTH, widthString);
×
247
        setAttributeIfNotExist(DOT_ATTR_KEY_HEIGHT, heightString);
×
248
    } else {
249
        setAttribute(DOT_ATTR_KEY_WIDTH, widthString);
152✔
250
        setAttribute(DOT_ATTR_KEY_HEIGHT, heightString);
152✔
251
    }
252
}
152✔
253

254
void SVGNode::updateNodeSize(const pair<double, double>& size) {
25✔
255
    updateNodeSize(size.first, size.second);
25✔
256
}
25✔
257

258
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
152✔
259
    appendSVGDrawsLabelWithCenter(svgDraws, _cx, _cy);
152✔
260
}
152✔
261

262
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
1✔
263
    vector<unique_ptr<SVGDraw>> svgDraws;
1✔
264
    appendSVGDrawsLabel(svgDraws);
1✔
265
    return svgDraws;
1✔
266
}
×
267

268
void SVGNode::adjustNodeSizeCircle() {
103✔
269
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
103✔
270
    updateNodeSize(diameter, diameter);
103✔
271
}
103✔
272

273
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
103✔
274
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
103✔
275
    const double height = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
103✔
276
    vector<unique_ptr<SVGDraw>> svgDraws;
103✔
277
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, max(width, height) / 2.0);
103✔
278
    circle->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
103✔
279
    circle->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
103✔
280
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
103✔
NEW
281
        circle->setStrokeWidth(strokeWidth);
×
282
    }
283
    svgDraws.emplace_back(std::move(circle));
103✔
284
    appendSVGDrawsLabel(svgDraws);
103✔
285
    return svgDraws;
206✔
286
}
103✔
287

288
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
106✔
289
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
106✔
290
    const double radius = (width + penWidth()) / 2.0;
106✔
291
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
106✔
292
}
293

294
void SVGNode::adjustNodeSizeRect() {
25✔
295
    updateNodeSize(computeTextSizeWithMargin());
25✔
296
}
25✔
297

298
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
24✔
299
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
24✔
300
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT));
24✔
301
    vector<unique_ptr<SVGDraw>> svgDraws;
24✔
302
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width, height);
24✔
303
    rect->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
24✔
304
    rect->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
24✔
305
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
24✔
NEW
306
        rect->setStrokeWidth(strokeWidth);
×
307
    }
308
    svgDraws.emplace_back(std::move(rect));
24✔
309
    appendSVGDrawsLabel(svgDraws);
24✔
310
    return svgDraws;
48✔
311
}
24✔
312

313
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
112✔
314
    const double strokeWidth = penWidth();
112✔
315
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH)) + strokeWidth;
112✔
316
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT)) + strokeWidth;
112✔
317
    double x1 = -width / 2, y1 = -height / 2;
112✔
318
    double x2 = width / 2, y2 = height / 2;
112✔
319
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
224✔
320
    for (const auto& [x, y] : vertices) {
560✔
321
        if (GeometryUtils::isSameAngle(angle, x, y)) {
448✔
322
            return {_cx + x, _cy + y};
×
323
        }
324
    }
325
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
262✔
326
        x1 = vertices[i].first;
262✔
327
        y1 = vertices[i].second;
262✔
328
        x2 = vertices[(i + 1) % vertices.size()].first;
262✔
329
        y2 = vertices[(i + 1) % vertices.size()].second;
262✔
330
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
262✔
331
            return {_cx + intersect.value().first, _cy + intersect.value().second};
112✔
332
        }
333
    }
334
    return {_cx, _cy};
×
335
}
112✔
336

337
void SVGNode::adjustNodeSizeEllipse() {
24✔
338
    const auto [textWidth, textHeight] = computeTextSize();
24✔
339
    const auto [marginX, marginY] = computeMargin();
24✔
340
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
24✔
341
}
24✔
342

343
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
24✔
344
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH));
24✔
345
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT));
24✔
346
    vector<unique_ptr<SVGDraw>> svgDraws;
24✔
347
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width, height);
24✔
348
    ellipse->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
24✔
349
    ellipse->setFill(getAttribute(DOT_ATTR_KEY_FILL_COLOR));
24✔
350
    if (const auto strokeWidth = penWidth(); strokeWidth != 1.0) {
24✔
351
        ellipse->setStrokeWidth(strokeWidth);
5✔
352
    }
353
    svgDraws.emplace_back(std::move(ellipse));
24✔
354
    appendSVGDrawsLabel(svgDraws);
24✔
355
    return svgDraws;
48✔
356
}
24✔
357

358
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
112✔
359
    const double strokeWidth = penWidth();
112✔
360
    const double width = stod(getAttribute(DOT_ATTR_KEY_WIDTH)) + strokeWidth;
112✔
361
    const double height = stod(getAttribute(DOT_ATTR_KEY_HEIGHT)) + strokeWidth;
112✔
362
    const double rx = width / 2, ry = height / 2;
112✔
363
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
112✔
364
    const double x = rx * ry * cos(angle) / base;
112✔
365
    const double y = rx * ry * sin(angle) / base;
112✔
366
    return {_cx + x, _cy + y};
112✔
367
}
368

369
SVGEdge::SVGEdge(const string& idFrom, const string& idTo) {
23✔
370
    _nodeFrom = idFrom;
23✔
371
    _nodeTo = idTo;
23✔
372
}
23✔
373

374
void SVGEdge::setNodeFrom(const string& id) {
1✔
375
    _nodeFrom = id;
1✔
376
}
1✔
377

378
const string& SVGEdge::nodeFrom() const {
330✔
379
    return _nodeFrom;
330✔
380
}
381

382
void SVGEdge::setNodeTo(const string& id) {
1✔
383
    _nodeTo = id;
1✔
384
}
1✔
385

386
const string& SVGEdge::nodeTo() const {
330✔
387
    return _nodeTo;
330✔
388
}
389

390
void SVGEdge::setConnection(const string& idFrom, const string& idTo) {
117✔
391
    _nodeFrom = idFrom;
117✔
392
    _nodeTo = idTo;
117✔
393
}
117✔
394

395
void SVGEdge::setSplines(const string& value) {
24✔
396
    setAttribute(DOT_ATTR_KEY_SPLINES, value);
24✔
397
}
24✔
398

399
void SVGEdge::setSplines(const string_view& value) {
24✔
400
    setSplines(string(value));
24✔
401
}
24✔
402

403
void SVGEdge::addConnectionPoint(const pair<double, double>& point) {
129✔
404
    _connectionPoints.emplace_back(point);
129✔
405
}
129✔
406

407
void SVGEdge::addConnectionPoint(const double x, const double y) {
129✔
408
    addConnectionPoint({x, y});
129✔
409
}
129✔
410

411
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDraws(const NodesMapping& nodes) {
165✔
412
    setAttributeIfNotExist(DOT_ATTR_KEY_SPLINES, string(EDGE_SPLINES_DEFAULT));
330✔
413
    setAttributeIfNotExist(DOT_ATTR_KEY_COLOR, "black");
330✔
414
    setAttributeIfNotExist(DOT_ATTR_KEY_ARROW_HEAD, "none");
330✔
415
    setAttributeIfNotExist(DOT_ATTR_KEY_ARROW_TAIL, "none");
330✔
416
    setAttributeIfNotExist(DOT_ATTR_KEY_MARGIN, "0,0");
330✔
417
    const auto splines = getAttribute(DOT_ATTR_KEY_SPLINES);
165✔
418
    if (splines == EDGE_SPLINES_LINE) {
165✔
419
        return produceSVGDrawsLine(nodes);
24✔
420
    }
421
    if (splines == EDGE_SPLINES_SPLINE) {
141✔
422
        return produceSVGDrawsSpline(nodes);
141✔
423
    }
424
    return {};
×
425
}
165✔
426

427
void SVGEdge::setArrowHead() {
83✔
428
    setArrowHead(ARROW_SHAPE_DEFAULT);
83✔
429
}
83✔
430

431
void SVGEdge::setArrowHead(const string_view& shape) {
91✔
432
    setAttribute(DOT_ATTR_KEY_ARROW_HEAD, string(shape));
182✔
433
}
91✔
434

435
void SVGEdge::setArrowTail() {
17✔
436
    setArrowTail(ARROW_SHAPE_DEFAULT);
17✔
437
}
17✔
438

439
void SVGEdge::setArrowTail(const string_view& shape) {
25✔
440
    setAttribute(DOT_ATTR_KEY_ARROW_TAIL, string(shape));
50✔
441
}
25✔
442

443
std::pair<double, double> SVGEdge::computeTextCenter(const double cx, const double cy, double dx, double dy) {
91✔
444
    const auto [width, height] = computeTextSizeWithMargin();
91✔
445
    const auto points = vector<pair<double, double>>{
446
        {cx - width / 2, cy - height / 2},
×
447
        {cx - width / 2, cy + height / 2},
×
448
        {cx + width / 2, cy + height / 2},
×
449
        {cx + width / 2, cy - height / 2},
×
450
    };
182✔
451
    const auto d = GeometryUtils::normalize(dx, dy);
91✔
452
    dx = d.first, dy = d.second;
91✔
453
    const double ux = -dy, uy = dx;
91✔
454
    double maxShift = 0.0;
91✔
455
    for (const auto& [x, y] : points) {
455✔
456
        const double totalArea = GeometryUtils::cross(x - cx, y - cy, dx, dy);
364✔
457
        const double unitArea = GeometryUtils::cross(dx, dy, ux, uy);
364✔
458
        const double shift = totalArea / unitArea;
364✔
459
        maxShift = max(maxShift, shift);
364✔
460
    }
461
    return {cx + ux * maxShift, cy + uy * maxShift};
182✔
462
}
91✔
463

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

536
vector<unique_ptr<SVGDraw>> SVGEdge::produceSVGDrawsSpline(const NodesMapping& nodes) {
141✔
537
    static constexpr double NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS = 100;
538
    constexpr double SPLINE_LENGTH_APPROXIMATION_STEP = 1.0 / NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS;
141✔
539
    if (_connectionPoints.empty()) {
141✔
540
        return produceSVGDrawsLine(nodes);
54✔
541
    }
542
    const auto& nodeFrom = nodes.at(_nodeFrom);
87✔
543
    const auto& nodeTo = nodes.at(_nodeTo);
87✔
544
    const auto arrowHeadShape = getAttribute(DOT_ATTR_KEY_ARROW_HEAD);
87✔
545
    const auto arrowTailShape = getAttribute(DOT_ATTR_KEY_ARROW_TAIL);
87✔
546
    vector<unique_ptr<SVGDraw>> svgDraws;
87✔
547
    vector<unique_ptr<SVGDraw>> svgDrawArrows;
87✔
548
    const auto strokeWidth = penWidth();
87✔
549
    vector<pair<double, double>> points;
87✔
550
    const double angleFrom = nodeFrom->computeAngle(_connectionPoints[0]);
87✔
551
    auto [sx, sy] = addArrow(arrowTailShape, svgDrawArrows, nodeFrom->computeConnectionPoint(angleFrom), angleFrom);
87✔
552
    points.emplace_back(sx, sy);
87✔
553
    points.emplace_back(sx, sy);
87✔
554
    for (const auto& [x, y] : _connectionPoints) {
216✔
555
        points.emplace_back(x, y);
129✔
556
    }
557
    const double angleTo = nodeTo->computeAngle(_connectionPoints[_connectionPoints.size() - 1]);
87✔
558
    auto [ex, ey] = addArrow(arrowHeadShape, svgDrawArrows, nodeTo->computeConnectionPoint(angleTo), angleTo);
87✔
559
    points.emplace_back(ex, ey);
87✔
560
    points.emplace_back(ex, ey);
87✔
561
    auto d = format("M {} {}", points[1].first, points[1].second);
87✔
562
    vector<vector<pair<double, double>>> splines;
87✔
563
    for (int i = 1; i + 2 < static_cast<int>(points.size()); ++i) {
303✔
564
        const auto [x0, y0] = points[i - 1];
216✔
565
        const auto [x1, y1] = points[i];
216✔
566
        const auto [x2, y2] = points[i + 1];
216✔
567
        const auto [x3, y3] = points[i + 2];
216✔
568
        const double c1x = x1 + (x2 - x0) / 6.0;
216✔
569
        const double c1y = y1 + (y2 - y0) / 6.0;
216✔
570
        const double c2x = x2 - (x3 - x1) / 6.0;
216✔
571
        const double c2y = y2 - (y3 - y1) / 6.0;
216✔
572
        d += format(" C {} {} {} {} {} {}", c1x, c1y, c2x, c2y, x2, y2);
216✔
573
        splines.emplace_back(vector{points[i], {c1x, c1y}, {c2x, c2y}, points[i + 1]});
648✔
574
    }
575
    auto path = make_unique<SVGDrawPath>(d);
87✔
576
    path->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
87✔
577
    path->setFill("none");
174✔
578
    if (strokeWidth != 1.0) {
87✔
579
        path->setStrokeWidth(strokeWidth);
4✔
580
    }
581
    svgDraws.emplace_back(std::move(path));
87✔
582
    for (auto& arrow : svgDrawArrows) {
185✔
583
        svgDraws.emplace_back(std::move(arrow));
98✔
584
    }
585
    if (const auto label = getAttribute(DOT_ATTR_KEY_LABEL); !label.empty()) {
87✔
586
        double totalLength = 0.0;
75✔
587
        vector<double> lengths(splines.size());
75✔
588
        for (size_t i = 0; i < splines.size(); ++i) {
259✔
589
            lengths[i] = GeometryUtils::computeBezierLength(splines[i][0], splines[i][1], splines[i][2], splines[i][3]);
184✔
590
            totalLength += lengths[i];
184✔
591
        }
592
        const double halfLength = totalLength / 2.0;
75✔
593
        double sumLength = 0.0;
75✔
594
        double splineX = 0.0, splineY = 0.0;
75✔
595
        double dx = 0.0, dy = 0.0;
75✔
596
        for (size_t i = 0; i < splines.size(); ++i) {
138✔
597
            const double nextSum = sumLength + lengths[i];
138✔
598
            if (nextSum > halfLength) {
138✔
599
                const double targetLength = halfLength - sumLength;
75✔
600
                auto [x1, y1] = splines[i][0];
75✔
601
                double totalSegmentLength = 0.0;
75✔
602
                for (int j = 1; j < NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS; ++j) {
3,034✔
603
                    const double t = j * SPLINE_LENGTH_APPROXIMATION_STEP;
3,034✔
604
                    const auto [x2, y2] = GeometryUtils::computeBezierAt(splines[i], t);
3,034✔
605
                    totalSegmentLength += GeometryUtils::distance(x1, y1, x2, y2);
3,034✔
606
                    if (j + 1 == NUM_SPLINE_LENGTH_APPROXIMATION_SEGMENTS || totalSegmentLength > targetLength - GeometryUtils::EPSILON) {
3,034✔
607
                        const double tMid = t - SPLINE_LENGTH_APPROXIMATION_STEP * 0.5;
75✔
608
                        auto point = GeometryUtils::computeBezierAt(splines[i], tMid);
75✔
609
                        splineX = point.first, splineY = point.second;
75✔
610
                        point = GeometryUtils::computeBezierDerivative(splines[i], tMid);
75✔
611
                        dx = point.first, dy = point.second;
75✔
612
                        break;
75✔
613
                    }
614
                    x1 = x2;
2,959✔
615
                    y1 = y2;
2,959✔
616
                }
617
                break;
75✔
618
            }
619
            sumLength = nextSum;
63✔
620
        }
621
        const auto [cx, cy] = computeTextCenter(splineX, splineY, dx, dy);
75✔
622
        appendSVGDrawsLabelWithCenter(svgDraws, cx, cy);
75✔
623
    }
162✔
624
    return svgDraws;
87✔
625
}
87✔
626

627
double SVGEdge::computeArrowTipMargin(const string_view& shape) const {
330✔
628
    if (shape == ARROW_SHAPE_NORMAL) {
330✔
629
        return computeArrowTipMarginNormal();
132✔
630
    }
631
    return 0.0;
198✔
632
}
633

634
double SVGEdge::computeArrowTipMarginNormal() const {
132✔
635
    const double angle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
132✔
636
    const double strokeWidth = penWidth();
132✔
637
    const double margin = strokeWidth / 2.0 / sin(angle);
132✔
638
    return margin;
132✔
639
}
640

641
pair<double, double> SVGEdge::addArrow(const string_view& shape, vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle) const {
330✔
642
    const double arrowTipMargin = computeArrowTipMargin(shape);
330✔
643
    const pair arrowTip = {connectionPoint.first + arrowTipMargin * cos(angle), connectionPoint.second + arrowTipMargin * sin(angle)};
330✔
644
    if (shape == ARROW_SHAPE_NORMAL) {
330✔
645
        return addArrowNormal(svgDraws, arrowTip, angle);
132✔
646
    }
647
    return {connectionPoint.first - 0.2 * cos(angle), connectionPoint.second - 0.2 * sin(angle)};
198✔
648
}
649

650
pair<double, double> SVGEdge::addArrowNormal(vector<unique_ptr<SVGDraw>>& svgDraws, const pair<double, double>& connectionPoint, const double angle) const {
132✔
651
    const double x0 = connectionPoint.first;
132✔
652
    const double y0 = connectionPoint.second;
132✔
653
    const double sideLen = GeometryUtils::distance(ARROW_WIDTH, ARROW_HALF_HEIGHT);
132✔
654
    const double halfAngle = atan(ARROW_HALF_HEIGHT / ARROW_WIDTH);
132✔
655
    const double x1 = x0 + sideLen * cos(angle - halfAngle);
132✔
656
    const double y1 = y0 + sideLen * sin(angle - halfAngle);
132✔
657
    const double x2 = x0 + sideLen * cos(angle + halfAngle);
132✔
658
    const double y2 = y0 + sideLen * sin(angle + halfAngle);
132✔
659
    auto polygon = make_unique<SVGDrawPolygon>(vector<pair<double, double>>{{x0, y0}, {x1, y1}, {x2, y2}, {x0, y0}});
264✔
660
    polygon->setStroke(getAttribute(DOT_ATTR_KEY_COLOR));
132✔
661
    polygon->setFill(getAttribute(DOT_ATTR_KEY_COLOR));
132✔
662
    if (const double strokeWidth = penWidth(); strokeWidth != 1.0) {
132✔
663
        polygon->setStrokeWidth(strokeWidth);
4✔
664
    }
665
    svgDraws.emplace_back(std::move(polygon));
132✔
666
    return {x0 + ARROW_WIDTH * cos(angle), y0 + ARROW_WIDTH * sin(angle)};
264✔
667
}
132✔
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