comparison grapher.cpp @ 46:bd3accba9b3f

* Better layout for branches; spline connection paths
author Chris Cannam
date Wed, 10 Nov 2010 17:11:41 +0000
parents 4286836bb3c9
children 24efab584ee5
comparison
equal deleted inserted replaced
45:4286836bb3c9 46:bd3accba9b3f
1 1
2 #include "grapher.h" 2 #include "grapher.h"
3 #include "connectionitem.h"
3 4
4 #include <QGraphicsScene> 5 #include <QGraphicsScene>
5 6
6 #include <iostream> 7 #include <iostream>
7 8
32 Grapher::layoutRow(QString id) 33 Grapher::layoutRow(QString id)
33 { 34 {
34 if (m_handled.contains(id)) { 35 if (m_handled.contains(id)) {
35 return; 36 return;
36 } 37 }
37 if (!m_idCsetMap.contains(id)) { 38 if (!m_changesets.contains(id)) {
38 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); 39 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
39 } 40 }
40 if (!m_items.contains(id)) { 41 if (!m_items.contains(id)) {
41 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); 42 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
42 } 43 }
43 Changeset *cs = m_idCsetMap[id]; 44 Changeset *cs = m_changesets[id];
44 ChangesetItem *item = m_items[id]; 45 ChangesetItem *item = m_items[id];
45 std::cerr << "Looking at " << id.toStdString() << std::endl; 46 std::cerr << "Looking at " << id.toStdString() << std::endl;
46 47
47 int row = 0; 48 int row = 0;
48 int nparents = cs->parents().size(); 49 int nparents = cs->parents().size();
49 50
50 if (nparents > 0) { 51 if (nparents > 0) {
51 bool haveRow = false; 52 bool haveRow = false;
52 foreach (QString parentId, cs->parents()) { 53 foreach (QString parentId, cs->parents()) {
53 54
54 if (!m_idCsetMap.contains(parentId)) continue; 55 if (!m_changesets.contains(parentId)) continue;
55 if (!m_items.contains(parentId)) continue; 56 if (!m_items.contains(parentId)) continue;
56 57
57 if (!m_handled.contains(parentId)) { 58 if (!m_handled.contains(parentId)) {
58 layoutRow(parentId); 59 layoutRow(parentId);
59 } 60 }
69 70
70 std::cerr << "putting " << cs->id().toStdString() << " at row " << row 71 std::cerr << "putting " << cs->id().toStdString() << " at row " << row
71 << std::endl; 72 << std::endl;
72 73
73 item->setRow(row); 74 item->setRow(row);
74 item->setY(row * 100);
75 m_handled.insert(id); 75 m_handled.insert(id);
76 } 76 }
77 77
78 void 78 void
79 Grapher::layoutCol(QString id) 79 Grapher::layoutCol(QString id)
80 { 80 {
81 if (m_handled.contains(id)) { 81 if (m_handled.contains(id)) {
82 std::cerr << "Already looked at " << id.toStdString() << std::endl; 82 std::cerr << "Already looked at " << id.toStdString() << std::endl;
83 return; 83 return;
84 } 84 }
85 if (!m_idCsetMap.contains(id)) { 85 if (!m_changesets.contains(id)) {
86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); 86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
87 } 87 }
88 if (!m_items.contains(id)) { 88 if (!m_items.contains(id)) {
89 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); 89 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
90 } 90 }
91 Changeset *cs = m_idCsetMap[id]; 91 Changeset *cs = m_changesets[id];
92 ChangesetItem *item = m_items[id]; 92 ChangesetItem *item = m_items[id];
93 std::cerr << "Looking at " << id.toStdString() << std::endl; 93 std::cerr << "Looking at " << id.toStdString() << std::endl;
94 94
95 foreach (QString parentId, cs->parents()) {
96 if (!m_changesets.contains(parentId)) continue;
97 if (!m_handled.contains(parentId)) {
98 layoutCol(parentId);
99 }
100 }
101
95 int col = 0; 102 int col = 0;
103 int row = item->row();
104 QString branch = cs->branch();
96 int nparents = cs->parents().size(); 105 int nparents = cs->parents().size();
97 106 QString parentId;
98 if (nparents > 0) { 107 int parentsOnSameBranch = 0;
99 bool preferParentCol = true; 108
100 foreach (QString parentId, cs->parents()) { 109 switch (nparents) {
101 110
102 if (!m_idCsetMap.contains(parentId)) continue; 111 case 0:
103 if (!m_items.contains(parentId)) continue; 112 col = m_branchHomes[cs->branch()];
104 113 col = findAvailableColumn(row, col, true);
105 if (nparents == 1) { 114 break;
106 // when introducing a new branch, aim _not_ to 115
107 // position child on the same column as parent 116 case 1:
108 Changeset *parent = m_idCsetMap[parentId]; 117 parentId = cs->parents()[0];
109 if (parent->branch() != cs->branch()) { 118
110 preferParentCol = false; 119 if (!m_changesets.contains(parentId) ||
120 m_changesets[parentId]->branch() != branch) {
121 // new branch
122 col = m_branchHomes[branch];
123 } else {
124 col = m_items[parentId]->column();
125 }
126
127 col = findAvailableColumn(row, col, true);
128 break;
129
130 case 2:
131 // a merge: behave differently if parents are both on the same
132 // branch (we also want to behave differently for nodes that
133 // have multiple children on the same branch -- spreading them
134 // out rather than having affinity to a specific branch)
135
136 foreach (QString parentId, cs->parents()) {
137 if (!m_changesets.contains(parentId)) continue;
138 if (m_changesets[parentId]->branch() == branch) {
139 ChangesetItem *parentItem = m_items[parentId];
140 col += parentItem->column();
141 parentsOnSameBranch++;
142 }
143 }
144
145 if (parentsOnSameBranch > 0) {
146 col /= parentsOnSameBranch;
147 col = findAvailableColumn(item->row(), col, true);
148 } else {
149 col = findAvailableColumn(item->row(), col, false);
150 }
151 break;
152 }
153
154 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
155
156 m_alloc[row].insert(col);
157 item->setColumn(col);
158 m_handled.insert(id);
159 }
160
161 bool
162 Grapher::rangesConflict(const Range &r1, const Range &r2)
163 {
164 // allow some additional space at edges. we really ought also to
165 // permit space at the end of a branch up to the point where the
166 // merge happens
167 int a1 = r1.first - 2, b1 = r1.second + 2;
168 int a2 = r2.first - 2, b2 = r2.second + 2;
169 if (a1 > b2 || b1 < a2) return false;
170 if (a2 > b1 || b2 < a1) return false;
171 return true;
172 }
173
174 void
175 Grapher::allocateBranchHomes(Changesets csets)
176 {
177 foreach (Changeset *cs, csets) {
178 QString branch = cs->branch();
179 ChangesetItem *item = m_items[cs->id()];
180 if (!item) continue;
181 int row = item->row();
182 if (!m_branchRanges.contains(branch)) {
183 m_branchRanges[branch] = Range(row, row);
184 } else {
185 Range p = m_branchRanges[branch];
186 if (row < p.first) p.first = row;
187 if (row > p.second) p.second = row;
188 m_branchRanges[branch] = p;
189 }
190 }
191
192 m_branchHomes[""] = 0;
193
194 foreach (QString branch, m_branchRanges.keys()) {
195 if (branch == "") continue;
196 QSet<int> taken;
197 taken.insert(0);
198 Range myRange = m_branchRanges[branch];
199 foreach (QString other, m_branchRanges.keys()) {
200 if (other == branch || other == "") continue;
201 Range otherRange = m_branchRanges[other];
202 if (rangesConflict(myRange, otherRange)) {
203 if (m_branchHomes.contains(other)) {
204 taken.insert(m_branchHomes[other]);
111 } 205 }
112 } 206 }
113 207 }
114 if (!m_handled.contains(parentId)) { 208 int home = 2;
115 layoutCol(parentId); 209 while (taken.contains(home)) {
116 } 210 if (home > 0) home = -home;
117 211 else home = -(home-2);
118 ChangesetItem *parentItem = m_items[parentId]; 212 }
119 col += parentItem->column(); 213 m_branchHomes[branch] = home;
120 } 214 }
121 215
122 col /= cs->parents().size(); 216 foreach (QString branch, m_branchRanges.keys()) {
123 col = findAvailableColumn(item->row(), col, preferParentCol); 217 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
124 m_alloc[item->row()].insert(col); 218 }
125 }
126
127 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
128
129 item->setColumn(col);
130 item->setX(col * 100);
131 m_handled.insert(id);
132 } 219 }
133 220
134 void 221 void
135 Grapher::layout(Changesets csets) 222 Grapher::layout(Changesets csets)
136 { 223 {
137 m_idCsetMap.clear(); 224 m_changesets.clear();
138 m_items.clear(); 225 m_items.clear();
139 m_alloc.clear(); 226 m_alloc.clear();
227 m_branchHomes.clear();
140 228
141 foreach (Changeset *cs, csets) { 229 foreach (Changeset *cs, csets) {
142 230
143 QString id = cs->id(); 231 QString id = cs->id();
144 std::cerr << id.toStdString() << std::endl; 232 std::cerr << id.toStdString() << std::endl;
145 233
146 if (id == "") { 234 if (id == "") {
147 throw LayoutException("Changeset has no ID"); 235 throw LayoutException("Changeset has no ID");
148 } 236 }
149 if (m_idCsetMap.contains(id)) { 237 if (m_changesets.contains(id)) {
150 throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); 238 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
151 } 239 }
152 240
153 m_idCsetMap[id] = cs; 241 m_changesets[id] = cs;
154 242
155 ChangesetItem *item = new ChangesetItem(cs); 243 ChangesetItem *item = new ChangesetItem(cs);
156 item->setX(0); 244 item->setX(0);
157 item->setY(0); 245 item->setY(0);
158 m_items[id] = item; 246 m_items[id] = item;
159 m_scene->addItem(item); 247 m_scene->addItem(item);
248 }
249
250 foreach (Changeset *cs, csets) {
251 QString id = cs->id();
252 ChangesetItem *item = m_items[id];
253 foreach (QString parentId, cs->parents()) {
254 if (!m_items.contains(parentId)) continue;
255 ConnectionItem *conn = new ConnectionItem();
256 conn->setChild(item);
257 conn->setParent(m_items[parentId]);
258 m_scene->addItem(conn);
259 }
160 } 260 }
161 261
162 // Layout in reverse order, i.e. forward chronological order. 262 // Layout in reverse order, i.e. forward chronological order.
163 // This ensures that parents will normally be laid out before 263 // This ensures that parents will normally be laid out before
164 // their children -- though we can recurse from layout() if we 264 // their children -- though we can recurse from layout() if we
165 // find any weird exceptions 265 // find any weird exceptions
166 m_handled.clear(); 266 m_handled.clear();
167 for (int i = csets.size() - 1; i >= 0; --i) { 267 for (int i = csets.size() - 1; i >= 0; --i) {
168 layoutRow(csets[i]->id()); 268 layoutRow(csets[i]->id());
169 } 269 }
270
271 allocateBranchHomes(csets);
272
170 m_handled.clear(); 273 m_handled.clear();
171 for (int i = csets.size() - 1; i >= 0; --i) { 274 for (int i = csets.size() - 1; i >= 0; --i) {
172 layoutCol(csets[i]->id()); 275 layoutCol(csets[i]->id());
173 } 276 }
174 277 /*
175 foreach (Changeset *cs, csets) { 278 foreach (Changeset *cs, csets) {
176 QString id = cs->id(); 279 QString id = cs->id();
177 if (!m_items.contains(id)) continue; 280 if (!m_items.contains(id)) continue;
178 ChangesetItem *me = m_items[id]; 281 ChangesetItem *me = m_items[id];
179 foreach (QString parentId, cs->parents()) { 282 foreach (QString parentId, cs->parents()) {
183 line->setLine(me->x() + 25, me->y() + 50, 286 line->setLine(me->x() + 25, me->y() + 50,
184 parent->x() + 25, parent->y()); 287 parent->x() + 25, parent->y());
185 m_scene->addItem(line); 288 m_scene->addItem(line);
186 } 289 }
187 } 290 }
188 } 291 */
189 292 }
293