+/* $Id$Revision: */
+/* vim:set shiftwidth=4 ts=8: */
+
+/*************************************************************************
+ * Copyright (c) 2011 AT&T Intellectual Property
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: See CVS logs. Details at http://www.graphviz.org/
+ *************************************************************************/
+
#include <stdio.h>
#include <stdlib.h>
#include <patchwork.h>
+#include <tree_map.h>
#include "render.h"
-extern void patchwork_init_graph(graph_t * g);
-
-#ifndef HAVE_DRAND48
-extern double drand48(void);
-#endif
-
-typedef boxf rect_t;
-
-typedef struct treenode_t {
- double area;
- rect_t r;
- struct treenode_t *leftchild, *rightsib;
- union {Agraph_t *subg; Agnode_t *n;} u;
- int kind;
-} treenode_t;
-
-#define BT 101
-#define LR 202
-
-typedef struct rlist_s {
- treenode_t *elt;
- int n, extent;
- double sum;
-} rlist_t;
-
-static treenode_t *newtreenode(treenode_t **first, treenode_t **prev)
-{
- treenode_t *p;
- p = NEW(treenode_t);
- if (!*first) *first = p;
- if (*prev) (*prev)->rightsib = p;
- *prev = p;
- return p;
-}
-
-/* return list of treenodes */
-static treenode_t *treebuilder(Agraph_t *g)
+typedef struct treenode_t treenode_t;
+struct treenode_t {
+ double area;
+ rectangle r;
+ treenode_t *leftchild, *rightsib;
+ union {
+ Agraph_t *subg;
+ Agnode_t *n;
+ } u;
+ int kind;
+ int n_children;
+};
+
+/* mkTreeNode:
+ */
+static treenode_t* mkTreeNode (Agnode_t* n, attrsym_t* ap)
{
- int i;
- treenode_t *first = 0;
- treenode_t *prev = 0;
- treenode_t *p;
- Agraph_t *subg;
- Agnode_t *n;
+ treenode_t *p = NEW(treenode_t);
- for (i = 1; i <= GD_n_cluster(g); i++) {
- subg = GD_clust(g)[i];
- if (agnnodes(subg) == 0) continue;
- p = newtreenode(&first,&prev);
-#ifndef WITH_CGRAPH
- p->kind = AGGRAPH;
-#else
- p->kind = AGRAPH;
-#endif
- p->u.subg = subg;
- p->leftchild = treebuilder(subg);
- }
+ p->area = late_double (n, ap, 1.0, 0);
+ if (p->area == 0) p->area = 1.0;
+ p->kind = AGNODE;
+ p->u.n = n;
- for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
- char *val;
- if (SPARENT(n)) continue;
- p = newtreenode(&first,&prev);
- val = agget(n,"area");
- if (val) p->area = atof(val);
- if (p->area <= 0.0) p->area = 1.0;
- p->kind = AGNODE;
- p->u.n = n;
- SPARENT(n) = g;
- }
- return first;
+ return p;
}
-/* get recursive area requirements */
-static double sizeit(treenode_t *tree)
+#define INSERT(cp) if(!first) first=cp; if(prev) prev->rightsib=cp; prev=cp;
+
+/* mkTree:
+ * Recursively build tree from graph
+ * Pre-condition: agnnodes(g) != 0
+ */
+static treenode_t *mkTree (Agraph_t * g, attrsym_t* ap)
{
- treenode_t *p;
- double mysize = 0.0;
+ treenode_t *p = NEW(treenode_t);
+ Agraph_t *subg;
+ Agnode_t *n;
+ treenode_t *cp;
+ treenode_t *first = 0;
+ treenode_t *prev = 0;
+ int i, n_children = 0;
+ double area = 0;
+
+ p->kind = AGRAPH;
+ p->u.subg = g;
+
+ for (i = 1; i <= GD_n_cluster(g); i++) {
+ subg = GD_clust(g)[i];
+ if (agnnodes(subg) == 0)
+ continue;
+ cp = mkTree (subg, ap);
+ n_children++;
+ area += cp->area;
+ INSERT(cp);
+ }
- if (tree->leftchild) {
- for (p = tree->leftchild; p; p = p->rightsib) mysize += sizeit(p);
- tree->area = mysize;
- }
- return tree->area;
-}
+ for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
+ if (SPARENT(n))
+ continue;
+ cp = mkTreeNode (n, ap);
+ n_children++;
+ area += cp->area;
+ INSERT(cp);
+ SPARENT(n) = g;
+ }
-static rect_t mkrectangle(double x0, double y0, double x1, double y1)
-{
- rect_t rv;
- rv.LL.x = x0; rv.LL.y = y0; rv.UR.x = x1; rv.UR.y = y1;
- return rv;
-}
+ p->n_children = n_children;
+ p->area = area;
+ p->leftchild = first;
-static double weight(treenode_t *tree, treenode_t *split)
-{
- double rv = 0.0;
- while (tree && (tree!=split)) {rv = rv + tree->area; tree = tree->rightsib;}
- return rv;
+ return p;
}
-static void layout(treenode_t *tree, treenode_t *split, rect_t r)
+static int nodecmp (treenode_t** p0, treenode_t** p1)
{
- double size, halfsize, w1, tmp, width, height;
- rect_t r1,r2;
- treenode_t *p;
-
- if (!tree) return;
- if (!tree->rightsib || (tree->rightsib==split)) {tree->r = r; layout(tree->leftchild,0,r); return;}
- size = weight(tree,split);
- halfsize = size / 2.0;
- w1 = 0; tmp = 0;
-
- for (p = tree; p && (p != split); p = p->rightsib) {
- tmp = w1 + p->area;
- if (abs(halfsize - tmp) > abs(halfsize- w1))
- break;
- w1 = tmp;
- }
+ double diff = (*p0)->area - (*p1)->area;
- width = r.UR.x - r.LL.x;
- height = r.UR.y - r.LL.y;
- if (width > height) {
- r1 = mkrectangle(r.LL.x,r.LL.y,r.LL.x + width * w1 / size, r.UR.y);
- r2 = mkrectangle(r1.UR.x,r.LL.y,r.UR.x,r.UR.y);
- }
- else {
- /* this was bottom to top - but we want top to bottom layout
- r1 = mkrectangle(r.LL.x,r.LL.y,r.UR.x,r.LL.y + height * w1 / size);
- r2 = mkrectangle(r.LL.x,r1.UR.y,r.UR.x,r.UR.y);
- */
- r1 = mkrectangle(r.LL.x,r.UR.y - height * w1 / size,r.UR.x,r.UR.y);
- r2 = mkrectangle(r.LL.x,r.LL.y,r.UR.x,r1.LL.y);
- }
- layout(tree,p,r1);
- layout(p,split,r2);
+ if (diff < 0) return 1;
+ else if (diff > 0) return -1;
+ else return 0;
}
-static void printer(treenode_t *tree)
+static void layoutTree(treenode_t * tree)
{
- static int onetime = 1;
- treenode_t *p;
+ rectangle *recs;
+ treenode_t** nodes;
+ double* areas_sorted;
+ int i, nc;
+ treenode_t* cp;
+
+ if (tree->kind == AGNODE) return;
+
+ nc = tree->n_children;
+ nodes = N_NEW(nc, treenode_t*);
+ cp = tree->leftchild;
+ for (i = 0; i < nc; i++) {
+ nodes[i] = cp;
+ cp = cp->rightsib;
+ }
- if (onetime) { fprintf(stderr,"%%!PS\n"); onetime=0;}
- fprintf(stderr,"newpath %.3lf %.3lf moveto %.3lf %.3lf lineto %.3lf %.3lf lineto %.3lf %.3lf lineto closepath \n",
- tree->r.LL.x,tree->r.LL.y,
- tree->r.UR.x,tree->r.LL.y,
- tree->r.UR.x,tree->r.UR.y,
- tree->r.LL.x,tree->r.UR.y);
- if (tree->leftchild) {
- fprintf(stderr,"stroke \n");
- for (p = tree->leftchild; p; p = p->rightsib) printer(p);
- }
- else {
- fprintf(stderr,"gsave %.3lf 1.0 1.0 sethsbcolor fill grestore stroke\n",drand48());
- }
-}
+ qsort (nodes, nc, sizeof(treenode_t*), (qsort_cmpf)nodecmp);
+ areas_sorted = N_NEW(nc,double);
+ for (i = 0; i < nc; i++) {
+ areas_sorted[i] = nodes[i]->area;
+ }
+ recs = tree_map(nc, areas_sorted, tree->r);
+ if (Verbose)
+ fprintf (stderr, "rec %f %f %f %f\n", tree->r.x[0], tree->r.x[1], tree->r.size[0], tree->r.size[1]);
+ for (i = 0; i < nc; i++) {
+ nodes[i]->r = recs[i];
+ if (Verbose)
+ fprintf (stderr, "%f - %f %f %f %f = %f (%f %f %f %f)\n", areas_sorted[i],
+ recs[i].x[0]-recs[i].size[0]*0.5, recs[i].x[1]-recs[i].size[1]*0.5,
+ 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],
+ recs[i].x[0], recs[i].x[1], recs[i].size[0], recs[i].size[1]);
-static void finishNode (node_t* n)
-{
- char* str = strdup_and_subst_obj(NODENAME_ESC, (void*)n);
- ND_shape(n) = bind_shape("box", n);
- ND_label(n) = make_label((void*)n, str, LT_NONE,
- late_double(n, N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE),
- late_nnstring(n, N_fontname, DEFAULT_FONTNAME),
- late_nnstring(n, N_fontcolor, DEFAULT_COLOR));
- ND_shape(n)->fns->initfn(n);
+ }
+ free (nodes);
+ free (areas_sorted);
+ free (recs);
+
+ cp = tree->leftchild;
+ for (i = 0; i < nc; i++) {
+ if (cp->kind == AGRAPH)
+ layoutTree (cp);
+ cp = cp->rightsib;
+ }
}
-static rect_t walker(treenode_t *tree)
+static void finishNode(node_t * n)
{
- treenode_t *p;
- Agnode_t *n;
- pointf center;
- rect_t r, rr;
-
- switch(tree->kind) {
+ char buf [40];
+ if (N_fontsize) {
#ifndef WITH_CGRAPH
- case AGGRAPH:
+ char* str = agxget(n, N_fontsize->index);
#else
- case AGRAPH:
+ char* str = agxget(n, N_fontsize);
#endif
- break;
- case AGNODE:
- rr = tree->r;
- center.x = (tree->r.UR.x + tree->r.LL.x) / 2.0;
- center.y = (tree->r.UR.y + tree->r.LL.y) / 2.0;
+ if (*str == '\0') {
+ sprintf (buf, "%.03f", ND_ht(n)*0.7);
+#ifndef WITH_CGRAPH
+ agxset(n, N_fontsize->index, buf);
+#else /* WITH_CGRAPH */
+ agxset(n, N_fontsize, buf);
+#endif /* WITH_CGRAPH */
+ }
+ }
+ common_init_node (n);
+}
- n = tree->u.n;
- ND_coord(n) = center;
- ND_height(n) = PS2INCH(tree->r.UR.y - tree->r.LL.y);
- ND_width(n) = PS2INCH(tree->r.UR.x - tree->r.LL.x);
- gv_nodesize(n,GD_flip(agraphof(n)));
- finishNode (n);
- /*fprintf(stderr,"%s coord %.5g %.5g ht %d width %d\n",
- agnameof(n), ND_coord(n).x, ND_coord(n).y, ND_ht(n),
- ND_rw(n)+ND_lw(n));*/
- break;
- default: abort();
+static void walkTree(treenode_t * tree)
+{
+ treenode_t *p;
+ Agnode_t *n;
+ pointf center;
+ rectangle rr;
+ boxf r;
+ double x0, y0, wd, ht;
+
+ if (tree->kind == AGRAPH) {
+ for (p = tree->leftchild; p; p = p->rightsib)
+ walkTree (p);
+ x0 = tree->r.x[0];
+ y0 = tree->r.x[1];
+ wd = tree->r.size[0];
+ ht = tree->r.size[1];
+ r.LL.x = x0 - wd/2.0;
+ r.LL.y = y0 - ht/2.0;
+ r.UR.x = r.LL.x + wd;
+ r.UR.y = r.LL.y + ht;
+ GD_bb(tree->u.subg) = r;
}
- if ((p = tree->leftchild)) {
- rr = walker (p);
- p = p->rightsib;
- for (; p; p = p->rightsib) {
- r = walker(p);
- EXPANDBB(rr,r);
- }
- rr.LL.x -= 2./72.;
- rr.LL.y -= 2./72.;
- rr.UR.x += 2./72.;
- rr.UR.y += 2./72.;
- GD_bb(tree->u.subg) = rr;
+ else {
+ rr = tree->r;
+ center.x = rr.x[0];
+ center.y = rr.x[1];
+
+ n = tree->u.n;
+ ND_coord(n) = center;
+ ND_width(n) = PS2INCH(rr.size[0]);
+ ND_height(n) = PS2INCH(rr.size[1]);
+ gv_nodesize(n, GD_flip(agraphof(n)));
+ finishNode(n);
+ if (Verbose)
+ fprintf(stderr,"%s coord %.5g %.5g ht %f width %f\n",
+ agnameof(n), ND_coord(n).x, ND_coord(n).y, ND_ht(n), ND_xsize(n));
}
- return rr;
}
-#ifdef PWDRIVER
-int main()
+/* freeTree:
+ */
+static void freeTree (treenode_t* tp)
{
- static treenode_t root;
- rect_t r = {{0.0, 0.0}, {100.0, 100.0}};
- Agraph_t *g;
+ treenode_t* cp = tp->leftchild;
+ int i, nc = tp->n_children;
- aginit();
- g = agread(stdin);
-#ifndef WITH_CGRAPH
- root.kind = AGGRAPH;
-#else
- root.kind = AGRAPH;
-#endif
- root.leftchild = treebuilder(g);
- sizeit(&root);
- /*layouter(&root,LR,r);*/
- printer(&root);
- return 0;
+ for (i = 0; i < nc; i++) {
+ freeTree (cp);
+ cp = cp->rightsib;
+ }
+ free (tp);
}
-#endif
-void patchwork_layout(Agraph_t *g)
+/* patchworkLayout:
+ */
+void patchworkLayout(Agraph_t * g)
{
- static treenode_t root;
- rect_t r = {{0.0, 0.0}, {100.0, 100.0}};
- patchwork_init_graph(g);
-#ifndef WITH_CGRAPH
- root.kind = AGGRAPH;
-#else
- root.kind = AGRAPH;
-#endif
- root.leftchild = treebuilder(g);
- root.u.subg = g;
- sizeit(&root);
- layout(&root,0,r);
- /* printer(&root); */
- walker(&root);
- /* compute_bb(g); */
- /* fprintf(stderr,"bb %d %d %d %d\n", */
- /* GD_bb(g).LL.x, GD_bb(g).LL.y, GD_bb(g).UR.x, GD_bb(g).UR.y); */
- dotneato_postprocess(g);
+ treenode_t* root;
+ attrsym_t * ap = agfindnodeattr(g, "size");
+ double total;
+
+ root = mkTree (g,ap);
+ total = root->area;
+ root->r = rectangle_new(0, 0, sqrt(total + 0.1), sqrt(total + 0.1));
+ layoutTree(root);
+ walkTree(root);
+ freeTree (root);
}
--- /dev/null
+/* $Id$Revision: */
+/* vim:set shiftwidth=4 ts=8: */
+
+/*************************************************************************
+ * Copyright (c) 2011 AT&T Intellectual Property
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: See CVS logs. Details at http://www.graphviz.org/
+ *************************************************************************/
+
+#include "render.h"
+#include "tree_map.h"
+
+static void squarify(int n, real *area, rectangle *recs, int nadded, real maxarea, real minarea, real totalarea,
+ real asp, rectangle fillrec){
+ /* add a list of area in fillrec using squarified treemap alg.
+ n: number of items to add
+ area: area of these items, Sum to 1 (?).
+ nadded: number of items already added
+ maxarea: maxarea of already added items
+ minarea: min areas of already added items
+ asp: current worst aspect ratio of the already added items so far
+ fillrec: the rectangle to be filled in.
+ */
+ real w = MIN(fillrec.size[0], fillrec.size[1]);
+ int i;
+
+ if (n <= 0) return;
+
+ if (Verbose) {
+ fprintf(stderr, "trying to add to rect {%f +/- %f, %f +/- %f}\n",fillrec.x[0], fillrec.size[0], fillrec.x[1], fillrec.size[1]);
+ fprintf(stderr, "total added so far = %d\n", nadded);
+ }
+
+ if (nadded == 0){
+ nadded = 1;
+ maxarea = minarea = area[0];
+ asp = MAX(area[0]/(w*w), (w*w)/area[0]);
+ totalarea = area[0];
+ squarify(n, area, recs, nadded, maxarea, minarea, totalarea, asp, fillrec);
+ } else {
+ real newmaxarea, newminarea, s, h, maxw, minw, newasp, hh, ww, xx, yy;
+ if (nadded < n){
+ newmaxarea = MAX(maxarea, area[nadded]);
+ newminarea = MIN(minarea, area[nadded]);
+ s = totalarea + area[nadded];
+ h = s/w;
+ maxw = newmaxarea/h;
+ minw = newminarea/h;
+ newasp = MAX(h/minw, maxw/h);/* same as MAX{s^2/(w^2*newminarea), (w^2*newmaxarea)/(s^2)}*/
+ }
+ if (nadded < n && newasp <= asp){/* aspectio improved, keep adding */
+ squarify(n, area, recs, ++nadded, newmaxarea, newminarea, s, newasp, fillrec);
+ } else {
+ /* aspectio worsen if add another area, fixed the already added recs */
+ if (Verbose) fprintf(stderr,"adding %d items, total area = %f, w = %f, area/w=%f\n",nadded, totalarea, w, totalarea/w);
+ if (w == fillrec.size[0]){/* tall rec. fix the items along x direction, left to right, at top*/
+ hh = totalarea/w;
+ xx = fillrec.x[0] - fillrec.size[0]/2;
+ for (i = 0; i < nadded; i++){
+ recs[i].size[1] = hh;
+ ww = area[i]/hh;
+ recs[i].size[0] = ww;
+ recs[i].x[1] = fillrec.x[1] + 0.5*(fillrec.size[1]) - hh/2;
+ recs[i].x[0] = xx + ww/2;
+ xx += ww;
+ }
+ fillrec.x[1] -= hh/2;/* the new empty space is below the filled space */
+ fillrec.size[1] -= hh;
+ } else {/* short rec. fix along y top to bot, at left*/
+ ww = totalarea/w;
+ yy = fillrec.x[1] + fillrec.size[1]/2;
+ for (i = 0; i < nadded; i++){
+ recs[i].size[0] = ww;
+ hh = area[i]/ww;
+ recs[i].size[1] = hh;
+ recs[i].x[0] = fillrec.x[0] - 0.5*(fillrec.size[0]) + ww/2;
+ recs[i].x[1] = yy - hh/2;
+ yy -= hh;
+ }
+ fillrec.x[0] += ww/2;/* the new empty space is right of the filled space */
+ fillrec.size[0] -= ww;
+ }
+ squarify(n - nadded, area + nadded, recs + nadded, 0, 0., 0., 0., 1., fillrec);
+ }
+
+ }
+}
+
+/* tree_map:
+ * Perform a squarified treemap layout on a single level.
+ * n - number of rectangles
+ * area - area of rectangles
+ * fillred - rectangle to be filled
+ * return array of rectangles
+ */
+rectangle* tree_map(int n, real *area, rectangle fillrec){
+ /* fill a rectangle rec with n items, each item i has area[i] area. */
+ rectangle *recs;
+ int i;
+ real total = 0, minarea = 1., maxarea = 0., asp = 1, totalarea = 0;
+ int nadded = 0;
+
+ for (i = 0; i < n; i++) total += area[i];
+ /* make sure there is enough area */
+ if (total > fillrec.size[0] * fillrec.size[1] + 0.001)
+ return NULL;
+
+ recs = N_NEW(n,rectangle);
+ squarify(n, area, recs, nadded, maxarea, minarea, totalarea, asp, fillrec);
+ return recs;
+}
+
+/* rectangle_new:
+ * Create and initialize a new rectangle structure
+ */
+rectangle rectangle_new(real x, real y, real width, real height){
+ rectangle r;
+ r.x[0] = x;
+ r.x[1] = y;
+ r.size[0] = width;
+ r.size[1] = height;
+ return r;
+}