data/RelationGraph.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "RelationGraph.h" | ||
| 2 | |||
| 3 | #include <QSet> | ||
| 4 | |||
| 5 | #include "RelationGraphFamilies.h" | ||
| 6 | #include "RelationVocabulary.h" | ||
| 7 | |||
| 8 | #include <QMap> | ||
| 9 | #include <algorithm> | ||
| 10 | #include <functional> | ||
| 11 | |||
| 12 | namespace RelationGraph { | ||
| 13 | |||
| 14 | namespace { | ||
| 15 | |||
| 16 | const QStringList kSectorOrder{QStringLiteral("family"), QStringLiteral("work"), QStringLiteral("friends"), | ||
| 17 | QStringLiteral("other"), QStringLiteral("introductions")}; | ||
| 18 | |||
| 19 | 36392 | QString categoryOf(const QString &type) { | |
| 20 | 36392 | return RelationVocabulary::categoryId(RelationVocabulary::categoryOf(type)); | |
| 21 | } | ||
| 22 | |||
| 23 | struct Neighbour { | ||
| 24 | qint64 cardId = 0; | ||
| 25 | QString sector; | ||
| 26 | }; | ||
| 27 | |||
| 28 | // Index of everything ego()/overview() need, built once per call. | ||
| 29 | struct Index { | ||
| 30 | QHash<qint64, const Person *> people; | ||
| 31 | QHash<qint64, QList<int>> edgesOf; // card → indices into input.edges | ||
| 32 | QHash<qint64, int> degree; | ||
| 33 | |||
| 34 | 156 | explicit Index(const Input &input) { | |
| 35 |
2/2✓ Branch 11 → 7 taken 42979 times.
✓ Branch 11 → 12 taken 156 times.
|
43135 | for (const Person &p : input.people) |
| 36 |
1/1✓ Branch 8 → 9 taken 42979 times.
|
42979 | people.insert(p.cardId, &p); |
| 37 | 156 | QSet<QString> relations; // S106-174: a mirrored pair is one relationship | |
| 38 |
2/2✓ Branch 58 → 14 taken 69858 times.
✓ Branch 58 → 59 taken 156 times.
|
70014 | for (int i = 0; i < input.edges.size(); ++i) { |
| 39 | 69858 | const StoredEdge &e = input.edges.at(i); | |
| 40 |
7/8✓ Branch 15 → 16 taken 69794 times.
✓ Branch 15 → 21 taken 64 times.
✓ Branch 16 → 17 taken 69790 times.
✓ Branch 16 → 21 taken 4 times.
✓ Branch 18 → 19 taken 69790 times.
✗ Branch 18 → 21 not taken.
✓ Branch 23 → 24 taken 68 times.
✓ Branch 23 → 25 taken 69790 times.
|
139648 | if (e.toCardId <= 0 || e.fromCardId == e.toCardId || !people.contains(e.fromCardId) || |
| 41 |
1/2✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 69790 times.
|
69790 | !people.contains(e.toCardId)) |
| 42 | 68 | continue; | |
| 43 |
2/2✓ Branch 25 → 26 taken 69790 times.
✓ Branch 26 → 27 taken 69790 times.
|
69790 | edgesOf[e.fromCardId].append(i); |
| 44 |
2/2✓ Branch 27 → 28 taken 69790 times.
✓ Branch 28 → 29 taken 69790 times.
|
69790 | edgesOf[e.toCardId].append(i); |
| 45 |
2/2✓ Branch 31 → 32 taken 67610 times.
✓ Branch 31 → 53 taken 2180 times.
|
69790 | if (e.role == QLatin1String("relation")) { |
| 46 |
1/1✓ Branch 32 → 33 taken 67610 times.
|
67610 | const QString mirrored = RelationVocabulary::inverseOf(e.type); |
| 47 |
9/11✓ Branch 34 → 35 taken 67546 times.
✓ Branch 34 → 39 taken 64 times.
✓ Branch 35 → 36 taken 67546 times.
✓ Branch 37 → 38 taken 29999 times.
✓ Branch 37 → 39 taken 37547 times.
✓ Branch 40 → 41 taken 67546 times.
✓ Branch 40 → 42 taken 64 times.
✓ Branch 42 → 43 taken 29999 times.
✓ Branch 42 → 44 taken 37611 times.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
|
67610 | if (!mirrored.isEmpty() && relations.contains(edgeKey(e.toCardId, e.fromCardId, mirrored))) |
| 48 | 29999 | continue; | |
| 49 |
2/2✓ Branch 44 → 45 taken 37611 times.
✓ Branch 45 → 46 taken 37611 times.
|
37611 | relations.insert(edgeKey(e.fromCardId, e.toCardId, e.type)); |
| 50 | 67610 | } | |
| 51 |
1/1✓ Branch 53 → 54 taken 39791 times.
|
39791 | degree[e.fromCardId] += 1; |
| 52 |
1/1✓ Branch 54 → 55 taken 39791 times.
|
39791 | degree[e.toCardId] += 1; |
| 53 | } | ||
| 54 | 156 | } | |
| 55 | |||
| 56 | 105157 | static QString edgeKey(qint64 from, qint64 to, const QString &type) { | |
| 57 |
6/6✓ Branch 4 → 5 taken 105157 times.
✓ Branch 7 → 8 taken 105157 times.
✓ Branch 8 → 9 taken 105157 times.
✓ Branch 9 → 10 taken 105157 times.
✓ Branch 10 → 11 taken 105157 times.
✓ Branch 11 → 12 taken 105157 times.
|
105157 | return QString::number(from) + QLatin1Char('|') + QString::number(to) + QLatin1Char('|') + type; |
| 58 | } | ||
| 59 | |||
| 60 | // The neighbours of a card with the sector their strongest edge gives. | ||
| 61 | 202 | QList<Neighbour> neighbours(const Input &input, qint64 card) const { | |
| 62 | 202 | QHash<qint64, QString> best; | |
| 63 |
4/4✓ Branch 3 → 4 taken 202 times.
✓ Branch 4 → 5 taken 202 times.
✓ Branch 34 → 6 taken 1523 times.
✓ Branch 34 → 35 taken 202 times.
|
1725 | for (int index : edgesOf.value(card)) { |
| 64 | 1523 | const StoredEdge &e = input.edges.at(index); | |
| 65 |
2/2✓ Branch 8 → 9 taken 947 times.
✓ Branch 8 → 10 taken 576 times.
|
1523 | const qint64 other = e.fromCardId == card ? e.toCardId : e.fromCardId; |
| 66 |
5/7✓ Branch 13 → 14 taken 89 times.
✓ Branch 13 → 17 taken 1434 times.
✓ Branch 17 → 18 taken 1434 times.
✓ Branch 18 → 19 taken 89 times.
✓ Branch 18 → 20 taken 1434 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
|
1612 | const QString sector = e.role == QLatin1String("via") ? QStringLiteral("introductions") : categoryOf(e.type); |
| 67 | 1523 | const QString current = best.value(other); | |
| 68 |
6/6✓ Branch 22 → 23 taken 53 times.
✓ Branch 22 → 26 taken 1470 times.
✓ Branch 25 → 26 taken 7 times.
✓ Branch 25 → 27 taken 46 times.
✓ Branch 28 → 29 taken 1477 times.
✓ Branch 28 → 30 taken 46 times.
|
1523 | if (current.isEmpty() || kSectorOrder.indexOf(sector) < kSectorOrder.indexOf(current)) |
| 69 |
1/1✓ Branch 29 → 30 taken 1477 times.
|
1477 | best.insert(other, sector); |
| 70 | 1725 | } | |
| 71 | 202 | QList<Neighbour> out; | |
| 72 |
2/2✓ Branch 45 → 37 taken 1470 times.
✓ Branch 45 → 46 taken 202 times.
|
1672 | for (auto it = best.cbegin(); it != best.cend(); ++it) |
| 73 |
1/1✓ Branch 40 → 41 taken 1470 times.
|
2940 | out.append({it.key(), it.value()}); |
| 74 |
3/3✓ Branch 46 → 47 taken 202 times.
✓ Branch 47 → 48 taken 202 times.
✓ Branch 48 → 49 taken 202 times.
|
6114 | std::sort(out.begin(), out.end(), [](const Neighbour &a, const Neighbour &b) { return a.cardId < b.cardId; }); |
| 75 | 202 | return out; | |
| 76 | 1672 | } | |
| 77 | }; | ||
| 78 | |||
| 79 | 33163 | Node nodeOf(const Person &p, int depth, const QString §or, int degree) { | |
| 80 | 33163 | Node n; | |
| 81 | 33163 | n.cardId = p.cardId; | |
| 82 | 33163 | n.key = p.key; | |
| 83 | 33163 | n.name = p.name; | |
| 84 | 33163 | n.gender = p.gender; | |
| 85 | 33163 | n.inScope = p.inScope; | |
| 86 | 33163 | n.me = p.me; | |
| 87 | 33163 | n.birthday = p.birthday; | |
| 88 | 33163 | n.depth = depth; | |
| 89 | 33163 | n.sector = sector; | |
| 90 | 33163 | n.degree = degree; | |
| 91 | 33163 | return n; | |
| 92 | } | ||
| 93 | |||
| 94 | // The node index of every card in the graph. S106-157: the per-edge linear | ||
| 95 | // Graph::indexOf() and the scan over all edges for the mirror check made | ||
| 96 | // the overview O(E·n + E²) — ~10^8 steps for a 5 000-person book. | ||
| 97 | 312 | QHash<qint64, int> nodeIndex(const Graph &graph) { | |
| 98 | 312 | QHash<qint64, int> index; | |
| 99 |
1/1✓ Branch 3 → 4 taken 312 times.
|
312 | index.reserve(graph.nodes.size()); |
| 100 |
2/2✓ Branch 11 → 5 taken 66332 times.
✓ Branch 11 → 12 taken 312 times.
|
66644 | for (int i = 0; i < graph.nodes.size(); ++i) |
| 101 |
2/2✓ Branch 6 → 7 taken 66324 times.
✓ Branch 6 → 9 taken 8 times.
|
66332 | if (graph.nodes.at(i).cardId > 0) |
| 102 |
1/1✓ Branch 8 → 9 taken 66324 times.
|
66324 | index.insert(graph.nodes.at(i).cardId, i); |
| 103 | 312 | return index; | |
| 104 | ✗ | } | |
| 105 | |||
| 106 | 156 | void addStoredEdges(const Input &input, Graph *graph) { | |
| 107 |
1/1✓ Branch 2 → 3 taken 156 times.
|
156 | const QHash<qint64, int> index = nodeIndex(*graph); |
| 108 | 156 | QSet<QString> relations; // "from|to|type" of the relation edges added | |
| 109 | 131589 | const auto key = [](int from, int to, const QString &type) { | |
| 110 |
6/6✓ Branch 4 → 5 taken 131589 times.
✓ Branch 7 → 8 taken 131589 times.
✓ Branch 8 → 9 taken 131589 times.
✓ Branch 9 → 10 taken 131589 times.
✓ Branch 10 → 11 taken 131589 times.
✓ Branch 11 → 12 taken 131589 times.
|
131589 | return QString::number(from) + QLatin1Char('|') + QString::number(to) + QLatin1Char('|') + type; |
| 111 | }; | ||
| 112 |
1/2✗ Branch 17 → 7 not taken.
✓ Branch 17 → 18 taken 156 times.
|
156 | for (const Edge &o : std::as_const(graph->edges)) |
| 113 | ✗ | if (o.kind == QLatin1String("relation")) | |
| 114 | ✗ | relations.insert(key(o.from, o.to, o.type)); | |
| 115 |
2/2✓ Branch 111 → 20 taken 69858 times.
✓ Branch 111 → 112 taken 156 times.
|
70014 | for (const StoredEdge &e : input.edges) { |
| 116 |
1/2✓ Branch 21 → 22 taken 69858 times.
✗ Branch 21 → 23 not taken.
|
69858 | const int from = e.fromCardId > 0 ? index.value(e.fromCardId, -1) : -1; |
| 117 |
2/2✓ Branch 24 → 25 taken 69794 times.
✓ Branch 24 → 26 taken 64 times.
|
69858 | const int to = e.toCardId > 0 ? index.value(e.toCardId, -1) : -1; |
| 118 |
6/6✓ Branch 27 → 28 taken 65883 times.
✓ Branch 27 → 30 taken 3975 times.
✓ Branch 28 → 29 taken 65110 times.
✓ Branch 28 → 30 taken 773 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 65108 times.
|
69858 | if (from < 0 || to < 0 || from == to) |
| 119 | 34749 | continue; | |
| 120 | // F103-22: a relationship entered on both cards is one edge. | ||
| 121 |
1/1✓ Branch 31 → 32 taken 65108 times.
|
65108 | const QString mirrored = RelationVocabulary::inverseOf(e.type); |
| 122 |
2/2✓ Branch 34 → 35 taken 63884 times.
✓ Branch 34 → 44 taken 1224 times.
|
128992 | if (e.role == QLatin1String("relation") && |
| 123 |
8/11✓ Branch 36 → 37 taken 63873 times.
✓ Branch 36 → 40 taken 11 times.
✓ Branch 37 → 38 taken 63873 times.
✓ Branch 39 → 40 taken 33874 times.
✓ Branch 39 → 43 taken 29999 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 33885 times.
✓ Branch 49 → 50 taken 29999 times.
✓ Branch 49 → 51 taken 35109 times.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
|
162877 | ((!mirrored.isEmpty() && relations.contains(key(to, from, mirrored))) || |
| 124 |
5/7✓ Branch 40 → 41 taken 33885 times.
✓ Branch 45 → 46 taken 33885 times.
✓ Branch 45 → 47 taken 31223 times.
✓ Branch 47 → 48 taken 63873 times.
✓ Branch 47 → 49 taken 1235 times.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
|
98993 | relations.contains(key(from, to, e.type)))) |
| 125 | 29999 | continue; | |
| 126 | 35109 | Edge edge; | |
| 127 | 35109 | edge.from = from; | |
| 128 | 35109 | edge.to = to; | |
| 129 | // Membership keeps a family together in the layouts but is drawn as the area (F103-10). | ||
| 130 |
2/4✓ Branch 53 → 54 taken 151 times.
✓ Branch 53 → 57 taken 34958 times.
✗ Branch 142 → 143 not taken.
✗ Branch 142 → 144 not taken.
|
105478 | edge.kind = e.role == QLatin1String("via") ? QStringLiteral("via") |
| 131 |
4/6✓ Branch 59 → 60 taken 1073 times.
✓ Branch 59 → 63 taken 33885 times.
✓ Branch 79 → 80 taken 151 times.
✓ Branch 79 → 81 taken 34958 times.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
|
70067 | : e.role == QLatin1String("inferred") ? QStringLiteral("inferred") // F103-31: follows from others |
| 132 |
5/7✓ Branch 63 → 64 taken 33885 times.
✓ Branch 64 → 65 taken 54 times.
✓ Branch 64 → 68 taken 33831 times.
✓ Branch 77 → 78 taken 1073 times.
✓ Branch 77 → 79 taken 34036 times.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
|
68994 | : Families::isMembership(e) ? QStringLiteral("member") |
| 133 |
4/6✓ Branch 73 → 74 taken 33831 times.
✓ Branch 73 → 75 taken 1278 times.
✓ Branch 75 → 76 taken 54 times.
✓ Branch 75 → 77 taken 35055 times.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
|
70218 | : QStringLiteral("relation"); |
| 134 | 35109 | edge.type = e.type; | |
| 135 | 35109 | edge.label = e.label; | |
| 136 |
5/7✓ Branch 85 → 86 taken 151 times.
✓ Branch 85 → 89 taken 34958 times.
✓ Branch 89 → 90 taken 34958 times.
✓ Branch 92 → 93 taken 151 times.
✓ Branch 92 → 94 taken 34958 times.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 150 not taken.
|
35260 | edge.category = edge.kind == QLatin1String("via") ? QStringLiteral("other") : categoryOf(e.type); |
| 137 | 35109 | edge.storedOnCardId = e.fromCardId; | |
| 138 | 35109 | edge.sourceIndex = e.sourceIndex; | |
| 139 |
2/2✓ Branch 96 → 97 taken 33831 times.
✓ Branch 96 → 101 taken 1278 times.
|
35109 | if (edge.kind == QLatin1String("relation")) |
| 140 |
2/2✓ Branch 97 → 98 taken 33831 times.
✓ Branch 98 → 99 taken 33831 times.
|
33831 | relations.insert(key(from, to, e.type)); |
| 141 |
1/1✓ Branch 101 → 102 taken 35109 times.
|
35109 | graph->edges.append(edge); |
| 142 | 65108 | } | |
| 143 | 156 | } | |
| 144 | |||
| 145 | // Derived layers (D-19): drawn between shown nodes; in an ego graph only | ||
| 146 | // between the centre and the others, in the overview as a chain per value. | ||
| 147 | 156 | void addLayers(const Input &input, const Layers &layers, const QHash<qint64, const Person *> &people, int center, | |
| 148 | Graph *graph) { | ||
| 149 | 12 | const auto link = [graph](int a, int b, const QString &kind, const QString &label, int evidence) { | |
| 150 |
5/6✓ Branch 2 → 3 taken 12 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 9 times.
|
12 | if (a < 0 || b < 0 || a == b) |
| 151 | 3 | return; | |
| 152 | 9 | Edge edge; | |
| 153 | 9 | edge.from = std::min(a, b); | |
| 154 | 9 | edge.to = std::max(a, b); | |
| 155 | 9 | edge.kind = kind; | |
| 156 | 9 | edge.label = label; | |
| 157 | 9 | edge.category = QStringLiteral("other"); | |
| 158 | 9 | edge.evidence = evidence; | |
| 159 |
1/1✓ Branch 16 → 17 taken 9 times.
|
9 | graph->edges.append(edge); |
| 160 | 9 | }; | |
| 161 | 2 | const auto byValue = [&](const QString &kind, const std::function<QStringList(const Person &)> &values) { | |
| 162 | 2 | QMap<QString, QList<int>> members; | |
| 163 |
2/2✓ Branch 27 → 3 taken 16 times.
✓ Branch 27 → 28 taken 2 times.
|
18 | for (int i = 0; i < graph->nodes.size(); ++i) |
| 164 |
1/2✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 25 not taken.
|
16 | if (const Person *p = people.value(graph->nodes.at(i).cardId)) |
| 165 |
5/5✓ Branch 6 → 7 taken 16 times.
✓ Branch 7 → 8 taken 16 times.
✓ Branch 8 → 9 taken 16 times.
✓ Branch 22 → 10 taken 10 times.
✓ Branch 22 → 23 taken 16 times.
|
26 | for (const QString &value : values(*p)) |
| 166 |
3/3✓ Branch 11 → 12 taken 10 times.
✓ Branch 14 → 15 taken 4 times.
✓ Branch 14 → 20 taken 6 times.
|
10 | if (!value.trimmed().isEmpty()) |
| 167 |
3/3✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
|
20 | members[value.trimmed()].append(i); |
| 168 |
4/4✓ Branch 28 → 29 taken 2 times.
✓ Branch 52 → 53 taken 4 times.
✓ Branch 54 → 30 taken 2 times.
✓ Branch 54 → 55 taken 2 times.
|
4 | for (auto it = members.cbegin(); it != members.cend(); ++it) { |
| 169 | 2 | const QList<int> &list = it.value(); | |
| 170 |
1/2✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 43 not taken.
|
2 | if (center >= 0) { |
| 171 |
1/2✓ Branch 33 → 34 taken 2 times.
✗ Branch 33 → 51 not taken.
|
2 | if (list.contains(center)) |
| 172 |
2/2✓ Branch 41 → 36 taken 4 times.
✓ Branch 41 → 42 taken 2 times.
|
6 | for (int i : list) |
| 173 |
1/1✓ Branch 38 → 39 taken 4 times.
|
4 | link(center, i, kind, it.key(), 0); |
| 174 | } else { | ||
| 175 | ✗ | for (int i = 1; i < list.size(); ++i) | |
| 176 | ✗ | link(list.at(i - 1), list.at(i), kind, it.key(), 0); | |
| 177 | } | ||
| 178 | } | ||
| 179 | 2 | }; | |
| 180 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 12 taken 155 times.
|
156 | if (layers.organization) |
| 181 |
4/10✓ Branch 3 → 4 taken 8 times.
✓ Branch 5 → 6 taken 8 times.
✓ Branch 5 → 7 taken 8 times.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 17 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
18 | byValue(QStringLiteral("organization"), [](const Person &p) { return QStringList{p.organization}; }); |
| 182 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 22 taken 155 times.
|
156 | if (layers.group) |
| 183 |
1/1✓ Branch 17 → 18 taken 1 time.
|
10 | byValue(QStringLiteral("group"), [](const Person &p) { return p.groups; }); |
| 184 |
1/1✓ Branch 22 → 23 taken 156 times.
|
156 | const QHash<qint64, int> index = nodeIndex(*graph); |
| 185 | 11 | const auto together = [&](const QList<Together> &pairs, const QString &kind) { | |
| 186 |
2/2✓ Branch 21 → 4 taken 8 times.
✓ Branch 21 → 22 taken 11 times.
|
19 | for (const Together &t : pairs) { |
| 187 |
1/2✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 7 not taken.
|
8 | const int a = t.a > 0 ? index.value(t.a, -1) : -1; |
| 188 |
1/2✓ Branch 8 → 9 taken 8 times.
✗ Branch 8 → 10 not taken.
|
8 | const int b = t.b > 0 ? index.value(t.b, -1) : -1; |
| 189 |
2/6✓ Branch 11 → 12 taken 8 times.
✗ Branch 11 → 15 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 8 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
|
8 | if (center >= 0 && a != center && b != center) |
| 190 | ✗ | continue; | |
| 191 |
1/1✓ Branch 16 → 17 taken 8 times.
|
8 | link(a, b, kind, QString(), t.count); |
| 192 | } | ||
| 193 | 11 | }; | |
| 194 |
2/2✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 31 taken 155 times.
|
156 | if (layers.events) |
| 195 |
1/1✓ Branch 27 → 28 taken 1 time.
|
1 | together(input.sharedEvents, QStringLiteral("events")); |
| 196 |
2/2✓ Branch 31 → 32 taken 10 times.
✓ Branch 31 → 39 taken 146 times.
|
156 | if (layers.suggestions) |
| 197 |
1/1✓ Branch 35 → 36 taken 10 times.
|
10 | together(input.suggestions, QStringLiteral("suggestion")); |
| 198 | 156 | } | |
| 199 | |||
| 200 | } // namespace | ||
| 201 | |||
| 202 | 1302 | int Graph::indexOf(qint64 cardId) const { | |
| 203 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1302 times.
|
1302 | if (cardId <= 0) |
| 204 | ✗ | return -1; | |
| 205 |
2/2✓ Branch 10 → 5 taken 11261 times.
✓ Branch 10 → 11 taken 549 times.
|
11810 | for (int i = 0; i < nodes.size(); ++i) |
| 206 |
2/2✓ Branch 6 → 7 taken 753 times.
✓ Branch 6 → 8 taken 10508 times.
|
11261 | if (nodes.at(i).cardId == cardId) |
| 207 | 753 | return i; | |
| 208 | 549 | return -1; | |
| 209 | } | ||
| 210 | |||
| 211 | 126 | Graph ego(const Input &input, qint64 centerCardId, int depth, const Layers &layers, | |
| 212 | const QSet<QString> &expandedSectors) { | ||
| 213 | 126 | Graph graph; | |
| 214 |
1/1✓ Branch 2 → 3 taken 126 times.
|
126 | const Index index(input); |
| 215 | 126 | const Person *center = index.people.value(centerCardId); | |
| 216 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 126 times.
|
126 | if (!center) |
| 217 | ✗ | return graph; | |
| 218 | 126 | Node centre = nodeOf(*center, 0, QString(), index.degree.value(centerCardId)); | |
| 219 | 126 | centre.center = true; | |
| 220 |
1/1✓ Branch 10 → 11 taken 126 times.
|
126 | graph.nodes.append(centre); |
| 221 | // First ring, sector by sector, capped (D-18, IX large sectors). | ||
| 222 | // F103-10: a family the centre belongs to is shown by its other members | ||
| 223 | // (and its area), not as a person of its own. | ||
| 224 |
1/1✓ Branch 11 → 12 taken 126 times.
|
126 | const QHash<qint64, QList<qint64>> families = Families::of(input); |
| 225 | 126 | QHash<qint64, QString> firstRing; | |
| 226 |
5/5✓ Branch 12 → 13 taken 126 times.
✓ Branch 13 → 14 taken 126 times.
✓ Branch 14 → 15 taken 126 times.
✓ Branch 20 → 16 taken 1149 times.
✓ Branch 20 → 21 taken 126 times.
|
1275 | for (const Neighbour &n : index.neighbours(input, centerCardId)) |
| 227 |
1/1✓ Branch 17 → 18 taken 1149 times.
|
1275 | firstRing.insert(n.cardId, n.sector); |
| 228 | 126 | QHash<qint64, qint64> familyOrder; // member → its family, to keep a family side by side | |
| 229 | // F103-16: a family next to the centre (the centre is a member, or related | ||
| 230 | // to the family) is shown by its members in that sector — never as a | ||
| 231 | // node of its own that would leave an empty place in the area. | ||
| 232 |
2/2✓ Branch 66 → 23 taken 137 times.
✓ Branch 66 → 67 taken 126 times.
|
263 | for (auto it = families.cbegin(); it != families.cend(); ++it) { |
| 233 | 137 | const bool member = it.value().contains(centerCardId); | |
| 234 |
6/6✓ Branch 25 → 26 taken 89 times.
✓ Branch 25 → 30 taken 48 times.
✓ Branch 28 → 29 taken 88 times.
✓ Branch 28 → 30 taken 1 time.
✓ Branch 31 → 32 taken 88 times.
✓ Branch 31 → 33 taken 49 times.
|
137 | if (!member && !firstRing.contains(it.key())) |
| 235 | 88 | continue; | |
| 236 |
4/4✓ Branch 33 → 34 taken 48 times.
✓ Branch 33 → 37 taken 1 time.
✓ Branch 39 → 40 taken 48 times.
✓ Branch 39 → 41 taken 1 time.
|
97 | const QString sector = member ? QStringLiteral("family") : firstRing.value(it.key()); |
| 237 |
1/1✓ Branch 42 → 43 taken 49 times.
|
49 | firstRing.remove(it.key()); |
| 238 |
2/2✓ Branch 60 → 46 taken 144 times.
✓ Branch 60 → 61 taken 49 times.
|
193 | for (qint64 person : it.value()) |
| 239 |
7/8✓ Branch 47 → 48 taken 96 times.
✓ Branch 47 → 53 taken 48 times.
✓ Branch 49 → 50 taken 96 times.
✗ Branch 49 → 53 not taken.
✓ Branch 51 → 52 taken 8 times.
✓ Branch 51 → 53 taken 88 times.
✓ Branch 54 → 55 taken 8 times.
✓ Branch 54 → 58 taken 136 times.
|
144 | if (person != centerCardId && index.people.contains(person) && !firstRing.contains(person)) { |
| 240 |
1/1✓ Branch 55 → 56 taken 8 times.
|
8 | firstRing.insert(person, sector); |
| 241 |
1/1✓ Branch 57 → 58 taken 8 times.
|
8 | familyOrder.insert(person, it.key()); |
| 242 | } | ||
| 243 | 49 | } | |
| 244 |
2/2✓ Branch 68 → 69 taken 4 times.
✓ Branch 68 → 80 taken 122 times.
|
126 | if (families.contains(centerCardId)) |
| 245 |
4/4✓ Branch 70 → 71 taken 4 times.
✓ Branch 71 → 72 taken 4 times.
✓ Branch 77 → 73 taken 12 times.
✓ Branch 77 → 78 taken 4 times.
|
16 | for (qint64 member : families.value(centerCardId)) |
| 246 |
1/1✓ Branch 74 → 75 taken 12 times.
|
16 | familyOrder.insert(member, centerCardId); |
| 247 | 126 | QHash<QString, QList<qint64>> sectors; | |
| 248 |
1/1✓ Branch 80 → 81 taken 126 times.
|
126 | QList<qint64> ringCards = firstRing.keys(); |
| 249 |
3/3✓ Branch 81 → 82 taken 126 times.
✓ Branch 82 → 83 taken 126 times.
✓ Branch 83 → 84 taken 126 times.
|
126 | std::sort(ringCards.begin(), ringCards.end()); |
| 250 |
2/2✓ Branch 94 → 87 taken 1108 times.
✓ Branch 94 → 95 taken 126 times.
|
1234 | for (qint64 card : std::as_const(ringCards)) |
| 251 |
2/2✓ Branch 89 → 90 taken 1108 times.
✓ Branch 90 → 91 taken 1108 times.
|
1108 | sectors[firstRing.value(card)].append(card); |
| 252 | 126 | QList<int> ring; | |
| 253 |
2/2✓ Branch 163 → 97 taken 630 times.
✓ Branch 163 → 164 taken 126 times.
|
756 | for (const QString §or : kSectorOrder) { |
| 254 | 630 | QList<qint64> members = sectors.value(sector); | |
| 255 |
3/3✓ Branch 99 → 100 taken 630 times.
✓ Branch 100 → 101 taken 630 times.
✓ Branch 101 → 102 taken 630 times.
|
630 | std::sort(members.begin(), members.end(), [&](qint64 a, qint64 b) { |
| 256 | 3455 | const qint64 fa = familyOrder.value(a, 0), fb = familyOrder.value(b, 0); | |
| 257 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 3455 times.
|
3455 | if (fa != fb) |
| 258 | ✗ | return fa > fb; // members of one family next to each other, families first | |
| 259 | 3455 | const int da = index.degree.value(a); | |
| 260 | 3455 | const int db = index.degree.value(b); | |
| 261 |
2/2✓ Branch 8 → 9 taken 1633 times.
✓ Branch 8 → 10 taken 1822 times.
|
3455 | if (da != db) |
| 262 | 1633 | return da > db; | |
| 263 | 1822 | const QString na = index.people.value(a)->name; | |
| 264 | 1822 | const QString nb = index.people.value(b)->name; | |
| 265 |
4/7✓ Branch 14 → 15 taken 1822 times.
✓ Branch 15 → 16 taken 787 times.
✓ Branch 15 → 19 taken 1035 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 787 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
|
1822 | return na.localeAwareCompare(nb) < 0 || (na == nb && a < b); |
| 266 | 1822 | }); | |
| 267 |
2/2✓ Branch 104 → 105 taken 126 times.
✓ Branch 104 → 106 taken 504 times.
|
630 | const int cap = sector == QLatin1String("family") ? MaxFamilyNodes : MaxSectorNodes; |
| 268 |
4/4✓ Branch 108 → 109 taken 6 times.
✓ Branch 108 → 112 taken 624 times.
✓ Branch 110 → 111 taken 3 times.
✓ Branch 110 → 112 taken 3 times.
|
630 | const bool capped = members.size() > cap && !expandedSectors.contains(sector); |
| 269 |
2/2✓ Branch 113 → 114 taken 3 times.
✓ Branch 113 → 115 taken 627 times.
|
630 | const int shown = capped ? cap - 1 : int(members.size()); |
| 270 | 630 | int added = 0; | |
| 271 |
6/6✓ Branch 128 → 129 taken 897 times.
✓ Branch 128 → 132 taken 629 times.
✓ Branch 130 → 131 taken 896 times.
✓ Branch 130 → 132 taken 1 time.
✓ Branch 133 → 118 taken 896 times.
✓ Branch 133 → 134 taken 630 times.
|
1526 | for (int i = 0; i < shown && graph.nodes.size() < MaxEgoNodes; ++i) { |
| 272 |
1/1✓ Branch 123 → 124 taken 896 times.
|
896 | graph.nodes.append(nodeOf(*index.people.value(members.at(i)), 1, sector, index.degree.value(members.at(i)))); |
| 273 |
1/1✓ Branch 126 → 127 taken 896 times.
|
896 | ring.append(int(graph.nodes.size()) - 1); |
| 274 | 896 | ++added; | |
| 275 | } | ||
| 276 | // S106-174: "+N more" counts the people really left out (also by the | ||
| 277 | // node cap) and stays within MaxEgoNodes — a shown person gives way. | ||
| 278 |
2/2✓ Branch 135 → 136 taken 4 times.
✓ Branch 135 → 160 taken 626 times.
|
630 | if (added < members.size()) { |
| 279 |
5/6✓ Branch 137 → 138 taken 1 time.
✓ Branch 137 → 140 taken 3 times.
✓ Branch 138 → 139 taken 1 time.
✗ Branch 138 → 140 not taken.
✓ Branch 141 → 142 taken 1 time.
✓ Branch 141 → 145 taken 3 times.
|
4 | if (graph.nodes.size() >= MaxEgoNodes && added > 0) { |
| 280 | 1 | graph.nodes.removeLast(); | |
| 281 | 1 | ring.removeLast(); | |
| 282 | 1 | --added; | |
| 283 | } | ||
| 284 |
1/2✓ Branch 146 → 147 taken 4 times.
✗ Branch 146 → 160 not taken.
|
4 | if (graph.nodes.size() < MaxEgoNodes) { |
| 285 | 4 | Node more; | |
| 286 | 4 | more.depth = 1; | |
| 287 | 4 | more.sector = sector; | |
| 288 | 4 | more.moreCount = int(members.size()) - added; | |
| 289 |
1/1✓ Branch 152 → 153 taken 4 times.
|
4 | more.key = QStringLiteral("more:") + sector; |
| 290 |
1/1✓ Branch 157 → 158 taken 4 times.
|
4 | graph.nodes.append(more); |
| 291 | 4 | } | |
| 292 | } | ||
| 293 | 630 | } | |
| 294 | // S103.5.8: the strongest suggestions with the centre join as their own row. | ||
| 295 |
2/2✓ Branch 164 → 165 taken 10 times.
✓ Branch 164 → 216 taken 116 times.
|
126 | if (layers.suggestions) { |
| 296 | 10 | QList<Together> mine; | |
| 297 |
2/2✓ Branch 181 → 167 taken 7 times.
✓ Branch 181 → 182 taken 10 times.
|
17 | for (const Together &s : input.suggestions) |
| 298 |
4/10✗ Branch 168 → 169 not taken.
✓ Branch 168 → 170 taken 7 times.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 176 not taken.
✓ Branch 170 → 171 taken 7 times.
✗ Branch 170 → 172 not taken.
✓ Branch 174 → 175 taken 7 times.
✗ Branch 174 → 176 not taken.
✓ Branch 177 → 178 taken 7 times.
✗ Branch 177 → 179 not taken.
|
7 | if ((s.a == centerCardId || s.b == centerCardId) && index.people.contains(s.a == centerCardId ? s.b : s.a)) |
| 299 |
1/1✓ Branch 178 → 179 taken 7 times.
|
7 | mine.append(s); |
| 300 |
3/3✓ Branch 182 → 183 taken 10 times.
✓ Branch 183 → 184 taken 10 times.
✓ Branch 184 → 185 taken 10 times.
|
12 | std::stable_sort(mine.begin(), mine.end(), [](const Together &x, const Together &y) { return x.count > y.count; }); |
| 301 |
6/8✓ Branch 207 → 208 taken 7 times.
✓ Branch 207 → 212 taken 10 times.
✓ Branch 208 → 209 taken 7 times.
✗ Branch 208 → 212 not taken.
✓ Branch 210 → 211 taken 7 times.
✗ Branch 210 → 212 not taken.
✓ Branch 213 → 186 taken 7 times.
✓ Branch 213 → 214 taken 10 times.
|
17 | for (int i = 0; i < mine.size() && i < MaxSuggestedNodes && graph.nodes.size() < MaxEgoNodes; ++i) { |
| 302 |
1/2✓ Branch 187 → 188 taken 7 times.
✗ Branch 187 → 190 not taken.
|
7 | const qint64 other = mine.at(i).a == centerCardId ? mine.at(i).b : mine.at(i).a; |
| 303 |
1/2✓ Branch 193 → 194 taken 7 times.
✗ Branch 193 → 205 not taken.
|
7 | if (graph.indexOf(other) < 0) |
| 304 |
1/1✓ Branch 200 → 201 taken 7 times.
|
14 | graph.nodes.append(nodeOf(*index.people.value(other), 1, QStringLiteral("suggestions"), index.degree.value(other))); |
| 305 | } | ||
| 306 | 10 | } | |
| 307 | // Second ring: people connected to a first-ring person, not shown yet. | ||
| 308 |
2/2✓ Branch 216 → 217 taken 10 times.
✓ Branch 216 → 252 taken 116 times.
|
126 | if (depth >= 2) { |
| 309 | 10 | int visited = 0; | |
| 310 |
4/4✓ Branch 217 → 218 taken 10 times.
✓ Branch 218 → 219 taken 10 times.
✓ Branch 250 → 220 taken 76 times.
✓ Branch 250 → 251 taken 10 times.
|
86 | for (int r : ring) { |
| 311 | 76 | const qint64 card = graph.nodes.at(r).cardId; | |
| 312 |
5/5✓ Branch 222 → 223 taken 76 times.
✓ Branch 223 → 224 taken 76 times.
✓ Branch 224 → 225 taken 76 times.
✓ Branch 246 → 226 taken 238 times.
✓ Branch 246 → 247 taken 58 times.
|
296 | for (const Neighbour &n : index.neighbours(input, card)) { |
| 313 |
5/6✓ Branch 227 → 228 taken 238 times.
✗ Branch 227 → 230 not taken.
✓ Branch 229 → 230 taken 18 times.
✓ Branch 229 → 231 taken 220 times.
✓ Branch 232 → 233 taken 18 times.
✓ Branch 232 → 234 taken 220 times.
|
238 | if (++visited > MaxVisited || graph.nodes.size() >= MaxEgoNodes) |
| 314 | 18 | break; | |
| 315 |
2/2✓ Branch 235 → 236 taken 93 times.
✓ Branch 235 → 237 taken 127 times.
|
220 | if (graph.indexOf(n.cardId) >= 0) |
| 316 | 93 | continue; | |
| 317 |
1/1✓ Branch 241 → 242 taken 127 times.
|
127 | graph.nodes.append(nodeOf(*index.people.value(n.cardId), 2, graph.nodes.at(r).sector, |
| 318 | 127 | index.degree.value(n.cardId))); | |
| 319 | 76 | } | |
| 320 | } | ||
| 321 | } | ||
| 322 |
1/1✓ Branch 252 → 253 taken 126 times.
|
126 | addStoredEdges(input, &graph); |
| 323 |
1/1✓ Branch 253 → 254 taken 126 times.
|
126 | addLayers(input, layers, index.people, 0, &graph); |
| 324 |
1/1✓ Branch 254 → 255 taken 126 times.
|
126 | Families::addAreas(input, families, centerCardId, &graph); |
| 325 | 126 | return graph; | |
| 326 | 126 | } | |
| 327 | |||
| 328 | 30 | Graph overview(const Input &input, const Layers &layers) { | |
| 329 | 30 | Graph graph; | |
| 330 |
1/1✓ Branch 2 → 3 taken 30 times.
|
30 | const Index index(input); |
| 331 | 30 | QList<const Person *> people; | |
| 332 |
2/2✓ Branch 10 → 5 taken 32529 times.
✓ Branch 10 → 11 taken 30 times.
|
32559 | for (const Person &p : input.people) |
| 333 |
1/2✓ Branch 6 → 7 taken 32529 times.
✗ Branch 6 → 8 not taken.
|
32529 | if (p.inScope) |
| 334 |
1/1✓ Branch 7 → 8 taken 32529 times.
|
32529 | people.append(&p); |
| 335 |
3/3✓ Branch 11 → 12 taken 30 times.
✓ Branch 12 → 13 taken 30 times.
✓ Branch 13 → 14 taken 30 times.
|
456821 | std::sort(people.begin(), people.end(), [](const Person *a, const Person *b) { return a->cardId < b->cardId; }); |
| 336 |
4/4✓ Branch 14 → 15 taken 30 times.
✓ Branch 15 → 16 taken 30 times.
✓ Branch 29 → 17 taken 32529 times.
✓ Branch 29 → 30 taken 30 times.
|
32559 | for (const Person *p : people) { |
| 337 | 32529 | const int degree = index.degree.value(p->cardId); | |
| 338 |
2/2✓ Branch 19 → 20 taken 522 times.
✓ Branch 19 → 21 taken 32007 times.
|
32529 | if (degree == 0) { |
| 339 | 522 | ++graph.hiddenWithoutRelationships; | |
| 340 | 522 | continue; | |
| 341 | } | ||
| 342 |
1/1✓ Branch 23 → 24 taken 32007 times.
|
32007 | graph.nodes.append(nodeOf(*p, 0, QString(), degree)); |
| 343 | } | ||
| 344 |
1/1✓ Branch 30 → 31 taken 30 times.
|
30 | addStoredEdges(input, &graph); |
| 345 |
1/1✓ Branch 31 → 32 taken 30 times.
|
30 | addLayers(input, layers, index.people, -1, &graph); |
| 346 |
2/2✓ Branch 32 → 33 taken 30 times.
✓ Branch 33 → 34 taken 30 times.
|
30 | Families::addAreas(input, Families::of(input), 0, &graph); |
| 347 | 30 | return graph; | |
| 348 | 30 | } | |
| 349 | |||
| 350 | } // namespace RelationGraph | ||
| 351 |