1 | /* $Id$ $Revision$ */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2011 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | |
15 | /* |
16 | * Written by Emden R. Gansner |
17 | */ |
18 | |
19 | |
20 | |
21 | #include "config.h" |
22 | |
23 | #include <getopt.h> |
24 | |
25 | #include <assert.h> |
26 | #include "gvc.h" |
27 | #include "render.h" |
28 | #include "neatoprocs.h" |
29 | #include "ingraphs.h" |
30 | #include "pack.h" |
31 | |
32 | /*visual studio*/ |
33 | #if defined(_WIN32) |
34 | #define extern __declspec(dllimport) |
35 | #endif |
36 | /*end visual studio*/ |
37 | extern gvplugin_library_t gvplugin_neato_layout_LTX_library; |
38 | #undef extern |
39 | |
40 | lt_symlist_t lt_preloaded_symbols[] = { |
41 | #if defined(_WIN32) |
42 | { "gvplugin_neato_layout_LTX_library" , 0 }, |
43 | #else |
44 | { "gvplugin_neato_layout_LTX_library" , (void*)(&gvplugin_neato_layout_LTX_library) }, |
45 | #endif |
46 | { 0, 0 } |
47 | }; |
48 | |
49 | /* gvpack: |
50 | * Input consists of graphs in dot format. |
51 | * The graphs should have pos, width and height set for nodes, |
52 | * bb set for clusters, and, optionally, spline info for edges. |
53 | * The graphs are packed geometrically and combined |
54 | * into a single output graph, ready to be sent to neato -s -n2. |
55 | * -m <i> specifies the margin, in points, about each graph. |
56 | */ |
57 | |
58 | typedef struct { |
59 | Dtlink_t link; |
60 | char *name; |
61 | char *value; |
62 | int cnt; |
63 | } attr_t; |
64 | |
65 | typedef struct { |
66 | Dtlink_t link; |
67 | char *name; |
68 | int cnt; |
69 | } pair_t; |
70 | |
71 | static int verbose = 0; |
72 | static char **myFiles = 0; |
73 | static int nGraphs = 0; /* Guess as to no. of graphs */ |
74 | static FILE *outfp; /* output; stdout by default */ |
75 | static Agdesc_t kind; /* type of graph */ |
76 | static int G_cnt; /* No. of -G arguments */ |
77 | static int G_sz; /* Storage size for -G arguments */ |
78 | static attr_t *G_args; /* Storage for -G arguments */ |
79 | static int doPack; /* Do packing if true */ |
80 | static char* gname = "root" ; |
81 | |
82 | #define NEWNODE(n) ((node_t*)ND_alg(n)) |
83 | |
84 | static char *useString = |
85 | "Usage: gvpack [-gnuv?] [-m<margin>] {-array[_rc][n]] [-o<outf>] <files>\n\ |
86 | -n - use node granularity\n\ |
87 | -g - use graph granularity\n\ |
88 | -array* - pack as array of graphs\n\ |
89 | -G<n>=<v> - attach name/value attribute to output graph\n\ |
90 | -m<n> - set margin to <n> points\n\ |
91 | -s<gname> - use <gname> for name of root graph\n\ |
92 | -o<outfile> - write output to <outfile>\n\ |
93 | -u - no packing; just combine graphs\n\ |
94 | -v - verbose\n\ |
95 | -? - print usage\n\ |
96 | If no files are specified, stdin is used\n" ; |
97 | |
98 | static void usage(int v) |
99 | { |
100 | printf("%s" ,useString); |
101 | exit(v); |
102 | } |
103 | |
104 | static FILE *openFile(char *name, char *mode) |
105 | { |
106 | FILE *fp; |
107 | char *modestr; |
108 | |
109 | fp = fopen(name, mode); |
110 | if (!fp) { |
111 | if (*mode == 'r') |
112 | modestr = "reading" ; |
113 | else |
114 | modestr = "writing" ; |
115 | fprintf(stderr, "gvpack: could not open file %s for %s\n" , |
116 | name, modestr); |
117 | exit(1); |
118 | } |
119 | return (fp); |
120 | } |
121 | |
122 | #define G_CHUNK 10 |
123 | |
124 | /* setNameValue: |
125 | * If arg is a name-value pair, add it to the list |
126 | * and return 0; otherwise, return 1. |
127 | */ |
128 | static int setNameValue(char *arg) |
129 | { |
130 | char *p; |
131 | char *rhs = "true" ; |
132 | |
133 | if ((p = strchr(arg, '='))) { |
134 | *p++ = '\0'; |
135 | rhs = p; |
136 | } |
137 | if (G_cnt >= G_sz) { |
138 | G_sz += G_CHUNK; |
139 | G_args = RALLOC(G_sz, G_args, attr_t); |
140 | } |
141 | G_args[G_cnt].name = arg; |
142 | G_args[G_cnt].value = rhs; |
143 | G_cnt++; |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | /* setUInt: |
149 | * If arg is an integer, value is stored in v |
150 | * and function returns 0; otherwise, returns 1. |
151 | */ |
152 | static int setUInt(unsigned int *v, char *arg) |
153 | { |
154 | char *p; |
155 | unsigned int i; |
156 | |
157 | i = (unsigned int) strtol(arg, &p, 10); |
158 | if (p == arg) { |
159 | fprintf(stderr, "Error: bad value in flag -%s - ignored\n" , |
160 | arg - 1); |
161 | return 1; |
162 | } |
163 | *v = i; |
164 | return 0; |
165 | } |
166 | |
167 | static Agsym_t *agraphattr(Agraph_t *g, char *name, char *value) |
168 | { |
169 | return agattr(g, AGRAPH, name, value); |
170 | } |
171 | |
172 | static Agsym_t *agnodeattr(Agraph_t *g, char *name, char *value) |
173 | { |
174 | return agattr(g, AGNODE, name, value); |
175 | } |
176 | |
177 | static Agsym_t *agedgeattr(Agraph_t *g, char *name, char *value) |
178 | { |
179 | return agattr(g, AGEDGE, name, value); |
180 | } |
181 | |
182 | /* init: |
183 | */ |
184 | static void init(int argc, char *argv[], pack_info* pinfo) |
185 | { |
186 | int c, len; |
187 | char buf[BUFSIZ]; |
188 | char* bp; |
189 | |
190 | agnodeattr(NULL, "label" , NODENAME_ESC); |
191 | pinfo->mode = l_clust; |
192 | pinfo->margin = CL_OFFSET; |
193 | pinfo->doSplines = TRUE; /* Use edges in packing */ |
194 | pinfo->fixed = 0; |
195 | pinfo->sz = 0; |
196 | |
197 | opterr = 0; |
198 | while ((c = getopt(argc, argv, ":na:gvum:s:o:G:" )) != -1) { |
199 | switch (c) { |
200 | case 'a': |
201 | len = strlen(optarg) + 2; |
202 | if (len > BUFSIZ) |
203 | bp = N_GNEW(len, char); |
204 | else |
205 | bp = buf; |
206 | sprintf (bp, "a%s\n" , optarg); |
207 | parsePackModeInfo (bp, pinfo->mode, pinfo); |
208 | if (bp != buf) |
209 | free (bp); |
210 | break; |
211 | case 'n': |
212 | parsePackModeInfo ("node" , pinfo->mode, pinfo); |
213 | break; |
214 | case 's': |
215 | gname = optarg; |
216 | break; |
217 | case 'g': |
218 | parsePackModeInfo ("graph" , pinfo->mode, pinfo); |
219 | break; |
220 | case 'm': |
221 | setUInt(&pinfo->margin, optarg); |
222 | break; |
223 | case 'o': |
224 | outfp = openFile(optarg, "w" ); |
225 | break; |
226 | case 'u': |
227 | pinfo->mode = l_undef; |
228 | break; |
229 | case 'G': |
230 | if (*optarg) |
231 | setNameValue(optarg); |
232 | else |
233 | fprintf(stderr, |
234 | "gvpack: option -G missing argument - ignored\n" ); |
235 | break; |
236 | case 'v': |
237 | verbose = 1; |
238 | Verbose = 1; |
239 | break; |
240 | case ':': |
241 | fprintf(stderr, "gvpack: option -%c missing argument - ignored\n" , optopt); |
242 | break; |
243 | case '?': |
244 | if (optopt == '?') |
245 | usage(0); |
246 | else |
247 | fprintf(stderr, |
248 | "gvpack: option -%c unrecognized - ignored\n" , optopt); |
249 | break; |
250 | } |
251 | } |
252 | argv += optind; |
253 | argc -= optind; |
254 | |
255 | if (argc > 0) { |
256 | myFiles = argv; |
257 | nGraphs = argc; /* guess one graph per file */ |
258 | } else |
259 | nGraphs = 10; /* initial guess as to no. of graphs */ |
260 | if (!outfp) |
261 | outfp = stdout; /* stdout the default */ |
262 | if (verbose) |
263 | fprintf (stderr, " margin %d\n" , pinfo->margin); |
264 | } |
265 | |
266 | /* init_node_edge: |
267 | * initialize node and edge attributes |
268 | */ |
269 | static void init_node_edge(Agraph_t * g) |
270 | { |
271 | node_t *n; |
272 | edge_t *e; |
273 | int nG = agnnodes(g); |
274 | attrsym_t *N_pos = agfindnodeattr(g, "pos" ); |
275 | attrsym_t *N_pin = agfindnodeattr(g, "pin" ); |
276 | |
277 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
278 | neato_init_node(n); |
279 | user_pos(N_pos, N_pin, n, nG); /* set user position if given */ |
280 | } |
281 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
282 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) |
283 | common_init_edge(e); |
284 | } |
285 | } |
286 | |
287 | /* init_graph: |
288 | * Initialize attributes. We always do the minimum required by |
289 | * libcommon. If fill is true, we use init_nop (neato -n) to |
290 | * read in attributes relevant to the layout. |
291 | */ |
292 | static void init_graph(Agraph_t * g, boolean fill, GVC_t* gvc) |
293 | { |
294 | int d; |
295 | node_t *n; |
296 | edge_t *e; |
297 | |
298 | aginit (g, AGRAPH, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); |
299 | aginit (g, AGNODE, "Agnodeinfo_t" , sizeof(Agnodeinfo_t), TRUE); |
300 | aginit (g, AGEDGE, "Agedgeinfo_t" , sizeof(Agedgeinfo_t), TRUE); |
301 | GD_gvc(g) = gvc; |
302 | graph_init(g, FALSE); |
303 | d = late_int(g, agfindgraphattr(g, "dim" ), 2, 2); |
304 | if (d != 2) { |
305 | fprintf(stderr, "Error: graph %s has dim = %d (!= 2)\n" , agnameof(g), |
306 | d); |
307 | exit(1); |
308 | } |
309 | Ndim = GD_ndim(g) = 2; |
310 | init_node_edge(g); |
311 | if (fill) { |
312 | int ret = init_nop(g,0); |
313 | if (ret) { |
314 | if (ret < 0) |
315 | fprintf(stderr, "Error loading layout info from graph %s\n" , |
316 | agnameof(g)); |
317 | else if (ret > 0) |
318 | fprintf(stderr, "gvpack does not support backgrounds as found in graph %s\n" , |
319 | agnameof(g)); |
320 | exit(1); |
321 | } |
322 | if (Concentrate) { /* check for edges without pos info */ |
323 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
324 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) { |
325 | if (ED_spl(e) == NULL) ED_edge_type(e) = IGNORED; |
326 | } |
327 | } |
328 | } |
329 | } |
330 | } |
331 | |
332 | /* cloneAttrs: |
333 | * Copy all attributes from old object to new. Assume |
334 | * attributes have been initialized. |
335 | */ |
336 | static void cloneDfltAttrs(Agraph_t *old, Agraph_t *new, int kind) |
337 | { |
338 | Agsym_t *a; |
339 | |
340 | for (a = agnxtattr(old, kind, 0); a; a = agnxtattr(old, kind, a)) { |
341 | if (aghtmlstr(a->defval)) |
342 | agattr (new, kind, a->name, agstrdup_html(new, a->defval)); |
343 | else |
344 | agattr (new, kind, a->name, a->defval); |
345 | } |
346 | } |
347 | static void cloneAttrs(void *old, void *new) |
348 | { |
349 | int kind = AGTYPE(old); |
350 | Agsym_t *a; |
351 | char* s; |
352 | Agraph_t *g = agroot(old); |
353 | Agraph_t *ng = agroot(new); |
354 | |
355 | for (a = agnxtattr(g, kind, 0); a; a = agnxtattr(g, kind, a)) { |
356 | s = agxget (old, a); |
357 | if (aghtmlstr(s)) |
358 | agset(new, a->name, agstrdup_html(ng, s)); |
359 | else |
360 | agset(new, a->name, s); |
361 | } |
362 | } |
363 | |
364 | /* cloneEdge: |
365 | * Note that here, and in cloneNode and cloneCluster, |
366 | * we do a shallow copy. We thus assume that attributes |
367 | * are not disturbed. In particular, we assume they are |
368 | * not deallocated. |
369 | */ |
370 | static void cloneEdge(Agedge_t * old, Agedge_t * new) |
371 | { |
372 | cloneAttrs(old, new); |
373 | ED_spl(new) = ED_spl(old); |
374 | ED_edge_type(new) = ED_edge_type(old); |
375 | ED_label(new) = ED_label(old); |
376 | ED_head_label(new) = ED_head_label(old); |
377 | ED_tail_label(new) = ED_tail_label(old); |
378 | ED_xlabel(new) = ED_xlabel(old); |
379 | } |
380 | |
381 | /* cloneNode: |
382 | */ |
383 | static void cloneNode(Agnode_t * old, Agnode_t * new) |
384 | { |
385 | cloneAttrs(old, new); |
386 | ND_coord(new).x = POINTS(ND_pos(old)[0]); |
387 | ND_coord(new).y = POINTS(ND_pos(old)[1]); |
388 | ND_height(new) = ND_height(old); |
389 | ND_ht(new) = ND_ht(old); |
390 | ND_width(new) = ND_width(old); |
391 | ND_lw(new) = ND_lw(old); |
392 | ND_rw(new) = ND_rw(old); |
393 | ND_shape(new) = ND_shape(old); |
394 | ND_shape_info(new) = ND_shape_info(old); |
395 | ND_xlabel(new) = ND_xlabel(old); |
396 | } |
397 | |
398 | /* cloneCluster: |
399 | */ |
400 | static void cloneCluster(Agraph_t * old, Agraph_t * new) |
401 | { |
402 | /* string attributes were cloned as subgraphs */ |
403 | GD_label(new) = GD_label(old); |
404 | GD_bb(new) = GD_bb(old); |
405 | } |
406 | |
407 | /* freef: |
408 | * Generic free function for dictionaries. |
409 | */ |
410 | static void freef(Dt_t * dt, void * obj, Dtdisc_t * disc) |
411 | { |
412 | free(obj); |
413 | } |
414 | |
415 | static Dtdisc_t attrdisc = { |
416 | offsetof(attr_t, name), /* key */ |
417 | -1, /* size */ |
418 | offsetof(attr_t, link), /* link */ |
419 | (Dtmake_f) 0, |
420 | (Dtfree_f) freef, |
421 | (Dtcompar_f) 0, /* use strcmp */ |
422 | (Dthash_f) 0, |
423 | (Dtmemory_f) 0, |
424 | (Dtevent_f) 0 |
425 | }; |
426 | |
427 | /* fillDict: |
428 | * Fill newdict with all the name-value attributes of |
429 | * objp. If the attribute has already been defined and |
430 | * has a different default, set default to "". |
431 | */ |
432 | static void fillDict(Dt_t * newdict, Agraph_t* g, int kind) |
433 | { |
434 | Agsym_t *a; |
435 | char *name; |
436 | char *value; |
437 | attr_t *rv; |
438 | |
439 | for (a = agnxtattr(g,kind,0); a; a = agnxtattr(g,kind,a)) { |
440 | name = a->name; |
441 | value = a->defval; |
442 | rv = (attr_t *) dtmatch(newdict, name); |
443 | if (!rv) { |
444 | rv = NEW(attr_t); |
445 | rv->name = name; |
446 | rv->value = value; |
447 | rv->cnt = 1; |
448 | dtinsert(newdict, rv); |
449 | } else if (!strcmp(value, rv->value)) |
450 | rv->cnt++; |
451 | } |
452 | } |
453 | |
454 | /* fillGraph: |
455 | * Use all the name-value entries in the dictionary d to define |
456 | * to define universal node/edge/graph attributes for g. |
457 | * For a non-empty default value, the attribute must be defined and the |
458 | * same in all graphs. |
459 | */ |
460 | static void |
461 | fillGraph(Agraph_t * g, Dt_t * d, |
462 | Agsym_t * (*setf) (Agraph_t *, char *, char *), int cnt) |
463 | { |
464 | attr_t *av; |
465 | for (av = (attr_t *) dtflatten(d); av; |
466 | av = (attr_t *) dtlink(d, (Dtlink_t *) av)) { |
467 | if (cnt == av->cnt) |
468 | setf(g, av->name, av->value); |
469 | else |
470 | setf(g, av->name, "" ); |
471 | } |
472 | } |
473 | |
474 | /* initAttrs: |
475 | * Initialize the attributes of root as the union of the |
476 | * attributes in the graphs gs. |
477 | */ |
478 | static void initAttrs(Agraph_t * root, Agraph_t ** gs, int cnt) |
479 | { |
480 | Agraph_t *g; |
481 | Dt_t *n_attrs; |
482 | Dt_t *e_attrs; |
483 | Dt_t *g_attrs; |
484 | int i; |
485 | |
486 | n_attrs = dtopen(&attrdisc, Dtoset); |
487 | e_attrs = dtopen(&attrdisc, Dtoset); |
488 | g_attrs = dtopen(&attrdisc, Dtoset); |
489 | |
490 | for (i = 0; i < cnt; i++) { |
491 | g = gs[i]; |
492 | fillDict(g_attrs, g, AGRAPH); |
493 | fillDict(n_attrs, g, AGNODE); |
494 | fillDict(e_attrs, g, AGEDGE); |
495 | } |
496 | |
497 | fillGraph(root, g_attrs, agraphattr, cnt); |
498 | fillGraph(root, n_attrs, agnodeattr, cnt); |
499 | fillGraph(root, e_attrs, agedgeattr, cnt); |
500 | |
501 | dtclose(n_attrs); |
502 | dtclose(e_attrs); |
503 | dtclose(g_attrs); |
504 | } |
505 | |
506 | /* cloneGraphAttr: |
507 | */ |
508 | static void cloneGraphAttr(Agraph_t * g, Agraph_t * ng) |
509 | { |
510 | cloneAttrs(g, ng); |
511 | cloneDfltAttrs(g, ng, AGNODE); |
512 | cloneDfltAttrs(g, ng, AGEDGE); |
513 | } |
514 | |
515 | #ifdef UNIMPL |
516 | /* redoBB: |
517 | * If "bb" attribute is valid, translate to reflect graph's |
518 | * new packed position. |
519 | */ |
520 | static void redoBB(Agraph_t * g, char *s, Agsym_t * G_bb, point delta) |
521 | { |
522 | box bb; |
523 | char buf[100]; |
524 | |
525 | if (sscanf(s, "%d,%d,%d,%d" , &bb.LL.x, &bb.LL.y, &bb.UR.x, &bb.UR.y) == |
526 | 4) { |
527 | bb.LL.x += delta.x; |
528 | bb.LL.y += delta.y; |
529 | bb.UR.x += delta.x; |
530 | bb.UR.y += delta.y; |
531 | sprintf(buf, "%d,%d,%d,%d" , bb.LL.x, bb.LL.y, bb.UR.x, bb.UR.y); |
532 | agxset(g, G_bb->index, buf); |
533 | } |
534 | } |
535 | #endif |
536 | |
537 | /* xName: |
538 | * Create a name for an object in the new graph using the |
539 | * dictionary names and the old name. If the old name has not |
540 | * been used, use it and add it to names. If it has been used, |
541 | * create a new name using the old name and a number. |
542 | * Note that returned string will immediately made into an agstring. |
543 | */ |
544 | static char *xName(Dt_t * names, char *oldname) |
545 | { |
546 | static char* name = NULL; |
547 | static int namelen = 0; |
548 | char *namep; |
549 | pair_t *p; |
550 | int len; |
551 | |
552 | p = (pair_t *) dtmatch(names, oldname); |
553 | if (p) { |
554 | p->cnt++; |
555 | len = strlen(oldname) + 100; /* 100 for "_gv" and decimal no. */ |
556 | if (namelen < len) { |
557 | if (name) free (name); |
558 | name = N_NEW(len, char); |
559 | namelen = len; |
560 | } |
561 | sprintf(name, "%s_gv%d" , oldname, p->cnt); |
562 | namep = name; |
563 | } else { |
564 | p = NEW(pair_t); |
565 | p->name = oldname; |
566 | dtinsert(names, p); |
567 | namep = oldname; |
568 | } |
569 | return namep; |
570 | } |
571 | |
572 | #define MARK(e) (ED_alg(e) = e) |
573 | #define MARKED(e) (ED_alg(e)) |
574 | #define ISCLUSTER(g) (!strncmp(agnameof(g),"cluster",7)) |
575 | #define SETCLUST(g,h) (GD_alg(g) = h) |
576 | #define GETCLUST(g) ((Agraph_t*)GD_alg(g)) |
577 | |
578 | /* cloneSubg: |
579 | * Create a copy of g in ng, copying attributes, inserting nodes |
580 | * and adding edges. |
581 | */ |
582 | static void |
583 | cloneSubg(Agraph_t * g, Agraph_t * ng, Agsym_t * G_bb, Dt_t * gnames) |
584 | { |
585 | node_t *n; |
586 | node_t *nn; |
587 | edge_t *e; |
588 | edge_t *ne; |
589 | node_t *nt; |
590 | node_t *nh; |
591 | Agraph_t *subg; |
592 | Agraph_t *nsubg; |
593 | |
594 | cloneGraphAttr(g, ng); |
595 | if (doPack) |
596 | agxset(ng, G_bb, "" ); /* Unset all subgraph bb */ |
597 | |
598 | /* clone subgraphs */ |
599 | for (subg = agfstsubg (g); subg; subg = agnxtsubg (subg)) { |
600 | nsubg = agsubg(ng, xName(gnames, agnameof(subg)), 1); |
601 | agbindrec (nsubg, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); |
602 | cloneSubg(subg, nsubg, G_bb, gnames); |
603 | /* if subgraphs are clusters, point to the new |
604 | * one so we can find it later. |
605 | */ |
606 | if (ISCLUSTER(subg)) |
607 | SETCLUST(subg, nsubg); |
608 | } |
609 | |
610 | /* add remaining nodes */ |
611 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
612 | nn = NEWNODE(n); |
613 | agsubnode(ng, nn, 1); |
614 | } |
615 | |
616 | /* add remaining edges. libgraph doesn't provide a way to find |
617 | * multiedges, so we add edges bottom up, marking edges when added. |
618 | */ |
619 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
620 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) { |
621 | if (MARKED(e)) |
622 | continue; |
623 | nt = NEWNODE(agtail(e)); |
624 | nh = NEWNODE(aghead(e)); |
625 | ne = agedge(ng, nt, nh, NULL, 1); |
626 | agbindrec (ne, "Agedgeinfo_t" , sizeof(Agedgeinfo_t), TRUE); |
627 | cloneEdge(e, ne); |
628 | MARK(e); |
629 | } |
630 | } |
631 | } |
632 | |
633 | /* cloneClusterTree: |
634 | * Given old and new subgraphs which are corresponding |
635 | * clusters, recursively create the subtree of clusters |
636 | * under ng using the subtree of clusters under g. |
637 | */ |
638 | static void cloneClusterTree(Agraph_t * g, Agraph_t * ng) |
639 | { |
640 | int i; |
641 | |
642 | cloneCluster(g, ng); |
643 | |
644 | if (GD_n_cluster(g)) { |
645 | GD_n_cluster(ng) = GD_n_cluster(g); |
646 | GD_clust(ng) = N_NEW(1 + GD_n_cluster(g), Agraph_t *); |
647 | for (i = 1; i <= GD_n_cluster(g); i++) { |
648 | Agraph_t *c = GETCLUST(GD_clust(g)[i]); |
649 | GD_clust(ng)[i] = c; |
650 | cloneClusterTree(GD_clust(g)[i], c); |
651 | } |
652 | } |
653 | } |
654 | |
655 | static Dtdisc_t pairdisc = { |
656 | offsetof(pair_t, name), /* key */ |
657 | -1, /* size */ |
658 | offsetof(attr_t, link), /* link */ |
659 | (Dtmake_f) 0, |
660 | (Dtfree_f) freef, |
661 | (Dtcompar_f) 0, /* use strcmp */ |
662 | (Dthash_f) 0, |
663 | (Dtmemory_f) 0, |
664 | (Dtevent_f) 0 |
665 | }; |
666 | |
667 | /* cloneGraph: |
668 | * Create and return a new graph which is the logical union |
669 | * of the graphs gs. |
670 | */ |
671 | static Agraph_t *cloneGraph(Agraph_t ** gs, int cnt, GVC_t * gvc) |
672 | { |
673 | Agraph_t *root; |
674 | Agraph_t *g; |
675 | Agraph_t *subg; |
676 | Agnode_t *n; |
677 | Agnode_t *np; |
678 | int i; |
679 | Dt_t *gnames; /* dict of used subgraph names */ |
680 | Dt_t *nnames; /* dict of used node names */ |
681 | Agsym_t *G_bb; |
682 | Agsym_t *rv; |
683 | boolean doWarn = TRUE; |
684 | |
685 | if (verbose) |
686 | fprintf(stderr, "Creating clone graph\n" ); |
687 | root = agopen(gname, kind, &AgDefaultDisc); |
688 | initAttrs(root, gs, cnt); |
689 | G_bb = agfindgraphattr(root, "bb" ); |
690 | if (doPack) assert(G_bb); |
691 | |
692 | /* add command-line attributes */ |
693 | for (i = 0; i < G_cnt; i++) { |
694 | rv = agfindgraphattr(root, G_args[i].name); |
695 | if (rv) |
696 | agxset(root, rv, G_args[i].value); |
697 | else |
698 | agattr(root, AGRAPH, G_args[i].name, G_args[i].value); |
699 | } |
700 | |
701 | /* do common initialization. This will handle root's label. */ |
702 | init_graph(root, FALSE, gvc); |
703 | State = GVSPLINES; |
704 | |
705 | gnames = dtopen(&pairdisc, Dtoset); |
706 | nnames = dtopen(&pairdisc, Dtoset); |
707 | for (i = 0; i < cnt; i++) { |
708 | g = gs[i]; |
709 | if (verbose) |
710 | fprintf(stderr, "Cloning graph %s\n" , agnameof(g)); |
711 | GD_n_cluster(root) += GD_n_cluster(g); |
712 | GD_has_labels(root) |= GD_has_labels(g); |
713 | |
714 | /* Clone nodes, checking for node name conflicts */ |
715 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
716 | if (doWarn && agfindnode(root, agnameof(n))) { |
717 | fprintf(stderr, |
718 | "Warning: node %s in graph[%d] %s already defined\n" , |
719 | agnameof(n), i, agnameof(g)); |
720 | fprintf(stderr, "Some nodes will be renamed.\n" ); |
721 | doWarn = FALSE; |
722 | } |
723 | np = agnode(root, xName(nnames, agnameof(n)), 1); |
724 | agbindrec (np, "Agnodeinfo_t" , sizeof(Agnodeinfo_t), TRUE); |
725 | ND_alg(n) = np; |
726 | cloneNode(n, np); |
727 | } |
728 | |
729 | /* wrap the clone of g in a subgraph of root */ |
730 | subg = agsubg(root, xName(gnames, agnameof(g)), 1); |
731 | agbindrec (subg, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); |
732 | cloneSubg(g, subg, G_bb, gnames); |
733 | } |
734 | dtclose(gnames); |
735 | dtclose(nnames); |
736 | |
737 | /* set up cluster tree */ |
738 | if (GD_n_cluster(root)) { |
739 | int j, idx; |
740 | GD_clust(root) = N_NEW(1 + GD_n_cluster(root), graph_t *); |
741 | |
742 | idx = 1; |
743 | for (i = 0; i < cnt; i++) { |
744 | g = gs[i]; |
745 | for (j = 1; j <= GD_n_cluster(g); j++) { |
746 | Agraph_t *c = GETCLUST(GD_clust(g)[j]); |
747 | GD_clust(root)[idx++] = c; |
748 | cloneClusterTree(GD_clust(g)[j], c); |
749 | } |
750 | } |
751 | } |
752 | |
753 | return root; |
754 | } |
755 | |
756 | static Agraph_t *gread(FILE * fp) |
757 | { |
758 | return agread(fp, (Agdisc_t *) 0); |
759 | } |
760 | |
761 | /* readGraphs: |
762 | * Read input, parse the graphs, use init_nop (neato -n) to |
763 | * read in all attributes need for layout. |
764 | * Return the list of graphs. If cp != NULL, set it to the number |
765 | * of graphs read. |
766 | * We keep track of the types of graphs read. They all must be |
767 | * either directed or undirected. If all graphs are strict, the |
768 | * combined graph will be strict; other, the combined graph will |
769 | * be non-strict. |
770 | */ |
771 | static Agraph_t **readGraphs(int *cp, GVC_t* gvc) |
772 | { |
773 | Agraph_t *g; |
774 | Agraph_t **gs = 0; |
775 | ingraph_state ig; |
776 | int cnt = 0; |
777 | int sz = 0; |
778 | int kindUnset = 1; |
779 | |
780 | /* set various state values */ |
781 | PSinputscale = POINTS_PER_INCH; |
782 | Nop = 2; |
783 | |
784 | newIngraph(&ig, myFiles, gread); |
785 | while ((g = nextGraph(&ig)) != 0) { |
786 | if (verbose) |
787 | fprintf(stderr, "Reading graph %s\n" , agnameof(g)); |
788 | if (agnnodes(g) == 0) { |
789 | fprintf(stderr, "Graph %s is empty - ignoring\n" , agnameof(g)); |
790 | continue; |
791 | } |
792 | if (cnt >= sz) { |
793 | sz += nGraphs; |
794 | gs = ALLOC(sz, gs, Agraph_t *); |
795 | } |
796 | if (kindUnset) { |
797 | kindUnset = 0; |
798 | kind = g->desc; |
799 | } |
800 | else if (kind.directed != g->desc.directed) { |
801 | fprintf(stderr, |
802 | "Error: all graphs must be directed or undirected\n" ); |
803 | exit(1); |
804 | } else if (!agisstrict(g)) |
805 | kind = g->desc; |
806 | init_graph(g, doPack, gvc); |
807 | gs[cnt++] = g; |
808 | } |
809 | |
810 | gs = RALLOC(cnt, gs, Agraph_t *); |
811 | if (cp) |
812 | *cp = cnt; |
813 | return gs; |
814 | } |
815 | |
816 | /* compBB: |
817 | * Compute the bounding box containing the graphs. |
818 | * We can just use the bounding boxes of the graphs. |
819 | */ |
820 | boxf compBB(Agraph_t ** gs, int cnt) |
821 | { |
822 | boxf bb, bb2; |
823 | int i; |
824 | |
825 | bb = GD_bb(gs[0]); |
826 | |
827 | for (i = 1; i < cnt; i++) { |
828 | bb2 = GD_bb(gs[i]); |
829 | bb.LL.x = MIN(bb.LL.x, bb2.LL.x); |
830 | bb.LL.y = MIN(bb.LL.y, bb2.LL.y); |
831 | bb.UR.x = MAX(bb.UR.x, bb2.UR.x); |
832 | bb.UR.y = MAX(bb.UR.y, bb2.UR.y); |
833 | } |
834 | |
835 | return bb; |
836 | } |
837 | |
838 | #ifdef DEBUG |
839 | void dump(Agraph_t * g) |
840 | { |
841 | node_t *v; |
842 | edge_t *e; |
843 | |
844 | for (v = agfstnode(g); v; v = agnxtnode(g, v)) { |
845 | fprintf(stderr, "%s\n" , agnameof(v)); |
846 | for (e = agfstout(g, v); e; e = agnxtout(g, e)) { |
847 | fprintf(stderr, " %s -- %s\n" , agnameof(agtail(e)), agnameof(aghead(e))); |
848 | } |
849 | } |
850 | } |
851 | |
852 | void dumps(Agraph_t * g) |
853 | { |
854 | graph_t *subg; |
855 | |
856 | for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) { |
857 | dump(subg); |
858 | fprintf(stderr, "====\n" ); |
859 | } |
860 | } |
861 | #endif |
862 | |
863 | int main(int argc, char *argv[]) |
864 | { |
865 | Agraph_t **gs; |
866 | Agraph_t *g; |
867 | int cnt; |
868 | pack_info pinfo; |
869 | GVC_t * gvc; |
870 | |
871 | init(argc, argv, &pinfo); |
872 | |
873 | doPack = (pinfo.mode != l_undef); |
874 | |
875 | #if defined(_WIN32) |
876 | lt_preloaded_symbols[0].address = (void*)(&gvplugin_neato_layout_LTX_library); |
877 | #endif |
878 | gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING); |
879 | gs = readGraphs(&cnt, gvc); |
880 | if (cnt == 0) |
881 | exit(0); |
882 | |
883 | /* pack graphs */ |
884 | if (doPack) { |
885 | if (packGraphs(cnt, gs, 0, &pinfo)) { |
886 | fprintf(stderr, "gvpack: packing of graphs failed.\n" ); |
887 | exit(1); |
888 | } |
889 | } |
890 | |
891 | /* create union graph and copy attributes */ |
892 | g = cloneGraph(gs, cnt, gvc); |
893 | |
894 | /* compute new top-level bb and set */ |
895 | if (doPack) { |
896 | GD_bb(g) = compBB(gs, cnt); |
897 | dotneato_postprocess(g); |
898 | attach_attrs(g); |
899 | } |
900 | agwrite(g, outfp); |
901 | exit(0); |
902 | } |
903 | |