Graphviz 12.0.1~dev.20240715.2254
Loading...
Searching...
No Matches
compound.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11
12/* Module for clipping splines to cluster boxes.
13 */
14
15#include <cgraph/agxbuf.h>
16#include <cgraph/alloc.h>
17#include <cgraph/gv_math.h>
18#include <dotgen/dot.h>
19#include <stdbool.h>
20#include <stddef.h>
21
22/* Return point where line segment [pp,cp] intersects
23 * the box bp. Assume cp is outside the box, and pp is
24 * on or in the box.
25 */
27{
28 pointf ipp;
29 double ppx = pp.x;
30 double ppy = pp.y;
31 double cpx = cp.x;
32 double cpy = cp.y;
33 pointf ll;
34 pointf ur;
35
36 ll = bp->LL;
37 ur = bp->UR;
38 if (cp.x < ll.x) {
39 ipp.x = ll.x;
40 ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
41 if (ipp.y >= ll.y && ipp.y <= ur.y)
42 return ipp;
43 }
44 if (cp.x > ur.x) {
45 ipp.x = ur.x;
46 ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
47 if (ipp.y >= ll.y && ipp.y <= ur.y)
48 return ipp;
49 }
50 if (cp.y < ll.y) {
51 ipp.y = ll.y;
52 ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
53 if (ipp.x >= ll.x && ipp.x <= ur.x)
54 return ipp;
55 }
56 if (cp.y > ur.y) {
57 ipp.y = ur.y;
58 ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
59 if (ipp.x >= ll.x && ipp.x <= ur.x)
60 return ipp;
61 }
62
63 /* failure */
65 "segment [(%.5g, %.5g),(%.5g,%.5g)] does not intersect box "
66 "ll=(%.5g,%.5g),ur=(%.5g,%.5g)\n", pp.x, pp.y, cp.x, cp.y, ll.x, ll.y,
67 ur.x, ur.y);
68 assert(0);
69
70 return ipp;
71}
72
73/* inBoxf:
74 * Returns true if p is on or in box bb
75 */
76static int inBoxf(pointf p, boxf * bb)
77{
78 return INSIDE(p, *bb);
79}
80
81/* getCluster:
82 * Returns subgraph with given name.
83 * Returns NULL if no name is given, or subgraph of
84 * that name does not exist.
85 */
86static graph_t *getCluster(char *cluster_name, Dt_t *map) {
87 Agraph_t* sg;
88
89 if (!cluster_name || *cluster_name == '\0')
90 return NULL;
91 sg = findCluster (map, cluster_name);
92 if (sg == NULL) {
93 agwarningf("cluster named %s not found\n", cluster_name);
94 }
95 return sg;
96}
97
98/* The following functions are derived from pp. 411-415 (pp. 791-795)
99 * of Graphics Gems. In the code there, they use a SGN function to
100 * count crossings. This doesn't seem to handle certain special cases,
101 * as when the last point is on the line. It certainly didn't work
102 * for us when we used int values; see bug 145. We needed to use `fcmp` instead.
103 *
104 * Possibly unnecessary with double values, but harmless.
105 */
106
107/* countVertCross:
108 * Return the number of times the Bezier control polygon crosses
109 * the vertical line x = xcoord.
110 */
111static int countVertCross(pointf * pts, double xcoord)
112{
113 int i;
114 int sign, old_sign;
115 int num_crossings = 0;
116
117 sign = fcmp(pts[0].x, xcoord);
118 if (sign == 0)
119 num_crossings++;
120 for (i = 1; i <= 3; i++) {
121 old_sign = sign;
122 sign = fcmp(pts[i].x, xcoord);
123 if (sign != old_sign && old_sign != 0)
124 num_crossings++;
125 }
126 return num_crossings;
127}
128
129/* countHorzCross:
130 * Return the number of times the Bezier control polygon crosses
131 * the horizontal line y = ycoord.
132 */
133static int countHorzCross(pointf * pts, double ycoord)
134{
135 int i;
136 int sign, old_sign;
137 int num_crossings = 0;
138
139 sign = fcmp(pts[0].y, ycoord);
140 if (sign == 0)
141 num_crossings++;
142 for (i = 1; i <= 3; i++) {
143 old_sign = sign;
144 sign = fcmp(pts[i].y, ycoord);
145 if (sign != old_sign && old_sign != 0)
146 num_crossings++;
147 }
148 return num_crossings;
149}
150
151/* findVertical:
152 * Given 4 Bezier control points pts, corresponding to the portion
153 * of an initial spline with path parameter in the range
154 * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline
155 * first crosses a vertical line segment
156 * [(xcoord,ymin),(xcoord,ymax)]. Return -1 if not found.
157 * This is done by binary subdivision.
158 */
159static double
160findVertical(pointf * pts, double tmin, double tmax,
161 double xcoord, double ymin, double ymax)
162{
163 pointf Left[4];
164 pointf Right[4];
165 double t;
166 int no_cross;
167
168 if (tmin == tmax)
169 return tmin;
170
171 no_cross = countVertCross(pts, xcoord);
172 if (no_cross == 0)
173 return -1.0;
174
175 /* if 1 crossing and on the line x == xcoord (within 0.005 point) */
176 if (no_cross == 1 && fabs(pts[3].x - xcoord) <= 0.005) {
177 if (ymin <= pts[3].y && pts[3].y <= ymax) {
178 return tmax;
179 } else
180 return -1.0;
181 }
182
183 /* split the Bezier into halves, trying the first half first. */
184 Bezier(pts, 0.5, Left, Right);
185 t = findVertical(Left, tmin, (tmin + tmax) / 2.0, xcoord, ymin, ymax);
186 if (t >= 0.0)
187 return t;
188 return findVertical(Right, (tmin + tmax) / 2.0, tmax, xcoord, ymin,
189 ymax);
190
191}
192
193/* findHorizontal:
194 * Given 4 Bezier control points pts, corresponding to the portion
195 * of an initial spline with path parameter in the range
196 * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline
197 * first crosses a horizontal line segment
198 * [(xmin,ycoord),(xmax,ycoord)]. Return -1 if not found.
199 * This is done by binary subdivision.
200 */
201static double
202findHorizontal(pointf * pts, double tmin, double tmax,
203 double ycoord, double xmin, double xmax)
204{
205 pointf Left[4];
206 pointf Right[4];
207 double t;
208 int no_cross;
209
210 if (tmin == tmax)
211 return tmin;
212
213 no_cross = countHorzCross(pts, ycoord);
214 if (no_cross == 0)
215 return -1.0;
216
217 /* if 1 crossing and on the line y == ycoord (within 0.005 point) */
218 if (no_cross == 1 && fabs(pts[3].y - ycoord) <= 0.005) {
219 if (xmin <= pts[3].x && pts[3].x <= xmax) {
220 return tmax;
221 } else
222 return -1.0;
223 }
224
225 /* split the Bezier into halves, trying the first half first. */
226 Bezier(pts, 0.5, Left, Right);
227 t = findHorizontal(Left, tmin, (tmin + tmax) / 2.0, ycoord, xmin,
228 xmax);
229 if (t >= 0.0)
230 return t;
231 return findHorizontal(Right, (tmin + tmax) / 2.0, tmax, ycoord, xmin,
232 xmax);
233}
234
235/* splineIntersectf:
236 * Given four spline control points and a box,
237 * find the shortest portion of the spline from
238 * pts[0] to the intersection with the box, if any.
239 * If an intersection is found, the four points are stored in pts[0..3]
240 * with pts[3] being on the box, and 1 is returned. Otherwise, pts
241 * is left unchanged and 0 is returned.
242 */
243static int splineIntersectf(pointf * pts, boxf * bb)
244{
245 double tmin = 2.0;
246 double t;
247 pointf origpts[4];
248 int i;
249
250 for (i = 0; i < 4; i++) {
251 origpts[i] = pts[i];
252 }
253
254 t = findVertical(pts, 0.0, 1.0, bb->LL.x, bb->LL.y, bb->UR.y);
255 if (t >= 0 && t < tmin) {
256 Bezier(origpts, t, pts, NULL);
257 tmin = t;
258 }
259 t = findVertical(pts, 0.0, MIN(1.0, tmin), bb->UR.x, bb->LL.y,
260 bb->UR.y);
261 if (t >= 0 && t < tmin) {
262 Bezier(origpts, t, pts, NULL);
263 tmin = t;
264 }
265 t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->LL.y, bb->LL.x,
266 bb->UR.x);
267 if (t >= 0 && t < tmin) {
268 Bezier(origpts, t, pts, NULL);
269 tmin = t;
270 }
271 t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->UR.y, bb->LL.x,
272 bb->UR.x);
273 if (t >= 0 && t < tmin) {
274 Bezier(origpts, t, pts, NULL);
275 tmin = t;
276 }
277
278 if (tmin < 2.0) {
279 return 1;
280 } else
281 return 0;
282}
283
284/* makeCompoundEdge:
285 * If edge e has a cluster head and/or cluster tail,
286 * clip spline to outside of cluster.
287 * Requirement: spline is composed of only one part,
288 * with n control points where n >= 4 and n (mod 3) = 1.
289 * If edge has arrowheads, reposition them.
290 */
291static void makeCompoundEdge(edge_t *e, Dt_t *clustMap) {
292 size_t starti = 0, endi = 0; // index of first and last control point
293
294 /* find head and tail target clusters, if defined */
295 graph_t *lh = getCluster(agget(e, "lhead"), clustMap); // cluster containing head
296 graph_t *lt = getCluster(agget(e, "ltail"), clustMap); // cluster containing tail
297 if (!lt && !lh)
298 return;
299 if (!ED_spl(e)) return;
300
301 /* at present, we only handle single spline case */
302 if (ED_spl(e)->size > 1) {
303 agwarningf("%s -> %s: spline size > 1 not supported\n",
304 agnameof(agtail(e)), agnameof(aghead(e)));
305 return;
306 }
307 bezier *bez = ED_spl(e)->list; // original Bezier for e
308 const size_t size = bez->size;
309
310 node_t *head = aghead(e);
311 node_t *tail = agtail(e);
312
313 /* allocate new Bezier */
314 bezier nbez = {0}; // new Bezier for `e`
315 nbez.eflag = bez->eflag;
316 nbez.sflag = bez->sflag;
317
318 /* if Bezier has four points, almost collinear,
319 * make line - unimplemented optimization?
320 */
321
322 /* If head cluster defined, find first Bezier
323 * crossing head cluster, and truncate spline to
324 * box edge.
325 * Otherwise, leave end alone.
326 */
327 bool fixed = false;
328 if (lh) {
329 boxf *bb = &GD_bb(lh);
330 if (!inBoxf(ND_coord(head), bb)) {
331 agwarningf("%s -> %s: head not inside head cluster %s\n",
332 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead"));
333 } else {
334 /* If first control point is in bb, degenerate case. Spline
335 * reduces to four points between the arrow head and the point
336 * where the segment between the first control point and arrow head
337 * crosses box.
338 */
339 if (inBoxf(bez->list[0], bb)) {
340 if (inBoxf(ND_coord(tail), bb)) {
342 "%s -> %s: tail is inside head cluster %s\n",
343 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead"));
344 } else {
345 assert(bez->sflag); /* must be arrowhead on tail */
346 pointf p = boxIntersectf(bez->list[0], bez->sp, bb);
347 bez->list[3] = p;
348 bez->list[1] = mid_pointf(p, bez->sp);
349 bez->list[0] = mid_pointf(bez->list[1], bez->sp);
350 bez->list[2] = mid_pointf(bez->list[1], p);
351 if (bez->eflag)
352 endi = arrowEndClip(e, bez->list,
353 starti, 0, &nbez, bez->eflag);
354 endi += 3;
355 fixed = true;
356 }
357 } else {
358 for (endi = 0; endi < size - 1; endi += 3) {
359 if (splineIntersectf(&bez->list[endi], bb))
360 break;
361 }
362 if (endi == size - 1) { /* no intersection */
363 assert(bez->eflag);
364 nbez.ep = boxIntersectf(bez->ep, bez->list[endi], bb);
365 } else {
366 if (bez->eflag)
367 endi =
368 arrowEndClip(e, bez->list,
369 starti, endi, &nbez, bez->eflag);
370 endi += 3;
371 }
372 fixed = true;
373 }
374 }
375 }
376 if (!fixed) { // if no lh, or something went wrong, use original head
377 endi = size - 1;
378 if (bez->eflag)
379 nbez.ep = bez->ep;
380 }
381
382 /* If tail cluster defined, find last Bezier
383 * crossing tail cluster, and truncate spline to
384 * box edge.
385 * Otherwise, leave end alone.
386 */
387 fixed = false;
388 if (lt) {
389 boxf *bb = &GD_bb(lt);
390 if (!inBoxf(ND_coord(tail), bb)) {
391 agwarningf("%s -> %s: tail not inside tail cluster %s\n",
392 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail"));
393 } else {
394 /* If last control point is in bb, degenerate case. Spline
395 * reduces to four points between arrow head, and the point
396 * where the segment between the last control point and the
397 * arrow head crosses box.
398 */
399 if (inBoxf(bez->list[endi], bb)) {
400 if (inBoxf(ND_coord(head), bb)) {
402 "%s -> %s: head is inside tail cluster %s\n",
403 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail"));
404 } else {
405 assert(bez->eflag); /* must be arrowhead on head */
406 pointf p = boxIntersectf(bez->list[endi], nbez.ep, bb);
407 starti = endi - 3;
408 bez->list[starti] = p;
409 bez->list[starti + 2] = mid_pointf(p, nbez.ep);
410 bez->list[starti + 3] = mid_pointf(bez->list[starti + 2], nbez.ep);
411 bez->list[starti + 1] = mid_pointf(bez->list[starti + 2], p);
412 if (bez->sflag)
413 starti = arrowStartClip(e, bez->list, starti,
414 endi - 3, &nbez, bez->sflag);
415 fixed = true;
416 }
417 } else {
418 for (starti = endi; starti > 0; starti -= 3) {
419 pointf pts[4];
420 for (size_t i = 0; i < 4; i++)
421 pts[i] = bez->list[starti - i];
422 if (splineIntersectf(pts, bb)) {
423 for (size_t i = 0; i < 4; i++)
424 bez->list[starti - i] = pts[i];
425 break;
426 }
427 }
428 if (starti == 0 && bez->sflag) {
429 nbez.sp = boxIntersectf(bez->sp, bez->list[starti], bb);
430 } else if (starti != 0) {
431 starti -= 3;
432 if (bez->sflag)
433 starti = arrowStartClip(e, bez->list, starti,
434 endi - 3, &nbez, bez->sflag);
435 }
436 fixed = true;
437 }
438 }
439 }
440 if (!fixed) { // if no lt, or something went wrong, use original tail
441 /* Note: starti == 0 */
442 if (bez->sflag)
443 nbez.sp = bez->sp;
444 }
445
446 /* complete Bezier, free garbage and attach new Bezier to edge
447 */
448 nbez.size = endi - starti + 1;
449 nbez.list = gv_calloc(nbez.size, sizeof(pointf));
450 for (size_t i = 0, j = starti; i < nbez.size; i++, j++)
451 nbez.list[i] = bez->list[j];
452 free(bez->list);
453 *ED_spl(e)->list = nbez;
454}
455
456/* dot_compoundEdges:
457 */
459{
460 edge_t *e;
461 node_t *n;
462 Dt_t* clustMap = mkClustMap (g);
463 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
464 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
465 makeCompoundEdge(e, clustMap);
466 }
467 }
468 dtclose(clustMap);
469}
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
#define MIN(a, b)
Definition arith.h:28
size_t arrowStartClip(edge_t *e, pointf *ps, size_t startp, size_t endp, bezier *spl, uint32_t sflag)
Definition arrows.c:320
size_t arrowEndClip(edge_t *e, pointf *ps, size_t startp, size_t endp, bezier *spl, uint32_t eflag)
Definition arrows.c:289
CDT_API int dtclose(Dt_t *)
Definition dtclose.c:8
pointf Bezier(pointf *V, double t, pointf *Left, pointf *Right)
Definition utils.c:169
Dt_t * mkClustMap(Agraph_t *g)
Definition utils.c:1612
Agraph_t * findCluster(Dt_t *map, char *name)
Definition utils.c:1622
static pointf boxIntersectf(pointf pp, pointf cp, boxf *bp)
Definition compound.c:26
static int splineIntersectf(pointf *pts, boxf *bb)
Definition compound.c:243
static int countVertCross(pointf *pts, double xcoord)
Definition compound.c:111
static graph_t * getCluster(char *cluster_name, Dt_t *map)
Definition compound.c:86
static void makeCompoundEdge(edge_t *e, Dt_t *clustMap)
Definition compound.c:291
static double findVertical(pointf *pts, double tmin, double tmax, double xcoord, double ymin, double ymax)
Definition compound.c:160
void dot_compoundEdges(graph_t *g)
Definition compound.c:458
static double findHorizontal(pointf *pts, double tmin, double tmax, double ycoord, double xmin, double xmax)
Definition compound.c:202
static int countHorzCross(pointf *pts, double ycoord)
Definition compound.c:133
static int inBoxf(pointf p, boxf *bb)
Definition compound.c:76
#define head
Definition dthdr.h:15
#define INSIDE(p, b)
Definition geom.h:45
double xmax
Definition geometry.c:15
double ymin
Definition geometry.c:15
double xmin
Definition geometry.c:15
double ymax
Definition geometry.c:15
static pointf mid_pointf(pointf p, pointf q)
Definition geomprocs.h:81
void free(void *)
node NULL
Definition grammar.y:149
char * agget(void *obj, char *name)
Definition attr.c:442
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:23
#define ED_spl(e)
Definition types.h:595
#define agtail(e)
Definition cgraph.h:889
#define aghead(e)
Definition cgraph.h:890
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:38
void agwarningf(const char *fmt,...)
Definition agerror.c:173
void agerrorf(const char *fmt,...)
Definition agerror.c:165
#define GD_bb(g)
Definition types.h:354
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
#define ND_coord(n)
Definition types.h:490
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:158
Arithmetic helper functions.
static int fcmp(double a, double b)
comparator for doubles
Definition gv_math.h:13
static int sign(double v)
Definition legal.c:54
graph or subgraph
Definition cgraph.h:425
Definition cdt.h:104
Definition types.h:89
size_t size
Definition types.h:91
pointf sp
Definition types.h:94
pointf * list
Definition types.h:90
uint32_t eflag
Definition types.h:93
pointf ep
Definition types.h:95
uint32_t sflag
Definition types.h:92
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
double x
Definition geom.h:29
double y
Definition geom.h:29