/*
This software may only be used by you under license from AT&T Corp.
("AT&T"). A copy of AT&T's Source Code Agreement is available at
AT&T's Internet website having the URL:
<http://www.research.att.com/sw/tools/graphviz/license/source.html>
If you received this software without first entering into a license
with AT&T, you have an infringing copy of this software and cannot use
it without violating AT&T's intellectual property rights.
*/
#pragma prototyped
/*
* position(g): set ND_coord_i(n) (x and y) for all nodes n of g, using GD_rank(g).
* (the graph may be modified by merging certain edges with a common endpoint.)
* the coordinates are computed by constructing and ranking an auxiliary graph.
* then leaf nodes are inserted in the fast graph. cluster boundary nodes are
* created and correctly separated.
*/
#include "dot.h"
void dot_position(graph_t* g)
{
if (GD_nlist(g) == NULL) return; /* ignore empty graph */
mark_lowclusters(g); /* we could remove from splines.c now */
set_ycoords(g);
if (Concentrate) dot_concentrate(g);
expand_leaves(g);
if (flat_edges(g)) set_ycoords(g);
create_aux_edges(g);
rank(g,2,nsiter2(g)); /* LR balance == 2*/
set_xcoords(g);
remove_aux_edges(g);
set_aspect(g);
}
int nsiter2(graph_t* g)
{
int maxiter = MAXINT;
char *s;
if ((s = agget(g,"nslimit")))
maxiter = atof(s) * agnnodes(g);
return maxiter;
}
static int searchcnt;
static int go(node_t* u, node_t* v)
{
int i;
edge_t *e;
if (u == v) return TRUE;
for (i = 0; (e = ND_out(u).list[i]); i++) {
if (go(e->head,v))
return TRUE;
}
return FALSE;
}
static int canreach(node_t* u, node_t* v)
{
if (++searchcnt == 0) searchcnt = 1;
return go(u,v);
}
edge_t *
make_aux_edge(node_t *u, node_t *v, int len, int wt)
{
edge_t *e;
e = NEW(edge_t);
e->tail = u;
e->head = v;
ED_minlen(e) = len;
ED_weight(e) = wt;
fast_edge(e);
return e;
}
void allocate_aux_edges(graph_t* g)
{
int i,j,n_in;
node_t *n;
/* allocate space for aux edge lists */
for (n = GD_nlist(g); n; n = ND_next(n)) {
ND_save_in(n) = ND_in(n);
ND_save_out(n) = ND_out(n);
for (i = 0; ND_out(n).list[i]; i++);
for (j = 0; ND_in(n).list[j]; j++);
n_in = i + j;
alloc_elist(n_in + 3,ND_in(n));
alloc_elist(3,ND_out(n));
}
}
void make_LR_constraints(graph_t* g)
{
int i,j,k;
int sw; /* self width */
int m0,m1;
int width;
edge_t *e, *e0, *e1, *f, *ff;
node_t *u,*v, *t0, *h0;
rank_t *rank = GD_rank(g);
/* make edges to constrain left-to-right ordering */
for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
int last;
last = rank[i].v[0]->u.rank = 0;
for (j = 0; j < rank[i].n; j++) {
u = rank[i].v[j];
ND_mval(u) = ND_rw_i(u); /* keep it somewhere safe */
if (ND_other(u).size > 0) { /* compute self size */
sw = 0;
for (k = 0; (e = ND_other(u).list[k]); k++) {
if (e->tail == e->head) {
sw += SELF_EDGE_SIZE;
if (ED_label(e)) {
double label_width;
label_width = GD_left_to_right(g)? ED_label(e)->dimen.y : ED_label(e)->dimen.x;
sw += POINTS(label_width);
}
}
}
ND_rw_i(u) += sw; /* increment to include self edges */
}
v = rank[i].v[j+1];
if (v) {
width = ND_rw_i(u) + ND_lw_i(v) + GD_nodesep(g);
e0 = make_aux_edge(u,v,width,0);
last = (ND_rank(v) = last + width);
}
/* position flat edge endpoints */
for (k = 0; k < ND_flat_out(u).size; k++) {
e = ND_flat_out(u).list[k];
v = e->head;
if (ND_order(e->tail) < ND_order(e->head))
{ t0 = e->tail; h0 = e->head; }
else
{ t0 = e->head; h0 = e->tail; }
/* case 1: flat edge with a label */
if ((f = ED_to_virt(e))) {
while (ED_to_virt(f)) f = ED_to_virt(f);
e0 = ND_save_out(f->tail).list[0];
e1 = ND_save_out(f->tail).list[1];
if (ND_order(e0->head) > ND_order(e1->head))
{ ff = e0; e0 = e1; e1 = ff; }
m0 = (ED_minlen(e) *GD_nodesep(g))/2;
m1 = m0 + ND_rw_i(e0->head) + ND_lw_i(e0->tail);
/* these guards are needed because the flat edges
work very poorly with cluster layout */
if (canreach(e0->tail,e0->head) == FALSE)
make_aux_edge(e0->head,e0->tail,m1,ED_weight(e));
m1 = m0 + ND_rw_i(e1->tail) + ND_lw_i(e1->head);
if (canreach(e1->head,e1->tail) == FALSE)
make_aux_edge(e1->tail,e1->head,m1,ED_weight(e));
continue;
}
m0 = ED_minlen(e) *GD_nodesep(g) + ND_rw_i(t0) + ND_lw_i(h0);
if ((e0 = find_fast_edge(t0,h0)))
/* case 2: flat edge between neighbors */
ED_minlen(e0) = MAX(ED_minlen(e0),m0);
else
/* case 3: flat edge between non-neighbors */
make_aux_edge(t0,h0,m0,ED_weight(e));
}
}
}
}
/* make_edge_pairs: make virtual edge pairs corresponding to input edges */
void make_edge_pairs(graph_t* g)
{
int i,m0,m1;
node_t *n,*sn;
edge_t *e;
for (n = GD_nlist(g); n; n = ND_next(n)) {
if (ND_save_out(n).list) for (i = 0; (e = ND_save_out(n).list[i]); i++) {
sn = virtual_node(g);
ND_node_type(sn) = SLACKNODE;
m0 = (ED_head_port(e).p.x - ED_tail_port(e).p.x);
if (m0 > 0) m1 = 0;
else {m1 = -m0; m0 = 0;}
#ifdef NOTDEF
/* was trying to improve LR balance */
if ((ND_save_out(n).size % 2 == 0) && (i == ND_save_out(n).size / 2 - 1)) {
node_t *u = ND_save_out(n).list[i]->head;
node_t *v = ND_save_out(n).list[i+1]->head;
int width = ND_rw_i(u) + ND_lw_i(v) + GD_nodesep(g);
m0 = width / 2 - 1;
}
#endif
make_aux_edge(sn,e->tail,m0+1,ED_weight(e));
make_aux_edge(sn,e->head,m1+1,ED_weight(e));
ND_rank(sn) = MIN(ND_rank(e->tail) - m0 -1, ND_rank(e->head) - m1 - 1);
}
}
}
/* pos_clusters: create constraints for:
* node containment in clusters,
* cluster containment in clusters,
* separation of sibling clusters.
*/
void pos_clusters(graph_t* g)
{
if (GD_n_cluster(g) > 0) {
contain_clustnodes(g);
keepout_othernodes(g);
contain_subclust(g);
separate_subclust(g);
}
}
void contain_clustnodes(graph_t* g)
{
int c;
if (g != g->root) {
contain_nodes(g);
make_aux_edge(GD_ln(g), GD_rn(g), 1, 128); /* clust compaction edge */
}
for (c = 1; c <= GD_n_cluster(g); c++)
contain_clustnodes(GD_clust(g)[c]);
}
int vnode_not_related_to(graph_t* g, node_t* v)
{
edge_t *e;
if (ND_node_type(v) != VIRTUAL) return FALSE;
for (e = ND_save_out(v).list[0]; ED_to_orig(e); e = ED_to_orig(e));
if (agcontains(g,e->tail)) return FALSE;
if (agcontains(g,e->head)) return FALSE;
return TRUE;
}
void keepout_othernodes(graph_t* g)
{
int i,c,r;
node_t *u,*v;
for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
if (GD_rank(g)[r].n == 0) continue;
v = GD_rank(g)[r].v[0];
if (v == NULL) continue;
for (i = ND_order(v) - 1; i >= 0; i--) {
u = GD_rank(g->root)[r].v[i];
/* can't use "is_a_vnode_of" because elists are swapped */
if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g,u)) {
make_aux_edge(u,GD_ln(g),CL_OFFSET+ND_rw_i(u)+GD_border(g)[LEFT_IX].x,0);
break;
}
}
for (i = ND_order(v) + GD_rank(g)[r].n; i < GD_rank(g->root)[r].n; i++) {
u = ND_rank(g->root)[r].v[i];
if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g,u)) {
make_aux_edge(GD_rn(g),u,CL_OFFSET+ND_lw_i(u)+GD_border(g)[RIGHT_IX].x,0);
break;
}
}
}
for (c = 1; c <= GD_n_cluster(g); c++)
keepout_othernodes(GD_clust(g)[c]);
}
void contain_subclust(graph_t* g)
{
int c;
graph_t *subg;
make_lrvn(g);
for (c = 1; c <= GD_n_cluster(g); c++) {
subg = GD_clust(g)[c];
make_lrvn(subg);
make_aux_edge(GD_ln(g), GD_ln(subg), CL_OFFSET + GD_border(subg)[LEFT_IX].x, 0);
make_aux_edge(GD_rn(subg), GD_rn(g), CL_OFFSET + GD_border(subg)[RIGHT_IX].x, 0);
contain_subclust(subg);
}
}
void separate_subclust(graph_t* g)
{
int i,j;
graph_t *low,*high;
graph_t *left,*right;
for (i = 1; i <= GD_n_cluster(g); i++) make_lrvn(GD_clust(g)[i]);
for (i = 1; i <= GD_n_cluster(g); i++) {
for (j = i + 1; j <= GD_n_cluster(g); j++) {
low = GD_clust(g)[i]; high = GD_clust(g)[j];
if (GD_minrank(low) > GD_minrank(high))
{ graph_t *temp = low; low = high; high= temp; }
if (GD_maxrank(low) < GD_minrank(high)) continue;
if (ND_order(GD_rank(low)[GD_minrank(high)].v[0])
< ND_order(GD_rank(high)[GD_minrank(high)].v[0]))
{ left = low; right = high; }
else
{ left = high; right = low; }
make_aux_edge(ND_rn(left), ND_ln(right),
CL_OFFSET+ND_border(left)[RIGHT_IX].x+ND_border(right)[LEFT_IX].x,0);
}
separate_subclust(GD_clust(g)[i]);
}
}
void create_aux_edges(graph_t* g)
{
allocate_aux_edges(g);
make_LR_constraints(g);
make_edge_pairs(g);
pos_clusters(g);
compress_graph(g);
}
void remove_aux_edges(graph_t* g)
{
int i;
node_t *n,*nnext,*nprev;
edge_t *e;
for (n = GD_nlist(g); n; n = ND_next(n)) {
for (i = 0; (e = ND_out(n).list[i]); i++) free(e);
free_list(ND_out(n));
free_list(ND_in(n));
ND_out(n) = ND_save_out(n);
ND_in(n) = ND_save_in(n);
}
/* cannot be merged with previous loop */
nprev = NULL;
for (n = GD_nlist(g); n; n = nnext) {
nnext = ND_next(n);
if (ND_node_type(n) == SLACKNODE) {
if (nprev) ND_next(nprev) = nnext;
else GD_nlist(g) = nnext;
free(n);
}
else nprev = n;
}
GD_nlist(g)->u.prev = NULL;
}
void set_xcoords(graph_t* g)
{
int i,j;
node_t *v;
rank_t *rank = GD_rank(g);
for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
for (j = 0; j < rank[i].n; j++) {
v = rank[i].v[j];
ND_coord_i(v).x = ND_rank(v);
ND_rank(v) = i;
}
}
}
/* set y coordinates of nodes, a rank at a time */
void set_ycoords(graph_t* g)
{
int i,j,r,ht2,maxht,delta,d0,d1;
node_t *n;
edge_t *e;
rank_t *rank = GD_rank(g);
graph_t *clust;
static void clust_ht(graph_t *g);
ht2 = maxht = 0;
/* scan ranks for tallest nodes. */
for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
for (i = 0; i < rank[r].n; i++) {
n = rank[r].v[i];
/* assumes symmetry, ht1 = ht2 */
ht2 = (ND_ht_i(n) + 1)/2;
/* have to look for high self-edge labels, too */
if (ND_other(n).list) for (j = 0; (e = ND_other(n).list[j]); j++) {
if (e->tail == e->head) {
if (ED_label(e)) ht2 = MAX(ht2,POINTS(ED_label(e)->dimen.y)/2);
}
}
/* update global rank ht */
if (rank[r].pht2 < ht2) rank[r].pht2 = rank[r].ht2 = ht2;
if (rank[r].pht1 < ht2) rank[r].pht1 = rank[r].ht1 = ht2;
/* update nearest enclosing cluster rank ht */
if ((clust = ND_clust(n))) {
if (ND_rank(n) == GD_minrank(clust))
GD_ht2(clust) = MAX(GD_ht2(clust),ht2 + CL_OFFSET);
if (ND_rank(n) == GD_maxrank(clust))
GD_ht1(clust) = MAX(GD_ht1(clust),ht2 + CL_OFFSET);
}
}
}
/* scan sub-clusters */
clust_ht(g);
/* make the initial assignment of ycoords to leftmost nodes by ranks */
maxht = 0;
r = GD_maxrank(g);
rank[r].v[0]->u.coord.y = rank[r].ht1;
while (--r >= GD_minrank(g)) {
d0 = rank[r+1].pht2 + rank[r].pht1 + GD_ranksep(g); /* prim node sep */
d1 = rank[r+1].ht2 + rank[r].ht1 + CL_OFFSET; /* cluster sep */
delta = MAX(d0,d1);
if (rank[r].n > 0) /* this may reflect some problem */
rank[r].v[0]->u.coord.y = rank[r + 1].v[0]->u.coord.y + delta;
#ifdef DEBUG
else fprintf(stderr,"dot set_ycoords: rank %d is empty\n",rank[r].n);
#endif
maxht = MAX(maxht,delta);
}
/* re-assign if ranks are equally spaced */
if (GD_exact_ranksep(g))
for (r = GD_maxrank(g) - 1; r >= GD_minrank(g); r--)
if (rank[r].n > 0) /* this may reflect the same problem :-() */
rank[r].v[0]->u.coord.y = rank[r + 1].v[0]->u.coord.y + maxht;
/* copy ycoord assignment from leftmost nodes to others */
for (n = GD_nlist(g); n; n = ND_next(n))
ND_coord_i(n).y = rank[ND_rank(n)].v[0]->u.coord.y;
}
void compute_bb(graph_t* g, graph_t* root)
{
int c,r,x;
node_t *v;
point LL,UR,p,offset;
LL.x = LL.y = MAXINT;
UR.x = UR.y = -MAXINT;
for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
if (GD_rank(g)[r].n == 0) continue;
if ((v = GD_rank(g)[r].v[0]) == NULL) continue;
x = ND_coord_i(v).x - ND_lw_i(v);
if (g != g->root) x-= CL_OFFSET;
LL.x = MIN(LL.x,x);
v = GD_rank(g)[r].v[GD_rank(g)[r].n-1];
x = ND_coord_i(v).x + ND_rw_i(v);
if (g != g->root) x+= CL_OFFSET;
UR.x = MAX(UR.x,x);
}
offset.x = offset.y = CL_OFFSET;
for (c = 1; c <= GD_n_cluster(g); c++) {
p = sub_points(GD_clust(g)[c]->u.bb.LL,offset);
if (LL.x > p.x) LL.x = p.x;
p = add_points(GD_clust(g)[c]->u.bb.UR,offset);
if (UR.x < p.x) UR.x = p.x;
}
LL.y = ND_rank(root)[GD_maxrank(g)].v[0]->u.coord.y - GD_ht1(g);
UR.y = ND_rank(root)[GD_minrank(g)].v[0]->u.coord.y + GD_ht2(g);
GD_bb(g).LL = LL; GD_bb(g).UR = UR;
}
void rec_bb(graph_t *g, graph_t *root)
{
int c;
for (c = 1; c <= GD_n_cluster(g); c++)
rec_bb(GD_clust(g)[c],root);
compute_bb(g,root);
}
void set_aspect(graph_t* g)
{
double xf=0.0,yf=0.0,actual,desired;
char *str;
node_t *n;
boolean scale_it,filled;
rec_bb(g,g);
if ((GD_maxrank(g) > 0) && (str = agget(g,"ratio"))) {
GD_bb(g).UR.x -= GD_bb(g).LL.x;
GD_bb(g).UR.y -= GD_bb(g).LL.y; /* normalize */
if (GD_left_to_right(g))
{int t = GD_bb(g).UR.x; GD_bb(g).UR.x = GD_bb(g).UR.y; GD_bb(g).UR.y = t;}
scale_it = TRUE;
if (streq(str,"auto")) filled = idealsize(g,.5);
else filled = (streq(str,"fill"));
if (filled) {
/* fill is weird because both X and Y can stretch */
if (GD_drawing(g)->size.x <= 0) scale_it = FALSE;
else {
xf = (double)GD_drawing(g)->size.x / (double)GD_bb(g).UR.x;
yf = (double)GD_drawing(g)->size.y / (double)GD_bb(g).UR.y;
if ((xf < 1.0) || (yf < 1.0)) {
if (xf < yf) {yf = yf / xf; xf = 1.0;}
else {xf = xf / yf; yf = 1.0;}
}
}
}
else {
desired = atof(str);
if (desired == 0.0) scale_it = FALSE;
else {
actual = ((double)GD_bb(g).UR.y)/((double)GD_bb(g).UR.x);
if (actual < desired) {yf = desired/actual; xf = 1.0;}
else {xf = actual/desired; yf = 1.0;}
}
}
if (scale_it) {
if (GD_left_to_right(g)) {double t = xf; xf = yf; yf = t;}
for (n = GD_nlist(g); n; n = ND_next(n)) {
ND_coord_i(n).x = ND_coord_i(n).x * xf;
ND_coord_i(n).y = ND_coord_i(n).y * yf;
}
}
}
rec_bb(g,g);
}
point
resize_leaf(node_t* leaf, point lbound)
{
dot_nodesize(leaf,GD_left_to_right(leaf->graph));
ND_coord_i(leaf).y = lbound.y;
ND_coord_i(leaf).x = lbound.x + ND_lw_i(leaf);
lbound.x = lbound.x + ND_lw_i(leaf) + ND_rw_i(leaf) + GD_nodesep(leaf->graph);
return lbound;
}
point
place_leaf(node_t* leaf, point lbound, int order)
{
node_t *leader;
graph_t *g = leaf->graph;
leader = UF_find(leaf);
if (leaf != leader) fast_nodeapp(leader,leaf);
ND_order(leaf) = order;
ND_rank(leaf) = ND_rank(leader);
GD_rank(g)[ND_rank(leaf)].v[ND_order(leaf)] = leaf;
return resize_leaf(leaf,lbound);
}
/* make space for the leaf nodes of each rank */
void make_leafslots(graph_t* g)
{
int i,j,r;
node_t *v;
for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
j = 0;
for (i = 0; i < GD_rank(g)[r].n; i++) {
v = GD_rank(g)[r].v[i];
ND_order(v) = j;
if (ND_ranktype(v) == LEAFSET) j = j + ND_UF_size(v);
else j++;
}
if (j <= GD_rank(g)[r].n) continue;
GD_rank(g)[r].v = ALLOC(j+1,GD_rank(g)[r].v,node_t*);
for (i = GD_rank(g)[r].n - 1; i >= 0; i--) {
v = GD_rank(g)[r].v[i];
GD_rank(g)[r].v[ND_order(v)] = v;
}
GD_rank(g)[r].n = j;
GD_rank(g)[r].v[j] = NULL;
}
}
void do_leaves(graph_t* g, node_t* leader)
{
int j;
point lbound;
node_t *n;
edge_t *e;
if (ND_UF_size(leader) <= 1) return;
lbound.x = ND_coord_i(leader).x - ND_lw_i(leader);
lbound.y = ND_coord_i(leader).y;
lbound = resize_leaf(leader,lbound);
if (ND_out(leader).size > 0) { /* in-edge leaves */
n = ND_out(leader).list[0]->head;
j = ND_order(leader) + 1;
for (e = agfstin(g,n); e; e = agnxtin(g,e)) {
if ((e->tail != leader) && (UF_find(e->tail) == leader)) {
lbound = place_leaf(e->tail,lbound,j++);
unmerge_oneway(e);
elist_append(e,ND_in(e->head));
}
}
}
else { /* out edge leaves */
n = ND_in(leader).list[0]->tail;
j = ND_order(leader) + 1;
for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
if ((e->head != leader) && (UF_find(e->head) == leader)) {
lbound = place_leaf(e->head,lbound,j++);
unmerge_oneway(e);
elist_append(e,ND_out(e->tail));
}
}
}
}
int ports_eq(edge_t *e,edge_t *f)
{
return (
(ED_head_port(e).defined == ED_head_port(f).defined)
&& ( ((ED_head_port(e).p.x == ED_head_port(f).p.x) &&
(ED_head_port(e).p.y == ED_head_port(f).p.y))
|| (ED_head_port(e).defined == FALSE))
&& ( ((ED_tail_port(e).p.x == ED_tail_port(f).p.x) &&
(ED_tail_port(e).p.y == ED_tail_port(f).p.y))
|| (ED_tail_port(e).defined == FALSE))
);
}
void expand_leaves(graph_t* g)
{
int i,d;
node_t *n;
edge_t *e,*f;
make_leafslots(g);
for (n = GD_nlist(g); n; n = ND_next(n)) {
if (ND_inleaf(n)) do_leaves(g,ND_inleaf(n));
if (ND_outleaf(n)) do_leaves(g,ND_outleaf(n));
if (ND_other(n).list) for (i = 0; (e = ND_other(n).list[i]); i++) {
if ((d = ND_rank(e->head) - ND_rank(e->head)) == 0) continue;
f = ED_to_orig(e);
if (ports_eq(e,f) == FALSE) {
zapinlist(&(ND_other(n)),e);
if (d == 1) fast_edge(e);
/*else unitize(e); ### */
i--;
}
}
}
}
void compress_graph(graph_t* g)
{
char *str;
double x;
point p;
p = GD_drawing(g)->size;
if ((str = agget(g,"ratio")) == NULL) return;
if (strcmp(str,"compress")) return;
if (p.x * p.y <= 1) return;
contain_nodes(g);
if (GD_left_to_right(g) == FALSE) x = p.x; else x = p.y;
make_aux_edge(GD_ln(g),GD_rn(g),(int)x,1000);
}
void make_lrvn(graph_t* g)
{
node_t *ln,*rn;
if (GD_ln(g)) return;
ln = virtual_node(g->root); ND_node_type(ln) = SLACKNODE;
rn = virtual_node(g->root); ND_node_type(rn) = SLACKNODE;
GD_ln(g) = ln; GD_rn(g) = rn;
}
/* contain_nodes: make left and right bounding box virtual nodes,
* constrain interior nodes
*/
void contain_nodes(graph_t* g)
{
int r;
node_t *ln,*rn,*v;
make_lrvn(g); ln = GD_ln(g); rn = GD_rn(g);
for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
if (GD_rank(g)[r].n == 0) continue;
v = GD_rank(g)[r].v[0];
if (v == NULL) {
agerr(AGERR, "contain_nodes clust %s rank %d missing node\n",g->name,r);
continue;
}
make_aux_edge(ln,v,ND_lw_i(v) + CL_OFFSET,0);
v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
make_aux_edge(v,rn,ND_rw_i(v) + CL_OFFSET,0);
}
}
/* set g->drawing->size to a reasonable default.
* returns a boolean to indicate if drawing is to
* be scaled and filled */
int idealsize(graph_t* g, double minallowed)
{
double xf,yf,f,R;
point b,relpage,margin;
/* try for one page */
relpage = GD_drawing(g)->page;
if (relpage.x == 0) return FALSE; /* no page was specified */
margin = GD_drawing(g)->margin;
relpage = sub_points(relpage,margin);
relpage = sub_points(relpage,margin);
b.x = GD_bb(g).UR.x; b.y = GD_bb(g).UR.y;
xf = (double)relpage.x / b.x;
yf = (double)relpage.y / b.y;
if ((xf >= 1.0) && (yf >= 1.0)) return FALSE; /* fits on one page */
f = MIN(xf,yf);
xf = yf = MAX(f,minallowed);
R = ceil((xf * b.x)/relpage.x);
xf = ((R * relpage.x) / b.x);
R = ceil((yf * b.y)/relpage.y);
yf = ((R * relpage.y) / b.y);
GD_drawing(g)->size.x = b.x * xf;
GD_drawing(g)->size.y = b.y * yf;
return TRUE;
}
/*
* recursively compute cluster ht requirements. assumes GD_ht1(subg) and ht2
* are computed from primitive nodes only. updates ht1 and ht2 to reflect
* cluster nesting and labels. also maintains global rank ht1 and ht2.
*/
static void
clust_ht(Agraph_t *g)
{
int c, ht1, ht2;
graph_t *subg;
rank_t *rank = GD_rank(g->root);
ht1 = GD_ht1(g);
ht2 = GD_ht2(g);
/* account for sub-clusters */
for (c = 1; c <= GD_n_cluster(g); c++) {
subg = GD_clust(g)[c];
clust_ht(subg);
if (GD_maxrank(subg) == GD_maxrank(g))
ht1 = MAX(ht1,GD_ht1(subg) + CL_OFFSET);
if (GD_minrank(subg) == GD_minrank(g))
ht2 = MAX(ht2,GD_ht2(subg) + CL_OFFSET);
}
/* account for a possible cluster label in clusters */
/* room for root graph label is handled in dotneato_postprocess */
if (g != g->root) {
ht1 += GD_border(g)[BOTTOM_IX].y;
ht2 += GD_border(g)[TOP_IX].y;
}
GD_ht1(g) = ht1;
GD_ht2(g) = ht2;
/* update the global ranks */
if (g != g->root) {
rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2,ht2);
rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1,ht1);
}
}
|