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