circpos.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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 "config.h"
  11. /* TODO:
  12. * If cut point is in exactly 2 blocks, expand block circles to overlap
  13. * especially in the case where one block is the sole child of the other.
  14. */
  15. #include <circogen/blockpath.h>
  16. #include <circogen/circpos.h>
  17. #include <circogen/nodelist.h>
  18. #include <math.h>
  19. #include <stddef.h>
  20. #include <util/alloc.h>
  21. /* The function determines how much the block should be rotated
  22. * for best positioning with parent, assuming its center is at x and y
  23. * relative to the parent.
  24. * angle gives the angle of the new position, i.e., tan(angle) = y/x.
  25. * If sn has 2 nodes, we arrange the line of the 2 normal to angle.
  26. * If sn has 1 node, parent_pos has already been set to the
  27. * correct angle assuming no rotation.
  28. * Otherwise, we find the node in sn connected to the parent and rotate
  29. * the block so that it is closer or at least visible to its node in the
  30. * parent.
  31. *
  32. * For COALESCED blocks, if neighbor is in left half plane,
  33. * use unCOALESCED case.
  34. * Else let theta be angle, R = LEN(x,y), pho the radius of actual
  35. * child block, phi be angle of neighbor in actual child block,
  36. * and r the distance from center of coalesced block to center of
  37. * actual block. Then, the angle to rotate the coalesced block to
  38. * that the edge from the parent is tangent to the neighbor on the
  39. * actual child block circle is
  40. * alpha = theta + M_PI/2 - phi - arcsin((l/R)*(sin B))
  41. * where l = r - rho/(cos phi) and beta = M_PI/2 + phi.
  42. * Thus,
  43. * alpha = theta + M_PI/2 - phi - arcsin((l/R)*(cos phi))
  44. */
  45. static double getRotation(block_t *sn, double x, double y, double theta) {
  46. double mindist2;
  47. Agraph_t *subg;
  48. Agnode_t *n, *closest_node, *neighbor;
  49. double len2, newX, newY;
  50. subg = sn->sub_graph;
  51. nodelist_t *list = &sn->circle_list;
  52. if (sn->parent_pos >= 0) {
  53. theta += M_PI - sn->parent_pos;
  54. if (theta < 0)
  55. theta += 2 * M_PI;
  56. return theta;
  57. }
  58. size_t count = nodelist_size(list);
  59. if (count == 2) {
  60. return theta - M_PI / 2.0;
  61. }
  62. /* Find node in block connected to block's parent */
  63. neighbor = CHILD(sn);
  64. newX = ND_pos(neighbor)[0] + x;
  65. newY = ND_pos(neighbor)[1] + y;
  66. mindist2 = LEN2(newX, newY); /* save sqrts by using sqr of dist to find min */
  67. closest_node = neighbor;
  68. for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
  69. if (n == neighbor)
  70. continue;
  71. newX = ND_pos(n)[0] + x;
  72. newY = ND_pos(n)[1] + y;
  73. len2 = LEN2(newX, newY);
  74. if (len2 < mindist2) {
  75. mindist2 = len2;
  76. closest_node = n;
  77. }
  78. }
  79. if (neighbor != closest_node) {
  80. double rho = sn->rad0;
  81. double r = sn->radius - rho;
  82. double n_x = ND_pos(neighbor)[0];
  83. if (COALESCED(sn) && -r < n_x) {
  84. const double R = hypot(x, y);
  85. double n_y = ND_pos(neighbor)[1];
  86. double phi = atan2(n_y, n_x + r);
  87. double l = r - rho / cos(phi);
  88. theta += M_PI / 2.0 - phi - asin(l / R * cos(phi));
  89. } else { /* Origin still at center of this block */
  90. double phi = atan2(ND_pos(neighbor)[1], ND_pos(neighbor)[0]);
  91. theta += M_PI - phi - PSI(neighbor);
  92. if (theta > 2 * M_PI)
  93. theta -= 2 * M_PI;
  94. }
  95. } else
  96. theta = 0;
  97. return theta;
  98. }
  99. /* Recursively apply rotation rotate followed by translation (x,y)
  100. * to block sn and its children.
  101. */
  102. static void applyDelta(block_t * sn, double x, double y, double rotate)
  103. {
  104. block_t *child;
  105. Agraph_t *subg;
  106. Agnode_t *n;
  107. subg = sn->sub_graph;
  108. for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
  109. const double tmpX = ND_pos(n)[0];
  110. const double tmpY = ND_pos(n)[1];
  111. const double cosR = cos(rotate);
  112. const double sinR = sin(rotate);
  113. const double X = tmpX * cosR - tmpY * sinR;
  114. const double Y = tmpX * sinR + tmpY * cosR;
  115. /* translate */
  116. ND_pos(n)[0] = X + x;
  117. ND_pos(n)[1] = Y + y;
  118. }
  119. for (child = sn->children.first; child; child = child->next)
  120. applyDelta(child, x, y, rotate);
  121. }
  122. /* firstangle and lastangle give the range of child angles.
  123. * These are set and used only when a block has just 1 node.
  124. * And are used to give the center angle between the two extremes.
  125. * The parent will then be attached at PI - center angle (parent_pos).
  126. * If this block has no children, this is PI. Otherwise, positionChildren will
  127. * be called once with the blocks node. firstangle will be 0, with
  128. * succeeding angles increasing.
  129. * position can always return the center angle - PI, since the block
  130. * must have children and if the block has 1 node, the limits will be
  131. * correctly set. If the block has more than 1 node, the value is
  132. * unused.
  133. */
  134. typedef struct {
  135. double radius; /* Basic radius of block */
  136. double subtreeR; /* Max of subtree radii */
  137. double nodeAngle; /* Angle allocated to each node in block */
  138. double firstAngle; /* Smallest child angle when block has 1 node */
  139. double lastAngle; /* Largest child angle when block has 1 node */
  140. block_t *cp; /* Children of block */
  141. node_t *neighbor; /* Node connected to parent block, if any */
  142. } posstate;
  143. typedef struct {
  144. Agnode_t* n;
  145. double theta; /* angle of node */
  146. double minRadius; /* minimum radius for child circle */
  147. double maxRadius; /* maximum radius of child blocks */
  148. double diameter; /* length of arc needed for child blocks */
  149. double scale; /* scale factor to increase minRadius to parents' children don't overlap */
  150. int childCount; /* no. of child blocks attached at n */
  151. } posinfo_t;
  152. /// get size info for blocks attached to the given node.
  153. static double
  154. getInfo (posinfo_t* pi, posstate * stp, double min_dist)
  155. {
  156. block_t *child;
  157. double maxRadius = 0; /* Max. radius of children */
  158. double diameter = 0; /* sum of child diameters */
  159. int childCount = 0;
  160. for (child = stp->cp; child; child = child->next) {
  161. if (BLK_PARENT(child) == pi->n) {
  162. childCount++;
  163. if (maxRadius < child->radius) {
  164. maxRadius = child->radius;
  165. }
  166. diameter += 2 * child->radius + min_dist;
  167. }
  168. }
  169. pi->diameter = diameter;
  170. pi->childCount = childCount;
  171. pi->minRadius = stp->radius + min_dist + maxRadius;
  172. pi->maxRadius = maxRadius;
  173. return maxRadius;
  174. }
  175. static void
  176. setInfo (posinfo_t* p0, posinfo_t* p1, double delta)
  177. {
  178. double t = p0->diameter * p1->minRadius + p1->diameter * p0->minRadius;
  179. t /= 2*delta*p0->minRadius*p1->minRadius;
  180. t = fmax(t, 1);
  181. p0->scale = fmax(p0->scale, t);
  182. p1->scale = fmax(p1->scale, t);
  183. }
  184. static void positionChildren(posinfo_t *info, posstate *stp, size_t length,
  185. double min_dist) {
  186. block_t *child;
  187. double childAngle, childRadius, incidentAngle;
  188. double mindistAngle, rotateAngle, midAngle = 0.0;
  189. int midChild, cnt = 0;
  190. double snRadius = stp->subtreeR; /* max subtree radius */
  191. double firstAngle = stp->firstAngle;
  192. double lastAngle = stp->lastAngle;
  193. double d, deltaX, deltaY;
  194. childRadius = info->scale * info->minRadius;
  195. if (length == 1) {
  196. childAngle = 0;
  197. d = info->diameter / (2 * M_PI);
  198. childRadius = fmax(childRadius, d);
  199. d = 2 * M_PI * childRadius - info->diameter;
  200. if (d > 0)
  201. min_dist += d / info->childCount;
  202. }
  203. else
  204. childAngle = info->theta - info->diameter / (2 * childRadius);
  205. if ((childRadius + info->maxRadius) > snRadius)
  206. snRadius = childRadius + info->maxRadius;
  207. mindistAngle = min_dist / childRadius;
  208. midChild = (info->childCount + 1) / 2;
  209. for (child = stp->cp; child; child = child->next) {
  210. if (BLK_PARENT(child) != info->n)
  211. continue;
  212. if (nodelist_is_empty(&child->circle_list))
  213. continue;
  214. incidentAngle = child->radius / childRadius;
  215. if (length == 1) {
  216. if (childAngle != 0) {
  217. if (info->childCount == 2)
  218. childAngle = M_PI;
  219. else
  220. childAngle += incidentAngle;
  221. }
  222. if (firstAngle < 0)
  223. firstAngle = childAngle;
  224. lastAngle = childAngle;
  225. } else {
  226. if (info->childCount == 1) {
  227. childAngle = info->theta;
  228. } else {
  229. childAngle += incidentAngle + mindistAngle / 2;
  230. }
  231. }
  232. deltaX = childRadius * cos(childAngle);
  233. deltaY = childRadius * sin(childAngle);
  234. /* first apply the delta to the immediate child and see if we need
  235. * to rotate it for better edge link
  236. * should return the theta value if there was a rotation else zero
  237. */
  238. rotateAngle = getRotation(child, deltaX, deltaY, childAngle);
  239. applyDelta(child, deltaX, deltaY, rotateAngle);
  240. if (length == 1) {
  241. childAngle += incidentAngle + mindistAngle;
  242. } else {
  243. childAngle += incidentAngle + mindistAngle / 2;
  244. }
  245. cnt++;
  246. if (cnt == midChild)
  247. midAngle = childAngle;
  248. }
  249. if (length > 1 && info->n == stp->neighbor) {
  250. PSI(info->n) = midAngle;
  251. }
  252. stp->subtreeR = snRadius;
  253. stp->firstAngle = firstAngle;
  254. stp->lastAngle = lastAngle;
  255. }
  256. /* Assume childCount > 0
  257. * For each node in the block with children, getInfo is called, with the
  258. * information stored in the parents array.
  259. * This information is used by setInfo to compute the amount of space allocated
  260. * to each parent and the radius at which to place its children.
  261. * Finally, positionChildren is called to do the actual positioning.
  262. * If length is 1, keeps track of minimum and maximum child angle.
  263. */
  264. static double position(size_t childCount, size_t length, nodelist_t *nodepath,
  265. block_t * sn, double min_dist)
  266. {
  267. posstate state;
  268. int i, counter = 0;
  269. double maxRadius = 0.0;
  270. double angle;
  271. double theta = 0.0;
  272. posinfo_t* parents = gv_calloc(childCount, sizeof(posinfo_t));
  273. int num_parents = 0;
  274. posinfo_t* next;
  275. posinfo_t* curr;
  276. double delta;
  277. state.cp = sn->children.first;
  278. state.subtreeR = sn->radius;
  279. state.radius = sn->radius;
  280. state.neighbor = CHILD(sn);
  281. state.nodeAngle = 2 * M_PI / (double)length;
  282. state.firstAngle = -1;
  283. state.lastAngle = -1;
  284. for (size_t item = 0; item < nodelist_size(nodepath); ++item) {
  285. Agnode_t *n = nodelist_get(nodepath, item);
  286. theta = counter * state.nodeAngle;
  287. counter++;
  288. if (ISPARENT(n)) {
  289. parents[num_parents].n = n;
  290. parents[num_parents].theta = theta;
  291. maxRadius = getInfo (parents+num_parents, &state, min_dist);
  292. num_parents++;
  293. }
  294. }
  295. if (num_parents == 1)
  296. parents->scale = 1.0;
  297. else if (num_parents == 2) {
  298. curr = parents;
  299. next = parents+1;
  300. delta = next->theta - curr->theta;
  301. if (delta > M_PI)
  302. delta = 2*M_PI - delta;
  303. setInfo (curr, next, delta);
  304. }
  305. else {
  306. curr = parents;
  307. for (i = 0; i < num_parents; i++) {
  308. if (i+1 == num_parents) {
  309. next = parents;
  310. delta = next->theta - curr->theta + 2*M_PI;
  311. }
  312. else {
  313. next = curr+1;
  314. delta = next->theta - curr->theta;
  315. }
  316. setInfo (curr, next, delta);
  317. curr++;
  318. }
  319. }
  320. for (i = 0; i < num_parents; i++) {
  321. positionChildren(parents + i, &state, length, min_dist);
  322. }
  323. free (parents);
  324. /* If block has only 1 child, to save space, we coalesce it with the
  325. * child. Instead of having final radius sn->radius + max child radius,
  326. * we have half that. However, the origin of the block is no longer in
  327. * the center of the block, so we cannot do a simple rotation to get
  328. * the neighbor node next to the parent block in getRotate.
  329. */
  330. if (childCount == 1) {
  331. applyDelta(sn, -(maxRadius + min_dist / 2), 0, 0);
  332. sn->radius += min_dist / 2 + maxRadius;
  333. SET_COALESCED(sn);
  334. } else
  335. sn->radius = state.subtreeR;
  336. angle = (state.firstAngle + state.lastAngle) / 2.0 - M_PI;
  337. return angle;
  338. }
  339. /// Set positions of block sn and its child blocks.
  340. ///
  341. /// @param state Context containing a counter to use for graph copy naming
  342. static void doBlock(Agraph_t *g, block_t *sn, double min_dist,
  343. circ_state *state) {
  344. block_t *child;
  345. double centerAngle = M_PI;
  346. /* layout child subtrees */
  347. size_t childCount = 0;
  348. for (child = sn->children.first; child; child = child->next) {
  349. doBlock(g, child, min_dist, state);
  350. childCount++;
  351. }
  352. /* layout this block */
  353. nodelist_t longest_path = layout_block(g, sn, min_dist, state);
  354. sn->circle_list = longest_path;
  355. size_t length = nodelist_size(&longest_path); // path contains everything in block
  356. /* attach children */
  357. if (childCount > 0)
  358. centerAngle = position(childCount, length, &longest_path, sn, min_dist);
  359. if (length == 1 && BLK_PARENT(sn)) {
  360. sn->parent_pos = centerAngle;
  361. if (sn->parent_pos < 0)
  362. sn->parent_pos += 2 * M_PI;
  363. }
  364. }
  365. void circPos(Agraph_t * g, block_t * sn, circ_state * state)
  366. {
  367. doBlock(g, sn, state->min_dist, state);
  368. }