| 1 | /* $Id$ $Revision$ */ |
| 2 | /* vim:set shiftwidth=4 ts=8: */ |
| 3 | |
| 4 | /************************************************************************* |
| 5 | * Copyright (c) 2011 AT&T Intellectual Property |
| 6 | * All rights reserved. This program and the accompanying materials |
| 7 | * are made available under the terms of the Eclipse Public License v1.0 |
| 8 | * which accompanies this distribution, and is available at |
| 9 | * http://www.eclipse.org/legal/epl-v10.html |
| 10 | * |
| 11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
| 12 | *************************************************************************/ |
| 13 | |
| 14 | |
| 15 | #include "config.h" |
| 16 | |
| 17 | /* TODO: |
| 18 | * If cut point is in exactly 2 blocks, expand block circles to overlap |
| 19 | * especially in the case where one block is the sole child of the other. |
| 20 | */ |
| 21 | |
| 22 | #include "blockpath.h" |
| 23 | |
| 24 | /* getRotation: |
| 25 | * The function determines how much the block should be rotated |
| 26 | * for best positioning with parent, assuming its center is at x and y |
| 27 | * relative to the parent. |
| 28 | * angle gives the angle of the new position, i.e., tan(angle) = y/x. |
| 29 | * If sn has 2 nodes, we arrange the line of the 2 normal to angle. |
| 30 | * If sn has 1 node, parent_pos has already been set to the |
| 31 | * correct angle assuming no rotation. |
| 32 | * Otherwise, we find the node in sn connected to the parent and rotate |
| 33 | * the block so that it is closer or at least visible to its node in the |
| 34 | * parent. |
| 35 | * |
| 36 | * For COALESCED blocks, if neighbor is in left half plane, |
| 37 | * use unCOALESCED case. |
| 38 | * Else let theta be angle, R = LEN(x,y), pho the radius of actual |
| 39 | * child block, phi be angle of neighbor in actual child block, |
| 40 | * and r the distance from center of coalesced block to center of |
| 41 | * actual block. Then, the angle to rotate the coalesced block to |
| 42 | * that the edge from the parent is tangent to the neighbor on the |
| 43 | * actual child block circle is |
| 44 | * alpha = theta + M_PI/2 - phi - arcsin((l/R)*(sin B)) |
| 45 | * where l = r - rho/(cos phi) and beta = M_PI/2 + phi. |
| 46 | * Thus, |
| 47 | * alpha = theta + M_PI/2 - phi - arcsin((l/R)*(cos phi)) |
| 48 | */ |
| 49 | static double |
| 50 | getRotation(block_t * sn, Agraph_t * g, double x, double y, double theta) |
| 51 | { |
| 52 | double mindist2; |
| 53 | Agraph_t *subg; |
| 54 | /* Agedge_t* e; */ |
| 55 | Agnode_t *n, *closest_node, *neighbor; |
| 56 | nodelist_t *list; |
| 57 | double len2, newX, newY; |
| 58 | int count; |
| 59 | |
| 60 | subg = sn->sub_graph; |
| 61 | #ifdef OLD |
| 62 | parent = sn->parent; |
| 63 | #endif |
| 64 | |
| 65 | list = sn->circle_list; |
| 66 | |
| 67 | if (sn->parent_pos >= 0) { |
| 68 | theta += M_PI - sn->parent_pos; |
| 69 | if (theta < 0) |
| 70 | theta += 2 * M_PI; |
| 71 | |
| 72 | return theta; |
| 73 | } |
| 74 | |
| 75 | count = sizeNodelist(list); |
| 76 | if (count == 2) { |
| 77 | return (theta - M_PI / 2.0); |
| 78 | } |
| 79 | |
| 80 | /* Find node in block connected to block's parent */ |
| 81 | neighbor = CHILD(sn); |
| 82 | #ifdef OLD |
| 83 | for (e = agfstedge(g, parent); e; e = agnxtedge(g, e, parent)) { |
| 84 | n = e->head; |
| 85 | if (n == parent) |
| 86 | n = e->tail; |
| 87 | |
| 88 | if ((BLOCK(n) == sn) && (PARENT(n) == parent)) { |
| 89 | neighbor = n; |
| 90 | break; |
| 91 | } |
| 92 | } |
| 93 | #endif |
| 94 | newX = ND_pos(neighbor)[0] + x; |
| 95 | newY = ND_pos(neighbor)[1] + y; |
| 96 | mindist2 = LEN2(newX, newY); /* save sqrts by using sqr of dist to find min */ |
| 97 | closest_node = neighbor; |
| 98 | |
| 99 | for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) { |
| 100 | if (n == neighbor) |
| 101 | continue; |
| 102 | |
| 103 | newX = ND_pos(n)[0] + x; |
| 104 | newY = ND_pos(n)[1] + y; |
| 105 | |
| 106 | len2 = LEN2(newX, newY); |
| 107 | if (len2 < mindist2) { |
| 108 | mindist2 = len2; |
| 109 | closest_node = n; |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | /* if((neighbor != closest_node) && !ISPARENT(neighbor)) { */ |
| 114 | if (neighbor != closest_node) { |
| 115 | double rho = sn->rad0; |
| 116 | double r = sn->radius - rho; |
| 117 | double n_x = ND_pos(neighbor)[0]; |
| 118 | if (COALESCED(sn) && (-r < n_x)) { |
| 119 | double R = LEN(x, y); |
| 120 | double n_y = ND_pos(neighbor)[1]; |
| 121 | double phi = atan2(n_y, n_x + r); |
| 122 | double l = r - rho / (cos(phi)); |
| 123 | |
| 124 | theta += M_PI / 2.0 - phi - asin((l / R) * (cos(phi))); |
| 125 | } else { /* Origin still at center of this block */ |
| 126 | double phi = atan2(ND_pos(neighbor)[1], ND_pos(neighbor)[0]); |
| 127 | theta += M_PI - phi - PSI(neighbor); |
| 128 | if (theta > 2 * M_PI) |
| 129 | theta -= 2 * M_PI; |
| 130 | } |
| 131 | } else |
| 132 | theta = 0; |
| 133 | return theta; |
| 134 | } |
| 135 | |
| 136 | |
| 137 | /* applyDelta: |
| 138 | * Recursively apply rotation rotate followed by translation (x,y) |
| 139 | * to block sn and its children. |
| 140 | */ |
| 141 | static void applyDelta(block_t * sn, double x, double y, double rotate) |
| 142 | { |
| 143 | block_t *child; |
| 144 | Agraph_t *subg; |
| 145 | Agnode_t *n; |
| 146 | |
| 147 | subg = sn->sub_graph; |
| 148 | |
| 149 | for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) { |
| 150 | double X, Y; |
| 151 | |
| 152 | if (rotate != 0) { |
| 153 | double tmpX, tmpY; |
| 154 | double cosR, sinR; |
| 155 | |
| 156 | tmpX = ND_pos(n)[0]; |
| 157 | tmpY = ND_pos(n)[1]; |
| 158 | cosR = cos(rotate); |
| 159 | sinR = sin(rotate); |
| 160 | |
| 161 | X = tmpX * cosR - tmpY * sinR; |
| 162 | Y = tmpX * sinR + tmpY * cosR; |
| 163 | } else { |
| 164 | X = ND_pos(n)[0]; |
| 165 | Y = ND_pos(n)[1]; |
| 166 | } |
| 167 | |
| 168 | /* translate */ |
| 169 | ND_pos(n)[0] = X + x; |
| 170 | ND_pos(n)[1] = Y + y; |
| 171 | } |
| 172 | |
| 173 | for (child = sn->children.first; child; child = child->next) |
| 174 | applyDelta(child, x, y, rotate); |
| 175 | } |
| 176 | |
| 177 | /* firstangle and lastangle give the range of child angles. |
| 178 | * These are set and used only when a block has just 1 node. |
| 179 | * And are used to give the center angle between the two extremes. |
| 180 | * The parent will then be attached at PI - center angle (parent_pos). |
| 181 | * If this block has no children, this is PI. Otherwise, positionChildren will |
| 182 | * be called once with the blocks node. firstangle will be 0, with |
| 183 | * succeeding angles increasing. |
| 184 | * position can always return the center angle - PI, since the block |
| 185 | * must have children and if the block has 1 node, the limits will be |
| 186 | * correctly set. If the block has more than 1 node, the value is |
| 187 | * unused. |
| 188 | */ |
| 189 | typedef struct { |
| 190 | double radius; /* Basic radius of block */ |
| 191 | double subtreeR; /* Max of subtree radii */ |
| 192 | double nodeAngle; /* Angle allocated to each node in block */ |
| 193 | double firstAngle; /* Smallest child angle when block has 1 node */ |
| 194 | double lastAngle; /* Largest child angle when block has 1 node */ |
| 195 | block_t *cp; /* Children of block */ |
| 196 | node_t *neighbor; /* Node connected to parent block, if any */ |
| 197 | } posstate; |
| 198 | |
| 199 | typedef struct { |
| 200 | Agnode_t* n; |
| 201 | double theta; /* angle of node */ |
| 202 | double minRadius; /* minimum radius for child circle */ |
| 203 | double maxRadius; /* maximum radius of child blocks */ |
| 204 | double diameter; /* length of arc needed for child blocks */ |
| 205 | double scale; /* scale factor to increase minRadius to parents' children don't overlap */ |
| 206 | int childCount; /* no. of child blocks attached at n */ |
| 207 | } posinfo_t; |
| 208 | |
| 209 | /* getInfo: |
| 210 | * get size info for blocks attached to the given node. |
| 211 | */ |
| 212 | static double |
| 213 | getInfo (posinfo_t* pi, posstate * stp, double min_dist) |
| 214 | { |
| 215 | block_t *child; |
| 216 | double maxRadius = 0; /* Max. radius of children */ |
| 217 | double diameter = 0; /* sum of child diameters */ |
| 218 | int childCount = 0; |
| 219 | |
| 220 | for (child = stp->cp; child; child = child->next) { |
| 221 | if (BLK_PARENT(child) == pi->n) { |
| 222 | childCount++; |
| 223 | if (maxRadius < child->radius) { |
| 224 | maxRadius = child->radius; |
| 225 | } |
| 226 | diameter += 2 * child->radius + min_dist; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | pi->diameter = diameter; |
| 231 | pi->childCount = childCount; |
| 232 | pi->minRadius = stp->radius + min_dist + maxRadius; |
| 233 | pi->maxRadius = maxRadius; |
| 234 | return maxRadius; |
| 235 | } |
| 236 | |
| 237 | /* setInfo: |
| 238 | */ |
| 239 | static void |
| 240 | setInfo (posinfo_t* p0, posinfo_t* p1, double delta) |
| 241 | { |
| 242 | double t = (p0->diameter*p1->minRadius) + (p1->diameter*p0->minRadius); |
| 243 | |
| 244 | t /= 2*delta*p0->minRadius*p1->minRadius; |
| 245 | |
| 246 | if (t < 1) |
| 247 | t = 1; |
| 248 | |
| 249 | if (t > p0->scale) |
| 250 | p0->scale = t; |
| 251 | if (t > p1->scale) |
| 252 | p1->scale = t; |
| 253 | } |
| 254 | |
| 255 | /* positionChildren: |
| 256 | */ |
| 257 | static void |
| 258 | positionChildren (Agraph_t* g, posinfo_t* pi, posstate * stp, int length, double min_dist) |
| 259 | { |
| 260 | block_t *child; |
| 261 | double childAngle, childRadius, incidentAngle; |
| 262 | double mindistAngle, rotateAngle, midAngle = 0.0; |
| 263 | int midChild, cnt = 0; |
| 264 | double snRadius = stp->subtreeR; /* max subtree radius */ |
| 265 | double firstAngle = stp->firstAngle; |
| 266 | double lastAngle = stp->lastAngle; |
| 267 | double d, deltaX, deltaY; |
| 268 | |
| 269 | childRadius = pi->scale * pi->minRadius; |
| 270 | if (length == 1) { |
| 271 | childAngle = 0; |
| 272 | d = pi->diameter/(2*M_PI); |
| 273 | childRadius = MAX(childRadius, d); |
| 274 | d = 2*M_PI*childRadius - pi->diameter; |
| 275 | if (d > 0) |
| 276 | min_dist += d/pi->childCount; |
| 277 | } |
| 278 | else |
| 279 | childAngle = pi->theta - pi->diameter/(2 * childRadius); |
| 280 | |
| 281 | if ((childRadius + pi->maxRadius) > snRadius) |
| 282 | snRadius = childRadius + pi->maxRadius; |
| 283 | |
| 284 | mindistAngle = min_dist / childRadius; |
| 285 | |
| 286 | midChild = (pi->childCount + 1) / 2; |
| 287 | for (child = stp->cp; child; child = child->next) { |
| 288 | if (BLK_PARENT(child) != pi->n) |
| 289 | continue; |
| 290 | if (sizeNodelist(child->circle_list) <= 0) |
| 291 | continue; |
| 292 | |
| 293 | incidentAngle = child->radius / childRadius; |
| 294 | if (length == 1) { |
| 295 | if (childAngle != 0) { |
| 296 | if (pi->childCount == 2) |
| 297 | childAngle = M_PI; |
| 298 | else |
| 299 | childAngle += incidentAngle; |
| 300 | } |
| 301 | |
| 302 | if (firstAngle < 0) |
| 303 | firstAngle = childAngle; |
| 304 | |
| 305 | lastAngle = childAngle; |
| 306 | } else { |
| 307 | if (pi->childCount == 1) { |
| 308 | childAngle = pi->theta; |
| 309 | } else { |
| 310 | childAngle += incidentAngle + mindistAngle / 2; |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | deltaX = childRadius * cos(childAngle); |
| 315 | deltaY = childRadius * sin(childAngle); |
| 316 | |
| 317 | /* first apply the delta to the immediate child and see if we need |
| 318 | * to rotate it for better edge link |
| 319 | * should return the theta value if there was a rotation else zero |
| 320 | */ |
| 321 | |
| 322 | rotateAngle = getRotation(child, g, deltaX, deltaY, childAngle); |
| 323 | applyDelta(child, deltaX, deltaY, rotateAngle); |
| 324 | |
| 325 | if (length == 1) { |
| 326 | childAngle += incidentAngle + mindistAngle; |
| 327 | } else { |
| 328 | childAngle += incidentAngle + mindistAngle / 2; |
| 329 | } |
| 330 | cnt++; |
| 331 | if (cnt == midChild) |
| 332 | midAngle = childAngle; |
| 333 | } |
| 334 | |
| 335 | if ((length > 1) && (pi->n == stp->neighbor)) { |
| 336 | PSI(pi->n) = midAngle; |
| 337 | } |
| 338 | |
| 339 | stp->subtreeR = snRadius; |
| 340 | stp->firstAngle = firstAngle; |
| 341 | stp->lastAngle = lastAngle; |
| 342 | } |
| 343 | |
| 344 | /* position: |
| 345 | * Assume childCount > 0 |
| 346 | * For each node in the block with children, getInfo is called, with the |
| 347 | * information stored in the parents array. |
| 348 | * This information is used by setInfo to compute the amount of space allocated |
| 349 | * to each parent and the radius at which to place its children. |
| 350 | * Finally, positionChildren is called to do the actual positioning. |
| 351 | * If length is 1, keeps track of minimum and maximum child angle. |
| 352 | */ |
| 353 | static double |
| 354 | position(Agraph_t * g, int childCount, int length, nodelist_t * path, |
| 355 | block_t * sn, double min_dist) |
| 356 | { |
| 357 | nodelistitem_t *item; |
| 358 | Agnode_t *n; |
| 359 | posstate state; |
| 360 | int i, counter = 0; |
| 361 | double maxRadius = 0.0; |
| 362 | double angle; |
| 363 | double theta = 0.0; |
| 364 | posinfo_t* parents = N_NEW(childCount, posinfo_t); |
| 365 | int num_parents = 0; |
| 366 | posinfo_t* next; |
| 367 | posinfo_t* curr; |
| 368 | double delta; |
| 369 | |
| 370 | state.cp = sn->children.first; |
| 371 | state.subtreeR = sn->radius; |
| 372 | state.radius = sn->radius; |
| 373 | state.neighbor = CHILD(sn); |
| 374 | state.nodeAngle = 2 * M_PI / length; |
| 375 | state.firstAngle = -1; |
| 376 | state.lastAngle = -1; |
| 377 | |
| 378 | for (item = path->first; item; item = item->next) { |
| 379 | n = item->curr; |
| 380 | |
| 381 | theta = counter * state.nodeAngle; |
| 382 | counter++; |
| 383 | |
| 384 | if (ISPARENT(n)) { |
| 385 | parents[num_parents].n = n; |
| 386 | parents[num_parents].theta = theta; |
| 387 | maxRadius = getInfo (parents+num_parents, &state, min_dist); |
| 388 | num_parents++; |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | if (num_parents == 1) |
| 393 | parents->scale = 1.0; |
| 394 | else if (num_parents == 2) { |
| 395 | curr = parents; |
| 396 | next = parents+1; |
| 397 | delta = next->theta - curr->theta; |
| 398 | if (delta > M_PI) |
| 399 | delta = 2*M_PI - delta; |
| 400 | setInfo (curr, next, delta); |
| 401 | } |
| 402 | else { |
| 403 | curr = parents; |
| 404 | for (i = 0; i < num_parents; i++) { |
| 405 | if (i+1 == num_parents) { |
| 406 | next = parents; |
| 407 | delta = next->theta - curr->theta + 2*M_PI; |
| 408 | } |
| 409 | else { |
| 410 | next = curr+1; |
| 411 | delta = next->theta - curr->theta; |
| 412 | } |
| 413 | setInfo (curr, next, delta); |
| 414 | curr++; |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | for (i = 0; i < num_parents; i++) { |
| 419 | positionChildren (g, parents + i, &state, length, min_dist); |
| 420 | } |
| 421 | |
| 422 | free (parents); |
| 423 | |
| 424 | /* If block has only 1 child, to save space, we coalesce it with the |
| 425 | * child. Instead of having final radius sn->radius + max child radius, |
| 426 | * we have half that. However, the origin of the block is no longer in |
| 427 | * the center of the block, so we cannot do a simple rotation to get |
| 428 | * the neighbor node next to the parent block in getRotate. |
| 429 | */ |
| 430 | if (childCount == 1) { |
| 431 | applyDelta(sn, -(maxRadius + min_dist / 2), 0, 0); |
| 432 | sn->radius += min_dist / 2 + maxRadius; |
| 433 | SET_COALESCED(sn); |
| 434 | } else |
| 435 | sn->radius = state.subtreeR; |
| 436 | |
| 437 | angle = (state.firstAngle + state.lastAngle) / 2.0 - M_PI; |
| 438 | return angle; |
| 439 | } |
| 440 | |
| 441 | /* doBlock: |
| 442 | * Set positions of block sn and its child blocks. |
| 443 | */ |
| 444 | static void doBlock(Agraph_t * g, block_t * sn, double min_dist) |
| 445 | { |
| 446 | block_t *child; |
| 447 | nodelist_t *longest_path; |
| 448 | int childCount, length; |
| 449 | double centerAngle = M_PI; |
| 450 | |
| 451 | /* layout child subtrees */ |
| 452 | childCount = 0; |
| 453 | for (child = sn->children.first; child; child = child->next) { |
| 454 | doBlock(g, child, min_dist); |
| 455 | childCount++; |
| 456 | } |
| 457 | |
| 458 | /* layout this block */ |
| 459 | longest_path = layout_block(g, sn, min_dist); |
| 460 | sn->circle_list = longest_path; |
| 461 | length = sizeNodelist(longest_path); /* path contains everything in block */ |
| 462 | |
| 463 | /* attach children */ |
| 464 | if (childCount > 0) |
| 465 | centerAngle = |
| 466 | position(g, childCount, length, longest_path, sn, min_dist); |
| 467 | |
| 468 | if ((length == 1) && (BLK_PARENT(sn))) { |
| 469 | sn->parent_pos = centerAngle; |
| 470 | if (sn->parent_pos < 0) |
| 471 | sn->parent_pos += 2 * M_PI; |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | void circPos(Agraph_t * g, block_t * sn, circ_state * state) |
| 476 | { |
| 477 | doBlock(g, sn, state->min_dist); |
| 478 | } |
| 479 | |