/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/lib/dotgen/compound.c

Go to the documentation of this file.
00001 /* $Id: compound.c,v 1.3 2005/06/22 19:20:51 erg Exp $ $Revision: 1.3 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
00015 **********************************************************/
00016 
00017 
00018 /* Module for clipping splines to cluster boxes.
00019  */
00020 
00021 #include        "dot.h"
00022 
00023 
00024 /* midPt:
00025  * Return midpoint between two given points.
00026  */
00027 static point midPt(point p, point q)
00028 {
00029     point v;
00030 
00031     v.x = (p.x + q.x) / 2;
00032     v.y = (p.y + q.y) / 2;
00033     return v;
00034 }
00035 
00036 /* p2s:
00037  * Convert a point to its string representation.
00038  */
00039 static char *p2s(point p, char *buf)
00040 {
00041     sprintf(buf, "(%d,%d)", p.x, p.y);
00042     return buf;
00043 }
00044 
00045 /* Return point where line segment [pp,cp] intersects
00046  * the box bp. Assume cp is outside the box, and pp is
00047  * on or in the box. 
00048  */
00049 static point boxIntersect(point pp, point cp, box * bp)
00050 {
00051     point ipp;
00052     double ppx = pp.x;
00053     double ppy = pp.y;
00054     double cpx = cp.x;
00055     double cpy = cp.y;
00056     point ll = bp->LL;
00057     point ur = bp->UR;
00058 
00059     if (cp.x < ll.x) {
00060         ipp.x = ll.x;
00061         ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
00062         if (ipp.y >= ll.y && ipp.y <= ur.y)
00063             return ipp;
00064     }
00065     if (cp.x > ur.x) {
00066         ipp.x = ur.x;
00067         ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
00068         if (ipp.y >= ll.y && ipp.y <= ur.y)
00069             return ipp;
00070     }
00071     if (cp.y < ll.y) {
00072         ipp.y = ll.y;
00073         ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
00074         if (ipp.x >= ll.x && ipp.x <= ur.x)
00075             return ipp;
00076     }
00077     if (cp.y > ur.y) {
00078         ipp.y = ur.y;
00079         ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
00080         if (ipp.x >= ll.x && ipp.x <= ur.x)
00081             return ipp;
00082     }
00083 
00084     /* failure */
00085     {
00086         char ppbuf[100], cpbuf[100], llbuf[100], urbuf[100];
00087 
00088         agerr(AGERR,
00089               "segment [%s,%s] does not intersect box ll=%s,ur=%s\n",
00090               p2s(pp, ppbuf), p2s(cp, cpbuf), p2s(ll, llbuf), p2s(ur,
00091                                                                   urbuf));
00092         assert(0);
00093     }
00094     return ipp;
00095 }
00096 
00097 /* inBox:
00098  * Returns true if p is on or in box bb
00099  */
00100 static int inBox(point p, box * bb)
00101 {
00102     return ((p.x >= bb->LL.x) && (p.x <= bb->UR.x) &&
00103             (p.y >= bb->LL.y) && (p.y <= bb->UR.y));
00104 }
00105 
00106 /* getCluster:
00107  * Returns subgraph of g with given name.
00108  * Returns NULL if no name is given, or subgraph of
00109  * that name does not exist.
00110  */
00111 static graph_t *getCluster(graph_t * g, char *cluster_name)
00112 {
00113     graph_t *sg;
00114 
00115     if (!cluster_name || (*cluster_name == '\0'))
00116         return NULL;
00117     sg = agfindsubg(g, cluster_name);
00118     if (sg == NULL)
00119         agerr(AGWARN, "cluster named %s not found\n", cluster_name);
00120     return sg;
00121 }
00122 
00123 /* The following functions are derived from pp. 411-415 (pp. 791-795)
00124  * of Graphics Gems. In the code there, they use a SGN function to
00125  * count crossings. This doesn't seem to handle certain special cases,
00126  * as when the last point is on the line. It certainly doesn't work
00127  * for use; see bug 145. We need to use ZSGN instead.
00128  */
00129 #define SGN(a,b)          (((a)<(b)) ? -1 : 1)
00130 #define ZSGN(a,b)         (((a)<(b)) ? -1 : (a)>(b) ? 1 : 0)
00131 
00132 /* countVertCross:
00133  * Return the number of times the Bezier control polygon crosses
00134  * the vertical line x = xcoord.
00135  */
00136 static int countVertCross(pointf * pts, int xcoord)
00137 {
00138     int i;
00139     int sign, old_sign;
00140     int num_crossings = 0;
00141 
00142     sign = old_sign = ZSGN(pts[0].x, xcoord);
00143     if (sign == 0)
00144         num_crossings++;
00145     for (i = 1; i <= 3; i++) {
00146         sign = ZSGN(pts[i].x, xcoord);
00147         if ((sign != old_sign) && (old_sign != 0))
00148             num_crossings++;
00149         old_sign = sign;
00150     }
00151 
00152     return num_crossings;
00153 
00154 }
00155 
00156 /* countHorzCross:
00157  * Return the number of times the Bezier control polygon crosses
00158  * the horizontal line y = ycoord.
00159  */
00160 static int countHorzCross(pointf * pts, int ycoord)
00161 {
00162     int i;
00163     int sign, old_sign;
00164     int num_crossings = 0;
00165 
00166     sign = old_sign = ZSGN(pts[0].y, ycoord);
00167     if (sign == 0)
00168         num_crossings++;
00169     for (i = 1; i <= 3; i++) {
00170         sign = ZSGN(pts[i].y, ycoord);
00171         if ((sign != old_sign) && (old_sign != 0))
00172             num_crossings++;
00173         old_sign = sign;
00174     }
00175 
00176     return num_crossings;
00177 
00178 }
00179 
00180 /* findVertical:
00181  * Given 4 Bezier control points pts, corresponding to the portion
00182  * of an initial spline with path parameter in the range
00183  * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline 
00184  * first crosses a vertical line segment
00185  * [(xcoord,ymin),(xcoord,ymax)]. Return -1 if not found.
00186  * This is done by binary subdivision. 
00187  */
00188 static double
00189 findVertical(pointf * pts, double tmin, double tmax,
00190              int xcoord, int ymin, int ymax)
00191 {
00192     pointf Left[4];
00193     pointf Right[4];
00194     double t;
00195     int no_cross = countVertCross(pts, xcoord);
00196 
00197     if (no_cross == 0)
00198         return -1.0;
00199 
00200     /* if 1 crossing and on the line x = xcoord */
00201     if ((no_cross == 1) && (ROUND(pts[3].x) == xcoord)) {
00202         if ((ymin <= pts[3].y) && (pts[3].y <= ymax)) {
00203             return tmax;
00204         } else
00205             return -1.0;
00206     }
00207 
00208     /* split the Bezier into halves, trying the first half first. */
00209     Bezier(pts, 3, 0.5, Left, Right);
00210     t = findVertical(Left, tmin, (tmin + tmax) / 2.0, xcoord, ymin, ymax);
00211     if (t >= 0.0)
00212         return t;
00213     return findVertical(Right, (tmin + tmax) / 2.0, tmax, xcoord, ymin,
00214                         ymax);
00215 
00216 }
00217 
00218 /* findHorizontal:
00219  * Given 4 Bezier control points pts, corresponding to the portion
00220  * of an initial spline with path parameter in the range
00221  * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline 
00222  * first crosses a horizontal line segment
00223  * [(xmin,ycoord),(xmax,ycoord)]. Return -1 if not found.
00224  * This is done by binary subdivision. 
00225  */
00226 static double
00227 findHorizontal(pointf * pts, double tmin, double tmax,
00228                int ycoord, int xmin, int xmax)
00229 {
00230     pointf Left[4];
00231     pointf Right[4];
00232     double t;
00233     int no_cross = countHorzCross(pts, ycoord);
00234 
00235     if (no_cross == 0)
00236         return -1.0;
00237 
00238     /* if 1 crossing and on the line y = ycoord */
00239     if ((no_cross == 1) && (ROUND(pts[3].y) == ycoord)) {
00240         if ((xmin <= pts[3].x) && (pts[3].x <= xmax)) {
00241             return tmax;
00242         } else
00243             return -1.0;
00244     }
00245 
00246     /* split the Bezier into halves, trying the first half first. */
00247     Bezier(pts, 3, 0.5, Left, Right);
00248     t = findHorizontal(Left, tmin, (tmin + tmax) / 2.0, ycoord, xmin,
00249                        xmax);
00250     if (t >= 0.0)
00251         return t;
00252     return findHorizontal(Right, (tmin + tmax) / 2.0, tmax, ycoord, xmin,
00253                           xmax);
00254 
00255 }
00256 
00257 #define P2PF(p, pf) (pf.x = p.x, pf.y = p.y)
00258 #define PF2P(pf, p) (p.x = ROUND (pf.x), p.y = ROUND (pf.y))
00259 
00260 /* splineIntersect:
00261  * Given four spline control points and a box,
00262  * find the shortest portion of the spline from
00263  * ipts[0] to the intersection with the box, if any.
00264  * If an intersection is found, the four points are stored in ipts[0..3]
00265  * with ipts[3] being on the box, and 1 is returned. Otherwise, ipts
00266  * is left unchanged and 0 is returned.
00267  */
00268 static int splineIntersect(point * ipts, box * bb)
00269 {
00270     double tmin = 2.0;
00271     double t;
00272     pointf pts[4];
00273     pointf origpts[4];
00274     int i;
00275 
00276     for (i = 0; i < 4; i++) {
00277         P2PF(ipts[i], origpts[i]);
00278         pts[i] = origpts[i];
00279     }
00280 
00281     t = findVertical(pts, 0.0, 1.0, bb->LL.x, bb->LL.y, bb->UR.y);
00282     if ((t >= 0) && (t < tmin)) {
00283         Bezier(origpts, 3, t, pts, NULL);
00284         tmin = t;
00285     }
00286     t = findVertical(pts, 0.0, MIN(1.0, tmin), bb->UR.x, bb->LL.y,
00287                      bb->UR.y);
00288     if ((t >= 0) && (t < tmin)) {
00289         Bezier(origpts, 3, t, pts, NULL);
00290         tmin = t;
00291     }
00292     t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->LL.y, bb->LL.x,
00293                        bb->UR.x);
00294     if ((t >= 0) && (t < tmin)) {
00295         Bezier(origpts, 3, t, pts, NULL);
00296         tmin = t;
00297     }
00298     t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->UR.y, bb->LL.x,
00299                        bb->UR.x);
00300     if ((t >= 0) && (t < tmin)) {
00301         Bezier(origpts, 3, t, pts, NULL);
00302         tmin = t;
00303     }
00304 
00305     if (tmin < 2.0) {
00306         for (i = 0; i < 4; i++) {
00307             PF2P(pts[i], ipts[i]);
00308         }
00309         return 1;
00310     } else
00311         return 0;
00312 
00313 }
00314 
00315 /* makeCompoundEdge:
00316  * If edge e has a cluster head and/or cluster tail,
00317  * clip spline to outside of cluster. 
00318  * Requirement: spline is composed of only one part, 
00319  * with n control points where n >= 4 and n (mod 3) = 1.
00320  * If edge has arrowheads, reposition them.
00321  */
00322 static void makeCompoundEdge(graph_t * g, edge_t * e)
00323 {
00324     graph_t *lh;                /* cluster containing head */
00325     graph_t *lt;                /* cluster containing tail */
00326     bezier *bez;                /* original Bezier for e */
00327     bezier *nbez;               /* new Bezier  for e */
00328     int starti = 0, endi = 0;   /* index of first and last control point */
00329     node_t *head;
00330     node_t *tail;
00331     box *bb;
00332     int i, j;
00333     int size;
00334     point pts[4];
00335     point p;
00336     int fixed;
00337 
00338     /* find head and tail target clusters, if defined */
00339     lh = getCluster(g, agget(e, "lhead"));
00340     lt = getCluster(g, agget(e, "ltail"));
00341     if (!lt && !lh)
00342         return;
00343     if (!ED_spl(e)) return;
00344 
00345     /* at present, we only handle single spline case */
00346     if (ED_spl(e)->size > 1) {
00347         agerr(AGWARN, "%s -> %s: spline size > 1 not supported\n",
00348               e->tail->name, e->head->name);
00349         return;
00350     }
00351     bez = ED_spl(e)->list;
00352     size = bez->size;
00353 
00354     head = e->head;
00355     tail = e->tail;
00356 
00357     /* allocate new Bezier */
00358     nbez = GNEW(bezier);
00359     nbez->eflag = bez->eflag;
00360     nbez->sflag = bez->sflag;
00361 
00362     /* if Bezier has four points, almost collinear,
00363      * make line - unimplemented optimization?
00364      */
00365 
00366     /* If head cluster defined, find first Bezier
00367      * crossing head cluster, and truncate spline to
00368      * box edge.
00369      * Otherwise, leave end alone.
00370      */
00371     fixed = 0;
00372     if (lh) {
00373         bb = &(GD_bb(lh));
00374         if (!inBox(ND_coord_i(head), bb)) {
00375             agerr(AGWARN, "%s -> %s: head not inside head cluster %s\n",
00376                   e->tail->name, e->head->name, agget(e, "lhead"));
00377         } else {
00378             /* If first control point is in bb, degenerate case. Spline
00379              * reduces to four points between the arrow head and the point 
00380              * where the segment between the first control point and arrow head 
00381              * crosses box.
00382              */
00383             if (inBox(bez->list[0], bb)) {
00384                 if (inBox(ND_coord_i(tail), bb)) {
00385                     agerr(AGWARN,
00386                           "%s -> %s: tail is inside head cluster %s\n",
00387                           e->tail->name, e->head->name, agget(e, "lhead"));
00388                 } else {
00389                     assert(bez->sflag); /* must be arrowhead on tail */
00390                     p = boxIntersect(bez->list[0], bez->sp, bb);
00391                     bez->list[3] = p;
00392                     bez->list[1] = midPt(p, bez->sp);
00393                     bez->list[0] = midPt(bez->list[1], bez->sp);
00394                     bez->list[2] = midPt(bez->list[1], p);
00395                     if (bez->eflag)
00396                         endi =
00397                             arrowEndClip(e, bez->list,
00398                                          starti, 0, nbez, bez->eflag);
00399                     endi += 3;
00400                     fixed = 1;
00401                 }
00402             } else {
00403                 for (endi = 0; endi < size - 1; endi += 3) {
00404                     if (splineIntersect(&(bez->list[endi]), bb))
00405                         break;
00406                 }
00407                 if (endi == size - 1) { /* no intersection */
00408                     assert(bez->eflag);
00409                     nbez->ep = boxIntersect(bez->ep, bez->list[endi], bb);
00410                 } else {
00411                     if (bez->eflag)
00412                         endi =
00413                             arrowEndClip(e, bez->list,
00414                                          starti, endi, nbez, bez->eflag);
00415                     endi += 3;
00416                 }
00417                 fixed = 1;
00418             }
00419         }
00420     }
00421     if (fixed == 0) {           /* if no lh, or something went wrong, use original head */
00422         endi = size - 1;
00423         if (bez->eflag)
00424             nbez->ep = bez->ep;
00425     }
00426 
00427     /* If tail cluster defined, find last Bezier
00428      * crossing tail cluster, and truncate spline to
00429      * box edge.
00430      * Otherwise, leave end alone.
00431      */
00432     fixed = 0;
00433     if (lt) {
00434         bb = &(GD_bb(lt));
00435         if (!inBox(ND_coord_i(tail), bb)) {
00436             agerr(AGWARN, "%s -> %s: tail not inside tail cluster %s\n",
00437                   e->tail->name, head->name, agget(e, "ltail"));
00438         } else {
00439             /* If last control point is in bb, degenerate case. Spline
00440              * reduces to four points between arrow head, and the point
00441              * where the segment between the last control point and the 
00442              * arrow head crosses box.
00443              */
00444             if (inBox(bez->list[endi], bb)) {
00445                 if (inBox(ND_coord_i(head), bb)) {
00446                     agerr(AGWARN,
00447                           "%s -> %s: head is inside tail cluster %s\n",
00448                           e->tail->name, e->head->name, agget(e, "ltail"));
00449                 } else {
00450                     assert(bez->eflag); /* must be arrowhead on head */
00451                     p = boxIntersect(bez->list[endi], nbez->ep, bb);
00452                     starti = endi - 3;
00453                     bez->list[starti] = p;
00454                     bez->list[starti + 2] = midPt(p, nbez->ep);
00455                     bez->list[starti + 3] =
00456                         midPt(bez->list[starti + 2], nbez->ep);
00457                     bez->list[starti + 1] =
00458                         midPt(bez->list[starti + 2], p);
00459                     if (bez->sflag)
00460                         starti =
00461                             arrowStartClip(e, bez->list,
00462                                            starti, endi - 3, nbez,
00463                                            bez->sflag);
00464                     fixed = 1;
00465                 }
00466             } else {
00467                 for (starti = endi; starti > 0; starti -= 3) {
00468                     for (i = 0; i < 4; i++)
00469                         pts[i] = bez->list[starti - i];
00470                     if (splineIntersect(pts, bb)) {
00471                         for (i = 0; i < 4; i++)
00472                             bez->list[starti - i] = pts[i];
00473                         break;
00474                     }
00475                 }
00476                 if (starti == 0) {
00477                     assert(bez->sflag);
00478                     nbez->sp =
00479                         boxIntersect(bez->sp, bez->list[starti], bb);
00480                 } else {
00481                     starti -= 3;
00482                     if (bez->sflag)
00483                         starti =
00484                             arrowStartClip(e, bez->list,
00485                                            starti, endi - 3, nbez,
00486                                            bez->sflag);
00487                 }
00488                 fixed = 1;
00489             }
00490         }
00491     }
00492     if (fixed == 0) {           /* if no lt, or something went wrong, use original tail */
00493         /* Note: starti == 0 */
00494         if (bez->sflag)
00495             nbez->sp = bez->sp;
00496     }
00497 
00498     /* complete Bezier, free garbage and attach new Bezier to edge 
00499      */
00500     nbez->size = endi - starti + 1;
00501     nbez->list = N_GNEW(nbez->size, point);
00502     for (i = 0, j = starti; i < nbez->size; i++, j++)
00503         nbez->list[i] = bez->list[j];
00504     free(bez->list);
00505     free(bez);
00506     ED_spl(e)->list = nbez;
00507 
00508 }
00509 
00510 /* dot_compoundEdges:
00511  */
00512 void dot_compoundEdges(graph_t * g)
00513 {
00514     edge_t *e;
00515     node_t *n;
00516 
00517     for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
00518         for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
00519             makeCompoundEdge(g, e);
00520         }
00521     }
00522 }

Generated on Mon Mar 31 19:03:25 2008 for Graphviz by  doxygen 1.5.1