pack.c   [plain text]


/*
    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

/* Module for packing disconnected graphs together.
 * Based on "Disconnected Graph Layout and the Polyomino Packing Approach", 
 * K. Freivalds et al., GD0'01, LNCS 2265, pp. 378-391.
 */

#include <stdlib.h>
#include <pointset.h>
#include <neatoprocs.h>
#include <pack.h>
#include <math.h>

#define C 100   /* Max. avg. polyomino size */

#define MOVEPT(p) ((p).x += dx, (p).y += dy)
#define GRID(x,s) (((x) + ((s)-1)) / (s))
#define CELL(p,s) ((p).x = (p).x/(s), (p).y = ((p).y/(s)))
#define SGN(a)    (((a)<0)? -1 : 1)

typedef struct {
  Agraph_t*  graph;   /* related graph */
  int        perim;   /* half size of bounding rectangle perimeter */
  point*     cells;   /* cells in covering polyomino */
  int        nc;      /* no. of cells */
  int        index;   /* index in original array */
  
} ginfo;

/* computeStep:
 * Compute grid step size. This is a root of the
 * quadratic equation al^2 +bl + c, where a, b and
 * c are defined below.
 */
static int
computeStep (int ng, Agraph_t** gs, int margin)
{
  double l1, l2;
  double a, b, c, d, r;
  double W, H;          /* width and height of graph, with margin */
  Agraph_t* g;
  int       i;

  a = C*ng - 1;
  c = 0;
  b = 0;
  for (i=0;i<ng;i++) {
    g = gs[i];
    W = GD_bb(g).UR.x - GD_bb(g).LL.x + 2*margin;
    H = GD_bb(g).UR.y - GD_bb(g).LL.y + 2*margin;
    b -= (W + H);
    c -= (W * H);
  }
  d = b*b - 4.0*a*c;
  if (d < 0) {
    agerr (AGERR, "libpack: disc = %f ( < 0)\n", d);
    return -1;
  }
  r = sqrt (d);
  l1 = (-b + r)/(2*a);
  l2 = (-b - r)/(2*a);
  if (Verbose > 2) {
    fprintf (stderr, "Packing: compute grid size\n");
    fprintf (stderr, "a %f b %f c %f d %f r %f\n", a, b, c,d,r);
    fprintf (stderr, "root %d (%f) %d (%f)\n", (int)l1, l1, (int)l2, l2);
    fprintf (stderr, " r1 %f r2 %f\n", a*l1*l1 + b*l1 + c, a*l2*l2 + b*l2 +c);
  }
  
  return (int)l1;
}

/* cmpf;
 * Comparison function for polyominoes.
 * Size is determined by perimeter.
 */
static int
cmpf (const void* X, const void* Y)
{
  ginfo* x = *(ginfo**)X;
  ginfo* y = *(ginfo**)Y;
    /* flip order to get descending array */
  return (y->perim - x->perim);  
}

/* fillLine:
 * Mark cells crossed by line from cell p to cell q.
 * Bresenham's algorithm, from Graphics Gems I, pp. 99-100.
 */
/* static  */
void
fillLine (point p, point q, PointSet* ps)
{
  int x1 = p.x;
  int y1 = p.y;
  int x2 = q.x;
  int y2 = q.y;
  int d, x, y, ax, ay, sx, sy, dx, dy;

  dx = x2-x1;  ax = ABS(dx)<<1;  sx = SGN(dx);
  dy = y2-y1;  ay = ABS(dy)<<1;  sy = SGN(dy);

/* fprintf (stderr, "fillLine %d %d - %d %d\n", x1,y1,x2,y2); */
  x = x1;
  y = y1;
  if (ax>ay) {                /* x dominant */
    d = ay-(ax>>1);
    for (;;) {
/* fprintf (stderr, "  addPS %d %d\n", x,y); */
      addPS(ps,x, y);
      if (x==x2) return;
      if (d>=0) {
        y += sy;
        d -= ax;
      }
      x += sx;
      d += ay;
    }
  }
  else {                      /* y dominant */
    d = ax-(ay>>1);
    for (;;) {
/* fprintf (stderr, "  addPS %d %d\n", x,y); */
      addPS(ps, x, y);
      if (y==y2) return;
      if (d>=0) {
        x += sx;
        d -= ay;
      }
      y += sy;
      d += ax;
    }
  }
}

/* fillEdge:
 * It appears that spline_edges always have the start point at the
 * beginning and the end point at the end.
 */
static void
fillEdge (Agedge_t* e, point pt, PointSet* ps, int dx, int dy, 
          int ssize, int doS)
{
  int       j, k;
  bezier    bz;
  point     hpt;
  Agnode_t* h;

    /* If doS is false or the edge has not splines, use line segment */
  if (!doS || !ED_spl(e)) {
    h = e->head;
    hpt = coord(h);
    MOVEPT(hpt);
    CELL(hpt,ssize);
    fillLine (pt,hpt,ps);
    return;
  }

  for (j = 0; j < ED_spl(e)->size; j++) {
    bz = ED_spl(e)->list[j];
    if (bz.sflag){
      pt = bz.sp;
      hpt = bz.list[0];
      k = 1;
    }
    else {
      pt = bz.list[0];
      hpt = bz.list[1];
      k = 2;
    }
    MOVEPT(pt); CELL(pt,ssize);
    MOVEPT(hpt); CELL(hpt,ssize);
    fillLine (pt,hpt,ps);

    for (; k < bz.size; k++) {
      pt = hpt;
      hpt = bz.list[k];
      MOVEPT(hpt); CELL(hpt,ssize);
      fillLine (pt,hpt,ps);
    }

    if (bz.eflag) {
      pt = hpt;
      hpt = bz.ep;
      MOVEPT(hpt); CELL(hpt,ssize);
      fillLine (pt,hpt,ps);
    }
  }
  
}

/* genBox:
 * Generate polyomino info from graph using the bounding box of
 * the graph.
 */
static void
genBox (Agraph_t* g, ginfo* info, int ssize, int margin, point center)
{
  PointSet* ps;
  int W, H;
  point UR, LL;
  box bb = GD_bb(g);
  int x,y;

  ps = newPS();

  LL.x = center.x - margin;
  LL.y = center.y - margin;
  UR.x = center.x + bb.UR.x - bb.LL.x + margin;
  UR.y = center.y + bb.UR.y - bb.LL.y + margin;
  CELL (LL,ssize);
  CELL (UR,ssize);

  for (x = LL.x; x <= UR.x; x++)
    for (y = LL.y; y <= UR.y; y++)
      addPS(ps,x,y);

  info->graph = g;
  info->cells = pointsOf(ps);
  info->nc = sizeOf(ps);
  W = GRID(bb.UR.x - bb.LL.x + 2*margin,ssize);
  H = GRID(bb.UR.y - bb.LL.y + 2*margin,ssize);
  info->perim = W + H;

  if (Verbose > 2) {
    int i;
    fprintf (stderr, "%s no. cells %d W %d H %d\n", g->name, info->nc, W, H);
    for (i = 0; i < info->nc; i++)
      fprintf (stderr, "  %d %d cell\n", info->cells[i].x, info->cells[i].y);
  }

  freePS(ps);
}

/* genPoly:
 * Generate polyomino info from graph.
 * We add all cells covered partially by the bounding box of the 
 * node. If doSplines is true and an edge has a spline, we use the 
 * polyline determined by the control point. Otherwise,
 * we use each cell crossed by a straight edge between the head and tail.
 * If mode = l_clust, we use the graph's GD_clust array to treat the
 * top level clusters like large nodes.
 * Returns 0 if okay.
 */
static int
genPoly (Agraph_t* root, Agraph_t* g, ginfo* info, 
         int ssize, pack_info* pinfo, point center)
{
  PointSet* ps;
  int W, H;
  point LL, UR;
  point pt, s2;
  Agraph_t* eg;   /* graph containing edges */
  Agnode_t* n;
  Agedge_t* e;
  int       x,y;
  int       dx,dy;
  graph_t*  subg;
  int       margin = pinfo->margin;
  int       doSplines = pinfo->doSplines;
  box       bb;

  if (root) eg = root;
  else eg = g;

  ps = newPS();
  dx = center.x - GD_bb(g).LL.x;
  dy = center.y - GD_bb(g).LL.y;

  if (pinfo->mode == l_clust) {
	int    i;
    void** alg;

    /* backup the alg data */
    alg = N_GNEW(agnnodes(g), void*);
    for (i = 0, n = agfstnode(g); n; n = agnxtnode(g,n)) {
        alg[i++] = n->u.alg;
        n->u.alg = 0;
    }

    /* do bbox of top clusters */
    for (i = 1; i <= GD_n_cluster(g); i++) {
      subg = GD_clust(g)[i];
      bb = GD_bb(subg);
      if ((bb.UR.x > bb.LL.x) && (bb.UR.y > bb.LL.y)) {
        MOVEPT(bb.LL);
        MOVEPT(bb.UR);
        bb.LL.x -= margin;
        bb.LL.y -= margin;
        bb.UR.x += margin;
        bb.UR.y += margin;
        CELL (bb.LL,ssize);
        CELL (bb.UR,ssize);

        for (x = bb.LL.x; x <= bb.UR.x; x++)
          for (y = bb.LL.y; y <= bb.UR.y; y++)
            addPS(ps,x,y);

        /* note which nodes are in clusters */
        for (n = agfstnode(subg); n; n = agnxtnode(subg,n))
          ND_clust(n) = subg;
      }
    }

      /* now do remaining nodes and edges */
    for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
      pt = coord(n);
      MOVEPT(pt);
      if (!ND_clust(n)) {  /* n is not in a top-level cluster */
        s2.x = margin + ND_xsize(n)/2; s2.y = margin + ND_ysize(n)/2;
        LL = sub_points(pt,s2);
        UR = add_points(pt,s2);
        CELL (LL,ssize);
        CELL (UR,ssize);

        for (x = LL.x; x <= UR.x; x++)
          for (y = LL.y; y <= UR.y; y++)
            addPS(ps,x,y);

        CELL(pt,ssize);
        for (e = agfstout(eg,n); e; e = agnxtout(eg,e)) {
          fillEdge (e, pt, ps, dx, dy, ssize, doSplines);
        }
      }
      else {
        CELL(pt,ssize);
        for (e = agfstout(eg,n); e; e = agnxtout(eg,e)) {
          if (ND_clust(n) == ND_clust(e->head)) continue;
          fillEdge (e, pt, ps, dx, dy, ssize, doSplines);
        }
      }
    }
 
    /* restore the alg data */
    for (i = 0, n = agfstnode(g); n; n = agnxtnode(g,n)) {
        n->u.alg = alg[i++];
    }
    free(alg);

  }
  else for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
    pt = coord(n);
    MOVEPT(pt);
    s2.x = margin + ND_xsize(n)/2; s2.y = margin + ND_ysize(n)/2;
    LL = sub_points(pt,s2);
    UR = add_points(pt,s2);
    CELL (LL,ssize);
    CELL (UR,ssize);

    for (x = LL.x; x <= UR.x; x++)
      for (y = LL.y; y <= UR.y; y++)
        addPS(ps,x,y);

    CELL(pt,ssize);
    for (e = agfstout(eg,n); e; e = agnxtout(eg,e)) {
      fillEdge (e, pt, ps, dx, dy, ssize, doSplines);
    }
  }

  info->graph = g;
  info->cells = pointsOf(ps);
  info->nc = sizeOf(ps);
  W = GRID(GD_bb(g).UR.x - GD_bb(g).LL.x + 2*margin,ssize);
  H = GRID(GD_bb(g).UR.y - GD_bb(g).LL.y + 2*margin,ssize);
  info->perim = W + H;

  if (Verbose > 2) {
    int i;
    fprintf (stderr, "%s no. cells %d W %d H %d\n", g->name, info->nc, W, H);
    for (i = 0; i < info->nc; i++)
      fprintf (stderr, "  %d %d cell\n", info->cells[i].x, info->cells[i].y);
  }

  freePS(ps);
  return 0;
}

/* fits:
 * Check if polyomino fits at given point.
 * If so, add cells to pointset, store point in place and return true.
 */
static int
fits (int x, int y, ginfo* info, PointSet* ps, point* place, int step)
{
  point* cells = info->cells;
  int    n = info->nc;
  point  cell;
  int    i;
  point  LL;

  for (i=0; i < n; i++) {
    cell = *cells;
    cell.x += x;
    cell.y += y;
    if (inPS(ps,cell)) return 0;
    cells++;
  }

  LL = GD_bb(info->graph).LL;
  place->x = step*x - LL.x;
  place->y = step*y - LL.y;

  cells = info->cells;
  for (i=0; i < n; i++) {
    cell = *cells;
    cell.x += x;
    cell.y += y;
    insertPS(ps,cell);
    cells++;
  }

  if (Verbose >= 2)
    fprintf (stderr, "cc (%d cells) at (%d,%d)\n", n, place->x, place->y);
  return 1;
}

/* placeFixed:
 * Position fixed graph. Store final translation and
 * fill polyomino set. Note that polyomino set for the
 * graph is constructed where it will be.
 */
static void
placeFixed (ginfo* info, PointSet* ps, point* place, point center)
{
  point* cells = info->cells;
  int    n = info->nc;
  int    i;

  place->x = - center.x;
  place->y = - center.y;

  for (i=0; i < n; i++) {
    insertPS(ps,*cells++);
  }

  if (Verbose >= 2)
    fprintf (stderr, "cc (%d cells) at (%d,%d)\n", n, place->x, place->y);
}

/* placeGraph:
 * Search for points on concentric "circles" out
 * from the origin. Check if polyomino can be placed
 * with bounding box origin at point.
 * First graph (i == 0) is centered on the origin if possible.
 */
static void
placeGraph (int i, ginfo* info, PointSet* ps, point* place, int step, int margin)
{
  int  x,y;
  int  W,H;
  int  bnd;

  if (i == 0) {
    Agraph_t*  g = info->graph;
    W = GRID(GD_bb(g).UR.x - GD_bb(g).LL.x + 2*margin,step);
    H = GRID(GD_bb(g).UR.y - GD_bb(g).LL.y + 2*margin,step);
    if (fits(-W/2,-H/2,info,ps,place,step)) return;
  }

  if (fits(0,0,info,ps,place,step)) return;
  W = GD_bb(info->graph).UR.x - GD_bb(info->graph).LL.x;
  H = GD_bb(info->graph).UR.y - GD_bb(info->graph).LL.y;
  if (W >= H) {
    for (bnd = 1; ; bnd++) {
      x = 0;
      y = -bnd;
      for (; x < bnd; x++)
        if (fits(x,y,info,ps,place,step)) return;
      for (; y < bnd; y++)
        if (fits(x,y,info,ps,place,step)) return;
      for (; x > -bnd; x--)
        if (fits(x,y,info,ps,place,step)) return;
      for (; y > -bnd; y--)
        if (fits(x,y,info,ps,place,step)) return;
      for (; x < 0; x++)
        if (fits(x,y,info,ps,place,step)) return;
    }
  }
  else {
    for (bnd = 1; ; bnd++) {
      y = 0;
      x = -bnd;
      for (; y > -bnd; y--)
        if (fits(x,y,info,ps,place,step)) return;
      for (; x < bnd; x++)
        if (fits(x,y,info,ps,place,step)) return;
      for (; y < bnd; y++)
        if (fits(x,y,info,ps,place,step)) return;
      for (; x > -bnd; x--)
        if (fits(x,y,info,ps,place,step)) return;
      for (; y > 0; y--)
        if (fits(x,y,info,ps,place,step)) return;
    }
  }
}

#ifdef DEBUG
void
dumpp (ginfo* info, char* pfx)
{
  point* cells = info->cells;
  int    i, c_cnt = info->nc;

  fprintf (stderr, "%s\n", pfx);
  for (i = 0; i < c_cnt; i++) {
    fprintf (stderr, "%d %d box\n", cells[i].x, cells[i].y);
  }
}
#endif

/* putGraphs:
 *  Given a collection of graphs, reposition them in the plane
 *  to not overlap but pack "nicely".
 *   ng is the number of graphs
 *   gs is a pointer to an array of graph pointers
 *   root gives the graph containing the edges; if null, the function
 *     looks in each graph in gs for its edges
 *   pinfo->margin gives the amount of extra space left around nodes in points
 *   If pinfo->doSplines is true, use edge splines, if computed,
 *     in calculating polyomino.
 *   pinfo->mode specifies the packing granularity and technique:
 *     l_node : pack at the node/cluster level
 *     l_graph : pack at the bounding box level
 *  Returns array of points to which graphs should be translated;
 *  the array needs to be freed;
 * Returns NULL if problem occur or if ng == 0.
 * 
 * Depends on graph fields bb, node fields pos, xsize and ysize, and
 * edge field spl.
 */
point*
putGraphs (int ng, Agraph_t** gs, Agraph_t* root, pack_info* pinfo)
{
  int      stepSize;
  ginfo*   info;
  ginfo**  sinfo;
  point*   places;
  Dict_t*  ps;
  int      i;
  boolean* fixed = pinfo->fixed;
  int      fixed_cnt = 0;      
  box      fixed_bb;
  point    center;

  if (ng <= 0) return 0;

    /* update bounding box info for each graph */
    /* If fixed, compute bbox of fixed graphs */
  for (i = 0; i < ng; i++) {
    Agraph_t* g = gs[i];
    neato_compute_bb (g);
    if (fixed && fixed[i]) {
      if (fixed_cnt) {
        box  bb = GD_bb(g);
        fixed_bb.LL.x = MIN(bb.LL.x, fixed_bb.LL.x);
        fixed_bb.LL.y = MIN(bb.LL.y, fixed_bb.LL.y);
        fixed_bb.UR.x = MAX(bb.UR.x, fixed_bb.UR.x);
        fixed_bb.UR.y = MAX(bb.UR.y, fixed_bb.UR.y);
      }
      else fixed_bb = GD_bb(g);
      fixed_cnt++;
    }
    if (Verbose > 2) {
      fprintf (stderr, "bb[%s] %d %d %d %d\n", g->name, GD_bb(g).LL.x,
        GD_bb(g).LL.y, GD_bb(g).UR.x, GD_bb(g).UR.y);
    }
  }

    /* calculate grid size */
  stepSize = computeStep (ng, gs, pinfo->margin);
  if (stepSize < 0) return 0;
  
    /* generate polyomino cover for the graphs */
  if (fixed) {
    center.x = (fixed_bb.LL.x + fixed_bb.UR.x)/2;
    center.y = (fixed_bb.LL.y + fixed_bb.UR.y)/2;
  }
  else center.x = center.y = 0;
  info = N_NEW(ng, ginfo);
  for (i=0; i < ng; i++) {
    info[i].index = i;
    if (pinfo->mode == l_graph)
      genBox (gs[i], info+i, stepSize, pinfo->margin, center);
    else if (genPoly (root, gs[i], info+i, stepSize, pinfo, center)) {
      return 0;
    }
  }

  /* sort */
  sinfo = N_NEW(ng, ginfo*);
  for (i=0; i < ng; i++) {
    sinfo[i] = info + i;
  }
  qsort(sinfo,ng,sizeof(ginfo*),cmpf);

  ps = newPS ();
  places = N_NEW(ng, point);
  if (fixed) {
    for (i=0; i < ng; i++) {
      if (fixed[i]) 
        placeFixed (sinfo[i], ps, places+(sinfo[i]->index), center);
    }
    for (i=0; i < ng; i++) {
      if (!fixed[i]) 
        placeGraph (i, sinfo[i], ps, places+(sinfo[i]->index), 
          stepSize, pinfo->margin);
    }
  }
  else {
    for (i=0; i < ng; i++)
      placeGraph (i, sinfo[i], ps, places+(sinfo[i]->index), 
        stepSize, pinfo->margin);
  }

  free (sinfo);
  for (i=0; i < ng; i++)
    free (info[i].cells);
  free (info);
  freePS (ps);

  if (Verbose > 1)
    for (i=0; i < ng; i++)
      fprintf (stderr, "pos[%d] %d %d\n", i, places[i].x, places[i].y);
  
  return places;
}

/* shiftEdge:
 * Translate all of the edge components by the given offset.
 */
static void
shiftEdge (Agedge_t* e, int dx, int dy)
{
  int         j,k;
  bezier      bz;

  if (ED_label(e)) MOVEPT(ED_label(e)->p);
  if (ED_head_label(e)) MOVEPT(ED_head_label(e)->p);
  if (ED_tail_label(e)) MOVEPT(ED_tail_label(e)->p);

  if (ED_spl(e) == NULL)
    return;

  for (j = 0; j < ED_spl(e)->size; j++) {
    bz = ED_spl(e)->list[j];
    for (k = 0; k < bz.size; k++)
      MOVEPT(bz.list[k]);
    if (bz.sflag)
      MOVEPT(ED_spl(e)->list[j].sp);
    if (bz.eflag)
      MOVEPT(ED_spl(e)->list[j].ep);
  }
}

/* shiftGraph:
 */
static void 
shiftGraph (Agraph_t* g, int dx, int dy)
{
  graph_t*  subg;
  box       bb = GD_bb(g);
  int       i;

  bb.LL.x += dx;
  bb.UR.x += dx;
  bb.LL.y += dy;
  bb.UR.y += dy;
  GD_bb(g) = bb;

  if (GD_label(g)) MOVEPT(GD_label(g)->p);

  for (i = 1; i <= GD_n_cluster(g); i++) {
    subg = GD_clust(g)[i];
    shiftGraph (subg, dx, dy);
  }
}

/* shiftGraphs:
 * Uses points computed from putGraphs to shift points, edges and clusters.
 * Always shifts pos.
 * If doSplines is true, assumes node position is also in coord, 
 * and edges and edge labels have been placed, so it shifts those.
 * If root is non-null, it is used to find edges.
 *
 * Depends on graph field bb, node field pos and coord, and edge field spl.
 * as well as labels in graphs and edges
 */
int 
shiftGraphs (int ng, Agraph_t** gs, point* pp, Agraph_t* root, int doSplines)
{
  int       i;
  int       dx, dy;
  double    fx, fy;
  point     p;
  Agraph_t* g;
  Agraph_t* eg;
  Agnode_t* n;
  Agedge_t* e;

  if (ng <= 0) return abs(ng);

  for (i = 0; i < ng; i++) {
    g = gs[i];
    if (root) eg = root;
    else eg = g;
    p = pp[i];
    dx = p.x;
    dy = p.y;
    fx = PS2INCH(dx);
    fy = PS2INCH(dy);

    for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
      ND_pos(n)[0] += fx;
      ND_pos(n)[1] += fy;
      if (doSplines) {
        MOVEPT(ND_coord_i(n));
        for (e = agfstout(eg,n); e; e = agnxtout(eg,e)) shiftEdge(e,dx,dy);
      }
    }
    shiftGraph (g, dx, dy);
  }

  return 0;
}

/* packGraphs:
 * Packs graphs.
 *  ng - number of graphs
 *  gs - pointer to array of graphs
 *  root - graph used to find edges
 *  info - parameters used in packing
 *  info->doSplines - if true, use already computed spline control points
 * This decides where to layout the graphs and repositions the graph's
 * position info.
 *
 * Returns 0 on success.
 */
int 
packGraphs (int ng, Agraph_t** gs, Agraph_t* root, pack_info* info)
{
  int ret;
  point*  pp = putGraphs (ng, gs, root, info);
  
  if (!pp) return 1;
  ret = shiftGraphs(ng, gs, pp, root, info->doSplines);
  free (pp);
  return ret;
}

/* packSubgraphs:
 *  Packs subgraphs of given root graph, then recalculates root's bounding
 * box.
 * Note that it does not recompute subgraph bounding boxes.
 * Cluster bounding boxes are recomputed in shiftGraphs.
 */
int 
packSubgraphs (int ng, Agraph_t** gs, Agraph_t* root, pack_info* info)
{
  int ret;

  ret = packGraphs (ng,gs,root,info);
  if (ret == 0) neato_compute_bb (root);
  return ret;
}

/* getPackMode;
 * Return pack_mode of graph using "packmode" attribute.
 * If not defined, return dflt
 */
pack_mode 
getPackMode (Agraph_t* g, pack_mode dflt)
{
  char*     p = agget (g, "packmode");
  pack_mode mode = dflt;

  if (p && *p) {
    switch (*p) {
#ifdef NOT_IMPLEMENTED
    case 'b' :
      if (streq(p,"bisect")) mode = l_bisect; 
      break;
#endif
    case 'c' :
      if (streq(p,"cluster")) mode = l_clust; 
      break;
    case 'g' :
      if (streq(p,"graph")) mode = l_graph; 
      break;
#ifdef NOT_IMPLEMENTED
    case 'h' :
      if (streq(p,"hull")) mode = l_hull; 
      break;
#endif
    case 'n' :
      if (streq(p,"node")) mode = l_node; 
      break;
#ifdef NOT_IMPLEMENTED
    case 't' :
      if (streq(p,"tile")) mode = l_tile; 
      break;
#endif
    }
  }
  return mode;
}

/* getPack;
 * Return "pack" attribute of g.
 * If not defined or negative, return not_def.
 * If defined but not specified, return dflt.
 */
int 
getPack (Agraph_t* g, int not_def, int dflt)
{
  char *p;
  int i;
  int v = not_def;

  if ((p = agget(g,"pack"))) {
    if ((sscanf(p,"%d",&i) == 1) && (i >= 0)) v = i;
    else if ((*p == 't') || (*p == 'T')) v = dflt;
  }

  return v;
}