blockpath.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  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. #include <cgraph/list.h>
  11. #include <circogen/blockpath.h>
  12. #include <circogen/circular.h>
  13. #include <circogen/edgelist.h>
  14. #include <stddef.h>
  15. #include <stdbool.h>
  16. #include <util/agxbuf.h>
  17. #include <util/alloc.h>
  18. /* The code below lays out a single block on a circle.
  19. */
  20. /* We use the unused fields order and to_orig in cloned nodes and edges */
  21. #define ORIGE(e) (ED_to_orig(e))
  22. /* clone_graph:
  23. * Create two copies of the argument graph
  24. * One is a subgraph, the other is an actual copy since we will be
  25. * adding edges to it.
  26. *
  27. * @param state Context containing a counter to use for graph copy naming
  28. */
  29. static Agraph_t *clone_graph(Agraph_t *ing, Agraph_t **xg, circ_state *state) {
  30. Agraph_t *clone;
  31. Agraph_t *xclone;
  32. Agnode_t *n;
  33. Agnode_t *xn;
  34. Agnode_t *xh;
  35. Agedge_t *e;
  36. Agedge_t *xe;
  37. agxbuf gname = {0};
  38. agxbprint(&gname, "_clone_%d", state->graphCopyCount++);
  39. clone = agsubg(ing, agxbuse(&gname), 1);
  40. agbindrec(clone, "Agraphinfo_t", sizeof(Agraphinfo_t), true); //node custom data
  41. agxbprint(&gname, "_clone_%d", state->graphCopyCount++);
  42. xclone = agopen(agxbuse(&gname), ing->desc, NULL);
  43. agxbfree(&gname);
  44. for (n = agfstnode(ing); n; n = agnxtnode(ing, n)) {
  45. agsubnode(clone,n,1);
  46. xn = agnode(xclone, agnameof(n),1);
  47. agbindrec(xn, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true); //node custom data
  48. CLONE(n) = xn;
  49. }
  50. for (n = agfstnode(ing); n; n = agnxtnode(ing, n)) {
  51. xn = CLONE(n);
  52. for (e = agfstout(ing, n); e; e = agnxtout(ing, e)) {
  53. agsubedge(clone,e,1);
  54. xh = CLONE(aghead(e));
  55. xe = agedge(xclone, xn, xh, NULL, 1);
  56. agbindrec(xe, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); //node custom data
  57. ORIGE(xe) = e;
  58. DEGREE(xn) += 1;
  59. DEGREE(xh) += 1;
  60. }
  61. }
  62. *xg = xclone;
  63. return clone;
  64. }
  65. DEFINE_LIST(deglist, Agnode_t*)
  66. /// comparison function for sorting nodes by degree, descending
  67. static int cmpDegree(const Agnode_t **a, const Agnode_t **b) {
  68. // Suppress Clang/GCC -Wcast-qual warnings. `DEGREE` does not modify the
  69. // underlying data, but uses casts that make it look like it does.
  70. #ifdef __GNUC__
  71. #pragma GCC diagnostic push
  72. #pragma GCC diagnostic ignored "-Wcast-qual"
  73. #endif
  74. if (DEGREE(*a) < DEGREE(*b)) {
  75. return 1;
  76. }
  77. if (DEGREE(*a) > DEGREE(*b)) {
  78. return -1;
  79. }
  80. #ifdef __GNUC__
  81. #pragma GCC diagnostic pop
  82. #endif
  83. return 0;
  84. }
  85. /// Add nodes to deg_list, storing them by descending degree.
  86. static deglist_t getList(Agraph_t *g) {
  87. deglist_t dl = {0};
  88. Agnode_t *n;
  89. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  90. deglist_append(&dl, n);
  91. }
  92. deglist_sort(&dl, cmpDegree);
  93. return dl;
  94. }
  95. static void find_pair_edges(Agraph_t * g, Agnode_t * n, Agraph_t * outg)
  96. {
  97. Agedge_t *e;
  98. Agedge_t *ep;
  99. Agedge_t *ex;
  100. Agnode_t *n1;
  101. Agnode_t *n2;
  102. int has_pair_edge;
  103. int diff;
  104. int has_pair_count = 0;
  105. int no_pair_count = 0;
  106. int node_degree;
  107. int edge_cnt = 0;
  108. node_degree = DEGREE(n);
  109. Agnode_t **neighbors_with = gv_calloc(node_degree, sizeof(Agnode_t*));
  110. Agnode_t **neighbors_without = gv_calloc(node_degree, sizeof(Agnode_t*));
  111. for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
  112. n1 = aghead(e);
  113. if (n1 == n)
  114. n1 = agtail(e);
  115. has_pair_edge = 0;
  116. for (ep = agfstedge(g, n); ep; ep = agnxtedge(g, ep, n)) {
  117. if (ep == e)
  118. continue;
  119. n2 = aghead(ep);
  120. if (n2 == n)
  121. n2 = agtail(ep);
  122. ex = agfindedge(g, n1, n2);
  123. if (ex) {
  124. has_pair_edge = 1;
  125. if (n1 < n2) { /* count edge only once */
  126. edge_cnt++;
  127. if (ORIGE(ex)) {
  128. agdelete(outg, ORIGE(ex));
  129. ORIGE(ex) = 0; /* delete only once */
  130. }
  131. }
  132. }
  133. }
  134. if (has_pair_edge) {
  135. neighbors_with[has_pair_count] = n1;
  136. has_pair_count++;
  137. } else {
  138. neighbors_without[no_pair_count] = n1;
  139. no_pair_count++;
  140. }
  141. }
  142. diff = node_degree - 1 - edge_cnt;
  143. if (diff > 0) {
  144. int mark;
  145. Agnode_t *hp;
  146. Agnode_t *tp;
  147. if (diff < no_pair_count) {
  148. for (mark = 0; mark < no_pair_count; mark += 2) {
  149. if ((mark + 1) >= no_pair_count)
  150. break;
  151. tp = neighbors_without[mark];
  152. hp = neighbors_without[mark + 1];
  153. agbindrec(agedge(g, tp, hp, NULL, 1), "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); // edge custom data
  154. DEGREE(tp)++;
  155. DEGREE(hp)++;
  156. diff--;
  157. }
  158. mark = 2;
  159. while (diff > 0) {
  160. tp = neighbors_without[0];
  161. hp = neighbors_without[mark];
  162. agbindrec(agedge(g, tp, hp, NULL, 1), "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); // edge custom data
  163. DEGREE(tp)++;
  164. DEGREE(hp)++;
  165. mark++;
  166. diff--;
  167. }
  168. }
  169. else if (diff == no_pair_count) {
  170. tp = neighbors_with[0];
  171. for (mark = 0; mark < no_pair_count; mark++) {
  172. hp = neighbors_without[mark];
  173. agbindrec(agedge(g, tp, hp, NULL, 1), "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); //node custom data
  174. DEGREE(tp)++;
  175. DEGREE(hp)++;
  176. }
  177. }
  178. }
  179. free(neighbors_without);
  180. free(neighbors_with);
  181. }
  182. /// Create layout skeleton of ing. Why is returned graph connected?
  183. ///
  184. /// @param state Context containing a counter to use for graph copy naming
  185. static Agraph_t *remove_pair_edges(Agraph_t *ing, circ_state *state) {
  186. int counter = 0;
  187. int nodeCount;
  188. Agraph_t *outg;
  189. Agraph_t *g;
  190. Agnode_t *currnode, *adjNode;
  191. Agedge_t *e;
  192. outg = clone_graph(ing, &g, state);
  193. nodeCount = agnnodes(g);
  194. deglist_t dl = getList(g);
  195. while (counter < nodeCount - 3) {
  196. currnode = deglist_is_empty(&dl) ? NULL : deglist_pop_back(&dl);
  197. /* Remove all adjacent nodes since they have to be reinserted */
  198. for (e = agfstedge(g, currnode); e; e = agnxtedge(g, e, currnode)) {
  199. adjNode = aghead(e);
  200. if (currnode == adjNode)
  201. adjNode = agtail(e);
  202. deglist_remove(&dl, adjNode);
  203. }
  204. find_pair_edges(g, currnode, outg);
  205. for (e = agfstedge(g, currnode); e; e = agnxtedge(g, e, currnode)) {
  206. adjNode = aghead(e);
  207. if (currnode == adjNode)
  208. adjNode = agtail(e);
  209. DEGREE(adjNode)--;
  210. deglist_append(&dl, adjNode);
  211. }
  212. deglist_sort(&dl, cmpDegree);
  213. agdelete(g, currnode);
  214. counter++;
  215. }
  216. agclose(g);
  217. deglist_free(&dl);
  218. return outg;
  219. }
  220. static void
  221. measure_distance(Agnode_t * n, Agnode_t * ancestor, int dist,
  222. Agnode_t * change)
  223. {
  224. Agnode_t *parent;
  225. parent = TPARENT(ancestor);
  226. if (parent == NULL)
  227. return;
  228. dist++;
  229. /* check parent to see if it has other leaf paths at greater distance
  230. than the context node.
  231. set the path/distance of the leaf at this ancestor node */
  232. if (DISTONE(parent) == 0) {
  233. LEAFONE(parent) = n;
  234. DISTONE(parent) = dist;
  235. } else if (dist > DISTONE(parent)) {
  236. if (LEAFONE(parent) != change) {
  237. if (!DISTTWO(parent) || LEAFTWO(parent) != change)
  238. change = LEAFONE(parent);
  239. LEAFTWO(parent) = LEAFONE(parent);
  240. DISTTWO(parent) = DISTONE(parent);
  241. }
  242. LEAFONE(parent) = n;
  243. DISTONE(parent) = dist;
  244. } else if (dist > DISTTWO(parent)) {
  245. LEAFTWO(parent) = n;
  246. DISTTWO(parent) = dist;
  247. return;
  248. } else
  249. return;
  250. measure_distance(n, parent, dist, change);
  251. }
  252. /// Find and return longest path in tree.
  253. static nodelist_t find_longest_path(Agraph_t *tree) {
  254. Agnode_t *n;
  255. Agedge_t *e;
  256. Agnode_t *common = 0;
  257. int maxlength = 0;
  258. int length;
  259. if (agnnodes(tree) == 1) {
  260. nodelist_t beginPath = {0};
  261. n = agfstnode(tree);
  262. nodelist_append(&beginPath, n);
  263. SET_ONPATH(n);
  264. return beginPath;
  265. }
  266. for (n = agfstnode(tree); n; n = agnxtnode(tree, n)) {
  267. int count = 0;
  268. for (e = agfstedge(tree, n); e; e = agnxtedge(tree, e, n)) {
  269. count++;
  270. }
  271. if (count == 1)
  272. measure_distance(n, n, 0, NULL);
  273. }
  274. /* find the branch node rooted at the longest path */
  275. for (n = agfstnode(tree); n; n = agnxtnode(tree, n)) {
  276. length = DISTONE(n) + DISTTWO(n);
  277. if (length > maxlength) {
  278. common = n;
  279. maxlength = length;
  280. }
  281. }
  282. nodelist_t beginPath = {0};
  283. for (n = LEAFONE(common); n != common; n = TPARENT(n)) {
  284. nodelist_append(&beginPath, n);
  285. SET_ONPATH(n);
  286. }
  287. nodelist_append(&beginPath, common);
  288. SET_ONPATH(common);
  289. if (DISTTWO(common)) { /* 2nd path might be empty */
  290. nodelist_t endPath = {0};
  291. for (n = LEAFTWO(common); n != common; n = TPARENT(n)) {
  292. nodelist_append(&endPath, n);
  293. SET_ONPATH(n);
  294. }
  295. reverseAppend(&beginPath, &endPath);
  296. }
  297. return beginPath;
  298. }
  299. /// Simple depth first search, adding traversed edges to tree.
  300. static void dfs(Agraph_t * g, Agnode_t * n, Agraph_t * tree)
  301. {
  302. Agedge_t *e;
  303. Agnode_t *neighbor;
  304. SET_VISITED(n);
  305. for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
  306. neighbor = aghead(e);
  307. if (neighbor == n)
  308. neighbor = agtail(e);
  309. if (!VISITED(neighbor)) {
  310. /* add the edge to the dfs tree */
  311. agsubedge(tree,e,1);
  312. TPARENT(neighbor) = n;
  313. dfs(g, neighbor, tree);
  314. }
  315. }
  316. }
  317. /// Construct spanning forest of g as subgraph
  318. ///
  319. /// @param state Context containing a counter to use for spanning tree naming
  320. static Agraph_t *spanning_tree(Agraph_t *g, circ_state *state) {
  321. Agnode_t *n;
  322. Agraph_t *tree;
  323. agxbuf gname = {0};
  324. agxbprint(&gname, "_span_%d", state->spanningTreeCount++);
  325. tree = agsubg(g, agxbuse(&gname), 1);
  326. agxbfree(&gname);
  327. agbindrec(tree, "Agraphinfo_t", sizeof(Agraphinfo_t), true); //node custom data
  328. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  329. agsubnode(tree,n,1);
  330. DISTONE(n) = 0;
  331. DISTTWO(n) = 0;
  332. UNSET_VISITED(n);
  333. }
  334. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  335. if (!VISITED(n)) {
  336. TPARENT(n) = NULL;
  337. dfs(g, n, tree);
  338. }
  339. }
  340. return tree;
  341. }
  342. /// Add induced edges.
  343. static void block_graph(Agraph_t * g, block_t * sn)
  344. {
  345. Agnode_t *n;
  346. Agedge_t *e;
  347. Agraph_t *subg = sn->sub_graph;
  348. for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
  349. for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
  350. if (BLOCK(aghead(e)) == sn)
  351. agsubedge(subg,e,1);
  352. }
  353. }
  354. }
  355. static int count_all_crossings(nodelist_t * list, Agraph_t * subg)
  356. {
  357. edgelist *openEdgeList = init_edgelist();
  358. Agnode_t *n;
  359. Agedge_t *e;
  360. int crossings = 0;
  361. int order = 1;
  362. for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
  363. for (e = agfstout(subg, n); e; e = agnxtout(subg, e)) {
  364. EDGEORDER(e) = 0;
  365. }
  366. }
  367. for (size_t item = 0; item < nodelist_size(list); ++item) {
  368. n = nodelist_get(list, item);
  369. for (e = agfstedge(subg, n); e; e = agnxtedge(subg, e, n)) {
  370. if (EDGEORDER(e) > 0) {
  371. edgelistitem *eitem;
  372. Agedge_t *ep;
  373. for (eitem = dtfirst(openEdgeList); eitem;
  374. eitem = dtnext(openEdgeList, eitem)) {
  375. ep = eitem->edge;
  376. if (EDGEORDER(ep) > EDGEORDER(e)) {
  377. if (aghead(ep) != n && agtail(ep) != n)
  378. crossings++;
  379. }
  380. }
  381. remove_edge(openEdgeList, e);
  382. }
  383. }
  384. for (e = agfstedge(subg, n); e; e = agnxtedge(subg, e, n)) {
  385. if (EDGEORDER(e) == 0) {
  386. EDGEORDER(e) = order;
  387. add_edge(openEdgeList, e);
  388. }
  389. }
  390. order++;
  391. }
  392. free_edgelist(openEdgeList);
  393. return crossings;
  394. }
  395. #define CROSS_ITER 10
  396. /* Attempt to reduce edge crossings by moving nodes.
  397. * Original crossing count is in cnt; final count is returned there.
  398. * list is the original list; return the best list found.
  399. */
  400. static nodelist_t reduce(nodelist_t list, Agraph_t *subg, int *cnt) {
  401. Agnode_t *curnode;
  402. Agedge_t *e;
  403. Agnode_t *neighbor;
  404. int crossings, j, newCrossings;
  405. crossings = *cnt;
  406. for (curnode = agfstnode(subg); curnode;
  407. curnode = agnxtnode(subg, curnode)) {
  408. /* move curnode next to its neighbors */
  409. for (e = agfstedge(subg, curnode); e;
  410. e = agnxtedge(subg, e, curnode)) {
  411. neighbor = agtail(e);
  412. if (neighbor == curnode)
  413. neighbor = aghead(e);
  414. for (j = 0; j < 2; j++) {
  415. nodelist_t listCopy = nodelist_copy(&list);
  416. insertNodelist(&list, curnode, neighbor, j);
  417. newCrossings = count_all_crossings(&list, subg);
  418. if (newCrossings < crossings) {
  419. crossings = newCrossings;
  420. nodelist_free(&listCopy);
  421. if (crossings == 0) {
  422. *cnt = 0;
  423. return list;
  424. }
  425. } else {
  426. nodelist_free(&list);
  427. list = listCopy;
  428. }
  429. }
  430. }
  431. }
  432. *cnt = crossings;
  433. return list;
  434. }
  435. static nodelist_t reduce_edge_crossings(nodelist_t list, Agraph_t *subg) {
  436. int i, crossings, origCrossings;
  437. crossings = count_all_crossings(&list, subg);
  438. if (crossings == 0)
  439. return list;
  440. for (i = 0; i < CROSS_ITER; i++) {
  441. origCrossings = crossings;
  442. list = reduce(list, subg, &crossings);
  443. /* return if no crossings or no improvement */
  444. if (origCrossings == crossings || crossings == 0)
  445. return list;
  446. }
  447. return list;
  448. }
  449. /// Return max dimension of nodes on list
  450. static double largest_nodesize(nodelist_t * list)
  451. {
  452. double size = 0;
  453. for (size_t item = 0; item < nodelist_size(list); ++item) {
  454. Agnode_t *n = ORIGN(nodelist_get(list, item));
  455. if (ND_width(n) > size)
  456. size = ND_width(n);
  457. if (ND_height(n) > size)
  458. size = ND_height(n);
  459. }
  460. return size;
  461. }
  462. /// Add n to list. By construction, n is not in list at start.
  463. static void place_node(Agraph_t * g, Agnode_t * n, nodelist_t * list)
  464. {
  465. Agedge_t *e;
  466. bool placed = false;
  467. nodelist_t neighbors = {0};
  468. for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
  469. nodelist_append(&neighbors, aghead(e));
  470. SET_NEIGHBOR(aghead(e));
  471. }
  472. for (e = agfstin(g, n); e; e = agnxtin(g, e)) {
  473. nodelist_append(&neighbors, agtail(e));
  474. SET_NEIGHBOR(agtail(e));
  475. }
  476. /* Look for 2 neighbors consecutive on list */
  477. if (nodelist_size(&neighbors) >= 2) {
  478. for (size_t one = 0; one < nodelist_size(list); ++one) {
  479. const size_t two = (one + 1) % nodelist_size(list);
  480. if (NEIGHBOR(nodelist_get(list, one)) &&
  481. NEIGHBOR(nodelist_get(list, two))) {
  482. appendNodelist(list, one + 1, n);
  483. placed = true;
  484. break;
  485. }
  486. }
  487. }
  488. /* Find any neighbor on list */
  489. if (!placed && !nodelist_is_empty(&neighbors)) {
  490. for (size_t one = 0; one < nodelist_size(list); ++one) {
  491. if (NEIGHBOR(nodelist_get(list, one))) {
  492. appendNodelist(list, one + 1, n);
  493. placed = true;
  494. break;
  495. }
  496. }
  497. }
  498. if (!placed)
  499. nodelist_append(list, n);
  500. for (size_t one = 0; one < nodelist_size(&neighbors); ++one)
  501. UNSET_NEIGHBOR(nodelist_get(&neighbors, one));
  502. nodelist_free(&neighbors);
  503. }
  504. /// Add nodes not in list to list.
  505. static void place_residual_nodes(Agraph_t * g, nodelist_t * list)
  506. {
  507. Agnode_t *n;
  508. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  509. if (!ONPATH(n))
  510. place_node(g, n, list);
  511. }
  512. }
  513. /// @param state Context containing a counter to use for graph copy naming
  514. nodelist_t layout_block(Agraph_t *g, block_t *sn, double min_dist,
  515. circ_state *state) {
  516. Agraph_t *copyG, *tree, *subg;
  517. int k;
  518. double theta, radius, largest_node;
  519. largest_node = 0;
  520. subg = sn->sub_graph;
  521. block_graph(g, sn); /* add induced edges */
  522. copyG = remove_pair_edges(subg, state);
  523. tree = spanning_tree(copyG, state);
  524. nodelist_t longest_path = find_longest_path(tree);
  525. place_residual_nodes(subg, &longest_path);
  526. /* at this point, longest_path is a list of all nodes in the block */
  527. /* apply crossing reduction algorithms here */
  528. longest_path = reduce_edge_crossings(longest_path, subg);
  529. size_t N = nodelist_size(&longest_path);
  530. largest_node = largest_nodesize(&longest_path);
  531. /* N*(min_dist+largest_node) is roughly circumference of required circle */
  532. if (N == 1)
  533. radius = 0;
  534. else
  535. radius = (double)N * (min_dist + largest_node) / (2 * M_PI);
  536. for (size_t item = 0; item < nodelist_size(&longest_path); ++item) {
  537. Agnode_t *n = nodelist_get(&longest_path, item);
  538. if (ISPARENT(n)) {
  539. /* QUESTION: Why is only one parent realigned? */
  540. realignNodelist(&longest_path, item);
  541. break;
  542. }
  543. }
  544. k = 0;
  545. for (size_t item = 0; item < nodelist_size(&longest_path); ++item) {
  546. Agnode_t *n = nodelist_get(&longest_path, item);
  547. POSITION(n) = k;
  548. PSI(n) = 0.0;
  549. theta = k * (2.0 * M_PI / (double)N);
  550. ND_pos(n)[0] = radius * cos(theta);
  551. ND_pos(n)[1] = radius * sin(theta);
  552. k++;
  553. }
  554. if (N == 1)
  555. sn->radius = largest_node / 2;
  556. else
  557. sn->radius = radius;
  558. sn->rad0 = sn->radius;
  559. /* initialize parent pos */
  560. sn->parent_pos = -1;
  561. agclose(copyG);
  562. return longest_path;
  563. }
  564. #ifdef DEBUG
  565. void prTree(Agraph_t * g)
  566. {
  567. Agnode_t *n;
  568. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  569. if (TPARENT(n)) {
  570. fprintf(stderr, "%s ", agnameof(n));
  571. fprintf(stderr, "-> %s\n", agnameof(TPARENT(n)));
  572. }
  573. }
  574. }
  575. #endif