annotate 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
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
cannam@45 102 int col = 0;
Chris@46 103 int row = item->row();
Chris@46 104 QString branch = cs->branch();
cannam@45 105 int nparents = cs->parents().size();
Chris@46 106 QString parentId;
Chris@46 107 int parentsOnSameBranch = 0;
cannam@45 108
Chris@46 109 switch (nparents) {
cannam@45 110
Chris@46 111 case 0:
Chris@46 112 col = m_branchHomes[cs->branch()];
Chris@46 113 col = findAvailableColumn(row, col, true);
Chris@46 114 break;
cannam@45 115
Chris@46 116 case 1:
Chris@46 117 parentId = cs->parents()[0];
cannam@45 118
Chris@46 119 if (!m_changesets.contains(parentId) ||
Chris@46 120 m_changesets[parentId]->branch() != branch) {
Chris@46 121 // new branch
Chris@46 122 col = m_branchHomes[branch];
Chris@46 123 } else {
Chris@46 124 col = m_items[parentId]->column();
Chris@44 125 }
cannam@45 126
Chris@46 127 col = findAvailableColumn(row, col, true);
Chris@46 128 break;
Chris@46 129
Chris@46 130 case 2:
Chris@46 131 // a merge: behave differently if parents are both on the same
Chris@46 132 // branch (we also want to behave differently for nodes that
Chris@46 133 // have multiple children on the same branch -- spreading them
Chris@46 134 // out rather than having affinity to a specific branch)
Chris@46 135
Chris@46 136 foreach (QString parentId, cs->parents()) {
Chris@46 137 if (!m_changesets.contains(parentId)) continue;
Chris@46 138 if (m_changesets[parentId]->branch() == branch) {
Chris@46 139 ChangesetItem *parentItem = m_items[parentId];
Chris@46 140 col += parentItem->column();
Chris@46 141 parentsOnSameBranch++;
Chris@46 142 }
Chris@46 143 }
Chris@46 144
Chris@46 145 if (parentsOnSameBranch > 0) {
Chris@46 146 col /= parentsOnSameBranch;
Chris@46 147 col = findAvailableColumn(item->row(), col, true);
Chris@46 148 } else {
Chris@46 149 col = findAvailableColumn(item->row(), col, false);
Chris@46 150 }
Chris@46 151 break;
Chris@44 152 }
Chris@44 153
cannam@45 154 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
cannam@45 155
Chris@46 156 m_alloc[row].insert(col);
cannam@45 157 item->setColumn(col);
cannam@45 158 m_handled.insert(id);
cannam@45 159 }
cannam@45 160
Chris@46 161 bool
Chris@46 162 Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 163 {
Chris@46 164 // allow some additional space at edges. we really ought also to
Chris@46 165 // permit space at the end of a branch up to the point where the
Chris@46 166 // merge happens
Chris@46 167 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 168 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 169 if (a1 > b2 || b1 < a2) return false;
Chris@46 170 if (a2 > b1 || b2 < a1) return false;
Chris@46 171 return true;
Chris@46 172 }
Chris@46 173
Chris@46 174 void
Chris@46 175 Grapher::allocateBranchHomes(Changesets csets)
Chris@46 176 {
Chris@46 177 foreach (Changeset *cs, csets) {
Chris@46 178 QString branch = cs->branch();
Chris@46 179 ChangesetItem *item = m_items[cs->id()];
Chris@46 180 if (!item) continue;
Chris@46 181 int row = item->row();
Chris@46 182 if (!m_branchRanges.contains(branch)) {
Chris@46 183 m_branchRanges[branch] = Range(row, row);
Chris@46 184 } else {
Chris@46 185 Range p = m_branchRanges[branch];
Chris@46 186 if (row < p.first) p.first = row;
Chris@46 187 if (row > p.second) p.second = row;
Chris@46 188 m_branchRanges[branch] = p;
Chris@46 189 }
Chris@46 190 }
Chris@46 191
Chris@46 192 m_branchHomes[""] = 0;
Chris@46 193
Chris@46 194 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 195 if (branch == "") continue;
Chris@46 196 QSet<int> taken;
Chris@46 197 taken.insert(0);
Chris@46 198 Range myRange = m_branchRanges[branch];
Chris@46 199 foreach (QString other, m_branchRanges.keys()) {
Chris@46 200 if (other == branch || other == "") continue;
Chris@46 201 Range otherRange = m_branchRanges[other];
Chris@46 202 if (rangesConflict(myRange, otherRange)) {
Chris@46 203 if (m_branchHomes.contains(other)) {
Chris@46 204 taken.insert(m_branchHomes[other]);
Chris@46 205 }
Chris@46 206 }
Chris@46 207 }
Chris@46 208 int home = 2;
Chris@46 209 while (taken.contains(home)) {
Chris@46 210 if (home > 0) home = -home;
Chris@46 211 else home = -(home-2);
Chris@46 212 }
Chris@46 213 m_branchHomes[branch] = home;
Chris@46 214 }
Chris@46 215
Chris@46 216 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 217 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
Chris@46 218 }
Chris@46 219 }
Chris@46 220
cannam@45 221 void
cannam@45 222 Grapher::layout(Changesets csets)
cannam@45 223 {
Chris@46 224 m_changesets.clear();
cannam@45 225 m_items.clear();
cannam@45 226 m_alloc.clear();
Chris@46 227 m_branchHomes.clear();
cannam@45 228
Chris@44 229 foreach (Changeset *cs, csets) {
cannam@45 230
cannam@45 231 QString id = cs->id();
cannam@45 232 std::cerr << id.toStdString() << std::endl;
cannam@45 233
cannam@45 234 if (id == "") {
cannam@45 235 throw LayoutException("Changeset has no ID");
cannam@45 236 }
Chris@46 237 if (m_changesets.contains(id)) {
cannam@45 238 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
cannam@45 239 }
cannam@45 240
Chris@46 241 m_changesets[id] = cs;
cannam@45 242
cannam@45 243 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 244 item->setX(0);
cannam@45 245 item->setY(0);
cannam@45 246 m_items[id] = item;
cannam@45 247 m_scene->addItem(item);
cannam@45 248 }
cannam@45 249
Chris@46 250 foreach (Changeset *cs, csets) {
Chris@46 251 QString id = cs->id();
Chris@46 252 ChangesetItem *item = m_items[id];
Chris@46 253 foreach (QString parentId, cs->parents()) {
Chris@46 254 if (!m_items.contains(parentId)) continue;
Chris@46 255 ConnectionItem *conn = new ConnectionItem();
Chris@46 256 conn->setChild(item);
Chris@46 257 conn->setParent(m_items[parentId]);
Chris@46 258 m_scene->addItem(conn);
Chris@46 259 }
Chris@46 260 }
Chris@46 261
cannam@45 262 // Layout in reverse order, i.e. forward chronological order.
cannam@45 263 // This ensures that parents will normally be laid out before
cannam@45 264 // their children -- though we can recurse from layout() if we
cannam@45 265 // find any weird exceptions
cannam@45 266 m_handled.clear();
cannam@45 267 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 268 layoutRow(csets[i]->id());
cannam@45 269 }
Chris@46 270
Chris@46 271 allocateBranchHomes(csets);
Chris@46 272
cannam@45 273 m_handled.clear();
cannam@45 274 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 275 layoutCol(csets[i]->id());
cannam@45 276 }
Chris@46 277 /*
cannam@45 278 foreach (Changeset *cs, csets) {
cannam@45 279 QString id = cs->id();
cannam@45 280 if (!m_items.contains(id)) continue;
cannam@45 281 ChangesetItem *me = m_items[id];
cannam@45 282 foreach (QString parentId, cs->parents()) {
cannam@45 283 if (!m_items.contains(parentId)) continue;
cannam@45 284 ChangesetItem *parent = m_items[parentId];
cannam@45 285 QGraphicsLineItem *line = new QGraphicsLineItem;
cannam@45 286 line->setLine(me->x() + 25, me->y() + 50,
cannam@45 287 parent->x() + 25, parent->y());
cannam@45 288 m_scene->addItem(line);
cannam@45 289 }
Chris@44 290 }
Chris@46 291 */
Chris@44 292 }
Chris@44 293