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

CyberZHG / SVGDiagram / 21575950793

02 Feb 2026 03:07AM UTC coverage: 99.477% (-0.1%) from 99.621%
21575950793

push

github

web-flow
Add peripheries (#64)

* Add peripheries

* Update documents

99 of 101 new or added lines in 1 file covered. (98.02%)

2 existing lines in 1 file now uncovered.

2661 of 2675 relevant lines covered (99.48%)

2376.91 hits per line

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

98.81
/src/diagram/svg_node.cpp
1
#include "svg_nodes.h"
2

3
#include <format>
4
#include <cmath>
5
#include <functional>
6
#include <numbers>
7

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

14
SVGNode::SVGNode(const double cx, const double cy) {
60✔
15
    _cx = cx;
60✔
16
    _cy = cy;
60✔
17
}
60✔
18

19
void SVGNode::setAttributeIfNotExist(const string_view &key, const string &value) {
8,985✔
20
    if (attributes().contains(key)) {
8,985✔
21
        return;
5,462✔
22
    }
23
    if (parent() != nullptr) {
3,523✔
24
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
3,523✔
25
            return;
134✔
26
        }
27
    }
28
    setAttribute(key, value);
3,389✔
29
}
30

31
const string& SVGNode::getAttribute(const std::string_view& key) const {
24,642✔
32
    static const string EMPTY_STRING;
24,642✔
33
    if (const auto it = attributes().find(key); it != attributes().end()) {
24,642✔
34
        return it->second;
20,965✔
35
    }
36
    if (parent() != nullptr) {
3,677✔
37
        if (const auto ret = parent()->defaultNodeAttribute(key); ret.has_value()) {
3,677✔
38
            return ret.value();
253✔
39
        }
40
    }
41
    return EMPTY_STRING;
3,424✔
42
}
43

44
void SVGNode::setShape(const string& shape) {
313✔
45
    setAttribute(ATTR_KEY_SHAPE, shape);
313✔
46
}
313✔
47

48
void SVGNode::setShape(const string_view& shape) {
313✔
49
    setShape(string(shape));
313✔
50
}
313✔
51

52
void SVGNode::setSides(const int sides) {
149✔
53
    setAttribute(ATTR_KEY_SIDES, sides);
149✔
54
}
149✔
55

56
void SVGNode::setSkew(const double skew) {
7✔
57
    setAttribute(ATTR_KEY_SKEW, skew);
7✔
58
}
7✔
59

60
void SVGNode::setDistortion(const double distortion) {
19✔
61
    setAttribute(ATTR_KEY_DISTORTION, distortion);
19✔
62
}
19✔
63

64
void SVGNode::setOrientation(const double orientation) {
19✔
65
    setAttribute(ATTR_KEY_ORIENTATION, orientation);
19✔
66
}
19✔
67

68
void SVGNode::setPeripheries(const int peripheries) {
46✔
69
    setAttribute(ATTR_KEY_PERIPHERIES, peripheries);
46✔
70
}
46✔
71

72
void SVGNode::setCenter(const double cx, const double cy) {
277✔
73
    _cx = cx;
277✔
74
    _cy = cy;
277✔
75
}
277✔
76

77
pair<double, double> SVGNode::center() const {
1,341✔
78
    return {_cx, _cy};
1,341✔
79
}
80

81
void SVGNode::adjustNodeSize() {
581✔
82
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,162✔
83
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
1,162✔
84
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
581✔
85
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
581✔
86
    if (shape == SHAPE_CIRCLE) {
581✔
87
        adjustNodeSizeCircle();
145✔
88
    } else if (shape == SHAPE_DOUBLE_CIRCLE) {
436✔
89
        setPeripheries(2);
15✔
90
        adjustNodeSizeCircle();
15✔
91
    } else if (shape == SHAPE_NONE || shape == SHAPE_RECT) {
421✔
92
        adjustNodeSizeRect();
73✔
93
    } else if (shape == SHAPE_ELLIPSE) {
348✔
94
        adjustNodeSizeEllipse();
156✔
95
    } else if (shape == SHAPE_DOUBLE_ELLIPSE) {
192✔
96
        setPeripheries(2);
19✔
97
        adjustNodeSizeEllipse();
19✔
98
    } else if (shape == SHAPE_POLYGON) {
173✔
99
        adjustNodeSizePolygon();
59✔
100
    } else if (shape == SHAPE_TRIANGLE) {
114✔
101
        setSides(3);
5✔
102
        adjustNodeSizePolygon();
5✔
103
    } else if (shape == SHAPE_PENTAGON) {
109✔
104
        setSides(5);
5✔
105
        adjustNodeSizePolygon();
5✔
106
    } else if (shape == SHAPE_HEXAGON) {
104✔
107
        setSides(6);
6✔
108
        adjustNodeSizePolygon();
6✔
109
    } else if (shape == SHAPE_SEPTAGON) {
98✔
110
        setSides(7);
5✔
111
        adjustNodeSizePolygon();
5✔
112
    } else if (shape == SHAPE_OCTAGON) {
93✔
113
        setSides(8);
6✔
114
        adjustNodeSizePolygon();
6✔
115
    } else if (shape == SHAPE_DOUBLE_OCTAGON) {
87✔
116
        setSides(8);
2✔
117
        setPeripheries(2);
2✔
118
        adjustNodeSizePolygon();
2✔
119
    } else if (shape == SHAPE_TRIPLE_OCTAGON) {
85✔
120
        setSides(8);
2✔
121
        setPeripheries(3);
2✔
122
        adjustNodeSizePolygon();
2✔
123
    } else if (shape == SHAPE_TRAPEZIUM) {
83✔
124
        setSides(4);
4✔
125
        setDistortion(-0.4);
4✔
126
        adjustNodeSizePolygon();
4✔
127
    } else if (shape == SHAPE_PARALLELOGRAM) {
79✔
128
        setSides(4);
4✔
129
        setSkew(0.6);
4✔
130
        adjustNodeSizePolygon();
4✔
131
    } else if (shape == SHAPE_HOUSE) {
75✔
132
        setSides(5);
4✔
133
        setDistortion(-0.64);
4✔
134
        adjustNodeSizePolygon();
4✔
135
    } else if (shape == SHAPE_DIAMOND) {
71✔
136
        setSides(4);
4✔
137
        setOrientation(45);
4✔
138
        adjustNodeSizePolygon();
4✔
139
    } else if (shape == SHAPE_INV_TRIANGLE) {
67✔
140
        setSides(3);
4✔
141
        setOrientation(180);
4✔
142
        adjustNodeSizePolygon();
4✔
143
    } else if (shape == SHAPE_INV_TRAPEZIUM) {
63✔
144
        setSides(4);
4✔
145
        setDistortion(-0.4);
4✔
146
        setOrientation(180);
4✔
147
        adjustNodeSizePolygon();
4✔
148
    } else if (shape == SHAPE_INV_HOUSE) {
59✔
149
        setSides(5);
4✔
150
        setDistortion(-0.64);
4✔
151
        setOrientation(180);
4✔
152
        adjustNodeSizePolygon();
4✔
153
    } else if (shape == SHAPE_RECORD) {
55✔
154
        adjustNodeSizeRecord();
55✔
155
    }
156
}
581✔
157

158
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDraws() {
581✔
159
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,162✔
160
    setAttributeIfNotExist(ATTR_KEY_COLOR, string(ATTR_DEF_COLOR));
1,162✔
161
    setAttributeIfNotExist(ATTR_KEY_FILL_COLOR, string(ATTR_DEF_FILL_COLOR));
1,162✔
162
    setAttributeIfNotExist(ATTR_KEY_FONT_COLOR, string(ATTR_DEF_FONT_COLOR));
1,162✔
163
    setAttributeIfNotExist(ATTR_KEY_FONT_NAME, string(ATTR_DEF_FONT_NAME));
1,162✔
164
    setAttributeIfNotExist(ATTR_KEY_FONT_SIZE, string(ATTR_DEF_FONT_SIZE));
1,162✔
165
    setAttributeIfNotExist(ATTR_KEY_STYLE, string(ATTR_DEF_STYLE));
581✔
166
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
581✔
167
    if (shape == SHAPE_CIRCLE || shape == SHAPE_DOUBLE_CIRCLE) {
581✔
168
        return produceSVGDrawsCircle();
160✔
169
    }
170
    if (shape == SHAPE_RECT) {
421✔
171
        return produceSVGDrawsRect();
70✔
172
    }
173
    if (shape == SHAPE_ELLIPSE || shape == SHAPE_DOUBLE_ELLIPSE) {
351✔
174
        return produceSVGDrawsEllipse();
175✔
175
    }
176
    if (shape == SHAPE_POLYGON || shape == SHAPE_TRIANGLE || shape == SHAPE_PENTAGON ||
400✔
177
        shape == SHAPE_HEXAGON || shape == SHAPE_SEPTAGON || shape == SHAPE_OCTAGON ||
298✔
178
        shape == SHAPE_DOUBLE_OCTAGON || shape == SHAPE_TRIPLE_OCTAGON ||
264✔
179
        shape == SHAPE_TRAPEZIUM || shape == SHAPE_PARALLELOGRAM || shape == SHAPE_HOUSE ||
242✔
180
        shape == SHAPE_DIAMOND || shape == SHAPE_INV_TRIANGLE || shape == SHAPE_INV_TRAPEZIUM ||
429✔
181
        shape == SHAPE_INV_HOUSE) {
238✔
182
        return produceSVGDrawsPolygon();
118✔
183
    }
184
    if (shape == SHAPE_RECORD) {
58✔
185
        return produceSVGDrawsRecord();
55✔
186
    }
187
    return produceSVGDrawsNone();
3✔
188
}
581✔
189

190
pair<double, double> SVGNode::computeConnectionPoint(const double angle) {
1,213✔
191
    setAttributeIfNotExist(ATTR_KEY_SHAPE, string(SHAPE_DEFAULT));
1,213✔
192
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,213✔
193
    if (shape == SHAPE_CIRCLE || shape == SHAPE_DOUBLE_CIRCLE) {
1,213✔
194
        return computeConnectionPointCircle(angle);
317✔
195
    }
196
    if (shape == SHAPE_RECT || shape == SHAPE_NONE) {
896✔
197
        return computeConnectionPointRect(angle);
271✔
198
    }
199
    if (shape == SHAPE_RECORD) {
625✔
200
        return computeConnectionPointRecord(angle);
33✔
201
    }
202
    if (shape == SHAPE_ELLIPSE || shape == SHAPE_DOUBLE_ELLIPSE) {
592✔
203
        return computeConnectionPointEllipse(angle);
438✔
204
    }
205
    if (shape == SHAPE_POLYGON || shape == SHAPE_TRIANGLE || shape == SHAPE_PENTAGON ||
174✔
206
        shape == SHAPE_HEXAGON || shape == SHAPE_SEPTAGON || shape == SHAPE_OCTAGON ||
11✔
NEW
207
        shape == SHAPE_DOUBLE_OCTAGON || shape == SHAPE_TRIPLE_OCTAGON ||
×
UNCOV
208
        shape == SHAPE_TRAPEZIUM || shape == SHAPE_PARALLELOGRAM || shape == SHAPE_HOUSE ||
×
209
        shape == SHAPE_DIAMOND || shape == SHAPE_INV_TRIANGLE || shape == SHAPE_INV_TRAPEZIUM ||
165✔
210
        shape == SHAPE_INV_HOUSE) {
154✔
211
        return computeConnectionPointPolygon(angle);
154✔
212
    }
UNCOV
213
    return computeConnectionPointEllipse(angle);
×
214
}
1,213✔
215

216
pair<double, double> SVGNode::computeFieldConnectionPoint(const string& fieldId, const double angle) {
1,233✔
217
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,233✔
218
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,233✔
219
        return computeConnectionPoint(angle);
1,213✔
220
    }
221
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
20✔
222
    return computeConnectionPointRect(cx, cy, width, height, angle);
20✔
223
}
1,233✔
224

225
double SVGNode::computeAngle(const double x, const double y) const {
1,144✔
226
    return atan2(y - _cy, x - _cx);
1,144✔
227
}
228

229
double SVGNode::computeAngle(const pair<double, double>& p) const {
1,144✔
230
    return computeAngle(p.first, p.second);
1,144✔
231
}
232

233
double SVGNode::computeFieldAngle(const string &fieldId, const pair<double, double>& p) const {
1,158✔
234
    const auto shape = getAttribute(ATTR_KEY_SHAPE);
1,158✔
235
    if (shape != SHAPE_RECORD || !_recordPositions.contains(fieldId)) {
1,158✔
236
        return computeAngle(p);
1,144✔
237
    }
238
    const auto& [cx, cy, width, height] = _recordPositions.at(fieldId);
14✔
239
    return atan2(p.second - cy, p.first - cx);
14✔
240
}
1,158✔
241

242
bool SVGNode::isFixedSize() const {
581✔
243
    return AttributeUtils::parseBool(getAttribute(ATTR_KEY_FIXED_SIZE));
581✔
244
}
245

246
void SVGNode::updateNodeSize(const double width, const double height) {
581✔
247
    if (isFixedSize()) {
581✔
248
        setDoubleAttributeIfNotExist(ATTR_KEY_WIDTH, AttributeUtils::pointToInch(width));
48✔
249
        setDoubleAttributeIfNotExist(ATTR_KEY_HEIGHT, AttributeUtils::pointToInch(height));
48✔
250
    } else {
251
        setWidth(width);
533✔
252
        setHeight(height);
533✔
253
    }
254
}
581✔
255

256
void SVGNode::updateNodeSize(const pair<double, double>& size) {
73✔
257
    updateNodeSize(size.first, size.second);
73✔
258
}
73✔
259

260
void SVGNode::appendSVGDrawsLabel(vector<unique_ptr<SVGDraw>>& svgDraws) {
526✔
261
    appendSVGDrawsLabelWithLocation(svgDraws, _cx, _cy);
526✔
262
}
526✔
263

264
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsNone() {
3✔
265
    vector<unique_ptr<SVGDraw>> svgDraws;
3✔
266
    appendSVGDrawsLabel(svgDraws);
3✔
267
    return svgDraws;
6✔
268
}
3✔
269

270
void SVGNode::adjustNodeSizeCircle() {
160✔
271
    const auto diameter = GeometryUtils::distance(computeTextSizeWithMargin());
160✔
272
    updateNodeSize(diameter, diameter);
160✔
273
}
160✔
274

275
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsCircle() {
160✔
276
    setAttributeIfNotExist(ATTR_KEY_PERIPHERIES, string(ATTR_DEF_PERIPHERIES));
160✔
277
    const int peripheries = stoi(getAttribute(ATTR_KEY_PERIPHERIES));
160✔
278
    vector<unique_ptr<SVGDraw>> svgDraws;
160✔
279
    const double radius = max(width(), height()) / 2.0;
160✔
280
    auto circle = make_unique<SVGDrawCircle>(_cx, _cy, radius);
160✔
281
    setStrokeStyles(circle.get());
160✔
282
    setFillStyles(circle.get(), svgDraws);
160✔
283
    svgDraws.emplace_back(std::move(circle));
160✔
284
    for (int p = 1; p < peripheries; ++p) {
176✔
285
        auto circleOuter = make_unique<SVGDrawCircle>(_cx, _cy, radius + DOUBLE_BORDER_MARGIN * p);
16✔
286
        setStrokeStyles(circleOuter.get());
16✔
287
        circleOuter->setFill("none");
32✔
288
        svgDraws.emplace_back(std::move(circleOuter));
16✔
289
    }
16✔
290
    appendSVGDrawsLabel(svgDraws);
160✔
291
    return svgDraws;
320✔
292
}
160✔
293

294
pair<double, double> SVGNode::computeConnectionPointCircle(const double angle) const {
317✔
295
    const auto& peripheriesStr = getAttribute(ATTR_KEY_PERIPHERIES);
317✔
296
    const int peripheries = peripheriesStr.empty() ? 1 : stoi(peripheriesStr);
317✔
297
    const double radius = (width() + penWidth()) / 2.0 + DOUBLE_BORDER_MARGIN * (peripheries - 1);
317✔
298
    return {_cx + radius * cos(angle), _cy + radius * sin(angle)};
317✔
299
}
300

301
void SVGNode::adjustNodeSizeRect() {
73✔
302
    updateNodeSize(computeTextSizeWithMargin());
73✔
303
}
73✔
304

305
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRect() {
70✔
306
    setAttributeIfNotExist(ATTR_KEY_PERIPHERIES, string(ATTR_DEF_PERIPHERIES));
70✔
307
    const int peripheries = stoi(getAttribute(ATTR_KEY_PERIPHERIES));
70✔
308
    vector<unique_ptr<SVGDraw>> svgDraws;
70✔
309
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, width(), height());
70✔
310
    setStrokeStyles(rect.get());
70✔
311
    setFillStyles(rect.get(), svgDraws);
70✔
312
    svgDraws.emplace_back(std::move(rect));
70✔
313
    for (int p = 1; p < peripheries; ++p) {
81✔
314
        auto rectOuter = make_unique<SVGDrawRect>(_cx, _cy, width() + DOUBLE_BORDER_MARGIN * 2 * p, height() + DOUBLE_BORDER_MARGIN * 2 * p);
11✔
315
        setStrokeStyles(rectOuter.get());
11✔
316
        rectOuter->setFill("none");
22✔
317
        svgDraws.emplace_back(std::move(rectOuter));
11✔
318
    }
11✔
319
    appendSVGDrawsLabel(svgDraws);
70✔
320
    return svgDraws;
140✔
321
}
70✔
322

323
pair<double, double> SVGNode::computeConnectionPointRect(const double angle) const {
304✔
324
    const auto& peripheriesStr = getAttribute(ATTR_KEY_PERIPHERIES);
304✔
325
    const int peripheries = peripheriesStr.empty() ? 1 : stoi(peripheriesStr);
304✔
326
    const double offset = DOUBLE_BORDER_MARGIN * (peripheries - 1);
304✔
327
    return computeConnectionPointRect(_cx, _cy, width() + offset * 2, height() + offset * 2, angle);
304✔
328
}
329

330
pair<double, double> SVGNode::computeConnectionPointRect(const double cx, const double cy, const double width, const double height, const double angle) const {
324✔
331
    const double strokeWidth = penWidth();
324✔
332
    const double totalWidth = width + strokeWidth;
324✔
333
    const double totalHeight = height + strokeWidth;
324✔
334
    double x1 = -totalWidth / 2, y1 = -totalHeight / 2;
324✔
335
    double x2 = totalWidth / 2, y2 = totalHeight / 2;
324✔
336
    const auto vertices = vector<pair<double, double>>{{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
648✔
337
    for (const auto& [x, y] : vertices) {
1,608✔
338
        if (GeometryUtils::isSameAngle(angle, x, y)) {
1,288✔
339
            return {cx + x, cy + y};
4✔
340
        }
341
    }
342
    double x = 0.0, y = 0.0;
320✔
343
    for (int i = 0; i < static_cast<int>(vertices.size()); ++i) {
1,600✔
344
        x1 = vertices[i].first;
1,280✔
345
        y1 = vertices[i].second;
1,280✔
346
        x2 = vertices[(i + 1) % vertices.size()].first;
1,280✔
347
        y2 = vertices[(i + 1) % vertices.size()].second;
1,280✔
348
        if (const auto intersect = GeometryUtils::intersect(angle, x1, y1, x2, y2); intersect != nullopt) {
1,280✔
349
            x = _cx + intersect.value().first;
320✔
350
            y = _cy + intersect.value().second;
320✔
351
        }
352
    }
353
    return {x, y};
320✔
354
}
324✔
355

356
void SVGNode::adjustNodeSizeEllipse() {
175✔
357
    const auto [textWidth, textHeight] = computeTextSize();
175✔
358
    const auto [marginX, marginY] = computeMargin();
175✔
359
    updateNodeSize((textWidth + marginX * 2) * sqrt(2.0), (textHeight + marginY * 2) * sqrt(2.0));
175✔
360
}
175✔
361

362
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsEllipse() {
175✔
363
    setAttributeIfNotExist(ATTR_KEY_PERIPHERIES, string(ATTR_DEF_PERIPHERIES));
175✔
364
    const int peripheries = stoi(getAttribute(ATTR_KEY_PERIPHERIES));
175✔
365
    vector<unique_ptr<SVGDraw>> svgDraws;
175✔
366
    auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
175✔
367
    setStrokeStyles(ellipse.get());
175✔
368
    setFillStyles(ellipse.get(), svgDraws);
175✔
369
    svgDraws.emplace_back(std::move(ellipse));
175✔
370
    for (int p = 1; p < peripheries; ++p) {
195✔
371
        auto ellipseOuter = make_unique<SVGDrawEllipse>(_cx, _cy, width() + DOUBLE_BORDER_MARGIN * 2 * p, height() + DOUBLE_BORDER_MARGIN * 2 * p);
20✔
372
        setStrokeStyles(ellipseOuter.get());
20✔
373
        ellipseOuter->setFill("none");
40✔
374
        svgDraws.emplace_back(std::move(ellipseOuter));
20✔
375
    }
20✔
376
    appendSVGDrawsLabel(svgDraws);
175✔
377
    return svgDraws;
350✔
378
}
175✔
379

380
pair<double, double> SVGNode::computeConnectionPointEllipse(const double angle) const {
438✔
381
    const auto& peripheriesStr = getAttribute(ATTR_KEY_PERIPHERIES);
438✔
382
    const int peripheries = peripheriesStr.empty() ? 1 : stoi(peripheriesStr);
438✔
383
    const double strokeWidth = penWidth();
438✔
384
    const double totalWidth = width() + strokeWidth + DOUBLE_BORDER_MARGIN * 2 * (peripheries - 1);
438✔
385
    const double totalHeight = height() + strokeWidth + DOUBLE_BORDER_MARGIN * 2 * (peripheries - 1);
438✔
386
    const double rx = totalWidth / 2, ry = totalHeight / 2;
438✔
387
    const double base = sqrt(ry * ry * cos(angle) * cos(angle) + rx * rx * sin(angle) * sin(angle));
438✔
388
    const double x = rx * ry * cos(angle) / base;
438✔
389
    const double y = rx * ry * sin(angle) / base;
438✔
390
    return {_cx + x, _cy + y};
438✔
391
}
392

393
void SVGNode::adjustNodeSizePolygon() {
118✔
394
    setAttributeIfNotExist(ATTR_KEY_SIDES, string(ATTR_DEF_SIDES));
236✔
395
    setAttributeIfNotExist(ATTR_KEY_SKEW, string(ATTR_DEF_SKEW));
236✔
396
    setAttributeIfNotExist(ATTR_KEY_DISTORTION, string(ATTR_DEF_DISTORTION));
236✔
397
    setAttributeIfNotExist(ATTR_KEY_ORIENTATION, string(ATTR_DEF_ORIENTATION));
118✔
398
    const auto [textWidth, textHeight] = computeTextSize();
118✔
399
    const auto [marginX, marginY] = computeMargin();
118✔
400
    const double boxWidth = textWidth + marginX * 2;
118✔
401
    const double boxHeight = textHeight + marginY * 2;
118✔
402
    updateNodeSize(boxWidth * numbers::sqrt2, boxHeight * numbers::sqrt2);
118✔
403
}
118✔
404

405
vector<unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsPolygon() {
118✔
406
    setAttributeIfNotExist(ATTR_KEY_PERIPHERIES, string(ATTR_DEF_PERIPHERIES));
118✔
407
    const int peripheries = stoi(getAttribute(ATTR_KEY_PERIPHERIES));
118✔
408
    vector<unique_ptr<SVGDraw>> svgDraws;
118✔
409
    if (enabledDebug()) {
118✔
410
        auto ellipse = make_unique<SVGDrawEllipse>(_cx, _cy, width(), height());
63✔
411
        ellipse->setStroke("green");
126✔
412
        ellipse->setFill("none");
126✔
413
        svgDraws.emplace_back(std::move(ellipse));
63✔
414
    }
63✔
415
    const auto vertices = computePolygonVertices();
118✔
416
    auto polygon = make_unique<SVGDrawPolygon>(vertices);
118✔
417
    setStrokeStyles(polygon.get());
118✔
418
    setFillStyles(polygon.get(), svgDraws);
118✔
419
    svgDraws.emplace_back(std::move(polygon));
118✔
420
    for (int p = 1; p < peripheries; ++p) {
125✔
421
        const auto verticesOuter = computePolygonVertices(DOUBLE_BORDER_MARGIN * p);
7✔
422
        auto polygonOuter = make_unique<SVGDrawPolygon>(verticesOuter);
7✔
423
        setStrokeStyles(polygonOuter.get());
7✔
424
        polygonOuter->setFill("none");
14✔
425
        svgDraws.emplace_back(std::move(polygonOuter));
7✔
426
    }
7✔
427
    appendSVGDrawsLabel(svgDraws);
118✔
428
    return svgDraws;
236✔
429
}
118✔
430

431
vector<pair<double, double>> SVGNode::computePolygonVertices() const {
118✔
432
    auto sides = stoi(getAttribute(ATTR_KEY_SIDES));
118✔
433
    const auto skew = stod(getAttribute(ATTR_KEY_SKEW));
118✔
434
    const auto distortion = stod(getAttribute(ATTR_KEY_DISTORTION));
118✔
435
    const auto orientation = stod(getAttribute(ATTR_KEY_ORIENTATION));
118✔
436
    if (sides < 3) {
118✔
437
        sides = 3;
2✔
438
    }
439
    const double rx = width() / 2.0;
118✔
440
    const double ry = height() / 2.0;
118✔
441
    const double sectorAngle = 2.0 * numbers::pi / sides;
118✔
442
    const double skewDist = hypot(fabs(distortion) + fabs(skew), 1.0);
118✔
443
    const double gDistortion = -distortion * numbers::sqrt2 / cos(sectorAngle / 2.0) / 2.0;
118✔
444
    const double gSkew = -skew / 2.0;
118✔
445
    const double orientationRad = -orientation * numbers::pi / 180.0;
118✔
446
    const double cosOri = cos(orientationRad);
118✔
447
    const double sinOri = sin(orientationRad);
118✔
448

449
    vector<pair<double, double>> vertices;
118✔
450
    vertices.reserve(sides);
118✔
451
    const double angleOffset = (sides % 2 == 0) ? sectorAngle / 2.0 : 0.0;
118✔
452
    for (int i = 0; i < sides; ++i) {
749✔
453
        const double baseAngle = -numbers::pi / 2.0 + angleOffset + sectorAngle * i;
631✔
454
        const double unitX = cos(baseAngle);
631✔
455
        const double unitY = sin(baseAngle);
631✔
456
        const double x = unitX * (skewDist + unitY * gDistortion) + unitY * gSkew;
631✔
457
        const double y = unitY;
631✔
458

459
        const double rotX = x * cosOri - y * sinOri;
631✔
460
        const double rotY = x * sinOri + y * cosOri;
631✔
461
        vertices.emplace_back(_cx + rotX * rx, _cy + rotY * ry);
631✔
462
    }
463
    return vertices;
236✔
464
}
118✔
465

466
vector<pair<double, double>> SVGNode::computePolygonVertices(const double scaleOffset) const {
161✔
467
    auto sides = stoi(getAttribute(ATTR_KEY_SIDES));
161✔
468
    const auto skew = stod(getAttribute(ATTR_KEY_SKEW));
161✔
469
    const auto distortion = stod(getAttribute(ATTR_KEY_DISTORTION));
161✔
470
    const auto orientation = stod(getAttribute(ATTR_KEY_ORIENTATION));
161✔
471
    if (sides < 3) {
161✔
NEW
472
        sides = 3;
×
473
    }
474
    const double rx = width() / 2.0 + scaleOffset;
161✔
475
    const double ry = height() / 2.0 + scaleOffset;
161✔
476
    const double sectorAngle = 2.0 * numbers::pi / sides;
161✔
477
    const double skewDist = hypot(fabs(distortion) + fabs(skew), 1.0);
161✔
478
    const double gDistortion = -distortion * numbers::sqrt2 / cos(sectorAngle / 2.0) / 2.0;
161✔
479
    const double gSkew = -skew / 2.0;
161✔
480
    const double orientationRad = -orientation * numbers::pi / 180.0;
161✔
481
    const double cosOri = cos(orientationRad);
161✔
482
    const double sinOri = sin(orientationRad);
161✔
483

484
    vector<pair<double, double>> vertices;
161✔
485
    vertices.reserve(sides);
161✔
486
    const double angleOffset = (sides % 2 == 0) ? sectorAngle / 2.0 : 0.0;
161✔
487
    for (int i = 0; i < sides; ++i) {
1,263✔
488
        const double baseAngle = -numbers::pi / 2.0 + angleOffset + sectorAngle * i;
1,102✔
489
        const double unitX = cos(baseAngle);
1,102✔
490
        const double unitY = sin(baseAngle);
1,102✔
491
        const double x = unitX * (skewDist + unitY * gDistortion) + unitY * gSkew;
1,102✔
492
        const double y = unitY;
1,102✔
493

494
        const double rotX = x * cosOri - y * sinOri;
1,102✔
495
        const double rotY = x * sinOri + y * cosOri;
1,102✔
496
        vertices.emplace_back(_cx + rotX * rx, _cy + rotY * ry);
1,102✔
497
    }
498
    return vertices;
322✔
499
}
161✔
500

501
pair<double, double> SVGNode::computeConnectionPointPolygon(const double angle) const {
154✔
502
    const auto& peripheriesStr = getAttribute(ATTR_KEY_PERIPHERIES);
154✔
503
    const int peripheries = peripheriesStr.empty() ? 1 : stoi(peripheriesStr);
154✔
504
    const auto vertices = computePolygonVertices(DOUBLE_BORDER_MARGIN * (peripheries - 1));
154✔
505
    const int n = static_cast<int>(vertices.size());
154✔
506
    for (int i = 0; i < n; ++i) {
581✔
507
        const auto& [x1, y1] = vertices[i];
581✔
508
        const auto& [x2, y2] = vertices[(i + 1) % n];
581✔
509

510
        const double rx1 = x1 - _cx;
581✔
511
        const double ry1 = y1 - _cy;
581✔
512
        const double rx2 = x2 - _cx;
581✔
513
        const double ry2 = y2 - _cy;
581✔
514

515
        if (const auto intersect = GeometryUtils::intersect(angle, rx1, ry1, rx2, ry2); intersect != nullopt) {
581✔
516
            return {_cx + intersect.value().first, _cy + intersect.value().second};
154✔
517
        }
518
    }
519
    return computeConnectionPointEllipse(angle);
×
520
}
154✔
521

522
void SVGNode::adjustNodeSizeRecord() {
55✔
523
    _recordSizes.clear();
55✔
524
    function<pair<double, double>(const unique_ptr<RecordLabel>&, bool)> adjustRecordSize;
55✔
525
    adjustRecordSize = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal) -> pair<double, double> {
438✔
526
        if (recordLabel->children.empty()) {
328✔
527
            const SVGTextSize textSize;
204✔
528
            const double fontSize = stod(getAttribute(ATTR_KEY_FONT_SIZE));
204✔
529
            const string fontFamily = getAttribute(ATTR_KEY_FONT_NAME);
204✔
530
            auto [width, height] = textSize.computeTextSize(recordLabel->label, fontSize, fontFamily);
204✔
531
            if (width == 0.0) {
204✔
532
                width = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_WIDTH_SCALE;
89✔
533
            }
534
            if (height == 0.0) {
204✔
535
                height = fontSize * SVGTextSize::DEFAULT_APPROXIMATION_HEIGHT_SCALE;
89✔
536
            }
537
            const auto [marginX, marginY] = computeMargin();
204✔
538
            width += marginX * 2;
204✔
539
            height += marginY * 2;
204✔
540
            _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
204✔
541
            return {width, height};
204✔
542
        }
204✔
543
        double width = 0.0, height = 0.0;
124✔
544
        for (const auto& child : recordLabel->children) {
397✔
545
            const auto [subWidth, subHeight] = adjustRecordSize(child, !horizontal);
273✔
546
            if (horizontal) {
273✔
547
                width += subWidth;
153✔
548
                height = max(height, subHeight);
153✔
549
            } else {
550
                height += subHeight;
120✔
551
                width = max(width, subWidth);
120✔
552
            }
553
        }
554
        _recordSizes[reinterpret_cast<uintptr_t>(recordLabel.get())] = {width, height};
124✔
555
        return {width, height};
124✔
556
    };
55✔
557
    _recordLabel = AttributeUtils::parseRecordLabel(getAttribute(ATTR_KEY_LABEL));
55✔
558
    const auto [width, height] = adjustRecordSize(_recordLabel, true);
55✔
559
    updateNodeSize(width, height);
55✔
560
}
55✔
561

562
std::vector<std::unique_ptr<SVGDraw>> SVGNode::produceSVGDrawsRecord() {
55✔
563
    const auto nodeWidth = width();
55✔
564
    const auto nodeHeight = height();
55✔
565
    vector<unique_ptr<SVGDraw>> svgDraws;
55✔
566
    auto rect = make_unique<SVGDrawRect>(_cx, _cy, nodeWidth, nodeHeight);
55✔
567
    setStrokeStyles(rect.get());
55✔
568
    setFillStyles(rect.get(), svgDraws);
55✔
569
    svgDraws.emplace_back(std::move(rect));
55✔
570
    const auto [marginX, marginY] = margin();
55✔
571

572
    _recordPositions.clear();
55✔
573
    function<void(const unique_ptr<RecordLabel>&, bool, double, double, double, double)> drawRecordLabel;
55✔
574
    drawRecordLabel = [&](const unique_ptr<RecordLabel>& recordLabel, const bool horizontal, double x1, double y1, const double x2, const double y2) {
×
575
        const double cx = (x1 + x2) / 2;
328✔
576
        const double cy = (y1 + y2) / 2;
328✔
577
        if (!recordLabel->fieldId.empty()) {
328✔
578
            _recordPositions[recordLabel->fieldId] = {cx, cy, x2 - x1, y2 - y1};
20✔
579
        }
580
        if (recordLabel->children.empty()) {
328✔
581
            appendSVGDrawsLabelWithLocation(svgDraws, recordLabel->label, cx, cy, x2 - x1 - marginX * 2, y2 - y1 - marginY * 2);
204✔
582
            return;
204✔
583
        }
584
        bool first = true;
124✔
585
        for (const auto& child : recordLabel->children) {
397✔
586
            if (first) {
273✔
587
                first = false;
124✔
588
            } else {
589
                if (horizontal) {
149✔
590
                    auto line = make_unique<SVGDrawLine>(x1, y1, x1, y2);
73✔
591
                    setStrokeStyles(line.get());
73✔
592
                    svgDraws.emplace_back(std::move(line));
73✔
593
                } else {
73✔
594
                    auto line = make_unique<SVGDrawLine>(x1, y1, x2, y1);
76✔
595
                    setStrokeStyles(line.get());
76✔
596
                    svgDraws.emplace_back(std::move(line));
76✔
597
                }
76✔
598
            }
599
            const auto [subWidth, subHeight] = _recordSizes[reinterpret_cast<uintptr_t>(child.get())];
273✔
600
            if (horizontal) {
273✔
601
                drawRecordLabel(child, false, x1, y1, x1 + subWidth, y2);
153✔
602
                x1 += subWidth;
153✔
603
            } else {
604
                drawRecordLabel(child, true, x1, y1, x2, y1 + subHeight);
120✔
605
                y1 += subHeight;
120✔
606
            }
607
        }
608
    };
55✔
609
    const double x1 = _cx - nodeWidth / 2, y1 = _cy - nodeHeight / 2;
55✔
610
    const double x2 = _cx + nodeWidth / 2, y2 = _cy + nodeHeight / 2;
55✔
611
    drawRecordLabel(_recordLabel, true, x1, y1, x2, y2);
55✔
612

613
    return svgDraws;
110✔
614
}
55✔
615

616
std::pair<double, double> SVGNode::computeConnectionPointRecord(const double angle) const {
33✔
617
    return computeConnectionPointRect(angle);
33✔
618
}
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