Graphviz 16.1.1~dev.20260906.1627
Loading...
Searching...
No Matches
constraint.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 v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11
12#include "config.h"
13#include <cgraph/cgraph.h>
14#include <common/geom.h>
15#include <math.h>
16#include <neatogen/neato.h>
17#include <neatogen/adjust.h>
18#include <stddef.h>
19#include <stdbool.h>
20#include <util/alloc.h>
21#include <util/gv_math.h>
22#include <util/itos.h>
23#include <util/list.h>
24
25/* For precision, scale up before algorithms, then scale down */
26#define SCALE 10
27#define SCALE2 (SCALE/2)
28
29typedef struct nitem {
31 int val;
32 point pos; /* position for sorting */
33 node_t *np; /* base node */
34 node_t *cnode; /* corresponding node in constraint graph */
35 node_t *vnode; /* corresponding node in neighbor graph */
38
39typedef int (*distfn) (box *, box *);
40typedef int (*intersectfn) (nitem *, nitem *);
41
42static int cmpitem(void *item1, void *item2) {
43 const int *p1 = item1;
44 const int *p2 = item2;
45 if (*p1 < *p2) {
46 return -1;
47 }
48 if (*p1 > *p2) {
49 return 1;
50 }
51 return 0;
52}
53
54static Dtdisc_t constr = {
55 offsetof(nitem, val),
56 sizeof(int),
57 offsetof(nitem, link),
58 NULL,
59 NULL,
60 cmpitem,
61};
62
63static int distY(box * b1, box * b2)
64{
65 return ((b1->UR.y - b1->LL.y) + (b2->UR.y - b2->LL.y)) / 2;
66}
67
68static int distX(box * b1, box * b2)
69{
70 return ((b1->UR.x - b1->LL.x) + (b2->UR.x - b2->LL.x)) / 2;
71}
72
73/* Return true if boxes could overlap if shifted in y but don't,
74 * or if actually overlap and an y move is smallest to remove overlap.
75 * Otherwise (no x overlap or a x move is smaller), return false.
76 * Assume q pos to above of p pos.
77 */
78static int intersectX0(nitem * p, nitem * q)
79{
80 int xdelta, ydelta;
81 int v = p->bb.LL.x <= q->bb.UR.x && q->bb.LL.x <= p->bb.UR.x;
82 if (v == 0) /* no x overlap */
83 return 0;
84 if (p->bb.UR.y < q->bb.LL.y) /* but boxes don't really overlap */
85 return 1;
86 ydelta = distY(&p->bb,&q->bb) - (q->pos.y - p->pos.y);
87 if (q->pos.x >= p->pos.x)
88 xdelta = distX(&p->bb,&q->bb) - (q->pos.x - p->pos.x);
89 else
90 xdelta = distX(&p->bb,&q->bb) - (p->pos.x - q->pos.x);
91 return ydelta <= xdelta;
92}
93
94/* Return true if boxes could overlap if shifted in x but don't,
95 * or if actually overlap and an x move is smallest to remove overlap.
96 * Otherwise (no y overlap or a y move is smaller), return false.
97 * Assume q pos to right of p pos.
98 */
99static int intersectY0(nitem * p, nitem * q)
100{
101 int xdelta, ydelta;
102 int v = p->bb.LL.y <= q->bb.UR.y && q->bb.LL.y <= p->bb.UR.y;
103 if (v == 0) /* no y overlap */
104 return 0;
105 if (p->bb.UR.x < q->bb.LL.x) /* but boxes don't really overlap */
106 return 1;
107 xdelta = distX(&p->bb,&q->bb) - (q->pos.x - p->pos.x);
108 if (q->pos.y >= p->pos.y)
109 ydelta = distY(&p->bb,&q->bb) - (q->pos.y - p->pos.y);
110 else
111 ydelta = distY(&p->bb,&q->bb) - (p->pos.y - q->pos.y);
112 return xdelta <= ydelta;
113}
114
115static int intersectY(nitem * p, nitem * q)
116{
117 return p->bb.LL.y <= q->bb.UR.y && q->bb.LL.y <= p->bb.UR.y;
118}
119
120static int intersectX(nitem * p, nitem * q)
121{
122 return p->bb.LL.x <= q->bb.UR.x && q->bb.LL.x <= p->bb.UR.x;
123}
124
125static void mapGraphs(graph_t * g, graph_t * cg, distfn dist)
126{
127 node_t *n;
128 edge_t *e;
129 edge_t *ce;
130 node_t *t;
131 node_t *h;
132 nitem *tp;
133 nitem *hp;
134 int delta;
135
136 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
137 tp = ND_alg(n);
138 t = tp->cnode;
139 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
140 hp = ND_alg(aghead(e));
141 delta = dist(&tp->bb, &hp->bb);
142 h = hp->cnode;
143 ce = agedge(cg, t, h, NULL, 1);
144 agbindrec(ce, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true);
145 ED_weight(ce) = 1;
146 if (ED_minlen(ce) < delta) {
147 if (ED_minlen(ce) == 0.0) {
148 elist_append(ce, ND_out(t));
149 elist_append(ce, ND_in(h));
150 }
151 ED_minlen(ce) = delta;
152 }
153 }
154 }
155}
156
157#if defined(DEBUG) && DEBUG > 1
158static int
159indegree (graph_t * g, node_t *n)
160{
161 edge_t *e;
162 int cnt = 0;
163 for (e = agfstin(g,n); e; e = agnxtin(g,e)) cnt++;
164 return cnt;
165}
166
167static int
168outdegree (graph_t * g, node_t *n)
169{
170 edge_t *e;
171 int cnt = 0;
172 for (e = agfstout(g,n); e; e = agnxtout(g,e)) cnt++;
173 return cnt;
174}
175
176static void
177validate(graph_t * g)
178{
179 node_t *n;
180 edge_t *e;
181 int i, cnt;
182
183 cnt = 0;
184 for (n = GD_nlist(g);n; n = ND_next(n)) {
185 assert(outdegree(g,n) == ND_out(n).size);
186 for (i = 0; (e = ND_out(n).list[i]); i++) {
187 assert(agtail(e) == n);
188 assert( e == agfindedge(g, n, aghead(e)));
189 }
190 assert(indegree(g,n) == ND_in(n).size);
191 for (i = 0; (e = ND_in(n).list[i]); i++) {
192 assert(aghead(e) == n);
193 assert( e == agfindedge(g, agtail(e), n));
194 }
195 cnt++;
196 }
197
198 assert (agnnodes(g) == cnt);
199}
200#endif
201
202
203/* Similar to mkConstraintG, except it doesn't enforce orthogonal
204 * ordering. If there is overlap, as defined by intersect, the
205 * nodes will kept/pushed apart in the current order. If not, no
206 * constraint is enforced. If a constraint edge is added, and it
207 * corresponds to a real edge, we increase the weight in an attempt
208 * to keep the resulting shift short.
209 */
210static graph_t *mkNConstraintG(graph_t * g, Dt_t * list,
212{
213 nitem *p;
214 nitem *nxp;
215 node_t *n;
216 edge_t *e;
217 node_t *lastn = NULL;
219 agbindrec(cg, "Agraphinfo_t", sizeof(Agraphinfo_t), true); // graph custom data
220
221 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
222 n = agnode(cg, agnameof(p->np), 1); /* FIX */
223 agbindrec(n, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true); //node custom data
224 ND_alg(n) = p;
225 p->cnode = n;
226 alloc_elist(0, ND_in(n));
227 alloc_elist(0, ND_out(n));
228 if (lastn) {
229 ND_next(lastn) = n;
230 lastn = n;
231 } else {
232 lastn = GD_nlist(cg) = n;
233 }
234 }
235 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
236 for (nxp = (nitem *)dtlink(link, p); nxp; nxp = (nitem *)dtlink(list, nxp)) {
237 e = NULL;
238 if (intersect(p, nxp)) {
239 double delta = dist(&p->bb, &nxp->bb);
240 e = agedge(cg, p->cnode, nxp->cnode, NULL, 1);
241 agbindrec(e, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); // edge custom data
242 assert (delta <= 0xFFFF);
243 ED_minlen(e) = delta;
244 ED_weight(e) = 1;
245 }
246 if (e && agfindedge(g,p->np, nxp->np)) {
247 ED_weight(e) = 100;
248 }
249 }
250 }
251
252 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
253 n = p->cnode;
254 for (e = agfstout(cg,n); e; e = agnxtout(cg,e)) {
255 elist_append(e, ND_out(n));
256 elist_append(e, ND_in(aghead(e)));
257 }
258 }
259
260 /* We could remove redundant constraints here. However, the cost of doing
261 * this may be a good deal more than the time saved in network simplex.
262 * Also, if the graph is changed, the ND_in and ND_out data has to be
263 * updated.
264 */
265 return cg;
266}
267
269 nitem *p;
270 nitem *nxt = NULL;
271 nitem *nxp;
272 graph_t *vg;
273 node_t *prev = NULL;
274 node_t *root = NULL;
275 node_t *n = NULL;
276 edge_t *e;
277 int lcnt, cnt;
278 int oldval = -INT_MAX;
279 node_t *lastn = NULL;
281 agbindrec(cg, "Agraphinfo_t", sizeof(Agraphinfo_t), true); // graph custom data
282
283 /* count distinct nodes */
284 cnt = 0;
285 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
286 if (oldval != p->val) {
287 oldval = p->val;
288 cnt++;
289 }
290 }
291
292 /* construct basic chain to enforce left to right order */
293 oldval = -INT_MAX;
294 lcnt = 0;
295 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
296 if (oldval != p->val) {
297 oldval = p->val;
298 /* n = newNode (cg); */
299 n = agnode(cg, agnameof(p->np), 1); /* FIX */
300 agbindrec(n, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true); //node custom data
301 ND_alg(n) = p;
302 if (root) {
303 ND_next(lastn) = n;
304 lastn = n;
305 } else {
306 root = n;
307 lastn = GD_nlist(cg) = n;
308 }
309 alloc_elist(lcnt, ND_in(n));
310 if (prev) {
311 if (prev == root)
312 alloc_elist(2 * (cnt - 1), ND_out(prev));
313 else
314 alloc_elist(cnt - lcnt - 1, ND_out(prev));
315 e = agedge(cg, prev, n, NULL, 1);
316 agbindrec(e, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); // edge custom data
317 ED_minlen(e) = SCALE;
318 ED_weight(e) = 1;
320 elist_append(e, ND_in(n));
321 }
322 lcnt++;
323 prev = n;
324 }
325 p->cnode = n;
326 }
328
329 /* add immediate right neighbor constraints
330 * Construct visibility graph, then perform transitive reduction.
331 * Remaining outedges are immediate right neighbors.
332 * FIX: Incremental algorithm to construct trans. reduction?
333 */
334 vg = agopen("vg", Agstrictdirected, NULL);
335 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
336 n = agnode(vg, agnameof(p->np), 1); /* FIX */
337 agbindrec(n, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true); //node custom data
338 p->vnode = n;
339 ND_alg(n) = p;
340 }
341 oldval = -INT_MAX;
342 for (p = (nitem *)dtflatten(list); p; p = (nitem *)dtlink(list, p)) {
343 if (oldval != p->val) { /* new pos: reset nxt */
344 oldval = p->val;
345 for (nxt = (nitem *)dtlink(link, p); nxt;
346 nxt = (nitem *)dtlink(list, nxt)) {
347 if (nxt->val != oldval)
348 break;
349 }
350 if (!nxt)
351 break;
352 }
353 for (nxp = nxt; nxp; nxp = (nitem *)dtlink(list, nxp)) {
354 if (intersect(p, nxp))
355 agedge(vg, p->vnode, nxp->vnode, NULL, 1);
356 }
357 }
358
359 /* Remove redundant constraints here. However, the cost of doing this
360 * may be a good deal more than the time saved in network simplex. Also,
361 * if the graph is changed, the ND_in and ND_out data has to be updated.
362 */
363 mapGraphs(vg, cg, dist);
364 agclose(vg);
365
366 return cg;
367}
368
369static void closeGraph(graph_t * cg)
370{
371 for (node_t *n = agfstnode(cg); n; n = agnxtnode(cg, n)) {
372 free_list(ND_in(n));
373 free_list(ND_out(n));
374 }
375 agclose(cg);
376}
377
378/* Create the X constrains and solve. We use a linear objective function
379 * (absolute values rather than squares), so we can reuse network simplex.
380 * The constraints are encoded as a dag with edges having a minimum length.
381 */
382static void constrainX(graph_t *g, nitem *nlist, size_t nnodes, intersectfn ifn,
383 int ortho)
384{
385 Dt_t *list = dtopen(&constr, Dtobag);
386 nitem *p = nlist;
387 graph_t *cg;
388
389 for (size_t i = 0; i < nnodes; i++) {
390 p->val = p->pos.x;
391 dtinsert(list, p);
392 p++;
393 }
394 if (ortho)
395 cg = mkConstraintG(list, ifn, distX);
396 else
397 cg = mkNConstraintG(g, list, ifn, distX);
398 rank(cg, 2, INT_MAX);
399
400 p = nlist;
401 for (size_t i = 0; i < nnodes; i++) {
402 int newpos, oldpos, delta;
403 oldpos = p->pos.x;
404 newpos = ND_rank(p->cnode);
405 delta = newpos - oldpos;
406 p->pos.x = newpos;
407 p->bb.LL.x += delta;
408 p->bb.UR.x += delta;
409 p++;
410 }
411
412 closeGraph(cg);
413 dtclose(list);
414}
415
417static void constrainY(graph_t *g, nitem *nlist, size_t nnodes, intersectfn ifn,
418 int ortho)
419{
420 Dt_t *list = dtopen(&constr, Dtobag);
421 nitem *p = nlist;
422 graph_t *cg;
423
424 for (size_t i = 0; i < nnodes; i++) {
425 p->val = p->pos.y;
426 dtinsert(list, p);
427 p++;
428 }
429 if (ortho)
430 cg = mkConstraintG(list, ifn, distY);
431 else
432 cg = mkNConstraintG(g, list, ifn, distY);
433 rank(cg, 2, INT_MAX);
434#ifdef DEBUG
435 {
436 Agsym_t *mlsym = agattr_text(cg, AGEDGE, "minlen", "");
437 Agsym_t *rksym = agattr_text(cg, AGNODE, "rank", "");
438 node_t *n;
439 edge_t *e;
440 for (n = agfstnode(cg); n; n = agnxtnode(cg, n)) {
441 agxset(n, rksym, ITOS(ND_rank(n)));
442 for (e = agfstedge(cg, n); e; e = agnxtedge(cg, e, n)) {
443 agxset(e, mlsym, ITOS(ED_minlen(e)));
444 }
445 }
446 }
447#endif
448
449 p = nlist;
450 for (size_t i = 0; i < nnodes; i++) {
451 int newpos, oldpos, delta;
452 oldpos = p->pos.y;
453 newpos = ND_rank(p->cnode);
454 delta = newpos - oldpos;
455 p->pos.y = newpos;
456 p->bb.LL.y += delta;
457 p->bb.UR.y += delta;
458 p++;
459 }
460
461 closeGraph(cg);
462 dtclose(list);
463}
464
465static int overlaps(nitem *p, size_t cnt) {
466 nitem *pi = p;
467 nitem *pj;
468
469 for (size_t i = 0; i + 1 < cnt; i++) {
470 pj = pi + 1;
471 for (size_t j = i + 1; j < cnt; j++) {
472 if (OVERLAP(pi->bb, pj->bb))
473 return 1;
474 pj++;
475 }
476 pi++;
477 }
478 return 0;
479}
480
481static void initItem(node_t * n, nitem * p, expand_t margin)
482{
483 int x = POINTS(SCALE * ND_pos(n)[0]);
484 int y = POINTS(SCALE * ND_pos(n)[1]);
485 int w2, h2;
486 box b;
487
488 if (margin.doAdd) {
489 w2 = d2i(SCALE * (POINTS(ND_width(n) / 2.0) + margin.x));
490 h2 = d2i(SCALE * (POINTS(ND_height(n) / 2.0) + margin.y));
491 }
492 else {
493 w2 = POINTS(margin.x * SCALE2 * ND_width(n));
494 h2 = POINTS(margin.y * SCALE2 * ND_height(n));
495 }
496
497 b.LL.x = x - w2;
498 b.LL.y = y - h2;
499 b.UR.x = x + w2;
500 b.UR.y = y + h2;
501
502 p->pos.x = x;
503 p->pos.y = y;
504 p->np = n;
505 p->bb = b;
506}
507
508/* Use optimization to remove overlaps.
509 * Modifications;
510 * - do y;x then x;y and use the better one
511 * - for all overlaps (or if overlap with leftmost nodes), add a constraint;
512 * constraint could move both x and y away, or the smallest, or some
513 * mixture.
514 * - follow by a scale down using actual shapes
515 * We use an optimization based on Marriott, Stuckey, Tam and He,
516 * "Removing Node Overlapping in Graph Layout Using Constrained Optimization",
517 * Constraints,8(2):143--172, 2003.
518 * We solve 2 constraint problem, one in X, one in Y. In each dimension,
519 * we require relative positions to remain the same. That is, if two nodes
520 * have the same x originally, they have the same x at the end, and if one
521 * node is to the left of another, it remains to the left. In addition, if
522 * two nodes could overlap by moving their X coordinates, we insert a constraint
523 * to keep the two nodes sufficiently apart. Similarly, for Y.
524 *
525 * mode = AM_ORTHOXY => first X, then Y
526 * mode = AM_ORTHOYX => first Y, then X
527 * mode = AM_ORTHO => first X, then Y
528 * mode = AM_ORTHO_YX => first Y, then X
529 * In the last 2 cases, relax the constraints as follows: during the X pass,
530 * if two nodes actually intersect and a smaller move in the Y direction
531 * will remove the overlap, we don't force the nodes apart in the X direction,
532 * but leave it for the Y pass to remove any remaining overlaps. Without this,
533 * the X pass will remove all overlaps, and the Y pass only compresses in the
534 * Y direction, causing a skewing of the aspect ratio.
535 */
536int cAdjust(graph_t * g, int mode)
537{
538 expand_t margin;
539 int ret;
540 const size_t nnodes = agnnodes_z(g);
541 nitem *nlist = gv_calloc(nnodes, sizeof(nitem));
542 nitem *p = nlist;
543 node_t *n;
544
545 margin = sepFactor (g);
546
547 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
548 initItem(n, p, margin);
549 p++;
550 }
551
552 if (overlaps(nlist, nnodes)) {
553 point pt;
554
555 switch ((adjust_mode)mode) {
556 case AM_ORTHOXY:
557 constrainX(g, nlist, nnodes, intersectY, 1);
558 constrainY(g, nlist, nnodes, intersectX, 1);
559 break;
560 case AM_ORTHOYX:
561 constrainY(g, nlist, nnodes, intersectX, 1);
562 constrainX(g, nlist, nnodes, intersectY, 1);
563 break;
564 case AM_ORTHO :
565 constrainX(g, nlist, nnodes, intersectY0, 1);
566 constrainY(g, nlist, nnodes, intersectX, 1);
567 break;
568 case AM_ORTHO_YX :
569 constrainY(g, nlist, nnodes, intersectX0, 1);
570 constrainX(g, nlist, nnodes, intersectY, 1);
571 break;
572 case AM_PORTHOXY:
573 constrainX(g, nlist, nnodes, intersectY, 0);
574 constrainY(g, nlist, nnodes, intersectX, 0);
575 break;
576 case AM_PORTHOYX:
577 constrainY(g, nlist, nnodes, intersectX, 0);
578 constrainX(g, nlist, nnodes, intersectY, 0);
579 break;
580 case AM_PORTHO_YX :
581 constrainY(g, nlist, nnodes, intersectX0, 0);
582 constrainX(g, nlist, nnodes, intersectY, 0);
583 break;
584 case AM_PORTHO :
585 default :
586 constrainX(g, nlist, nnodes, intersectY0, 0);
587 constrainY(g, nlist, nnodes, intersectX, 0);
588 break;
589 }
590 p = nlist;
591 for (size_t i = 0; i < nnodes; i++) {
592 n = p->np;
593 pt = p->pos;
594 ND_pos(n)[0] = PS2INCH(pt.x) / SCALE;
595 ND_pos(n)[1] = PS2INCH(pt.y) / SCALE;
596 p++;
597 }
598 ret = 1;
599 }
600 else ret = 0;
601 free(nlist);
602 return ret;
603}
604
605typedef struct {
606 pointf pos; /* position for sorting */
608 double wd2;
609 double ht2;
611} info;
612
613static int sortf(const void *x, const void *y) {
614 const pointf *p = x;
615 const pointf *q = y;
616 if (p->x < q->x)
617 return -1;
618 else if (p->x > q->x)
619 return 1;
620 else if (p->y < q->y)
621 return -1;
622 else if (p->y > q->y)
623 return 1;
624 else
625 return 0;
626}
627
628static double compress(info *nl, size_t nn) {
629 info *p = nl;
630 info *q;
631 double s, sc = 0;
632 pointf pt;
633
634 for (size_t i = 0; i < nn; i++) {
635 q = p + 1;
636 for (size_t j = i + 1; j < nn; j++) {
637 if (OVERLAP(p->bb, q->bb))
638 return 0;
639 if (p->pos.x == q->pos.x)
640 pt.x = HUGE_VAL;
641 else {
642 pt.x = (p->wd2 + q->wd2) / fabs(p->pos.x - q->pos.x);
643 }
644 if (p->pos.y == q->pos.y)
645 pt.y = HUGE_VAL;
646 else {
647 pt.y = (p->ht2 + q->ht2) / fabs(p->pos.y - q->pos.y);
648 }
649 if (pt.y < pt.x)
650 s = pt.y;
651 else
652 s = pt.x;
653 if (s > sc)
654 sc = s;
655 q++;
656 }
657 p++;
658 }
659 return sc;
660}
661
662static pointf *mkOverlapSet(info *nl, size_t nn, size_t *cntp) {
663 info *p = nl;
664 info *q;
665 LIST(pointf) S = {0};
666
667 LIST_APPEND(&S, (pointf){0});
668
669 for (size_t i = 0; i < nn; i++) {
670 q = p + 1;
671 for (size_t j = i + 1; j < nn; j++) {
672 if (OVERLAP(p->bb, q->bb)) {
673 pointf pt;
674 if (p->pos.x == q->pos.x)
675 pt.x = HUGE_VAL;
676 else {
677 pt.x = (p->wd2 + q->wd2) / fabs(p->pos.x - q->pos.x);
678 if (pt.x < 1)
679 pt.x = 1;
680 }
681 if (p->pos.y == q->pos.y)
682 pt.y = HUGE_VAL;
683 else {
684 pt.y = (p->ht2 + q->ht2) / fabs(p->pos.y - q->pos.y);
685 if (pt.y < 1)
686 pt.y = 1;
687 }
688 LIST_APPEND(&S, pt);
689 }
690 q++;
691 }
692 p++;
693 }
694
696 pointf *ret;
697 LIST_DETACH(&S, &ret, cntp);
698 return ret;
699}
700
701static pointf computeScaleXY(pointf *aarr, size_t m) {
702 double cost, bestcost;
704
705 aarr[0].x = 1;
706 aarr[0].y = HUGE_VAL;
707 qsort(aarr + 1, m - 1, sizeof(pointf), sortf);
708
709 pointf *barr = gv_calloc(m, sizeof(pointf));
710 barr[m - 1].x = aarr[m - 1].x;
711 barr[m - 1].y = 1;
712 for (size_t k = m - 2; m > 1; k--) {
713 barr[k].x = aarr[k].x;
714 barr[k].y = fmax(aarr[k + 1].y, barr[k + 1].y);
715 if (k == 0) {
716 break;
717 }
718 }
719
720 size_t best = 0;
721 bestcost = HUGE_VAL;
722 for (size_t k = 0; k < m; k++) {
723 cost = barr[k].x * barr[k].y;
724 if (cost < bestcost) {
725 bestcost = cost;
726 best = k;
727 }
728 }
729 assert(bestcost < HUGE_VAL);
730 scale.x = barr[best].x;
731 scale.y = barr[best].y;
732
733 free(barr);
734 return scale;
735}
736
737/* For each (x,y) in aarr, scale has to be bigger than the smallest one.
738 * So, the scale is the max min.
739 */
740static double computeScale(pointf *aarr, size_t m) {
741 double sc = 0;
742 double v;
743 pointf p;
744
745 aarr++;
746 for (size_t i = 1; i < m; i++) {
747 p = *aarr++;
748 v = fmin(p.x, p.y);
749 if (v > sc)
750 sc = v;
751 }
752 return sc;
753}
754
755/* Scale the layout.
756 * equal > 0 => scale uniformly in x and y to remove overlaps
757 * equal = 0 => scale separately in x and y to remove overlaps
758 * equal < 0 => scale down uniformly in x and y to remove excess space
759 * The last assumes there are no overlaps at present.
760 * Based on Marriott, Stuckey, Tam and He,
761 * "Removing Node Overlapping in Graph Layout Using Constrained Optimization",
762 * Constraints,8(2):143--172, 2003.
763 */
764int scAdjust(graph_t * g, int equal)
765{
766 const size_t nnodes = agnnodes_z(g);
767 info *nlist = gv_calloc(nnodes, sizeof(info));
768 info *p = nlist;
769 node_t *n;
770 pointf s;
771 expand_t margin;
772 pointf *aarr;
773
774 margin = sepFactor (g);
775 if (margin.doAdd) {
776 /* we use inches below */
777 margin.x = PS2INCH(margin.x);
778 margin.y = PS2INCH(margin.y);
779 }
780
781 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
782 double w2, h2;
783 if (margin.doAdd) {
784 w2 = ND_width(n) / 2.0 + margin.x;
785 h2 = ND_height(n) / 2.0 + margin.y;
786 }
787 else {
788 w2 = margin.x * ND_width(n) / 2.0;
789 h2 = margin.y * ND_height(n) / 2.0;
790 }
791 p->pos.x = ND_pos(n)[0];
792 p->pos.y = ND_pos(n)[1];
793 p->bb.LL.x = p->pos.x - w2;
794 p->bb.LL.y = p->pos.y - h2;
795 p->bb.UR.x = p->pos.x + w2;
796 p->bb.UR.y = p->pos.y + h2;
797 p->wd2 = w2;
798 p->ht2 = h2;
799 p->np = n;
800 p++;
801 }
802
803 if (equal < 0) {
804 s.x = s.y = compress(nlist, nnodes);
805 if (s.x == 0) { /* overlaps exist */
806 free(nlist);
807 return 0;
808 }
809 if (Verbose) fprintf(stderr, "compress %g \n", s.x);
810 } else {
811 size_t m;
812 aarr = mkOverlapSet(nlist, nnodes, &m);
813
814 if (m == 1) { // no overlaps
815 free(aarr);
816 free(nlist);
817 return 0;
818 }
819
820 if (equal) {
821 s.x = s.y = computeScale(aarr, m);
822 } else {
823 s = computeScaleXY(aarr, m);
824 }
825 free(aarr);
826 if (Verbose) fprintf(stderr, "scale by %g,%g \n", s.x, s.y);
827 }
828
829 p = nlist;
830 for (size_t i = 0; i < nnodes; i++) {
831 ND_pos(p->np)[0] = s.x * p->pos.x;
832 ND_pos(p->np)[1] = s.y * p->pos.y;
833 p++;
834 }
835
836 free(nlist);
837 return 1;
838}
static void newpos(Info_t *ip)
Definition adjust.c:329
expand_t sepFactor(graph_t *g)
Definition adjust.c:1046
adjust_mode
Definition adjust.h:26
@ AM_PORTHOYX
Definition adjust.h:30
@ AM_PORTHO
Definition adjust.h:30
@ AM_ORTHO_YX
Definition adjust.h:29
@ AM_ORTHO
Definition adjust.h:29
@ AM_PORTHO_YX
Definition adjust.h:30
@ AM_ORTHOYX
Definition adjust.h:29
@ AM_ORTHOXY
Definition adjust.h:29
@ AM_PORTHOXY
Definition adjust.h:30
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
CDT_API Dtlink_t * dtflatten(Dt_t *)
Definition dtflatten.c:12
CDT_API Dtmethod_t * Dtobag
ordered multiset
Definition dttree.c:307
#define dtlink(d, e)
Definition cdt.h:176
#define dtinsert(d, o)
Definition cdt.h:186
CDT_API int dtclose(Dt_t *)
Definition dtclose.c:10
CDT_API Dt_t * dtopen(Dtdisc_t *, Dtmethod_t *)
Definition dtopen.c:11
abstract graph C library, Cgraph API
static graph_t * mkNConstraintG(graph_t *g, Dt_t *list, intersectfn intersect, distfn dist)
Definition constraint.c:210
#define SCALE
Definition constraint.c:26
static graph_t * mkConstraintG(Dt_t *list, intersectfn intersect, distfn dist)
Definition constraint.c:268
static double compress(info *nl, size_t nn)
Definition constraint.c:628
static int intersectY0(nitem *p, nitem *q)
Definition constraint.c:99
int scAdjust(graph_t *g, int equal)
Definition constraint.c:764
int(* intersectfn)(nitem *, nitem *)
Definition constraint.c:40
static int distX(box *b1, box *b2)
Definition constraint.c:68
static void closeGraph(graph_t *cg)
Definition constraint.c:369
static Dtdisc_t constr
Definition constraint.c:54
static pointf computeScaleXY(pointf *aarr, size_t m)
Definition constraint.c:701
static int distY(box *b1, box *b2)
Definition constraint.c:63
static void constrainX(graph_t *g, nitem *nlist, size_t nnodes, intersectfn ifn, int ortho)
Definition constraint.c:382
static int cmpitem(void *item1, void *item2)
Definition constraint.c:42
static double computeScale(pointf *aarr, size_t m)
Definition constraint.c:740
static void constrainY(graph_t *g, nitem *nlist, size_t nnodes, intersectfn ifn, int ortho)
see constrainX
Definition constraint.c:417
static int intersectX(nitem *p, nitem *q)
Definition constraint.c:120
static void initItem(node_t *n, nitem *p, expand_t margin)
Definition constraint.c:481
static pointf * mkOverlapSet(info *nl, size_t nn, size_t *cntp)
Definition constraint.c:662
#define SCALE2
Definition constraint.c:27
static int overlaps(nitem *p, size_t cnt)
Definition constraint.c:465
int(* distfn)(box *, box *)
Definition constraint.c:39
static int intersectX0(nitem *p, nitem *q)
Definition constraint.c:78
static int sortf(const void *x, const void *y)
Definition constraint.c:613
static void mapGraphs(graph_t *g, graph_t *cg, distfn dist)
Definition constraint.c:125
int cAdjust(graph_t *g, int mode)
Definition constraint.c:536
static int intersectY(nitem *p, nitem *q)
Definition constraint.c:115
mode
Definition cvtgxl.c:33
#define S
Definition expr.h:72
static double dist(int dim, double *x, double *y)
geometric types and macros (e.g. points and boxes)
#define PS2INCH(a_points)
Definition geom.h:64
#define OVERLAP(b0, b1)
Definition geom.h:48
#define POINTS(a_inches)
Definition geom.h:62
static WUR pointf scale(double c, pointf p)
Definition geomprocs.h:148
static bool Verbose
Definition gml2gv.c:26
void free(void *)
node NULL
Definition grammar.y:181
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:204
int agnnodes(Agraph_t *g)
Definition graph.c:163
size_t agnnodes_z(const Agraph_t *g)
Definition graph.c:161
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:333
int agxset(void *obj, Agsym_t *sym, const char *value)
Definition attr.c:521
Agedge_t * agedge(Agraph_t *g, Agnode_t *t, Agnode_t *h, char *name, int createflag)
Definition edge.c:255
#define ED_minlen(e)
Definition types.h:592
Agedge_t * agnxtin(Agraph_t *g, Agedge_t *e)
Definition edge.c:73
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:28
#define agtail(e)
Definition cgraph.h:982
#define ED_weight(e)
Definition types.h:603
#define agfindedge(g, t, h)
Definition types.h:609
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:98
#define aghead(e)
Definition cgraph.h:983
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:43
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:89
Agedge_t * agfstin(Agraph_t *g, Agnode_t *n)
Definition edge.c:59
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:97
#define GD_nlist(g)
Definition types.h:393
Agdesc_t Agstrictdirected
strict directed. A strict graph cannot have multi-edges or self-arcs.
Definition graph.c:279
Agraph_t * agopen(char *name, Agdesc_t desc, Agdisc_t *disc)
creates a new graph with the given name and kind
Definition graph.c:44
Agnode_t * agnode(Agraph_t *g, char *name, int createflag)
Definition node.c:143
#define ND_rank(n)
Definition types.h:523
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:50
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:43
#define ND_next(n)
Definition types.h:510
#define ND_alg(n)
Definition types.h:484
#define ND_height(n)
Definition types.h:498
#define ND_width(n)
Definition types.h:536
#define ND_pos(n)
Definition types.h:520
#define ND_in(n)
Definition types.h:501
#define ND_out(n)
Definition types.h:515
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
void * agbindrec(void *obj, const char *name, unsigned int recsize, int move_to_front)
attaches a new record of the given size to the object
Definition rec.c:91
Arithmetic helper functions.
static int d2i(double v)
Definition gv_math.h:157
$2 prev
Definition htmlparse.y:291
#define ITOS(i)
Definition itos.h:43
type-generic dynamically expanding list
#define LIST_DETACH(list, datap, sizep)
Definition list.h:427
#define LIST_APPEND(list,...)
Definition list.h:124
#define LIST(type)
Definition list.h:55
#define LIST_SHRINK_TO_FIT(list)
Definition list.h:338
#define delta
Definition maze.c:136
int rank(graph_t *g, int balance, int maxiter)
Definition ns.c:1029
static double cg(SparseMatrix A, const double *precond, size_t n, int dim, double *x0, double *rhs, double tol, double maxit)
graph or subgraph
Definition cgraph.h:424
string attribute descriptor symbol in Agattr_s.dict
Definition cgraph.h:641
Definition geom.h:39
point LL
Definition geom.h:39
point UR
Definition geom.h:39
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
Definition cdt.h:98
double x
Definition adjust.h:42
double y
Definition adjust.h:42
bool doAdd
Definition adjust.h:43
boxf bb
Definition constraint.c:607
double wd2
Definition constraint.c:608
double ht2
Definition constraint.c:609
pointf pos
Definition constraint.c:606
node_t * np
Definition constraint.c:610
node_t * cnode
Definition constraint.c:34
int val
Definition constraint.c:31
node_t * np
Definition constraint.c:33
point pos
Definition constraint.c:32
box bb
Definition constraint.c:36
Dtlink_t link
Definition constraint.c:30
node_t * vnode
Definition constraint.c:35
Definition geom.h:27
int y
Definition geom.h:27
int x
Definition geom.h:27
double x
Definition geom.h:29
double y
Definition geom.h:29
#define free_list(L)
Definition types.h:272
#define elist_append(item, L)
Definition types.h:261
#define alloc_elist(n, L)
Definition types.h:267
Definition grammar.c:90
static bool intersect(Ppoint_t a, Ppoint_t b, Ppoint_t c, Ppoint_t d)
Definition visibility.c:80