Graphviz  2.31.20130525.0447
lib/dotgen/position.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 /*
00016  * position(g): set ND_coord(n) (x and y) for all nodes n of g, using GD_rank(g).
00017  * (the graph may be modified by merging certain edges with a common endpoint.)
00018  * the coordinates are computed by constructing and ranking an auxiliary graph.
00019  * then leaf nodes are inserted in the fast graph.  cluster boundary nodes are
00020  * created and correctly separated.
00021  */
00022 
00023 #include "dot.h"
00024 #include "aspect.h"
00025 
00026 static int nsiter2(graph_t * g);
00027 static void create_aux_edges(graph_t * g);
00028 static void remove_aux_edges(graph_t * g);
00029 static void set_xcoords(graph_t * g);
00030 static void set_ycoords(graph_t * g);
00031 static void set_aspect(graph_t * g, aspect_t* );
00032 static void expand_leaves(graph_t * g);
00033 static void make_lrvn(graph_t * g);
00034 static void contain_nodes(graph_t * g);
00035 static boolean idealsize(graph_t * g, double);
00036 
00037 #ifdef DEBUG
00038 static void
00039 dumpNS (graph_t * g)
00040 {
00041     node_t* n = GD_nlist(g);
00042     elist el;
00043     edge_t* e;
00044     int i;
00045 
00046     while (n) {
00047         el = ND_out(n);
00048         for (i = 0; i < el.size; i++) {
00049             e = el.list[i];
00050             fprintf (stderr, "%s(%x) -> ", agnameof(agtail(e)),agtail(e));
00051             fprintf (stderr, "%s(%x) : %d\n", agnameof(aghead(e)), aghead(e),
00052                 ED_minlen(e));
00053         }
00054         n = ND_next(n); 
00055     }
00056 }
00057 #endif
00058 
00059 static double
00060 largeMinlen (double l)
00061 {
00062     agerr (AGERR, "Edge length %f larger than maximum %u allowed.\nCheck for overwide node(s).\n", l, USHRT_MAX); 
00063     return (double)USHRT_MAX;
00064 }
00065 
00066 /* connectGraph:
00067  * When source and/or sink nodes are defined, it is possible that
00068  * after the auxiliary edges are added, the graph may still have 2 or
00069  * 3 components. To fix this, we put trivial constraints connecting the
00070  * first items of each rank.
00071  */
00072 static void
00073 connectGraph (graph_t* g)
00074 {
00075     int i, j, r, found;
00076     node_t* tp;
00077     node_t* hp;
00078     node_t* sn;
00079     edge_t* e;
00080     rank_t* rp;
00081 
00082     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00083         rp = GD_rank(g)+r;
00084         found =FALSE;
00085         tp = NULL;
00086         for (i = 0; i < rp->n; i++) {
00087             tp = rp->v[i];
00088             if (ND_save_out(tp).list) {
00089                 for (j = 0; (e = ND_save_out(tp).list[j]); j++) {
00090                     if ((ND_rank(aghead(e)) > r) || (ND_rank(agtail(e)) > r)) {
00091                         found = TRUE;
00092                         break;
00093                     }
00094                 }
00095                 if (found) break;
00096             }
00097             if (ND_save_in(tp).list) {
00098                 for (j = 0; (e = ND_save_in(tp).list[j]); j++) {
00099                     if ((ND_rank(agtail(e)) > r) || (ND_rank(aghead(e)) > r)) {
00100                         found = TRUE;
00101                         break;
00102                     }
00103                 }
00104                 if (found) break;
00105             }
00106         }
00107         if (found || !tp) continue;
00108         tp = rp->v[0];
00109         if (r < GD_maxrank(g)) hp = (rp+1)->v[0];
00110         else hp = (rp-1)->v[0];
00111         assert (hp);
00112         sn = virtual_node(g);
00113         ND_node_type(sn) = SLACKNODE;
00114         make_aux_edge(sn, tp, 0, 0);
00115         make_aux_edge(sn, hp, 0, 0);
00116         ND_rank(sn) = MIN(ND_rank(tp), ND_rank(hp));
00117     }
00118 }
00119 
00120 void dot_position(graph_t * g, aspect_t* asp)
00121 {
00122     if (GD_nlist(g) == NULL)
00123         return;                 /* ignore empty graph */
00124     mark_lowclusters(g);        /* we could remove from splines.c now */
00125     set_ycoords(g);
00126     if (Concentrate)
00127         dot_concentrate(g);
00128     expand_leaves(g);
00129     if (flat_edges(g))
00130         set_ycoords(g);
00131     create_aux_edges(g);
00132     if (rank(g, 2, nsiter2(g))) { /* LR balance == 2 */
00133         connectGraph (g);
00134         assert(rank(g, 2, nsiter2(g)) == 0);
00135     }
00136     set_xcoords(g);
00137     set_aspect(g, asp);
00138     remove_aux_edges(g);        /* must come after set_aspect since we now
00139                                  * use GD_ln and GD_rn for bbox width.
00140                                  */
00141 }
00142 
00143 static int nsiter2(graph_t * g)
00144 {
00145     int maxiter = INT_MAX;
00146     char *s;
00147 
00148     if ((s = agget(g, "nslimit")))
00149         maxiter = atof(s) * agnnodes(g);
00150     return maxiter;
00151 }
00152 
00153 static int go(node_t * u, node_t * v)
00154 {
00155     int i;
00156     edge_t *e;
00157 
00158     if (u == v)
00159         return TRUE;
00160     for (i = 0; (e = ND_out(u).list[i]); i++) {
00161         if (go(aghead(e), v))
00162             return TRUE;
00163     }
00164     return FALSE;
00165 }
00166 
00167 static int canreach(node_t * u, node_t * v)
00168 {
00169     return go(u, v);
00170 }
00171 
00172 edge_t *make_aux_edge(node_t * u, node_t * v, double len, int wt)
00173 {
00174     edge_t *e;
00175 
00176 #ifndef WITH_CGRAPH
00177     e = NEW(edge_t);
00178 #else
00179     Agedgepair_t* e2 = NEW(Agedgepair_t);
00180     AGTYPE(&(e2->in)) = AGINEDGE;
00181     AGTYPE(&(e2->out)) = AGOUTEDGE;
00182     e2->out.base.data = (Agrec_t*)NEW(Agedgeinfo_t);
00183     e = &(e2->out);
00184 #endif /* WITH_CGRAPH */
00185 
00186     agtail(e) = u;
00187     aghead(e) = v;
00188     if (len > USHRT_MAX)
00189         len = largeMinlen (len);
00190     ED_minlen(e) = ROUND(len);
00191     ED_weight(e) = wt;
00192     fast_edge(e);
00193     return e;
00194 }
00195 
00196 static void allocate_aux_edges(graph_t * g)
00197 {
00198     int i, j, n_in;
00199     node_t *n;
00200 
00201     /* allocate space for aux edge lists */
00202     for (n = GD_nlist(g); n; n = ND_next(n)) {
00203         ND_save_in(n) = ND_in(n);
00204         ND_save_out(n) = ND_out(n);
00205         for (i = 0; ND_out(n).list[i]; i++);
00206         for (j = 0; ND_in(n).list[j]; j++);
00207         n_in = i + j;
00208         alloc_elist(n_in + 3, ND_in(n));
00209         alloc_elist(3, ND_out(n));
00210     }
00211 }
00212 
00213 /* make_LR_constraints:
00214  */
00215 static void 
00216 make_LR_constraints(graph_t * g)
00217 {
00218     int i, j, k;
00219     int sw;                     /* self width */
00220     int m0, m1;
00221     double width;
00222     int sep[2];
00223     int nodesep;      /* separation between nodes on same rank */
00224     edge_t *e, *e0, *e1, *ff;
00225     node_t *u, *v, *t0, *h0;
00226     rank_t *rank = GD_rank(g);
00227 
00228     /* Use smaller separation on odd ranks if g has edge labels */
00229     if (GD_has_labels(g) & EDGE_LABEL) {
00230         sep[0] = GD_nodesep(g);
00231         sep[1] = 5;
00232     }
00233     else {
00234         sep[1] = sep[0] = GD_nodesep(g);
00235     }
00236     /* make edges to constrain left-to-right ordering */
00237     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
00238         double last;
00239         last = ND_rank(rank[i].v[0]) = 0;
00240         nodesep = sep[i & 1];
00241         for (j = 0; j < rank[i].n; j++) {
00242             u = rank[i].v[j];
00243             ND_mval(u) = ND_rw(u);      /* keep it somewhere safe */
00244             if (ND_other(u).size > 0) { /* compute self size */
00245                 /* FIX: dot assumes all self-edges go to the right. This
00246                  * is no longer true, though makeSelfEdge still attempts to
00247                  * put as many as reasonable on the right. The dot code
00248                  * should be modified to allow a box reflecting the placement
00249                  * of all self-edges, and use that to reposition the nodes.
00250                  * Note that this would not only affect left and right
00251                  * positioning but may also affect interrank spacing.
00252                  */
00253                 sw = 0;
00254                 for (k = 0; (e = ND_other(u).list[k]); k++) {
00255                     if (agtail(e) == aghead(e)) {
00256                         sw += selfRightSpace (e);
00257                     }
00258                 }
00259                 ND_rw(u) += sw; /* increment to include self edges */
00260             }
00261             v = rank[i].v[j + 1];
00262             if (v) {
00263                 width = ND_rw(u) + ND_lw(v) + nodesep;
00264                 e0 = make_aux_edge(u, v, width, 0);
00265                 last = (ND_rank(v) = last + width);
00266             }
00267 
00268             /* constraints from labels of flat edges on previous rank */
00269             if ((e = (edge_t*)ND_alg(u))) {
00270                 e0 = ND_save_out(u).list[0];
00271                 e1 = ND_save_out(u).list[1];
00272                 if (ND_order(aghead(e0)) > ND_order(aghead(e1))) {
00273                     ff = e0;
00274                     e0 = e1;
00275                     e1 = ff;
00276                 }
00277                 m0 = (ED_minlen(e) * GD_nodesep(g)) / 2;
00278                 m1 = m0 + ND_rw(aghead(e0)) + ND_lw(agtail(e0));
00279                 /* these guards are needed because the flat edges
00280                  * work very poorly with cluster layout */
00281                 if (canreach(agtail(e0), aghead(e0)) == FALSE)
00282                     make_aux_edge(aghead(e0), agtail(e0), m1,
00283                         ED_weight(e));
00284                 m1 = m0 + ND_rw(agtail(e1)) + ND_lw(aghead(e1));
00285                 if (canreach(aghead(e1), agtail(e1)) == FALSE)
00286                     make_aux_edge(agtail(e1), aghead(e1), m1,
00287                         ED_weight(e));
00288             }
00289 
00290             /* position flat edge endpoints */
00291             for (k = 0; k < ND_flat_out(u).size; k++) {
00292                 e = ND_flat_out(u).list[k];
00293                 if (ND_order(agtail(e)) < ND_order(aghead(e))) {
00294                     t0 = agtail(e);
00295                     h0 = aghead(e);
00296                 } else {
00297                     t0 = aghead(e);
00298                     h0 = agtail(e);
00299                 }
00300 
00301                 width = ND_rw(t0) + ND_lw(h0);
00302                 m0 = ED_minlen(e) * GD_nodesep(g) + width;
00303 
00304                 if ((e0 = find_fast_edge(t0, h0))) {
00305                     /* flat edge between adjacent neighbors 
00306                      * ED_dist contains the largest label width.
00307                      */
00308                     m0 = MAX(m0, width + GD_nodesep(g) + ROUND(ED_dist(e)));
00309                     if (m0 > USHRT_MAX)
00310                         m0 = largeMinlen (m0);
00311                     ED_minlen(e0) = MAX(ED_minlen(e0), m0);
00312                 }
00313                 else if (!ED_label(e)) {
00314                     /* unlabeled flat edge between non-neighbors 
00315                      * ED_minlen(e) is max of ED_minlen of all equivalent 
00316                      * edges.
00317                      */
00318                     make_aux_edge(t0, h0, m0, ED_weight(e));
00319                 }
00320                 /* labeled flat edges between non-neighbors have already
00321                  * been constrained by the label above. 
00322                  */ 
00323             }
00324         }
00325     }
00326 }
00327 
00328 /* make_edge_pairs: make virtual edge pairs corresponding to input edges */
00329 static void make_edge_pairs(graph_t * g)
00330 {
00331     int i, m0, m1;
00332     node_t *n, *sn;
00333     edge_t *e;
00334 
00335     for (n = GD_nlist(g); n; n = ND_next(n)) {
00336         if (ND_save_out(n).list)
00337             for (i = 0; (e = ND_save_out(n).list[i]); i++) {
00338                 sn = virtual_node(g);
00339                 ND_node_type(sn) = SLACKNODE;
00340                 m0 = (ED_head_port(e).p.x - ED_tail_port(e).p.x);
00341                 if (m0 > 0)
00342                     m1 = 0;
00343                 else {
00344                     m1 = -m0;
00345                     m0 = 0;
00346                 }
00347 #ifdef NOTDEF
00348 /* was trying to improve LR balance */
00349                 if ((ND_save_out(n).size % 2 == 0)
00350                     && (i == ND_save_out(n).size / 2 - 1)) {
00351                     node_t *u = ND_save_out(n).list[i]->head;
00352                     node_t *v = ND_save_out(n).list[i + 1]->head;
00353                     double width = ND_rw(u) + ND_lw(v) + GD_nodesep(g);
00354                     m0 = width / 2 - 1;
00355                 }
00356 #endif
00357                 make_aux_edge(sn, agtail(e), m0 + 1, ED_weight(e));
00358                 make_aux_edge(sn, aghead(e), m1 + 1, ED_weight(e));
00359                 ND_rank(sn) =
00360                     MIN(ND_rank(agtail(e)) - m0 - 1,
00361                         ND_rank(aghead(e)) - m1 - 1);
00362             }
00363     }
00364 }
00365 
00366 static void contain_clustnodes(graph_t * g)
00367 {
00368     int c;
00369     edge_t      *e;
00370 
00371     if (g != agroot(g)) {
00372         contain_nodes(g);
00373         if ((e = find_fast_edge(GD_ln(g),GD_rn(g))))    /* maybe from lrvn()?*/
00374             ED_weight(e) += 128;
00375         else
00376             make_aux_edge(GD_ln(g), GD_rn(g), 1, 128);  /* clust compaction edge */
00377     }
00378     for (c = 1; c <= GD_n_cluster(g); c++)
00379         contain_clustnodes(GD_clust(g)[c]);
00380 }
00381 
00382 static int vnode_not_related_to(graph_t * g, node_t * v)
00383 {
00384     edge_t *e;
00385 
00386     if (ND_node_type(v) != VIRTUAL)
00387         return FALSE;
00388     for (e = ND_save_out(v).list[0]; ED_to_orig(e); e = ED_to_orig(e));
00389     if (agcontains(g, agtail(e)))
00390         return FALSE;
00391     if (agcontains(g, aghead(e)))
00392         return FALSE;
00393     return TRUE;
00394 }
00395 
00396 /* keepout_othernodes:
00397  * Guarantee nodes outside the cluster g are placed outside of it.
00398  * This is done by adding constraints to make sure such nodes have
00399  * a gap of margin from the left or right bounding box node ln or rn.
00400  * 
00401  * We could probably reduce some of these constraints by checking if
00402  * the node is in a cluster, since elsewhere we make constrain a
00403  * separate between clusters. Also, we should be able to skip the
00404  * first loop if g is the root graph.
00405  */
00406 static void keepout_othernodes(graph_t * g)
00407 {
00408     int i, c, r, margin;
00409     node_t *u, *v;
00410 
00411     margin = late_int (g, G_margin, CL_OFFSET, 0);
00412     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00413         if (GD_rank(g)[r].n == 0)
00414             continue;
00415         v = GD_rank(g)[r].v[0];
00416         if (v == NULL)
00417             continue;
00418         for (i = ND_order(v) - 1; i >= 0; i--) {
00419             u = GD_rank(agroot(g))[r].v[i];
00420             /* can't use "is_a_vnode_of" because elists are swapped */
00421             if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
00422                 make_aux_edge(u, GD_ln(g), margin + ND_rw(u), 0);
00423                 break;
00424             }
00425         }
00426         for (i = ND_order(v) + GD_rank(g)[r].n; i < GD_rank(agroot(g))[r].n;
00427              i++) {
00428             u = GD_rank(agroot(g))[r].v[i];
00429             if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
00430                 make_aux_edge(GD_rn(g), u, margin + ND_lw(u), 0);
00431                 break;
00432             }
00433         }
00434     }
00435 
00436     for (c = 1; c <= GD_n_cluster(g); c++)
00437         keepout_othernodes(GD_clust(g)[c]);
00438 }
00439 
00440 /* contain_subclust:
00441  * Make sure boxes of subclusters of g are offset from the
00442  * box of g. This is done by a constraint between the left and
00443  * right bounding box nodes ln and rn of g and a subcluster.
00444  * The gap needs to include any left or right labels.
00445  */
00446 static void contain_subclust(graph_t * g)
00447 {
00448     int margin, c;
00449     graph_t *subg;
00450 
00451     margin = late_int (g, G_margin, CL_OFFSET, 0);
00452     make_lrvn(g);
00453     for (c = 1; c <= GD_n_cluster(g); c++) {
00454         subg = GD_clust(g)[c];
00455         make_lrvn(subg);
00456         make_aux_edge(GD_ln(g), GD_ln(subg),
00457                       margin + GD_border(g)[LEFT_IX].x, 0);
00458         make_aux_edge(GD_rn(subg), GD_rn(g),
00459                       margin + GD_border(g)[RIGHT_IX].x, 0);
00460         contain_subclust(subg);
00461     }
00462 }
00463 
00464 /* separate_subclust:
00465  * Guarantee space between subcluster of g.
00466  * This is done by adding a constraint between the right bbox node rn
00467  * of the left cluster and the left bbox node ln of the right cluster.
00468  * This is only done if the two clusters overlap in some rank.
00469  */
00470 static void separate_subclust(graph_t * g)
00471 {
00472     int i, j, margin;
00473     graph_t *low, *high;
00474     graph_t *left, *right;
00475 
00476     margin = late_int (g, G_margin, CL_OFFSET, 0);
00477     for (i = 1; i <= GD_n_cluster(g); i++)
00478         make_lrvn(GD_clust(g)[i]);
00479     for (i = 1; i <= GD_n_cluster(g); i++) {
00480         for (j = i + 1; j <= GD_n_cluster(g); j++) {
00481             low = GD_clust(g)[i];
00482             high = GD_clust(g)[j];
00483             if (GD_minrank(low) > GD_minrank(high)) {
00484                 graph_t *temp = low;
00485                 low = high;
00486                 high = temp;
00487             }
00488             if (GD_maxrank(low) < GD_minrank(high))
00489                 continue;
00490             if (ND_order(GD_rank(low)[GD_minrank(high)].v[0])
00491                 < ND_order(GD_rank(high)[GD_minrank(high)].v[0])) {
00492                 left = low;
00493                 right = high;
00494             } else {
00495                 left = high;
00496                 right = low;
00497             }
00498             make_aux_edge(GD_rn(left), GD_ln(right), margin, 0);
00499         }
00500         separate_subclust(GD_clust(g)[i]);
00501     }
00502 }
00503 
00504 /* pos_clusters: create constraints for:
00505  *      node containment in clusters,
00506  *      cluster containment in clusters,
00507  *      separation of sibling clusters.
00508  */
00509 static void pos_clusters(graph_t * g)
00510 {
00511     if (GD_n_cluster(g) > 0) {
00512         contain_clustnodes(g);
00513         keepout_othernodes(g);
00514         contain_subclust(g);
00515         separate_subclust(g);
00516     }
00517 }
00518 
00519 static void compress_graph(graph_t * g)
00520 {
00521     double x;
00522     pointf p;
00523 
00524     if (GD_drawing(g)->ratio_kind != R_COMPRESS)
00525         return;
00526     p = GD_drawing(g)->size;
00527     if (p.x * p.y <= 1)
00528         return;
00529     contain_nodes(g);
00530     if (GD_flip(g) == FALSE)
00531         x = p.x;
00532     else
00533         x = p.y;
00534 
00535     /* Guard against huge size attribute since max. edge length is USHRT_MAX
00536      * A warning might be called for. Also, one could check that the graph
00537      * already fits GD_drawing(g)->size and return immediately.
00538      */
00539     x = MIN(x,USHRT_MAX); 
00540     make_aux_edge(GD_ln(g), GD_rn(g), x, 1000);
00541 }
00542 
00543 static void create_aux_edges(graph_t * g)
00544 {
00545     allocate_aux_edges(g);
00546     make_LR_constraints(g);
00547     make_edge_pairs(g);
00548     pos_clusters(g);
00549     compress_graph(g);
00550 }
00551 
00552 static void remove_aux_edges(graph_t * g)
00553 {
00554     int i;
00555     node_t *n, *nnext, *nprev;
00556     edge_t *e;
00557 
00558     for (n = GD_nlist(g); n; n = ND_next(n)) {
00559         for (i = 0; (e = ND_out(n).list[i]); i++) {
00560 #ifdef WITH_CGRAPH
00561             free(e->base.data);
00562 #endif
00563             free(e);
00564         }
00565         free_list(ND_out(n));
00566         free_list(ND_in(n));
00567         ND_out(n) = ND_save_out(n);
00568         ND_in(n) = ND_save_in(n);
00569     }
00570     /* cannot be merged with previous loop */
00571     nprev = NULL;
00572     for (n = GD_nlist(g); n; n = nnext) {
00573         nnext = ND_next(n);
00574         if (ND_node_type(n) == SLACKNODE) {
00575             if (nprev)
00576                 ND_next(nprev) = nnext;
00577             else
00578                 GD_nlist(g) = nnext;
00579 #ifdef WITH_CGRAPH
00580             free(n->base.data);
00581 #endif
00582             free(n);
00583         } else
00584             nprev = n;
00585     }
00586     ND_prev(GD_nlist(g)) = NULL;
00587 }
00588 
00589 /* set_xcoords:
00590  * Set x coords of nodes.
00591  */
00592 static void 
00593 set_xcoords(graph_t * g)
00594 {
00595     int i, j;
00596     node_t *v;
00597     rank_t *rank = GD_rank(g);
00598 
00599     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
00600         for (j = 0; j < rank[i].n; j++) {
00601             v = rank[i].v[j];
00602             ND_coord(v).x = ND_rank(v);
00603             ND_rank(v) = i;
00604         }
00605     }
00606 }
00607 
00608 /* adjustEqual:
00609  * Expand cluster g vertically by delta, assuming ranks
00610  * are equally spaced. We first try to split delta evenly
00611  * using any available space at the top and bottom. If there
00612  * is not enough, we have to widen the space between the ranks.
00613  * We divide delta equally within the ranks of g plus its ht1
00614  * and ht2. To preserve equality of ranks, we add this space
00615  * between every pair of ranks.
00616  *
00617  * There is probably some way to add less than delta, by using
00618  * whatever available space there is at top and bottom, but for
00619  * now, trying to figure that out seems more trouble than it is worth.
00620  */
00621 static void adjustEqual(graph_t * g, int delta)
00622 {
00623     int r, avail, half, deltop, delbottom;
00624     graph_t *root = agroot(g);
00625     rank_t *rank = GD_rank(root);
00626     int maxr = GD_maxrank(g);
00627     int minr = GD_minrank(g);
00628 
00629     deltop = rank[minr].ht2 - GD_ht2(g);
00630     delbottom = rank[maxr].ht1 - GD_ht1(g);
00631     avail = deltop + delbottom;
00632     if (avail >= delta) {
00633         half = (delta+1) / 2;
00634         if (deltop <= delbottom) {
00635             if (half <= deltop) {
00636                 GD_ht2(g) += half;
00637                 GD_ht1(g) += (delta - half);
00638             }
00639             else {    
00640                 GD_ht2(g) += deltop;
00641                 GD_ht1(g) += (delta - deltop);
00642             }
00643         }
00644         else {
00645             if (half <= delbottom) {
00646                 GD_ht1(g) += half;
00647                 GD_ht2(g) += (delta - half);
00648             }
00649             else {    
00650                 GD_ht1(g) += delbottom;
00651                 GD_ht2(g) += (delta - delbottom);
00652             }
00653         }
00654     }
00655     else {
00656         int gaps = maxr - minr + 2;
00657         int yoff = (delta + (gaps - 1)) / gaps;
00658         int y = yoff;
00659         for (r = GD_maxrank(root) - 1; r >= GD_minrank(root); r--) {
00660             if (rank[r].n > 0)
00661                 ND_coord(rank[r].v[0]).y += y;
00662             y += yoff;
00663         }
00664         GD_ht2(g) += yoff;
00665         GD_ht1(g) += yoff;
00666     }
00667 }
00668 
00669 /* adjustSimple:
00670  * Expand cluster height by delta, adding half to top
00671  * and half to bottom. If the bottom expansion exceeds the
00672  * ht1 of the rank, shift the ranks in the cluster up.
00673  * If the top expansion, including any shift from the bottom
00674  * expansion, exceeds to ht2 of the rank, shift the ranks above
00675  * the cluster up.
00676  */
00677 static void adjustSimple(graph_t * g, int delta)
00678 {
00679     int r, bottom, deltop, delbottom;
00680     graph_t *root = agroot(g);
00681     rank_t *rank = GD_rank(root);
00682     int maxr = GD_maxrank(g);
00683     int minr = GD_minrank(g);
00684 
00685     bottom = (delta+1) / 2;
00686     delbottom = GD_ht1(g) + bottom - rank[maxr].ht1;
00687     if (delbottom > 0) {
00688         for (r = maxr; r >= minr; r--) {
00689             if (rank[r].n > 0)
00690                 ND_coord(rank[r].v[0]).y += delbottom;
00691         }
00692         deltop = GD_ht2(g) + (delta-bottom) + delbottom - rank[minr].ht2;
00693     }
00694     else
00695         deltop = GD_ht2(g) + (delta-bottom) - rank[minr].ht2;
00696     if (deltop > 0) {
00697         for (r = minr-1; r >= GD_minrank(root); r--) {
00698             if (rank[r].n > 0)
00699                 ND_coord(rank[r].v[0]).y += deltop;
00700         }
00701     }
00702     GD_ht2(g) += (delta - bottom);
00703     GD_ht1(g) += bottom;
00704 }
00705 
00706 /* adjustRanks:
00707  * Recursively adjust ranks to take into account
00708  * wide cluster labels when rankdir=LR.
00709  * We divide the extra space between the top and bottom.
00710  * Adjust the ht1 and ht2 values in the process.
00711  */
00712 static void adjustRanks(graph_t * g, int equal)
00713 {
00714     int lht;                    /* label height */
00715     int rht;                    /* height between top and bottom ranks */
00716     int delta, maxr, minr, margin;
00717     int c, ht1, ht2;
00718 
00719     rank_t *rank = GD_rank(agroot(g));
00720     margin = late_int (g, G_margin, CL_OFFSET, 0);
00721 
00722     ht1 = GD_ht1(g);
00723     ht2 = GD_ht2(g);
00724 
00725     for (c = 1; c <= GD_n_cluster(g); c++) {
00726         graph_t *subg = GD_clust(g)[c];
00727         adjustRanks(subg, equal);
00728         if (GD_maxrank(subg) == GD_maxrank(g))
00729             ht1 = MAX(ht1, GD_ht1(subg) + margin);
00730         if (GD_minrank(subg) == GD_minrank(g))
00731             ht2 = MAX(ht2, GD_ht2(subg) + margin);
00732     }
00733 
00734     GD_ht1(g) = ht1;
00735     GD_ht2(g) = ht2;
00736 
00737     if ((g != agroot(g)) && GD_label(g)) {
00738         lht = MAX(GD_border(g)[LEFT_IX].y, GD_border(g)[RIGHT_IX].y);
00739         maxr = GD_maxrank(g);
00740         minr = GD_minrank(g);
00741         rht = ND_coord(rank[minr].v[0]).y - ND_coord(rank[maxr].v[0]).y;
00742         delta = lht - (rht + ht1 + ht2);
00743         if (delta > 0) {
00744             if (equal)
00745                 adjustEqual(g, delta);
00746             else
00747                 adjustSimple(g, delta);
00748         }
00749     }
00750 
00751     /* update the global ranks */
00752     if (g != agroot(g)) {
00753         rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, GD_ht2(g));
00754         rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, GD_ht1(g));
00755     }
00756 }
00757 
00758 /* clust_ht:
00759  * recursively compute cluster ht requirements.  assumes GD_ht1(subg) and ht2
00760  * are computed from primitive nodes only.  updates ht1 and ht2 to reflect
00761  * cluster nesting and labels.  also maintains global rank ht1 and ht2.
00762  * Return true if some cluster has a label.
00763  */
00764 static int clust_ht(Agraph_t * g)
00765 {
00766     int c, ht1, ht2;
00767     graph_t *subg;
00768     rank_t *rank = GD_rank(agroot(g));
00769     int margin, haveClustLabel = 0;
00770 
00771     if (g == agroot(g)) 
00772         margin = CL_OFFSET;
00773     else
00774         margin = late_int (g, G_margin, CL_OFFSET, 0);
00775 
00776     ht1 = GD_ht1(g);
00777     ht2 = GD_ht2(g);
00778 
00779     /* account for sub-clusters */
00780     for (c = 1; c <= GD_n_cluster(g); c++) {
00781         subg = GD_clust(g)[c];
00782         haveClustLabel |= clust_ht(subg);
00783         if (GD_maxrank(subg) == GD_maxrank(g))
00784             ht1 = MAX(ht1, GD_ht1(subg) + margin);
00785         if (GD_minrank(subg) == GD_minrank(g))
00786             ht2 = MAX(ht2, GD_ht2(subg) + margin);
00787     }
00788 
00789     /* account for a possible cluster label in clusters */
00790     /* room for root graph label is handled in dotneato_postprocess */
00791     if ((g != agroot(g)) && GD_label(g)) {
00792         haveClustLabel = 1;
00793         if (!GD_flip(agroot(g))) {
00794             ht1 += GD_border(g)[BOTTOM_IX].y;
00795             ht2 += GD_border(g)[TOP_IX].y;
00796         }
00797     }
00798     GD_ht1(g) = ht1;
00799     GD_ht2(g) = ht2;
00800 
00801     /* update the global ranks */
00802     if (g != agroot(g)) {
00803         rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, ht2);
00804         rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, ht1);
00805     }
00806 
00807     return haveClustLabel;
00808 }
00809 
00810 /* set y coordinates of nodes, a rank at a time */
00811 static void set_ycoords(graph_t * g)
00812 {
00813     int i, j, r, ht2, maxht, delta, d0, d1;
00814     node_t *n;
00815     edge_t *e;
00816     rank_t *rank = GD_rank(g);
00817     graph_t *clust;
00818     int lbl;
00819 
00820     ht2 = maxht = 0;
00821 
00822     /* scan ranks for tallest nodes.  */
00823     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00824         for (i = 0; i < rank[r].n; i++) {
00825             n = rank[r].v[i];
00826 
00827             /* assumes symmetry, ht1 = ht2 */
00828             ht2 = (ROUND(ND_ht(n)) + 1) / 2;
00829 
00830 
00831             /* have to look for high self-edge labels, too */
00832             if (ND_other(n).list)
00833                 for (j = 0; (e = ND_other(n).list[j]); j++) {
00834                     if (agtail(e) == aghead(e)) {
00835                         if (ED_label(e))
00836                             ht2 = MAX(ht2, ED_label(e)->dimen.y / 2);
00837                     }
00838                 }
00839 
00840             /* update global rank ht */
00841             if (rank[r].pht2 < ht2)
00842                 rank[r].pht2 = rank[r].ht2 = ht2;
00843             if (rank[r].pht1 < ht2)
00844                 rank[r].pht1 = rank[r].ht1 = ht2;
00845 
00846             /* update nearest enclosing cluster rank ht */
00847             if ((clust = ND_clust(n))) {
00848                 int yoff = (clust == g ? 0 : late_int (clust, G_margin, CL_OFFSET, 0));
00849                 if (ND_rank(n) == GD_minrank(clust))
00850                     GD_ht2(clust) = MAX(GD_ht2(clust), ht2 + yoff);
00851                 if (ND_rank(n) == GD_maxrank(clust))
00852                     GD_ht1(clust) = MAX(GD_ht1(clust), ht2 + yoff);
00853             }
00854         }
00855     }
00856 
00857     /* scan sub-clusters */
00858     lbl = clust_ht(g);
00859 
00860     /* make the initial assignment of ycoords to leftmost nodes by ranks */
00861     maxht = 0;
00862     r = GD_maxrank(g);
00863     (ND_coord(rank[r].v[0])).y = rank[r].ht1;
00864     while (--r >= GD_minrank(g)) {
00865         d0 = rank[r + 1].pht2 + rank[r].pht1 + GD_ranksep(g);   /* prim node sep */
00866         d1 = rank[r + 1].ht2 + rank[r].ht1 + CL_OFFSET; /* cluster sep */
00867         delta = MAX(d0, d1);
00868         if (rank[r].n > 0)      /* this may reflect some problem */
00869                 (ND_coord(rank[r].v[0])).y = (ND_coord(rank[r + 1].v[0])).y + delta;
00870 #ifdef DEBUG
00871         else
00872             fprintf(stderr, "dot set_ycoords: rank %d is empty\n",
00873                     rank[r].n);
00874 #endif
00875         maxht = MAX(maxht, delta);
00876     }
00877 
00878     /* re-assign if ranks are equally spaced */
00879     if (GD_exact_ranksep(g)) {
00880         for (r = GD_maxrank(g) - 1; r >= GD_minrank(g); r--)
00881             if (rank[r].n > 0)  /* this may reflect the same problem :-() */
00882                         (ND_coord(rank[r].v[0])).y=
00883                     (ND_coord(rank[r + 1].v[0])).y + maxht;
00884     }
00885 
00886     if (lbl && GD_flip(g))
00887         adjustRanks(g, GD_exact_ranksep(g));
00888 
00889     /* copy ycoord assignment from leftmost nodes to others */
00890     for (n = GD_nlist(g); n; n = ND_next(n))
00891         ND_coord(n).y = (ND_coord(rank[ND_rank(n)].v[0])).y;
00892 }
00893 
00894 /* dot_compute_bb:
00895  * Compute bounding box of g.
00896  * The x limits of clusters are given by the x positions of ln and rn.
00897  * This information is stored in the rank field, since it was calculated
00898  * using network simplex.
00899  * For the root graph, we don't enforce all the constraints on lr and 
00900  * rn, so we traverse the nodes and subclusters.
00901  */
00902 static void dot_compute_bb(graph_t * g, graph_t * root)
00903 {
00904     int r, c, x, offset;
00905     node_t *v;
00906     pointf LL, UR;
00907 
00908     if (g == agroot(g)) {
00909         LL.x = (double)(INT_MAX);
00910         UR.x = (double)(-INT_MAX);
00911         for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00912             int rnkn = GD_rank(g)[r].n;
00913             if (rnkn == 0)
00914                 continue;
00915             if ((v = GD_rank(g)[r].v[0]) == NULL)
00916                 continue;
00917             for (c = 1; (ND_node_type(v) != NORMAL) && c < rnkn; c++)
00918                 v = GD_rank(g)[r].v[c];
00919             if (ND_node_type(v) == NORMAL) {
00920                 x = ND_coord(v).x - ND_lw(v);
00921                 LL.x = MIN(LL.x, x);
00922             }
00923             else continue;
00924                 /* At this point, we know the rank contains a NORMAL node */
00925             v = GD_rank(g)[r].v[rnkn - 1];
00926             for (c = rnkn-2; ND_node_type(v) != NORMAL; c--)
00927                 v = GD_rank(g)[r].v[c];
00928             x = ND_coord(v).x + ND_rw(v);
00929             UR.x = MAX(UR.x, x);
00930         }
00931         offset = CL_OFFSET;
00932         for (c = 1; c <= GD_n_cluster(g); c++) {
00933             x = (double)(GD_bb(GD_clust(g)[c]).LL.x - offset);
00934             LL.x = MIN(LL.x, x);
00935             x = (double)(GD_bb(GD_clust(g)[c]).UR.x + offset);
00936             UR.x = MAX(UR.x, x);
00937         }
00938     } else {
00939         LL.x = (double)(ND_rank(GD_ln(g)));
00940         UR.x = (double)(ND_rank(GD_rn(g)));
00941     }
00942     LL.y = ND_coord(GD_rank(root)[GD_maxrank(g)].v[0]).y - GD_ht1(g);
00943     UR.y = ND_coord(GD_rank(root)[GD_minrank(g)].v[0]).y + GD_ht2(g);
00944     GD_bb(g).LL = LL;
00945     GD_bb(g).UR = UR;
00946 }
00947 
00948 static void rec_bb(graph_t * g, graph_t * root)
00949 {
00950     int c;
00951     for (c = 1; c <= GD_n_cluster(g); c++)
00952         rec_bb(GD_clust(g)[c], root);
00953     dot_compute_bb(g, root);
00954 }
00955 
00956 /* scale_bb:
00957  * Recursively rescale all bounding boxes using scale factors
00958  * xf and yf. We assume all the bboxes have been computed.
00959  */
00960 static void scale_bb(graph_t * g, graph_t * root, double xf, double yf)
00961 {
00962     int c;
00963 
00964     for (c = 1; c <= GD_n_cluster(g); c++)
00965         scale_bb(GD_clust(g)[c], root, xf, yf);
00966     GD_bb(g).LL.x *= xf;
00967     GD_bb(g).LL.y *= yf;
00968     GD_bb(g).UR.x *= xf;
00969     GD_bb(g).UR.y *= yf;
00970 }
00971 
00972 /* adjustAspectRatio:
00973  */
00974 static void adjustAspectRatio (graph_t* g, aspect_t* asp)
00975 {
00976     double AR = (GD_bb(g).UR.x - GD_bb(g).LL.x)/(GD_bb(g).UR.y - GD_bb(g).LL.y);
00977     if (Verbose) {
00978         fprintf(stderr, "AR=%0.4lf\t Area= %0.4lf\t", AR, (double)(GD_bb(g).UR.x - GD_bb(g).LL.x)*(GD_bb(g).UR.y - GD_bb(g).LL.y)/10000.0);
00979         fprintf(stderr, "Dummy=%d\n", countDummyNodes(g));
00980     }
00981     if (AR > 1.1*asp->targetAR) {
00982       asp->nextIter = (int)(asp->targetAR * (double)(asp->curIterations - asp->prevIterations)/(AR));
00983     }
00984     else if (AR <= 0.8 * asp->targetAR) {
00985       asp->nextIter = -1;
00986       if (Verbose)
00987         fprintf(stderr, "Going to apply another expansion.\n");
00988     }
00989     else {
00990         asp->nextIter = 0;
00991     }
00992     if (Verbose)
00993         fprintf(stderr, "next#iter=%d\n", asp->nextIter);
00994 }
00995 
00996 /* set_aspect:
00997  * Set bounding boxes and, if ratio is set, rescale graph.
00998  * Note that if some dimension shrinks, there may be problems
00999  * with labels.
01000  */
01001 static void set_aspect(graph_t * g, aspect_t* asp)
01002 {
01003     double xf = 0.0, yf = 0.0, actual, desired;
01004     node_t *n;
01005     boolean scale_it, filled;
01006     point sz;
01007 
01008     rec_bb(g, g);
01009     if ((GD_maxrank(g) > 0) && (GD_drawing(g)->ratio_kind)) {
01010         sz.x = GD_bb(g).UR.x - GD_bb(g).LL.x;
01011         sz.y = GD_bb(g).UR.y - GD_bb(g).LL.y;   /* normalize */
01012         if (GD_flip(g)) {
01013             int t = sz.x;
01014             sz.x = sz.y;
01015             sz.y = t;
01016         }
01017         scale_it = TRUE;
01018         if (GD_drawing(g)->ratio_kind == R_AUTO)
01019             filled = idealsize(g, .5);
01020         else
01021             filled = (GD_drawing(g)->ratio_kind == R_FILL);
01022         if (filled) {
01023             /* fill is weird because both X and Y can stretch */
01024             if (GD_drawing(g)->size.x <= 0)
01025                 scale_it = FALSE;
01026             else {
01027                 xf = (double) GD_drawing(g)->size.x / (double) sz.x;
01028                 yf = (double) GD_drawing(g)->size.y / (double) sz.y;
01029                 if ((xf < 1.0) || (yf < 1.0)) {
01030                     if (xf < yf) {
01031                         yf = yf / xf;
01032                         xf = 1.0;
01033                     } else {
01034                         xf = xf / yf;
01035                         yf = 1.0;
01036                     }
01037                 }
01038             }
01039         } else if (GD_drawing(g)->ratio_kind == R_EXPAND) {
01040             if (GD_drawing(g)->size.x <= 0)
01041                 scale_it = FALSE;
01042             else {
01043                 xf = (double) GD_drawing(g)->size.x /
01044                     (double) GD_bb(g).UR.x;
01045                 yf = (double) GD_drawing(g)->size.y /
01046                     (double) GD_bb(g).UR.y;
01047                 if ((xf > 1.0) && (yf > 1.0)) {
01048                     double scale = MIN(xf, yf);
01049                     xf = yf = scale;
01050                 } else
01051                     scale_it = FALSE;
01052             }
01053         } else if (GD_drawing(g)->ratio_kind == R_VALUE) {
01054             desired = GD_drawing(g)->ratio;
01055             actual = ((double) sz.y) / ((double) sz.x);
01056             if (actual < desired) {
01057                 yf = desired / actual;
01058                 xf = 1.0;
01059             } else {
01060                 xf = actual / desired;
01061                 yf = 1.0;
01062             }
01063         } else
01064             scale_it = FALSE;
01065         if (scale_it) {
01066             if (GD_flip(g)) {
01067                 double t = xf;
01068                 xf = yf;
01069                 yf = t;
01070             }
01071             for (n = GD_nlist(g); n; n = ND_next(n)) {
01072                 ND_coord(n).x = ROUND(ND_coord(n).x * xf);
01073                 ND_coord(n).y = ROUND(ND_coord(n).y * yf);
01074             }
01075             scale_bb(g, g, xf, yf);
01076         }
01077     }
01078 
01079     if (asp) adjustAspectRatio (g, asp);
01080 }
01081 
01082 static point resize_leaf(node_t * leaf, point lbound)
01083 {
01084     gv_nodesize(leaf, GD_flip(agraphof(leaf)));
01085     ND_coord(leaf).y = lbound.y;
01086     ND_coord(leaf).x = lbound.x + ND_lw(leaf);
01087     lbound.x = lbound.x + ND_lw(leaf) + ND_rw(leaf) + GD_nodesep(agraphof(leaf));
01088     return lbound;
01089 }
01090 
01091 static point place_leaf(node_t * leaf, point lbound, int order)
01092 {
01093     node_t *leader;
01094     graph_t *g = agraphof(leaf);
01095 
01096     leader = UF_find(leaf);
01097     if (leaf != leader)
01098         fast_nodeapp(leader, leaf);
01099     ND_order(leaf) = order;
01100     ND_rank(leaf) = ND_rank(leader);
01101     GD_rank(g)[ND_rank(leaf)].v[ND_order(leaf)] = leaf;
01102     return resize_leaf(leaf, lbound);
01103 }
01104 
01105 /* make space for the leaf nodes of each rank */
01106 static void make_leafslots(graph_t * g)
01107 {
01108     int i, j, r;
01109     node_t *v;
01110 
01111     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
01112         j = 0;
01113         for (i = 0; i < GD_rank(g)[r].n; i++) {
01114             v = GD_rank(g)[r].v[i];
01115             ND_order(v) = j;
01116             if (ND_ranktype(v) == LEAFSET)
01117                 j = j + ND_UF_size(v);
01118             else
01119                 j++;
01120         }
01121         if (j <= GD_rank(g)[r].n)
01122             continue;
01123         GD_rank(g)[r].v = ALLOC(j + 1, GD_rank(g)[r].v, node_t *);
01124         for (i = GD_rank(g)[r].n - 1; i >= 0; i--) {
01125             v = GD_rank(g)[r].v[i];
01126             GD_rank(g)[r].v[ND_order(v)] = v;
01127         }
01128         GD_rank(g)[r].n = j;
01129         GD_rank(g)[r].v[j] = NULL;
01130     }
01131 }
01132 
01133 static void do_leaves(graph_t * g, node_t * leader)
01134 {
01135     int j;
01136     point lbound;
01137     node_t *n;
01138     edge_t *e;
01139 
01140     if (ND_UF_size(leader) <= 1)
01141         return;
01142     lbound.x = ND_coord(leader).x - ND_lw(leader);
01143     lbound.y = ND_coord(leader).y;
01144     lbound = resize_leaf(leader, lbound);
01145     if (ND_out(leader).size > 0) {      /* in-edge leaves */
01146         n = aghead(ND_out(leader).list[0]);
01147         j = ND_order(leader) + 1;
01148         for (e = agfstin(g, n); e; e = agnxtin(g, e)) {
01149 #ifndef WITH_CGRAPH
01150             edge_t *e1 = e;
01151 #else
01152             edge_t *e1 = AGMKOUT(e);
01153 #endif
01154             if ((agtail(e1) != leader) && (UF_find(agtail(e1)) == leader)) {
01155                 lbound = place_leaf(agtail(e1), lbound, j++);
01156                 unmerge_oneway(e1);
01157                 elist_append(e1, ND_in(aghead(e1)));
01158             }
01159         }
01160     } else {                    /* out edge leaves */
01161         n = agtail(ND_in(leader).list[0]);
01162         j = ND_order(leader) + 1;
01163         for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
01164             if ((aghead(e) != leader) && (UF_find(aghead(e)) == leader)) {
01165                 lbound = place_leaf(aghead(e), lbound, j++);
01166                 unmerge_oneway(e);
01167                 elist_append(e, ND_out(agtail(e)));
01168             }
01169         }
01170     }
01171 }
01172 
01173 int ports_eq(edge_t * e, edge_t * f)
01174 {
01175     return ((ED_head_port(e).defined == ED_head_port(f).defined)
01176             && (((ED_head_port(e).p.x == ED_head_port(f).p.x) &&
01177                  (ED_head_port(e).p.y == ED_head_port(f).p.y))
01178                 || (ED_head_port(e).defined == FALSE))
01179             && (((ED_tail_port(e).p.x == ED_tail_port(f).p.x) &&
01180                  (ED_tail_port(e).p.y == ED_tail_port(f).p.y))
01181                 || (ED_tail_port(e).defined == FALSE))
01182         );
01183 }
01184 
01185 static void expand_leaves(graph_t * g)
01186 {
01187     int i, d;
01188     node_t *n;
01189     edge_t *e, *f;
01190 
01191     make_leafslots(g);
01192     for (n = GD_nlist(g); n; n = ND_next(n)) {
01193         if (ND_inleaf(n))
01194             do_leaves(g, ND_inleaf(n));
01195         if (ND_outleaf(n))
01196             do_leaves(g, ND_outleaf(n));
01197         if (ND_other(n).list)
01198             for (i = 0; (e = ND_other(n).list[i]); i++) {
01199                 if ((d = ND_rank(aghead(e)) - ND_rank(aghead(e))) == 0)
01200                     continue;
01201                 f = ED_to_orig(e);
01202                 if (ports_eq(e, f) == FALSE) {
01203                     zapinlist(&(ND_other(n)), e);
01204                     if (d == 1)
01205                         fast_edge(e);
01206                     /*else unitize(e); ### */
01207                     i--;
01208                 }
01209             }
01210     }
01211 }
01212 
01213 /* make_lrvn:
01214  * Add left and right slacknodes to a cluster which
01215  * are used in the LP to constrain nodes not in g but
01216  * sharing its ranks to be to the left or right of g
01217  * by a specified amount.
01218  * The slacknodes ln and rn give the x position of the
01219  * left and right side of the cluster's bounding box. In
01220  * particular, any cluster labels on the left or right side
01221  * are inside.
01222  * If a cluster has a label, and we have rankdir!=LR, we make
01223  * sure the cluster is wide enough for the label. Note that
01224  * if the label is wider than the cluster, the nodes in the
01225  * cluster may not be centered.
01226  */
01227 static void make_lrvn(graph_t * g)
01228 {
01229     node_t *ln, *rn;
01230 
01231     if (GD_ln(g))
01232         return;
01233     ln = virtual_node(agroot(g));
01234     ND_node_type(ln) = SLACKNODE;
01235     rn = virtual_node(agroot(g));
01236     ND_node_type(rn) = SLACKNODE;
01237 
01238     if (GD_label(g) && (g != agroot(g)) && !GD_flip(agroot(g))) {
01239         int w = MAX(GD_border(g)[BOTTOM_IX].x, GD_border(g)[TOP_IX].x);
01240         make_aux_edge(ln, rn, w, 0);
01241     }
01242 
01243     GD_ln(g) = ln;
01244     GD_rn(g) = rn;
01245 }
01246 
01247 /* contain_nodes: 
01248  * make left and right bounding box virtual nodes ln and rn
01249  * constrain interior nodes
01250  */
01251 static void contain_nodes(graph_t * g)
01252 {
01253     int margin, r;
01254     node_t *ln, *rn, *v;
01255 
01256     margin = late_int (g, G_margin, CL_OFFSET, 0);
01257     make_lrvn(g);
01258     ln = GD_ln(g);
01259     rn = GD_rn(g);
01260     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
01261         if (GD_rank(g)[r].n == 0)
01262             continue;
01263         v = GD_rank(g)[r].v[0];
01264         if (v == NULL) {
01265             agerr(AGERR, "contain_nodes clust %s rank %d missing node\n",
01266                   agnameof(g), r);
01267             continue;
01268         }
01269         make_aux_edge(ln, v,
01270                       ND_lw(v) + margin + GD_border(g)[LEFT_IX].x, 0);
01271         v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
01272         make_aux_edge(v, rn,
01273                       ND_rw(v) + margin + GD_border(g)[RIGHT_IX].x, 0);
01274     }
01275 }
01276 
01277 /* idealsize:
01278  * set g->drawing->size to a reasonable default.
01279  * returns a boolean to indicate if drawing is to
01280  * be scaled and filled */
01281 static boolean idealsize(graph_t * g, double minallowed)
01282 {
01283     double xf, yf, f, R;
01284     pointf b, relpage, margin;
01285 
01286     /* try for one page */
01287     relpage = GD_drawing(g)->page;
01288     if (relpage.x < 0.001 || relpage.y < 0.001)
01289         return FALSE;           /* no page was specified */
01290     margin = GD_drawing(g)->margin;
01291     relpage = sub_pointf(relpage, margin);
01292     relpage = sub_pointf(relpage, margin);
01293     b.x = GD_bb(g).UR.x;
01294     b.y = GD_bb(g).UR.y;
01295     xf = relpage.x / b.x;
01296     yf = relpage.y / b.y;
01297     if ((xf >= 1.0) && (yf >= 1.0))
01298         return FALSE;           /* fits on one page */
01299 
01300     f = MIN(xf, yf);
01301     xf = yf = MAX(f, minallowed);
01302 
01303     R = ceil((xf * b.x) / relpage.x);
01304     xf = ((R * relpage.x) / b.x);
01305     R = ceil((yf * b.y) / relpage.y);
01306     yf = ((R * relpage.y) / b.y);
01307     GD_drawing(g)->size.x = b.x * xf;
01308     GD_drawing(g)->size.y = b.y * yf;
01309     return TRUE;
01310 }