| 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 | #include <stdio.h> |
| 15 | #include <stdlib.h> |
| 16 | #include <patchwork.h> |
| 17 | #include <tree_map.h> |
| 18 | #include "render.h" |
| 19 | |
| 20 | typedef struct treenode_t treenode_t; |
| 21 | struct treenode_t { |
| 22 | double area; |
| 23 | double child_area; |
| 24 | rectangle r; |
| 25 | treenode_t *leftchild, *rightsib; |
| 26 | union { |
| 27 | Agraph_t *subg; |
| 28 | Agnode_t *n; |
| 29 | } u; |
| 30 | int kind; |
| 31 | int n_children; |
| 32 | }; |
| 33 | |
| 34 | #define DFLT_SZ 1.0 |
| 35 | #define SCALE 1000.0 /* scale up so that 1 is a reasonable default size */ |
| 36 | |
| 37 | #ifdef DEBUG |
| 38 | void dumpTree (treenode_t* r, int ind) |
| 39 | { |
| 40 | int i; |
| 41 | treenode_t* cp; |
| 42 | |
| 43 | for (i=0; i < ind; i++) fputs(" " , stderr); |
| 44 | fprintf (stderr, "%s (%f)\n" , (r->kind == AGNODE?agnameof(r->u.n):agnameof(r->u.subg)), r->area); |
| 45 | for (cp = r->leftchild; cp; cp = cp->rightsib) |
| 46 | dumpTree (cp, ind+1); |
| 47 | } |
| 48 | #endif |
| 49 | |
| 50 | /* fullArea: |
| 51 | * Allow extra area for specified inset. Assume p->kind = AGRAPH |
| 52 | * and p->child_area is set. At present, inset is additive; we |
| 53 | * may want to allow a multiplicative inset as well. |
| 54 | */ |
| 55 | static double fullArea (treenode_t* p, attrsym_t* mp) |
| 56 | { |
| 57 | double m = late_double (p->u.subg, mp, 0, 0); |
| 58 | if (m == 0) return p->child_area; |
| 59 | else { |
| 60 | double wid = (2.0*m + sqrt(p->child_area)); |
| 61 | return wid*wid; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | static double getArea (void* obj, attrsym_t* ap) |
| 66 | { |
| 67 | double area = late_double (obj, ap, DFLT_SZ, 0); |
| 68 | if (area == 0) area = DFLT_SZ; |
| 69 | area *= SCALE; |
| 70 | return area; |
| 71 | } |
| 72 | |
| 73 | /* mkTreeNode: |
| 74 | */ |
| 75 | static treenode_t* mkTreeNode (Agnode_t* n, attrsym_t* ap) |
| 76 | { |
| 77 | treenode_t *p = NEW(treenode_t); |
| 78 | |
| 79 | p->area = getArea (n, ap); |
| 80 | p->kind = AGNODE; |
| 81 | p->u.n = n; |
| 82 | |
| 83 | return p; |
| 84 | } |
| 85 | |
| 86 | #define INSERT(cp) if(!first) first=cp; if(prev) prev->rightsib=cp; prev=cp; |
| 87 | |
| 88 | /* mkTree: |
| 89 | * Recursively build tree from graph |
| 90 | * Pre-condition: agnnodes(g) != 0 |
| 91 | */ |
| 92 | static treenode_t *mkTree (Agraph_t * g, attrsym_t* gp, attrsym_t* ap, attrsym_t* mp) |
| 93 | { |
| 94 | treenode_t *p = NEW(treenode_t); |
| 95 | Agraph_t *subg; |
| 96 | Agnode_t *n; |
| 97 | treenode_t *cp; |
| 98 | treenode_t *first = 0; |
| 99 | treenode_t *prev = 0; |
| 100 | int i, n_children = 0; |
| 101 | double area = 0; |
| 102 | |
| 103 | p->kind = AGRAPH; |
| 104 | p->u.subg = g; |
| 105 | |
| 106 | for (i = 1; i <= GD_n_cluster(g); i++) { |
| 107 | subg = GD_clust(g)[i]; |
| 108 | cp = mkTree (subg, gp, ap, mp); |
| 109 | n_children++; |
| 110 | area += cp->area; |
| 111 | INSERT(cp); |
| 112 | } |
| 113 | |
| 114 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 115 | if (SPARENT(n)) |
| 116 | continue; |
| 117 | cp = mkTreeNode (n, ap); |
| 118 | n_children++; |
| 119 | area += cp->area; |
| 120 | INSERT(cp); |
| 121 | SPARENT(n) = g; |
| 122 | } |
| 123 | |
| 124 | p->n_children = n_children; |
| 125 | if (n_children) { |
| 126 | p->child_area = area; |
| 127 | p->area = fullArea (p, mp); |
| 128 | } |
| 129 | else { |
| 130 | p->area = getArea (g, gp); |
| 131 | } |
| 132 | p->leftchild = first; |
| 133 | |
| 134 | return p; |
| 135 | } |
| 136 | |
| 137 | static int nodecmp (treenode_t** p0, treenode_t** p1) |
| 138 | { |
| 139 | double diff = (*p0)->area - (*p1)->area; |
| 140 | |
| 141 | if (diff < 0) return 1; |
| 142 | else if (diff > 0) return -1; |
| 143 | else return 0; |
| 144 | } |
| 145 | |
| 146 | static void layoutTree(treenode_t * tree) |
| 147 | { |
| 148 | rectangle *recs; |
| 149 | treenode_t** nodes; |
| 150 | double* areas_sorted; |
| 151 | int i, nc; |
| 152 | treenode_t* cp; |
| 153 | |
| 154 | /* if (tree->kind == AGNODE) return; */ |
| 155 | if (tree->n_children == 0) return; |
| 156 | |
| 157 | nc = tree->n_children; |
| 158 | nodes = N_NEW(nc, treenode_t*); |
| 159 | cp = tree->leftchild; |
| 160 | for (i = 0; i < nc; i++) { |
| 161 | nodes[i] = cp; |
| 162 | cp = cp->rightsib; |
| 163 | } |
| 164 | |
| 165 | qsort (nodes, nc, sizeof(treenode_t*), (qsort_cmpf)nodecmp); |
| 166 | areas_sorted = N_NEW(nc,double); |
| 167 | for (i = 0; i < nc; i++) { |
| 168 | areas_sorted[i] = nodes[i]->area; |
| 169 | } |
| 170 | if (tree->area == tree->child_area) |
| 171 | recs = tree_map(nc, areas_sorted, tree->r); |
| 172 | else { |
| 173 | rectangle crec; |
| 174 | double disc, delta, m, h = tree->r.size[1], w = tree->r.size[0]; |
| 175 | crec.x[0] = tree->r.x[0]; |
| 176 | crec.x[1] = tree->r.x[1]; |
| 177 | delta = h - w; |
| 178 | disc = sqrt(delta*delta + 4.0*tree->child_area); |
| 179 | m = (h + w - disc)/2.0; |
| 180 | crec.size[0] = w - m; |
| 181 | crec.size[1] = h - m; |
| 182 | recs = tree_map(nc, areas_sorted, crec); |
| 183 | } |
| 184 | if (Verbose) |
| 185 | fprintf (stderr, "rec %f %f %f %f\n" , tree->r.x[0], tree->r.x[1], tree->r.size[0], tree->r.size[1]); |
| 186 | for (i = 0; i < nc; i++) { |
| 187 | nodes[i]->r = recs[i]; |
| 188 | if (Verbose) |
| 189 | fprintf (stderr, "%f - %f %f %f %f = %f (%f %f %f %f)\n" , areas_sorted[i], |
| 190 | recs[i].x[0]-recs[i].size[0]*0.5, recs[i].x[1]-recs[i].size[1]*0.5, |
| 191 | recs[i].x[0]+recs[i].size[0]*0.5, recs[i].x[1]+recs[i].size[1]*0.5, recs[i].size[0]*recs[i].size[1], |
| 192 | recs[i].x[0], recs[i].x[1], recs[i].size[0], recs[i].size[1]); |
| 193 | |
| 194 | } |
| 195 | free (nodes); |
| 196 | free (areas_sorted); |
| 197 | free (recs); |
| 198 | |
| 199 | cp = tree->leftchild; |
| 200 | for (i = 0; i < nc; i++) { |
| 201 | if (cp->kind == AGRAPH) |
| 202 | layoutTree (cp); |
| 203 | cp = cp->rightsib; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | static void finishNode(node_t * n) |
| 208 | { |
| 209 | char buf [40]; |
| 210 | if (N_fontsize) { |
| 211 | char* str = agxget(n, N_fontsize); |
| 212 | if (*str == '\0') { |
| 213 | sprintf (buf, "%.03f" , ND_ht(n)*0.7); |
| 214 | agxset(n, N_fontsize, buf); |
| 215 | } |
| 216 | } |
| 217 | common_init_node (n); |
| 218 | } |
| 219 | |
| 220 | static void walkTree(treenode_t * tree) |
| 221 | { |
| 222 | treenode_t *p; |
| 223 | Agnode_t *n; |
| 224 | pointf center; |
| 225 | rectangle rr; |
| 226 | boxf r; |
| 227 | double x0, y0, wd, ht; |
| 228 | |
| 229 | if (tree->kind == AGRAPH) { |
| 230 | for (p = tree->leftchild; p; p = p->rightsib) |
| 231 | walkTree (p); |
| 232 | x0 = tree->r.x[0]; |
| 233 | y0 = tree->r.x[1]; |
| 234 | wd = tree->r.size[0]; |
| 235 | ht = tree->r.size[1]; |
| 236 | r.LL.x = x0 - wd/2.0; |
| 237 | r.LL.y = y0 - ht/2.0; |
| 238 | r.UR.x = r.LL.x + wd; |
| 239 | r.UR.y = r.LL.y + ht; |
| 240 | GD_bb(tree->u.subg) = r; |
| 241 | } |
| 242 | else { |
| 243 | rr = tree->r; |
| 244 | center.x = rr.x[0]; |
| 245 | center.y = rr.x[1]; |
| 246 | |
| 247 | n = tree->u.n; |
| 248 | ND_coord(n) = center; |
| 249 | ND_width(n) = PS2INCH(rr.size[0]); |
| 250 | ND_height(n) = PS2INCH(rr.size[1]); |
| 251 | gv_nodesize(n, GD_flip(agraphof(n))); |
| 252 | finishNode(n); |
| 253 | if (Verbose) |
| 254 | fprintf(stderr,"%s coord %.5g %.5g ht %f width %f\n" , |
| 255 | agnameof(n), ND_coord(n).x, ND_coord(n).y, ND_ht(n), ND_xsize(n)); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | /* freeTree: |
| 260 | */ |
| 261 | static void freeTree (treenode_t* tp) |
| 262 | { |
| 263 | treenode_t* cp = tp->leftchild; |
| 264 | treenode_t* rp; |
| 265 | int i, nc = tp->n_children; |
| 266 | |
| 267 | for (i = 0; i < nc; i++) { |
| 268 | rp = cp->rightsib; |
| 269 | freeTree (cp); |
| 270 | cp = rp; |
| 271 | } |
| 272 | free (tp); |
| 273 | } |
| 274 | |
| 275 | /* patchworkLayout: |
| 276 | */ |
| 277 | void patchworkLayout(Agraph_t * g) |
| 278 | { |
| 279 | treenode_t* root; |
| 280 | attrsym_t * ap = agfindnodeattr(g, "area" ); |
| 281 | attrsym_t * gp = agfindgraphattr(g, "area" ); |
| 282 | attrsym_t * mp = agfindgraphattr(g, "inset" ); |
| 283 | double total; |
| 284 | |
| 285 | root = mkTree (g,gp,ap,mp); |
| 286 | total = root->area; |
| 287 | root->r = rectangle_new(0, 0, sqrt(total + 0.1), sqrt(total + 0.1)); |
| 288 | layoutTree(root); |
| 289 | walkTree(root); |
| 290 | freeTree (root); |
| 291 | } |
| 292 | |