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