Graphviz 2.29.20120208.0545
lib/circogen/circpos.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 
00015 #ifdef HAVE_CONFIG_H
00016 #include "config.h"
00017 #endif
00018 
00019 /* TODO:
00020  * If cut point is in exactly 2 blocks, expand block circles to overlap
00021  * especially in the case where one block is the sole child of the other.
00022  */
00023 
00024 #include        "blockpath.h"
00025 
00026 /* getRotation:
00027  * The function determines how much the block should be rotated
00028  * for best positioning with parent, assuming its center is at x and y
00029  * relative to the parent.
00030  * angle gives the angle of the new position, i.e., tan(angle) = y/x.
00031  * If sn has 2 nodes, we arrange the line of the 2 normal to angle.
00032  * If sn has 1 node, parent_pos has already been set to the 
00033  * correct angle assuming no rotation.
00034  * Otherwise, we find the node in sn connected to the parent and rotate
00035  * the block so that it is closer or at least visible to its node in the
00036  * parent.
00037  *
00038  * For COALESCED blocks, if neighbor is in left half plane, 
00039  * use unCOALESCED case.
00040  * Else let theta be angle, R = LEN(x,y), pho the radius of actual 
00041  * child block, phi be angle of neighbor in actual child block,
00042  * and r the distance from center of coalesced block to center of 
00043  * actual block. Then, the angle to rotate the coalesced block to
00044  * that the edge from the parent is tangent to the neighbor on the
00045  * actual child block circle is
00046  *    alpha = theta + M_PI/2 - phi - arcsin((l/R)*(sin B))
00047  * where l = r - rho/(cos phi) and beta = M_PI/2 + phi.
00048  * Thus, 
00049  *    alpha = theta + M_PI/2 - phi - arcsin((l/R)*(cos phi))
00050  */
00051 static double
00052 getRotation(block_t * sn, Agraph_t * g, double x, double y, double theta)
00053 {
00054     double mindist2;
00055     Agraph_t *subg;
00056     /* Agedge_t* e; */
00057     Agnode_t *n, *closest_node, *neighbor;
00058     nodelist_t *list;
00059     double len2, newX, newY;
00060     int count;
00061 
00062     subg = sn->sub_graph;
00063 #ifdef OLD
00064     parent = sn->parent;
00065 #endif
00066 
00067     list = sn->circle_list;
00068 
00069     if (sn->parent_pos >= 0) {
00070         theta += M_PI - sn->parent_pos;
00071         if (theta < 0)
00072             theta += 2 * M_PI;
00073 
00074         return theta;
00075     }
00076 
00077     count = sizeNodelist(list);
00078     if (count == 2) {
00079         return (theta - M_PI / 2.0);
00080     }
00081 
00082     /* Find node in block connected to block's parent */
00083     neighbor = CHILD(sn);
00084 #ifdef OLD
00085     for (e = agfstedge(g, parent); e; e = agnxtedge(g, e, parent)) {
00086         n = e->head;
00087         if (n == parent)
00088             n = e->tail;
00089 
00090         if ((BLOCK(n) == sn) && (PARENT(n) == parent)) {
00091             neighbor = n;
00092             break;
00093         }
00094     }
00095 #endif
00096     newX = ND_pos(neighbor)[0] + x;
00097     newY = ND_pos(neighbor)[1] + y;
00098     mindist2 = LEN2(newX, newY);    /* save sqrts by using sqr of dist to find min */
00099     closest_node = neighbor;
00100 
00101     for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
00102         if (n == neighbor)
00103             continue;
00104 
00105         newX = ND_pos(n)[0] + x;
00106         newY = ND_pos(n)[1] + y;
00107 
00108         len2 = LEN2(newX, newY);
00109         if (len2 < mindist2) {
00110             mindist2 = len2;
00111             closest_node = n;
00112         }
00113     }
00114 
00115     /* if((neighbor != closest_node) && !ISPARENT(neighbor)) { */
00116     if (neighbor != closest_node) {
00117         double rho = sn->rad0;
00118         double r = sn->radius - rho;
00119         double n_x = ND_pos(neighbor)[0];
00120         if (COALESCED(sn) && (-r < n_x)) {
00121             double R = LEN(x, y);
00122             double n_y = ND_pos(neighbor)[1];
00123             double phi = atan2(n_y, n_x + r);
00124             double l = r - rho / (cos(phi));
00125 
00126             theta += M_PI / 2.0 - phi - asin((l / R) * (cos(phi)));
00127         } else {                /* Origin still at center of this block */
00128             double phi = atan2(ND_pos(neighbor)[1], ND_pos(neighbor)[0]);
00129             theta += M_PI - phi - PSI(neighbor);
00130             if (theta > 2 * M_PI)
00131                 theta -= 2 * M_PI;
00132         }
00133     } else
00134         theta = 0;
00135     return theta;
00136 }
00137 
00138 
00139 /* applyDelta:
00140  * Recursively apply rotation rotate followed by translation (x,y)
00141  * to block sn and its children.
00142  */
00143 static void applyDelta(block_t * sn, double x, double y, double rotate)
00144 {
00145     block_t *child;
00146     Agraph_t *subg;
00147     Agnode_t *n;
00148 
00149     subg = sn->sub_graph;
00150 
00151     for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
00152         double X, Y;
00153 
00154         if (rotate != 0) {
00155             double tmpX, tmpY;
00156             double cosR, sinR;
00157 
00158             tmpX = ND_pos(n)[0];
00159             tmpY = ND_pos(n)[1];
00160             cosR = cos(rotate);
00161             sinR = sin(rotate);
00162 
00163             X = tmpX * cosR - tmpY * sinR;
00164             Y = tmpX * sinR + tmpY * cosR;
00165         } else {
00166             X = ND_pos(n)[0];
00167             Y = ND_pos(n)[1];
00168         }
00169 
00170         /* translate */
00171         ND_pos(n)[0] = X + x;
00172         ND_pos(n)[1] = Y + y;
00173     }
00174 
00175     for (child = sn->children.first; child; child = child->next)
00176         applyDelta(child, x, y, rotate);
00177 }
00178 
00179 /* firstangle and lastangle give the range of child angles.
00180  * These are set and used only when a block has just 1 node.
00181  * And are used to give the center angle between the two extremes.
00182  * The parent will then be attached at PI - center angle (parent_pos).
00183  * If this block has no children, this is PI. Otherwise, positionChildren will
00184  * be called once with the blocks node. firstangle will be 0, with
00185  * succeeding angles increasing. 
00186  * position can always return the center angle - PI, since the block
00187  * must have children and if the block has 1 node, the limits will be
00188  * correctly set. If the block has more than 1 node, the value is
00189  * unused.
00190  */
00191 typedef struct {
00192     double radius;              /* Basic radius of block */
00193     double subtreeR;            /* Max of subtree radii */
00194     double nodeAngle;           /* Angle allocated to each node in block */
00195     double firstAngle;          /* Smallest child angle when block has 1 node */
00196     double lastAngle;           /* Largest child angle when block has 1 node */
00197     block_t *cp;                /* Children of block */
00198     node_t *neighbor;           /* Node connected to parent block, if any */
00199 } posstate;
00200 
00201 typedef struct {
00202     Agnode_t* n;
00203     double theta;        /* angle of node */
00204     double minRadius;    /* minimum radius for child circle */
00205     double maxRadius;    /* maximum radius of child blocks */
00206     double diameter;     /* length of arc needed for child blocks */
00207     double scale;        /* scale factor to increase minRadius to parents' children don't overlap */
00208     int childCount;      /* no. of child blocks attached at n */
00209 } posinfo_t;
00210 
00211 /* getInfo:
00212  * get size info for blocks attached to the given node.
00213  */
00214 static double
00215 getInfo (posinfo_t* pi, posstate * stp, double min_dist)
00216 {
00217     block_t *child;
00218     double maxRadius = 0;       /* Max. radius of children */
00219     double diameter = 0;        /* sum of child diameters */
00220     int childCount = 0;
00221 
00222     for (child = stp->cp; child; child = child->next) {
00223         if (BLK_PARENT(child) == pi->n) {
00224             childCount++;
00225             if (maxRadius < child->radius) {
00226                 maxRadius = child->radius;
00227             }
00228             diameter += 2 * child->radius + min_dist;
00229         }
00230     }
00231 
00232     pi->diameter = diameter;
00233     pi->childCount = childCount;
00234     pi->minRadius = stp->radius + min_dist + maxRadius;
00235     pi->maxRadius = maxRadius;
00236     return maxRadius;
00237 }
00238 
00239 /* setInfo:
00240  */
00241 static void
00242 setInfo (posinfo_t* p0, posinfo_t* p1, double delta)
00243 {
00244     double t = (p0->diameter*p1->minRadius) + (p1->diameter*p0->minRadius);
00245 
00246     t /= 2*delta*p0->minRadius*p1->minRadius;
00247 
00248     if (t < 1)
00249         t = 1;
00250 
00251     if (t > p0->scale)
00252         p0->scale = t;
00253     if (t > p1->scale)
00254         p1->scale = t;
00255 }
00256 
00257 /* positionChildren:
00258  */
00259 static void
00260 positionChildren (Agraph_t* g, posinfo_t* pi, posstate * stp, int length, double min_dist)
00261 {
00262     block_t *child;
00263     double childAngle, childRadius, incidentAngle;
00264     double mindistAngle, rotateAngle, midAngle;
00265     int midChild, cnt = 0;
00266     double snRadius = stp->subtreeR;    /* max subtree radius */
00267     double firstAngle = stp->firstAngle;
00268     double lastAngle = stp->lastAngle;
00269     double d, deltaX, deltaY;
00270 
00271     childRadius = pi->scale * pi->minRadius;
00272     if (length == 1) {
00273         childAngle = 0;
00274         d = pi->diameter/(2*M_PI);
00275         childRadius = MAX(childRadius, d);
00276         d = 2*M_PI*childRadius - pi->diameter;
00277         if (d > 0)
00278             min_dist += d/pi->childCount;
00279     }
00280     else
00281         childAngle = pi->theta - pi->diameter/(2 * childRadius);
00282 
00283     if ((childRadius + pi->maxRadius) > snRadius)
00284         snRadius = childRadius + pi->maxRadius;
00285 
00286     mindistAngle = min_dist / childRadius;
00287 
00288     midChild = (pi->childCount + 1) / 2;
00289     for (child = stp->cp; child; child = child->next) {
00290         if (BLK_PARENT(child) != pi->n)
00291             continue;
00292         if (sizeNodelist(child->circle_list) <= 0)
00293             continue;
00294 
00295         incidentAngle = child->radius / childRadius;
00296         if (length == 1) {
00297             if (childAngle != 0) {
00298                 if (pi->childCount == 2)
00299                     childAngle = M_PI;
00300                 else
00301                     childAngle += incidentAngle;
00302             }
00303 
00304             if (firstAngle < 0)
00305                 firstAngle = childAngle;
00306 
00307             lastAngle = childAngle;
00308         } else {
00309             if (pi->childCount == 1) {
00310                 childAngle = pi->theta;
00311             } else {
00312                 childAngle += incidentAngle + mindistAngle / 2;
00313             }
00314         }
00315 
00316         deltaX = childRadius * cos(childAngle);
00317         deltaY = childRadius * sin(childAngle);
00318 
00319         /* first apply the delta to the immediate child and see if we need
00320          * to rotate it for better edge link                                            
00321          * should return the theta value if there was a rotation else zero
00322          */
00323 
00324         rotateAngle = getRotation(child, g, deltaX, deltaY, childAngle);
00325         applyDelta(child, deltaX, deltaY, rotateAngle);
00326 
00327         if (length == 1) {
00328             childAngle += incidentAngle + mindistAngle;
00329         } else {
00330             childAngle += incidentAngle + mindistAngle / 2;
00331         }
00332         cnt++;
00333         if (cnt == midChild)
00334             midAngle = childAngle;
00335     }
00336 
00337     if ((length > 1) && (pi->n == stp->neighbor)) {
00338         PSI(pi->n) = midAngle;
00339     }
00340 
00341     stp->subtreeR = snRadius;
00342     stp->firstAngle = firstAngle;
00343     stp->lastAngle = lastAngle;
00344 }
00345 
00346 /* position:
00347  * Assume childCount > 0
00348  * For each node in the block with children, getInfo is called, with the
00349  * information stored in the parents array.
00350  * This information is used by setInfo to compute the amount of space allocated
00351  * to each parent and the radius at which to place its children.
00352  * Finally, positionChildren is called to do the actual positioning.
00353  * If length is 1, keeps track of minimum and maximum child angle.
00354  */
00355 static double
00356 position(Agraph_t * g, int childCount, int length, nodelist_t * path,
00357          block_t * sn, double min_dist)
00358 {
00359     nodelistitem_t *item;
00360     Agnode_t *n;
00361     posstate state;
00362     int i, counter = 0;
00363     double maxRadius = 0.0;
00364     double angle;
00365     double theta = 0.0;
00366     posinfo_t* parents = N_NEW(childCount, posinfo_t);
00367     int num_parents = 0;
00368     posinfo_t* next;
00369     posinfo_t* curr;
00370     double delta;
00371 
00372     state.cp = sn->children.first;
00373     state.subtreeR = sn->radius;
00374     state.radius = sn->radius;
00375     state.neighbor = CHILD(sn);
00376     state.nodeAngle = 2 * M_PI / length;
00377     state.firstAngle = -1;
00378     state.lastAngle = -1;
00379 
00380     for (item = path->first; item; item = item->next) {
00381         n = item->curr;
00382 
00383         theta = counter * state.nodeAngle;
00384         counter++;
00385 
00386         if (ISPARENT(n)) {
00387             parents[num_parents].n = n;
00388             parents[num_parents].theta = theta;
00389             maxRadius = getInfo (parents+num_parents, &state, min_dist);
00390             num_parents++;
00391         }
00392     }
00393 
00394     if (num_parents == 1)
00395         parents->scale = 1.0;
00396     else if (num_parents == 2) {
00397         curr = parents;
00398         next = parents+1;
00399         delta = next->theta - curr->theta;
00400         if (delta > M_PI)
00401             delta = 2*M_PI - delta;
00402         setInfo (curr, next, delta);
00403     }
00404     else {
00405         curr = parents;
00406         for (i = 0; i < num_parents; i++) {
00407             if (i+1 == num_parents) {
00408                 next = parents;
00409                 delta = next->theta - curr->theta + 2*M_PI; 
00410             }
00411             else {
00412                 next = curr+1;
00413                 delta = next->theta - curr->theta; 
00414             }
00415             setInfo (curr, next, delta);
00416             curr++;
00417         }
00418     }
00419 
00420     for (i = 0; i < num_parents; i++) {
00421         positionChildren (g, parents + i, &state, length, min_dist);
00422     }
00423 
00424     free (parents);
00425 
00426     /* If block has only 1 child, to save space, we coalesce it with the
00427      * child. Instead of having final radius sn->radius + max child radius,
00428      * we have half that. However, the origin of the block is no longer in
00429      * the center of the block, so we cannot do a simple rotation to get
00430      * the neighbor node next to the parent block in getRotate.
00431      */
00432     if (childCount == 1) {
00433         applyDelta(sn, -(maxRadius + min_dist / 2), 0, 0);
00434         sn->radius += min_dist / 2 + maxRadius;
00435         SET_COALESCED(sn);
00436     } else
00437         sn->radius = state.subtreeR;
00438 
00439     angle = (state.firstAngle + state.lastAngle) / 2.0 - M_PI;
00440     return angle;
00441 }
00442 
00443 /* doBlock:
00444  * Set positions of block sn and its child blocks.
00445  */
00446 static void doBlock(Agraph_t * g, block_t * sn, double min_dist)
00447 {
00448     block_t *child;
00449     nodelist_t *longest_path;
00450     int childCount, length;
00451     double centerAngle = M_PI;
00452 
00453     /* layout child subtrees */
00454     childCount = 0;
00455     for (child = sn->children.first; child; child = child->next) {
00456         doBlock(g, child, min_dist);
00457         childCount++;
00458     }
00459 
00460     /* layout this block */
00461     longest_path = layout_block(g, sn, min_dist);
00462     sn->circle_list = longest_path;
00463     length = sizeNodelist(longest_path);        /* path contains everything in block */
00464 
00465     /* attach children */
00466     if (childCount > 0)
00467         centerAngle =
00468             position(g, childCount, length, longest_path, sn, min_dist);
00469 
00470     if ((length == 1) && (BLK_PARENT(sn))) {
00471         sn->parent_pos = centerAngle;
00472         if (sn->parent_pos < 0)
00473             sn->parent_pos += 2 * M_PI;
00474     }
00475 }
00476 
00477 void circPos(Agraph_t * g, block_t * sn, circ_state * state)
00478 {
00479     doBlock(g, sn, state->min_dist);
00480 }