annotate grapher.cpp @ 49:f9b53c10a3f6

* A fix that will only work when we sort changesets by date (test with classical-rdf)
author Chris Cannam
date Wed, 10 Nov 2010 22:27:58 +0000
parents 24efab584ee5
children bf3ab0ffb559
rev   line source
Chris@44 1
Chris@44 2 #include "grapher.h"
Chris@46 3 #include "connectionitem.h"
Chris@44 4
cannam@45 5 #include <QGraphicsScene>
Chris@44 6
Chris@44 7 #include <iostream>
Chris@44 8
cannam@45 9 int
cannam@45 10 Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
cannam@45 11 {
cannam@45 12 int col = parent;
cannam@45 13 if (preferParentCol) {
cannam@45 14 if (!m_alloc[row].contains(col)) {
cannam@45 15 return col;
cannam@45 16 }
cannam@45 17 }
cannam@45 18 while (col > 0) {
cannam@45 19 if (!m_alloc[row].contains(--col)) return col;
cannam@45 20 }
cannam@45 21 while (col < 0) {
cannam@45 22 if (!m_alloc[row].contains(++col)) return col;
cannam@45 23 }
cannam@45 24 col = parent;
cannam@45 25 int sign = (col < 0 ? -1 : 1);
cannam@45 26 while (1) {
cannam@45 27 col += sign;
cannam@45 28 if (!m_alloc[row].contains(col)) return col;
cannam@45 29 }
cannam@45 30 }
Chris@44 31
cannam@45 32 void
cannam@45 33 Grapher::layoutRow(QString id)
Chris@44 34 {
cannam@45 35 if (m_handled.contains(id)) {
cannam@45 36 return;
Chris@44 37 }
Chris@46 38 if (!m_changesets.contains(id)) {
cannam@45 39 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
Chris@44 40 }
cannam@45 41 if (!m_items.contains(id)) {
cannam@45 42 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
Chris@44 43 }
Chris@46 44 Changeset *cs = m_changesets[id];
cannam@45 45 ChangesetItem *item = m_items[id];
cannam@45 46 std::cerr << "Looking at " << id.toStdString() << std::endl;
cannam@45 47
Chris@44 48 int row = 0;
cannam@45 49 int nparents = cs->parents().size();
cannam@45 50
cannam@45 51 if (nparents > 0) {
Chris@44 52 bool haveRow = false;
Chris@44 53 foreach (QString parentId, cs->parents()) {
cannam@45 54
Chris@46 55 if (!m_changesets.contains(parentId)) continue;
cannam@45 56 if (!m_items.contains(parentId)) continue;
cannam@45 57
cannam@45 58 if (!m_handled.contains(parentId)) {
cannam@45 59 layoutRow(parentId);
cannam@45 60 }
cannam@45 61
cannam@45 62 ChangesetItem *parentItem = m_items[parentId];
Chris@44 63 if (!haveRow || parentItem->row() < row) {
Chris@44 64 row = parentItem->row();
Chris@44 65 haveRow = true;
Chris@44 66 }
Chris@44 67 }
Chris@44 68 row = row - 1;
cannam@45 69 }
cannam@45 70
cannam@45 71 std::cerr << "putting " << cs->id().toStdString() << " at row " << row
cannam@45 72 << std::endl;
cannam@45 73
Chris@44 74 item->setRow(row);
cannam@45 75 m_handled.insert(id);
Chris@44 76 }
Chris@44 77
Chris@44 78 void
cannam@45 79 Grapher::layoutCol(QString id)
Chris@44 80 {
cannam@45 81 if (m_handled.contains(id)) {
cannam@45 82 std::cerr << "Already looked at " << id.toStdString() << std::endl;
cannam@45 83 return;
cannam@45 84 }
Chris@46 85 if (!m_changesets.contains(id)) {
cannam@45 86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
cannam@45 87 }
cannam@45 88 if (!m_items.contains(id)) {
cannam@45 89 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
cannam@45 90 }
Chris@46 91 Changeset *cs = m_changesets[id];
cannam@45 92 ChangesetItem *item = m_items[id];
cannam@45 93 std::cerr << "Looking at " << id.toStdString() << std::endl;
cannam@45 94
Chris@46 95 foreach (QString parentId, cs->parents()) {
Chris@46 96 if (!m_changesets.contains(parentId)) continue;
Chris@46 97 if (!m_handled.contains(parentId)) {
Chris@46 98 layoutCol(parentId);
Chris@46 99 }
Chris@46 100 }
Chris@46 101
Chris@47 102 // Parent may have layed out child in the recursive call
Chris@47 103 if (m_handled.contains(id)) {
Chris@47 104 std::cerr << "Looks like we've dealt with " << id.toStdString() << std::endl;
Chris@47 105 return;
Chris@47 106 }
Chris@47 107
cannam@45 108 int col = 0;
Chris@46 109 int row = item->row();
Chris@46 110 QString branch = cs->branch();
Chris@47 111
cannam@45 112 int nparents = cs->parents().size();
Chris@46 113 QString parentId;
Chris@46 114 int parentsOnSameBranch = 0;
cannam@45 115
Chris@46 116 switch (nparents) {
cannam@45 117
Chris@46 118 case 0:
Chris@46 119 col = m_branchHomes[cs->branch()];
Chris@46 120 col = findAvailableColumn(row, col, true);
Chris@46 121 break;
cannam@45 122
Chris@46 123 case 1:
Chris@46 124 parentId = cs->parents()[0];
cannam@45 125
Chris@46 126 if (!m_changesets.contains(parentId) ||
Chris@46 127 m_changesets[parentId]->branch() != branch) {
Chris@46 128 // new branch
Chris@46 129 col = m_branchHomes[branch];
Chris@46 130 } else {
Chris@46 131 col = m_items[parentId]->column();
Chris@44 132 }
cannam@45 133
Chris@46 134 col = findAvailableColumn(row, col, true);
Chris@46 135 break;
Chris@46 136
Chris@46 137 case 2:
Chris@46 138 // a merge: behave differently if parents are both on the same
Chris@46 139 // branch (we also want to behave differently for nodes that
Chris@46 140 // have multiple children on the same branch -- spreading them
Chris@46 141 // out rather than having affinity to a specific branch)
Chris@46 142
Chris@46 143 foreach (QString parentId, cs->parents()) {
Chris@46 144 if (!m_changesets.contains(parentId)) continue;
Chris@46 145 if (m_changesets[parentId]->branch() == branch) {
Chris@46 146 ChangesetItem *parentItem = m_items[parentId];
Chris@46 147 col += parentItem->column();
Chris@46 148 parentsOnSameBranch++;
Chris@46 149 }
Chris@46 150 }
Chris@46 151
Chris@46 152 if (parentsOnSameBranch > 0) {
Chris@46 153 col /= parentsOnSameBranch;
Chris@46 154 col = findAvailableColumn(item->row(), col, true);
Chris@46 155 } else {
Chris@46 156 col = findAvailableColumn(item->row(), col, false);
Chris@46 157 }
Chris@46 158 break;
Chris@44 159 }
Chris@44 160
cannam@45 161 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
cannam@45 162
Chris@46 163 m_alloc[row].insert(col);
cannam@45 164 item->setColumn(col);
cannam@45 165 m_handled.insert(id);
Chris@47 166
Chris@47 167 int nchildren = cs->children().size();
Chris@49 168
Chris@49 169 // look for merging children and make sure nobody
Chris@49 170 // is going to overwrite their "merge lines"
Chris@49 171 foreach (QString childId, cs->children()) {
Chris@49 172 if (!m_changesets.contains(childId)) continue;
Chris@49 173 Changeset *child = m_changesets[childId];
Chris@49 174 if (child->parents().size() > 1) {
Chris@49 175 int childRow = m_items[childId]->row();
Chris@49 176 std::cerr << "I'm at " << row << ", child with >1 parents is at " << childRow << std::endl;
Chris@49 177 for (int r = row; r >= childRow; --r) {
Chris@49 178 std::cerr << "setting row " << r << ", col " << col << std::endl;
Chris@49 179 m_alloc[r].insert(col);
Chris@49 180 }
Chris@49 181 }
Chris@49 182 }
Chris@49 183
Chris@47 184 if (nchildren > 1) {
Chris@47 185 // Normally the children will lay out themselves. We just
Chris@47 186 // want to handle the case where exactly two children have the
Chris@47 187 // same branch as us, because we can handle that neatly
Chris@47 188 QList<QString> special;
Chris@47 189 foreach (QString childId, cs->children()) {
Chris@47 190 if (!m_changesets.contains(childId)) continue;
Chris@47 191 Changeset *child = m_changesets[childId];
Chris@47 192 if (child->branch() == branch &&
Chris@47 193 child->parents().size() == 1) {
Chris@47 194 special.push_back(childId);
Chris@47 195 }
Chris@47 196 }
Chris@47 197 if (special.size() == 2) {
Chris@47 198 m_items[special[0]]->setColumn
Chris@47 199 (findAvailableColumn(item->row() - 1, col - 1, true));
Chris@47 200 m_items[special[1]]->setColumn
Chris@47 201 (findAvailableColumn(item->row() - 1, col + 1, true));
Chris@47 202 m_handled.insert(special[0]);
Chris@47 203 m_handled.insert(special[1]);
Chris@47 204 }
Chris@47 205 }
cannam@45 206 }
cannam@45 207
Chris@46 208 bool
Chris@46 209 Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 210 {
Chris@46 211 // allow some additional space at edges. we really ought also to
Chris@46 212 // permit space at the end of a branch up to the point where the
Chris@46 213 // merge happens
Chris@46 214 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 215 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 216 if (a1 > b2 || b1 < a2) return false;
Chris@46 217 if (a2 > b1 || b2 < a1) return false;
Chris@46 218 return true;
Chris@46 219 }
Chris@46 220
Chris@46 221 void
Chris@46 222 Grapher::allocateBranchHomes(Changesets csets)
Chris@46 223 {
Chris@46 224 foreach (Changeset *cs, csets) {
Chris@46 225 QString branch = cs->branch();
Chris@46 226 ChangesetItem *item = m_items[cs->id()];
Chris@46 227 if (!item) continue;
Chris@46 228 int row = item->row();
Chris@46 229 if (!m_branchRanges.contains(branch)) {
Chris@46 230 m_branchRanges[branch] = Range(row, row);
Chris@46 231 } else {
Chris@46 232 Range p = m_branchRanges[branch];
Chris@46 233 if (row < p.first) p.first = row;
Chris@46 234 if (row > p.second) p.second = row;
Chris@46 235 m_branchRanges[branch] = p;
Chris@46 236 }
Chris@46 237 }
Chris@46 238
Chris@46 239 m_branchHomes[""] = 0;
Chris@46 240
Chris@46 241 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 242 if (branch == "") continue;
Chris@46 243 QSet<int> taken;
Chris@46 244 taken.insert(0);
Chris@46 245 Range myRange = m_branchRanges[branch];
Chris@46 246 foreach (QString other, m_branchRanges.keys()) {
Chris@46 247 if (other == branch || other == "") continue;
Chris@46 248 Range otherRange = m_branchRanges[other];
Chris@46 249 if (rangesConflict(myRange, otherRange)) {
Chris@46 250 if (m_branchHomes.contains(other)) {
Chris@46 251 taken.insert(m_branchHomes[other]);
Chris@46 252 }
Chris@46 253 }
Chris@46 254 }
Chris@47 255 int home = 3;
Chris@46 256 while (taken.contains(home)) {
Chris@46 257 if (home > 0) home = -home;
Chris@47 258 else home = -(home-3);
Chris@46 259 }
Chris@46 260 m_branchHomes[branch] = home;
Chris@46 261 }
Chris@46 262
Chris@46 263 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 264 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
Chris@46 265 }
Chris@46 266 }
Chris@46 267
cannam@45 268 void
cannam@45 269 Grapher::layout(Changesets csets)
cannam@45 270 {
Chris@46 271 m_changesets.clear();
cannam@45 272 m_items.clear();
cannam@45 273 m_alloc.clear();
Chris@46 274 m_branchHomes.clear();
cannam@45 275
Chris@44 276 foreach (Changeset *cs, csets) {
cannam@45 277
cannam@45 278 QString id = cs->id();
cannam@45 279 std::cerr << id.toStdString() << std::endl;
cannam@45 280
cannam@45 281 if (id == "") {
cannam@45 282 throw LayoutException("Changeset has no ID");
cannam@45 283 }
Chris@46 284 if (m_changesets.contains(id)) {
cannam@45 285 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
cannam@45 286 }
cannam@45 287
Chris@46 288 m_changesets[id] = cs;
cannam@45 289
cannam@45 290 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 291 item->setX(0);
cannam@45 292 item->setY(0);
cannam@45 293 m_items[id] = item;
cannam@45 294 m_scene->addItem(item);
cannam@45 295 }
cannam@45 296
Chris@46 297 foreach (Changeset *cs, csets) {
Chris@46 298 QString id = cs->id();
Chris@46 299 ChangesetItem *item = m_items[id];
Chris@46 300 foreach (QString parentId, cs->parents()) {
Chris@47 301 if (!m_changesets.contains(parentId)) continue;
Chris@47 302 Changeset *parent = m_changesets[parentId];
Chris@47 303 parent->addChild(id);
Chris@46 304 ConnectionItem *conn = new ConnectionItem();
Chris@46 305 conn->setChild(item);
Chris@46 306 conn->setParent(m_items[parentId]);
Chris@46 307 m_scene->addItem(conn);
Chris@46 308 }
Chris@46 309 }
Chris@46 310
cannam@45 311 // Layout in reverse order, i.e. forward chronological order.
cannam@45 312 // This ensures that parents will normally be laid out before
cannam@45 313 // their children -- though we can recurse from layout() if we
cannam@45 314 // find any weird exceptions
cannam@45 315 m_handled.clear();
cannam@45 316 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 317 layoutRow(csets[i]->id());
cannam@45 318 }
Chris@46 319
Chris@46 320 allocateBranchHomes(csets);
Chris@46 321
cannam@45 322 m_handled.clear();
cannam@45 323 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 324 layoutCol(csets[i]->id());
cannam@45 325 }
Chris@44 326 }
Chris@44 327