data/RelationGraphClusters.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "RelationGraphClusters.h" | ||
| 2 | |||
| 3 | #include "RelationGraphBlocks.h" | ||
| 4 | #include "RelationGraphForce.h" | ||
| 5 | #include "RelationGraphLayout.h" | ||
| 6 | #include "RelationGraphRows.h" | ||
| 7 | |||
| 8 | #include <QMap> | ||
| 9 | #include <QSet> | ||
| 10 | #include <algorithm> | ||
| 11 | #include <cmath> | ||
| 12 | #include <functional> | ||
| 13 | |||
| 14 | namespace RelationGraphLayout { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | // Relationships that join people into one cluster (not memberships, layers or suggestions). | ||
| 19 | 2996 | bool joins(const RelationGraph::Edge &edge) { | |
| 20 |
6/6✓ Branch 4 → 5 taken 206 times.
✓ Branch 4 → 11 taken 2790 times.
✓ Branch 7 → 8 taken 56 times.
✓ Branch 7 → 11 taken 150 times.
✓ Branch 10 → 11 taken 24 times.
✓ Branch 10 → 12 taken 32 times.
|
3052 | return edge.kind == QLatin1String("relation") || edge.kind == QLatin1String("inferred") || |
| 21 | 3052 | edge.kind == QLatin1String("via"); | |
| 22 | } | ||
| 23 | |||
| 24 | 22 | QHash<int, QList<int>> neighboursOf(const RelationGraph::Graph &graph) { | |
| 25 | 22 | QHash<int, QList<int>> out; | |
| 26 | 22 | const int n = int(graph.nodes.size()); | |
| 27 |
2/2✓ Branch 29 → 5 taken 2986 times.
✓ Branch 29 → 30 taken 22 times.
|
3008 | for (const RelationGraph::Edge &e : graph.edges) { |
| 28 |
5/10✓ Branch 8 → 9 taken 2954 times.
✗ Branch 8 → 19 not taken.
✓ Branch 9 → 10 taken 2954 times.
✗ Branch 9 → 19 not taken.
✓ Branch 10 → 11 taken 2954 times.
✗ Branch 10 → 19 not taken.
✓ Branch 11 → 12 taken 2954 times.
✗ Branch 11 → 19 not taken.
✓ Branch 12 → 13 taken 2954 times.
✗ Branch 12 → 19 not taken.
|
5940 | if (!joins(e) || e.from < 0 || e.to < 0 || e.from >= n || e.to >= n || e.from == e.to || |
| 29 |
6/8✓ Branch 7 → 8 taken 2954 times.
✓ Branch 7 → 19 taken 32 times.
✓ Branch 15 → 16 taken 2954 times.
✗ Branch 15 → 19 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2954 times.
✓ Branch 21 → 22 taken 32 times.
✓ Branch 21 → 23 taken 2954 times.
|
5940 | !isShown(graph.nodes.at(e.from)) || !isShown(graph.nodes.at(e.to))) |
| 30 | 32 | continue; | |
| 31 |
2/2✓ Branch 23 → 24 taken 2954 times.
✓ Branch 24 → 25 taken 2954 times.
|
2954 | out[e.from].append(e.to); |
| 32 |
2/2✓ Branch 25 → 26 taken 2954 times.
✓ Branch 26 → 27 taken 2954 times.
|
2954 | out[e.to].append(e.from); |
| 33 | } | ||
| 34 |
3/3✓ Branch 30 → 31 taken 22 times.
✓ Branch 45 → 32 taken 2118 times.
✓ Branch 45 → 46 taken 22 times.
|
2140 | for (QList<int> &list : out) { |
| 35 |
3/3✓ Branch 33 → 34 taken 2118 times.
✓ Branch 34 → 35 taken 2118 times.
✓ Branch 35 → 36 taken 2118 times.
|
2118 | std::sort(list.begin(), list.end()); |
| 36 |
5/5✓ Branch 36 → 37 taken 2118 times.
✓ Branch 38 → 39 taken 2118 times.
✓ Branch 39 → 40 taken 2118 times.
✓ Branch 40 → 41 taken 2118 times.
✓ Branch 42 → 43 taken 2118 times.
|
2118 | list.erase(std::unique(list.begin(), list.end()), list.end()); |
| 37 | } | ||
| 38 | 22 | return out; | |
| 39 | ✗ | } | |
| 40 | |||
| 41 | 202 | bool byName(const RelationGraph::Graph &graph, int a, int b) { | |
| 42 | 202 | const int c = graph.nodes.at(a).name.localeAwareCompare(graph.nodes.at(b).name); | |
| 43 |
1/2✓ Branch 5 → 6 taken 202 times.
✗ Branch 5 → 7 not taken.
|
202 | return c != 0 ? c < 0 : graph.nodes.at(a).key < graph.nodes.at(b).key; |
| 44 | } | ||
| 45 | |||
| 46 | // The satellites of a family cluster in columns beside its tree: each on | ||
| 47 | // the side of the family member it is joined to and as near that member's | ||
| 48 | // height as the column allows, those joined through it right below it. | ||
| 49 | 336 | void placeSatellites(const RelationGraph::Graph &graph, const Cluster &cluster, QHash<int, QPointF> *placed) { | |
| 50 | 336 | double minX = 0, maxX = 0, minY = 0, maxY = 0; | |
| 51 | 336 | bool first = true; | |
| 52 |
2/2✓ Branch 28 → 4 taken 1492 times.
✓ Branch 28 → 29 taken 336 times.
|
1828 | for (int i : cluster.tree) { |
| 53 | 1492 | const QPointF p = placed->value(i); | |
| 54 |
2/2✓ Branch 6 → 7 taken 336 times.
✓ Branch 6 → 8 taken 1156 times.
|
1492 | minX = first ? p.x() : std::min(minX, p.x()); |
| 55 |
2/2✓ Branch 11 → 12 taken 336 times.
✓ Branch 11 → 13 taken 1156 times.
|
1492 | maxX = first ? p.x() : std::max(maxX, p.x()); |
| 56 |
2/2✓ Branch 16 → 17 taken 336 times.
✓ Branch 16 → 18 taken 1156 times.
|
1492 | minY = first ? p.y() : std::min(minY, p.y()); |
| 57 |
2/2✓ Branch 21 → 22 taken 336 times.
✓ Branch 21 → 23 taken 1156 times.
|
1492 | maxY = first ? p.y() : std::max(maxY, p.y()); |
| 58 | 1492 | first = false; | |
| 59 | } | ||
| 60 |
1/1✓ Branch 31 → 32 taken 336 times.
|
336 | const QSet<int> inTree(cluster.tree.cbegin(), cluster.tree.cend()); |
| 61 | 336 | QHash<int, QList<int>> children; | |
| 62 | 336 | QList<int> top; | |
| 63 |
2/2✓ Branch 44 → 34 taken 615 times.
✓ Branch 44 → 45 taken 336 times.
|
951 | for (int s : cluster.satellites) { |
| 64 | 615 | const int up = cluster.parent.value(s, -1); | |
| 65 |
4/4✓ Branch 37 → 38 taken 538 times.
✓ Branch 37 → 39 taken 77 times.
✓ Branch 39 → 40 taken 77 times.
✓ Branch 41 → 42 taken 615 times.
|
615 | (inTree.contains(up) ? top : children[up]).append(s); |
| 66 | } | ||
| 67 | 336 | const double middle = (minX + maxX) / 2; | |
| 68 | 336 | QList<int> left, right; | |
| 69 |
2/2✓ Branch 62 → 48 taken 538 times.
✓ Branch 62 → 63 taken 336 times.
|
874 | for (int s : std::as_const(top)) { |
| 70 | 538 | const double x = placed->value(cluster.parent.value(s)).x(); | |
| 71 |
7/7✓ Branch 52 → 53 taken 292 times.
✓ Branch 52 → 57 taken 246 times.
✓ Branch 53 → 54 taken 53 times.
✓ Branch 53 → 58 taken 239 times.
✓ Branch 56 → 57 taken 34 times.
✓ Branch 56 → 58 taken 19 times.
✓ Branch 59 → 60 taken 538 times.
|
538 | (x < middle || (x == middle && left.size() <= right.size()) ? left : right).append(s); |
| 72 | } | ||
| 73 | 601 | const auto byAnchor = [&](int a, int b) { | |
| 74 | 601 | const QPointF pa = placed->value(cluster.parent.value(a)), pb = placed->value(cluster.parent.value(b)); | |
| 75 |
2/2✓ Branch 8 → 9 taken 249 times.
✓ Branch 8 → 12 taken 352 times.
|
601 | if (pa.y() != pb.y()) |
| 76 | 249 | return pa.y() < pb.y(); | |
| 77 |
2/2✓ Branch 14 → 15 taken 150 times.
✓ Branch 14 → 18 taken 202 times.
|
352 | if (pa.x() != pb.x()) |
| 78 | 150 | return pa.x() < pb.x(); | |
| 79 |
1/1✓ Branch 18 → 19 taken 202 times.
|
202 | return byName(graph, a, b); |
| 80 | 336 | }; | |
| 81 |
2/2✓ Branch 123 → 65 taken 672 times.
✓ Branch 123 → 124 taken 336 times.
|
1008 | for (const int side : {-1, 1}) { |
| 82 |
2/2✓ Branch 65 → 66 taken 336 times.
✓ Branch 65 → 67 taken 336 times.
|
672 | QList<int> &tops = side < 0 ? left : right; |
| 83 |
3/3✓ Branch 68 → 69 taken 672 times.
✓ Branch 69 → 70 taken 672 times.
✓ Branch 70 → 71 taken 672 times.
|
672 | std::sort(tops.begin(), tops.end(), byAnchor); |
| 84 | 672 | QList<int> column; | |
| 85 | ✗ | const std::function<void(int)> visit = [&](int s) { | |
| 86 | 615 | column.append(s); | |
| 87 |
4/4✓ Branch 4 → 5 taken 615 times.
✓ Branch 5 → 6 taken 615 times.
✓ Branch 11 → 7 taken 77 times.
✓ Branch 11 → 12 taken 615 times.
|
692 | for (int child : children.value(s)) |
| 88 |
1/1✓ Branch 8 → 9 taken 77 times.
|
692 | visit(child); |
| 89 |
1/1✓ Branch 71 → 72 taken 672 times.
|
1287 | }; |
| 90 |
2/2✓ Branch 79 → 75 taken 538 times.
✓ Branch 79 → 80 taken 672 times.
|
1210 | for (int s : std::as_const(tops)) |
| 91 |
1/1✓ Branch 76 → 77 taken 538 times.
|
538 | visit(s); |
| 92 | // Each sub-column of ColumnHeight people stands as near the height of | ||
| 93 | // the people they are joined to as ColumnStep allows (isotonic regression). | ||
| 94 |
2/2✓ Branch 119 → 81 taken 296 times.
✓ Branch 119 → 120 taken 672 times.
|
968 | for (int from = 0; from < column.size(); from += ColumnHeight) { |
| 95 | 296 | const int sub = from / ColumnHeight; | |
| 96 |
1/1✓ Branch 81 → 82 taken 296 times.
|
296 | const QList<int> part = column.mid(from, ColumnHeight); |
| 97 | // The aim is the height of the family member the chain starts at; in | ||
| 98 | // the column's order (by that height, a chain together) the aims ascend. | ||
| 99 | 296 | QList<double> targets; | |
| 100 |
2/2✓ Branch 101 → 84 taken 615 times.
✓ Branch 101 → 102 taken 296 times.
|
911 | for (int s : part) { |
| 101 | 615 | int anchor = cluster.parent.value(s, -1); | |
| 102 |
5/6✓ Branch 90 → 91 taken 83 times.
✓ Branch 90 → 94 taken 615 times.
✓ Branch 92 → 93 taken 83 times.
✗ Branch 92 → 94 not taken.
✓ Branch 95 → 87 taken 83 times.
✓ Branch 95 → 96 taken 615 times.
|
698 | while (!inTree.contains(anchor) && cluster.parent.contains(anchor)) |
| 103 | 83 | anchor = cluster.parent.value(anchor); | |
| 104 |
1/1✓ Branch 98 → 99 taken 615 times.
|
615 | targets.append(placed->value(anchor).y()); |
| 105 | } | ||
| 106 |
1/1✓ Branch 102 → 103 taken 296 times.
|
296 | const QList<double> ys = place(targets, ColumnStep); |
| 107 |
2/2✓ Branch 103 → 104 taken 163 times.
✓ Branch 103 → 105 taken 133 times.
|
296 | const double x = side < 0 ? minX - ColumnGap - sub * ColumnGap : maxX + ColumnGap + sub * ColumnGap; |
| 108 |
2/2✓ Branch 113 → 107 taken 615 times.
✓ Branch 113 → 114 taken 296 times.
|
911 | for (int k = 0; k < part.size(); ++k) |
| 109 |
1/1✓ Branch 110 → 111 taken 615 times.
|
615 | placed->insert(part.at(k), QPointF(x, ys.at(k) + (sub % 2) * ColumnStep / 2)); |
| 110 | 296 | } | |
| 111 | 672 | } | |
| 112 | 336 | } | |
| 113 | |||
| 114 | // A cluster without a family: a small force layout of its people, pushed apart until no two overlap. | ||
| 115 | 1 | void placeGroup(const RelationGraph::Graph &graph, const Cluster &cluster, QHash<int, QPointF> *placed) { | |
| 116 | 1 | const QList<int> &people = cluster.satellites; | |
| 117 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
1 | if (people.size() == 1) { |
| 118 | ✗ | placed->insert(people.first(), QPointF()); | |
| 119 | ✗ | return; | |
| 120 | } | ||
| 121 | 1 | QHash<int, int> local; | |
| 122 |
2/2✓ Branch 13 → 9 taken 2 times.
✓ Branch 13 → 14 taken 1 time.
|
3 | for (int k = 0; k < people.size(); ++k) |
| 123 |
1/1✓ Branch 10 → 11 taken 2 times.
|
2 | local.insert(people.at(k), k); |
| 124 | 1 | const double spread = 130 * std::sqrt(double(people.size())); | |
| 125 | 1 | QList<QPointF> positions; | |
| 126 |
2/2✓ Branch 23 → 17 taken 2 times.
✓ Branch 23 → 24 taken 1 time.
|
3 | for (int i : people) |
| 127 |
2/2✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
|
2 | positions.append(RelationGraphForce::seeded(graph.nodes.at(i).key, spread)); |
| 128 | 1 | QList<QPair<int, int>> links; | |
| 129 |
2/2✓ Branch 44 → 26 taken 10 times.
✓ Branch 44 → 45 taken 1 time.
|
11 | for (const RelationGraph::Edge &e : graph.edges) |
| 130 |
7/10✓ Branch 28 → 29 taken 10 times.
✗ Branch 28 → 35 not taken.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 35 taken 9 times.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 32 → 35 not taken.
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 35 not taken.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 42 taken 9 times.
|
10 | if (joins(e) && local.contains(e.from) && local.contains(e.to) && e.from != e.to) |
| 131 |
1/1✓ Branch 40 → 41 taken 1 time.
|
1 | links.append({local.value(e.from), local.value(e.to)}); |
| 132 |
2/2✓ Branch 47 → 48 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
|
1 | RelationGraphForce::layout(&positions, links, QList<bool>(people.size(), false), 130, 200, spread / 10); |
| 133 |
2/2✓ Branch 55 → 56 taken 1 time.
✓ Branch 56 → 57 taken 1 time.
|
1 | RelationGraphForce::separate(&positions, QList<QSizeF>(people.size(), QSizeF(TreeGap, 110)), |
| 134 |
1/1✓ Branch 52 → 53 taken 1 time.
|
2 | QList<bool>(people.size(), false)); |
| 135 |
2/2✓ Branch 65 → 60 taken 2 times.
✓ Branch 65 → 66 taken 1 time.
|
3 | for (int k = 0; k < people.size(); ++k) |
| 136 |
1/1✓ Branch 62 → 63 taken 2 times.
|
2 | placed->insert(people.at(k), positions.at(k)); |
| 137 | 1 | } | |
| 138 | |||
| 139 | // Where a line from a cluster's middle towards `towards` leaves its rectangle. | ||
| 140 | 214 | QPointF rimPoint(QPointF centre, QSizeF size, QPointF towards) { | |
| 141 | 214 | const QPointF d = towards - centre; | |
| 142 |
2/2✓ Branch 5 → 6 taken 206 times.
✓ Branch 5 → 10 taken 8 times.
|
214 | const double tx = std::abs(d.x()) > 1e-6 ? size.width() / 2 / std::abs(d.x()) : 1e9; |
| 143 |
2/2✓ Branch 13 → 14 taken 180 times.
✓ Branch 13 → 18 taken 34 times.
|
214 | const double ty = std::abs(d.y()) > 1e-6 ? size.height() / 2 / std::abs(d.y()) : 1e9; |
| 144 |
1/1✓ Branch 19 → 20 taken 214 times.
|
214 | return centre + d * std::min({tx, ty, 1.0}); |
| 145 | } | ||
| 146 | |||
| 147 | } // namespace | ||
| 148 | |||
| 149 | 22 | QList<Cluster> overviewClusters(const RelationGraph::Graph &graph, QList<int> *clusterOf) { | |
| 150 | 22 | const int n = int(graph.nodes.size()); | |
| 151 |
1/1✓ Branch 3 → 4 taken 22 times.
|
22 | *clusterOf = QList<int>(n, -1); |
| 152 | 22 | QSet<int> inArea; | |
| 153 | 22 | QList<Cluster> clusters; | |
| 154 | 22 | QList<int> singles; | |
| 155 |
5/5✓ Branch 7 → 8 taken 22 times.
✓ Branch 8 → 9 taken 22 times.
✓ Branch 9 → 10 taken 22 times.
✓ Branch 38 → 11 taken 960 times.
✓ Branch 38 → 39 taken 22 times.
|
982 | for (const QList<int> &block : overviewBlocks(graph, &inArea)) { |
| 156 |
1/1✓ Branch 14 → 15 taken 960 times.
|
3010 | const bool area = std::any_of(block.cbegin(), block.cend(), [&](int i) { return inArea.contains(i); }); |
| 157 |
5/6✓ Branch 16 → 17 taken 622 times.
✓ Branch 16 → 19 taken 338 times.
✓ Branch 17 → 18 taken 622 times.
✗ Branch 17 → 19 not taken.
✓ Branch 20 → 21 taken 622 times.
✓ Branch 20 → 24 taken 338 times.
|
960 | if (block.size() == 1 && !area) { |
| 158 |
1/1✓ Branch 22 → 23 taken 622 times.
|
622 | singles.append(block.first()); |
| 159 | 622 | continue; | |
| 160 | } | ||
| 161 | 338 | Cluster cluster; | |
| 162 | 338 | cluster.tree = block; | |
| 163 | 338 | cluster.family = area; | |
| 164 |
2/2✓ Branch 32 → 27 taken 1497 times.
✓ Branch 32 → 33 taken 338 times.
|
1835 | for (int i : block) |
| 165 |
1/1✓ Branch 29 → 30 taken 1497 times.
|
1497 | (*clusterOf)[i] = int(clusters.size()); |
| 166 |
1/1✓ Branch 33 → 34 taken 338 times.
|
338 | clusters.append(cluster); |
| 167 | 360 | } | |
| 168 |
1/1✓ Branch 40 → 41 taken 22 times.
|
22 | const QHash<int, QList<int>> neighbours = neighboursOf(graph); |
| 169 | // Breadth first from every family at once: each other person joins the family it reaches first. | ||
| 170 | 22 | QList<int> queue; | |
| 171 |
2/2✓ Branch 48 → 44 taken 338 times.
✓ Branch 48 → 49 taken 22 times.
|
360 | for (const Cluster &cluster : std::as_const(clusters)) |
| 172 |
1/1✓ Branch 45 → 46 taken 338 times.
|
338 | queue += cluster.tree; |
| 173 |
2/2✓ Branch 71 → 50 taken 2115 times.
✓ Branch 71 → 72 taken 22 times.
|
2137 | for (int head = 0; head < queue.size(); ++head) { |
| 174 | 2115 | const int u = queue.at(head); | |
| 175 |
4/4✓ Branch 52 → 53 taken 2115 times.
✓ Branch 53 → 54 taken 2115 times.
✓ Branch 67 → 55 taken 5876 times.
✓ Branch 67 → 68 taken 2115 times.
|
7991 | for (int v : neighbours.value(u)) |
| 176 |
2/2✓ Branch 57 → 58 taken 618 times.
✓ Branch 57 → 65 taken 5258 times.
|
5876 | if (clusterOf->at(v) < 0) { |
| 177 | 618 | const int c = clusterOf->at(u); | |
| 178 |
1/1✓ Branch 59 → 60 taken 618 times.
|
618 | (*clusterOf)[v] = c; |
| 179 |
2/2✓ Branch 60 → 61 taken 618 times.
✓ Branch 61 → 62 taken 618 times.
|
618 | clusters[c].satellites.append(v); |
| 180 |
2/2✓ Branch 62 → 63 taken 618 times.
✓ Branch 63 → 64 taken 618 times.
|
618 | clusters[c].parent.insert(v, u); |
| 181 |
1/1✓ Branch 64 → 65 taken 618 times.
|
618 | queue.append(v); |
| 182 | 2115 | } | |
| 183 | } | ||
| 184 | // Everyone else: one cluster per group joined by relationships. | ||
| 185 |
2/2✓ Branch 106 → 75 taken 622 times.
✓ Branch 106 → 107 taken 22 times.
|
644 | for (int start : std::as_const(singles)) { |
| 186 |
2/2✓ Branch 77 → 78 taken 620 times.
✓ Branch 77 → 79 taken 2 times.
|
622 | if (clusterOf->at(start) >= 0) |
| 187 | 620 | continue; | |
| 188 | 2 | Cluster cluster; | |
| 189 | 2 | const int c = int(clusters.size()); | |
| 190 |
1/1✓ Branch 80 → 81 taken 2 times.
|
2 | (*clusterOf)[start] = c; |
| 191 |
1/1✓ Branch 81 → 82 taken 2 times.
|
2 | cluster.satellites.append(start); |
| 192 |
2/2✓ Branch 100 → 83 taken 4 times.
✓ Branch 100 → 101 taken 2 times.
|
6 | for (int head = 0; head < cluster.satellites.size(); ++head) { |
| 193 | 4 | const int u = cluster.satellites.at(head); | |
| 194 |
4/4✓ Branch 85 → 86 taken 4 times.
✓ Branch 86 → 87 taken 4 times.
✓ Branch 96 → 88 taken 4 times.
✓ Branch 96 → 97 taken 4 times.
|
8 | for (int v : neighbours.value(u)) |
| 195 |
2/2✓ Branch 90 → 91 taken 2 times.
✓ Branch 90 → 94 taken 2 times.
|
4 | if (clusterOf->at(v) < 0) { |
| 196 |
1/1✓ Branch 91 → 92 taken 2 times.
|
2 | (*clusterOf)[v] = c; |
| 197 |
1/1✓ Branch 92 → 93 taken 2 times.
|
2 | cluster.satellites.append(v); |
| 198 |
1/1✓ Branch 93 → 94 taken 2 times.
|
2 | cluster.parent.insert(v, u); |
| 199 | 4 | } | |
| 200 | } | ||
| 201 |
1/1✓ Branch 101 → 102 taken 2 times.
|
2 | clusters.append(cluster); |
| 202 | 2 | } | |
| 203 | 22 | return clusters; | |
| 204 | 22 | } | |
| 205 | |||
| 206 | 337 | QHash<int, QPointF> clusterLayout(const RelationGraph::Graph &graph, const Cluster &cluster, QSizeF *size) { | |
| 207 | 337 | QHash<int, QPointF> placed; | |
| 208 |
2/2✓ Branch 3 → 4 taken 336 times.
✓ Branch 3 → 10 taken 1 time.
|
337 | if (!cluster.tree.isEmpty()) { |
| 209 |
2/2✓ Branch 4 → 5 taken 336 times.
✓ Branch 5 → 6 taken 336 times.
|
336 | placed = rows(graph, cluster.tree, allGenerations(graph, cluster.tree)); |
| 210 |
1/1✓ Branch 9 → 11 taken 336 times.
|
336 | placeSatellites(graph, cluster, &placed); |
| 211 | } else { | ||
| 212 |
1/1✓ Branch 10 → 11 taken 1 time.
|
1 | placeGroup(graph, cluster, &placed); |
| 213 | } | ||
| 214 | 337 | double minX = 0, maxX = 0, minY = 0, maxY = 0; | |
| 215 | 337 | bool first = true; | |
| 216 |
2/2✓ Branch 37 → 14 taken 2109 times.
✓ Branch 37 → 38 taken 337 times.
|
2446 | for (const QPointF &p : std::as_const(placed)) { |
| 217 |
2/2✓ Branch 15 → 16 taken 337 times.
✓ Branch 15 → 17 taken 1772 times.
|
2109 | minX = first ? p.x() : std::min(minX, p.x()); |
| 218 |
2/2✓ Branch 20 → 21 taken 337 times.
✓ Branch 20 → 22 taken 1772 times.
|
2109 | maxX = first ? p.x() : std::max(maxX, p.x()); |
| 219 |
2/2✓ Branch 25 → 26 taken 337 times.
✓ Branch 25 → 27 taken 1772 times.
|
2109 | minY = first ? p.y() : std::min(minY, p.y()); |
| 220 |
2/2✓ Branch 30 → 31 taken 337 times.
✓ Branch 30 → 32 taken 1772 times.
|
2109 | maxY = first ? p.y() : std::max(maxY, p.y()); |
| 221 | 2109 | first = false; | |
| 222 | } | ||
| 223 | // A person is an avatar with a name and a role below: TreeGap × 110 px | ||
| 224 | // with room around. A family's area reaches AreaRoom beyond its people, | ||
| 225 | // with the family name on its rim: the cluster keeps that room free. | ||
| 226 |
2/2✓ Branch 38 → 39 taken 10 times.
✓ Branch 38 → 40 taken 327 times.
|
337 | const double room = cluster.family ? AreaRoom : 0; |
| 227 | 337 | const QPointF middle((minX + maxX) / 2, (minY + maxY) / 2 + 13); | |
| 228 |
3/3✓ Branch 42 → 43 taken 337 times.
✓ Branch 49 → 44 taken 2109 times.
✓ Branch 49 → 50 taken 337 times.
|
2446 | for (auto it = placed.begin(); it != placed.end(); ++it) |
| 229 | 2109 | it.value() -= middle; | |
| 230 | 337 | *size = QSizeF(maxX - minX + TreeGap + 2 * room, maxY - minY + 110 + 2 * room); | |
| 231 | 337 | return placed; | |
| 232 | ✗ | } | |
| 233 | |||
| 234 | 21 | void bundleRoutes(const RelationGraph::Graph &graph, const QList<QPointF> &positions, const QList<int> &clusterOf, | |
| 235 | const QList<QPointF> ¢res, const QList<QSizeF> &sizes, QList<Route> *routes) { | ||
| 236 | 21 | QMap<QPair<int, int>, QList<int>> between; | |
| 237 |
5/6✓ Branch 30 → 31 taken 2976 times.
✓ Branch 30 → 34 taken 21 times.
✓ Branch 32 → 33 taken 2976 times.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 3 taken 2976 times.
✓ Branch 35 → 36 taken 21 times.
|
2997 | for (int e = 0; e < graph.edges.size() && e < routes->size(); ++e) { |
| 238 | 2976 | const RelationGraph::Edge &edge = graph.edges.at(e); | |
| 239 |
6/6✓ Branch 6 → 7 taken 2944 times.
✓ Branch 6 → 11 taken 32 times.
✓ Branch 10 → 11 taken 156 times.
✓ Branch 10 → 12 taken 2788 times.
✓ Branch 13 → 14 taken 188 times.
✓ Branch 13 → 15 taken 2788 times.
|
2976 | if (edge.kind == QLatin1String("member") || routes->at(e).shape == QLatin1String("none")) |
| 240 | 2023 | continue; | |
| 241 | 2788 | const int a = clusterOf.value(edge.from, -1), b = clusterOf.value(edge.to, -1); | |
| 242 |
4/6✓ Branch 17 → 18 taken 2788 times.
✗ Branch 17 → 20 not taken.
✓ Branch 18 → 19 taken 2788 times.
✗ Branch 18 → 20 not taken.
✓ Branch 19 → 20 taken 1835 times.
✓ Branch 19 → 21 taken 953 times.
|
2788 | if (a < 0 || b < 0 || a == b) |
| 243 | 1835 | continue; | |
| 244 |
2/2✓ Branch 24 → 25 taken 953 times.
✓ Branch 25 → 26 taken 953 times.
|
953 | between[{std::min(a, b), std::max(a, b)}].append(e); |
| 245 | } | ||
| 246 |
4/4✓ Branch 36 → 37 taken 21 times.
✓ Branch 103 → 104 taken 817 times.
✓ Branch 105 → 38 taken 796 times.
✓ Branch 105 → 106 taken 21 times.
|
817 | for (auto it = between.cbegin(); it != between.cend(); ++it) { |
| 247 |
2/2✓ Branch 40 → 41 taken 689 times.
✓ Branch 40 → 42 taken 107 times.
|
796 | if (it.value().size() < 2) |
| 248 | 689 | continue; | |
| 249 | 107 | const int a = it.key().first, b = it.key().second; | |
| 250 | 107 | const QPointF ca = centres.at(a), cb = centres.at(b); | |
| 251 | 107 | const double length = std::hypot(cb.x() - ca.x(), cb.y() - ca.y()); | |
| 252 |
1/2✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 107 times.
|
107 | if (length < 1) |
| 253 | ✗ | continue; | |
| 254 | 107 | const QPointF d = (cb - ca) / length; | |
| 255 |
2/2✓ Branch 55 → 56 taken 107 times.
✓ Branch 57 → 58 taken 107 times.
|
107 | const QPointF hubA = rimPoint(ca, sizes.at(a), cb), hubB = rimPoint(cb, sizes.at(b), ca); |
| 256 |
2/2✓ Branch 99 → 61 taken 264 times.
✓ Branch 99 → 100 taken 107 times.
|
371 | for (int e : it.value()) { |
| 257 | 264 | const RelationGraph::Edge &edge = graph.edges.at(e); | |
| 258 | 264 | const bool fromA = clusterOf.at(edge.from) == a; | |
| 259 | 264 | const QPointF start = positions.at(edge.from), end = positions.at(edge.to); | |
| 260 |
4/4✓ Branch 66 → 67 taken 108 times.
✓ Branch 66 → 68 taken 156 times.
✓ Branch 69 → 70 taken 108 times.
✓ Branch 69 → 71 taken 156 times.
|
264 | const QPointF first = fromA ? hubA : hubB, second = fromA ? hubB : hubA; |
| 261 |
2/2✓ Branch 72 → 73 taken 108 times.
✓ Branch 72 → 74 taken 156 times.
|
264 | const QPointF along = fromA ? d : -d; // the trunk's direction from the start's side |
| 262 | // Into the first hub heading along the trunk, out of the second the same way. | ||
| 263 | 264 | const double in = std::hypot(first.x() - start.x(), first.y() - start.y()) / 2; | |
| 264 | 264 | const double out = std::hypot(end.x() - second.x(), end.y() - second.y()) / 2; | |
| 265 |
1/3✓ Branch 90 → 91 taken 264 times.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
|
528 | Route route{QStringLiteral("bundle"), {start, first - along * in, first, second, second + along * out, end}}; |
| 266 | 264 | route.bundle = int(it.value().size()); | |
| 267 |
1/1✓ Branch 94 → 95 taken 264 times.
|
264 | (*routes)[e] = route; |
| 268 | 264 | } | |
| 269 | } | ||
| 270 | 21 | } | |
| 271 | |||
| 272 | } // namespace RelationGraphLayout | ||
| 273 |