minglemain.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. *************************************************************************/
  9. #include "config.h"
  10. #include "../tools/openFile.h"
  11. #include <algorithm>
  12. #include <cgraph/cgraph.h>
  13. #include <cgraph/ingraphs.h>
  14. #include <getopt.h>
  15. #include <iomanip>
  16. #include <iostream>
  17. #include <sstream>
  18. #include <unordered_map>
  19. #include <utility>
  20. #include <util/exit.h>
  21. #include <vector>
  22. #include <sparse/DotIO.h>
  23. #include <mingle/edge_bundling.h>
  24. #include <mingle/nearest_neighbor_graph.h>
  25. typedef enum {
  26. FMT_GV,
  27. FMT_SIMPLE,
  28. } fmt_t;
  29. typedef struct {
  30. Agrec_t hdr;
  31. int idx;
  32. } etoi_t;
  33. #define ED_idx(e) (((etoi_t*)AGDATA(e))->idx)
  34. typedef struct {
  35. int outer_iter;
  36. int method;
  37. int compatibility_method;
  38. double K;
  39. fmt_t fmt;
  40. int nneighbors;
  41. int max_recursion;
  42. double angle_param;
  43. double angle;
  44. } opts_t;
  45. static char *fname;
  46. static FILE *outfile;
  47. static char **Files;
  48. static const char use_msg[] =
  49. "Usage: mingle <options> <file>\n\
  50. -a t - max. turning angle [0-180] (40)\n\
  51. -c i - compatability measure; 0 : distance, 1: full (default)\n\
  52. -i iter: number of outer iterations/subdivisions (4)\n\
  53. -k k - number of neighbors in the nearest neighbor graph of edges (10)\n\
  54. -K k - the force constant\n\
  55. -m method - method used. 0 (force directed), 1 (agglomerative ink saving, default), 2 (cluster+ink saving)\n\
  56. -o fname - write output to file fname (stdout)\n\
  57. -p t - balance for avoiding sharp angles\n\
  58. The larger the t, the more sharp angles are allowed\n\
  59. -r R - max. recursion level with agglomerative ink saving method (100)\n\
  60. -T fmt - output format: gv (default) or simple\n\
  61. -v - verbose\n";
  62. static void
  63. usage (int eval)
  64. {
  65. std::cerr << use_msg;
  66. graphviz_exit(eval);
  67. }
  68. /* checkG:
  69. * Return non-zero if g has loops or multiedges.
  70. * Relies on multiedges occurring consecutively in edge list.
  71. */
  72. static int
  73. checkG (Agraph_t* g)
  74. {
  75. Agedge_t* e;
  76. Agnode_t* n;
  77. Agnode_t* h;
  78. Agnode_t* prevh = nullptr;
  79. for (n = agfstnode (g); n; n = agnxtnode (g, n)) {
  80. for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
  81. if ((h = aghead(e)) == n) return 1; // loop
  82. if (h == prevh) return 1; // multiedge
  83. prevh = h;
  84. }
  85. prevh = nullptr; // reset
  86. }
  87. return 0;
  88. }
  89. static void init(int argc, char *argv[], opts_t &opts) {
  90. int c;
  91. char* cmd = argv[0];
  92. double s;
  93. int i;
  94. opterr = 0;
  95. opts.outer_iter = 4;
  96. opts.method = METHOD_INK_AGGLOMERATE;
  97. opts.compatibility_method = COMPATIBILITY_FULL;
  98. opts.K = -1;
  99. opts.fmt = FMT_GV;
  100. opts.nneighbors = 10;
  101. opts.max_recursion = 100;
  102. opts.angle_param = -1;
  103. opts.angle = 40.0/180.0*M_PI;
  104. while ((c = getopt(argc, argv, ":a:c:i:k:K:m:o:p:r:T:v:?")) != -1) {
  105. switch (c) {
  106. case 'a':
  107. if (sscanf(optarg, "%lf", &s) > 0 && s >= 0)
  108. opts.angle = M_PI * s / 180;
  109. else
  110. std::cerr << "-a arg " << optarg << " must be positive real - ignored\n";
  111. break;
  112. case 'c':
  113. if (sscanf(optarg, "%d", &i) > 0 && 0 <= i && i <= COMPATIBILITY_FULL)
  114. opts.compatibility_method = i;
  115. else
  116. std::cerr << "-c arg " << optarg << " must be an integer in [0,"
  117. << COMPATIBILITY_FULL << "] - ignored\n";
  118. break;
  119. case 'i':
  120. if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
  121. opts.outer_iter = i;
  122. else
  123. std::cerr << "-i arg " << optarg << " must be a non-negative integer - "
  124. "ignored\n";
  125. break;
  126. case 'k':
  127. if (sscanf(optarg, "%d", &i) > 0 && i >= 2)
  128. opts.nneighbors = i;
  129. else
  130. std::cerr << "-k arg " << optarg << " must be an integer >= 2 - ignored\n";
  131. break;
  132. case 'K':
  133. if (sscanf(optarg, "%lf", &s) > 0 && s > 0)
  134. opts.K = s;
  135. else
  136. std::cerr << "-K arg " << optarg << " must be positive real - ignored\n";
  137. break;
  138. case 'm':
  139. if (sscanf(optarg, "%d", &i) > 0 && 0 <= i && i <= METHOD_INK)
  140. opts.method = i;
  141. else
  142. std::cerr << "-k arg " << optarg << " must be an integer >= 2 - ignored\n";
  143. break;
  144. case 'o':
  145. outfile = openFile(cmd, optarg, "w");
  146. break;
  147. case 'p':
  148. if (sscanf(optarg, "%lf", &s) > 0)
  149. opts.angle_param = s;
  150. else
  151. std::cerr << "-p arg " << optarg << " must be real - ignored\n";
  152. break;
  153. case 'r':
  154. if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
  155. opts.max_recursion = i;
  156. else
  157. std::cerr << "-r arg " << optarg << " must be a non-negative integer - "
  158. "ignored\n";
  159. break;
  160. case 'T':
  161. if (!strcmp(optarg, "gv"))
  162. opts.fmt = FMT_GV;
  163. else if (!strcmp(optarg,"simple"))
  164. opts.fmt = FMT_SIMPLE;
  165. else
  166. std::cerr << "-T arg " << optarg << " must be \"gv\" or \"simple\" - "
  167. "ignored\n";
  168. break;
  169. case 'v':
  170. Verbose = 1;
  171. if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
  172. Verbose = static_cast<unsigned char>(i);
  173. else
  174. optind--;
  175. break;
  176. case ':':
  177. if (optopt == 'v')
  178. Verbose = 1;
  179. else {
  180. std::cerr << cmd << ": option -" << optopt << " missing argument\n";
  181. usage(1);
  182. }
  183. break;
  184. case '?':
  185. if (optopt == '\0' || optopt == '?')
  186. usage(0);
  187. else {
  188. std::cerr << cmd << ": option -" << optopt << " unrecognized\n";
  189. usage(1);
  190. }
  191. break;
  192. default:
  193. break;
  194. }
  195. }
  196. argv += optind;
  197. argc -= optind;
  198. if (argc > 0)
  199. Files = argv;
  200. if (!outfile) outfile = stdout;
  201. if (Verbose) {
  202. std::cerr
  203. << "Mingle params:\n"
  204. << " outer_iter = " << opts.outer_iter << '\n'
  205. << " method = " << opts.method << '\n'
  206. << " compatibility_method = " << opts.compatibility_method << '\n'
  207. << " K = " << std::setprecision(2) << opts.K << '\n'
  208. << " fmt = " << (opts.fmt ? "simple" : "gv") << '\n'
  209. << " nneighbors = " << opts.nneighbors << '\n'
  210. << " max_recursion = " << opts.max_recursion << '\n'
  211. << " angle_param = " << std::setprecision(2) << opts.angle_param << '\n'
  212. << " angle = " << std::setprecision(2) << (180 * opts.angle / M_PI) << '\n';
  213. }
  214. }
  215. /* genBundleSpline:
  216. * We have ninterval+1 points. We drop the ninterval-1 internal points, and add 4 points to the first
  217. * and last intervals, and 3 to the rest, giving the needed 3*ninterval+4 points.
  218. */
  219. static void genBundleSpline(const pedge &edge, std::ostream &os) {
  220. int k, j;
  221. const int dim = edge.dim;
  222. const std::vector<double> &x = edge.x;
  223. for (j = 0; j < edge.npoints; j++){
  224. if (j != 0) {
  225. os << ' ';
  226. const std::vector<double> tt1{0.15, 0.5, 0.85};
  227. const std::vector<double> tt2{0.15, 0.4, 0.6, 0.85};
  228. const std::vector<double> &tt = (j == 1 || j == edge.npoints - 1) ? tt2 : tt1;
  229. for (const double t : tt){
  230. for (k = 0; k < dim; k++) {
  231. if (k != 0) os << ',';
  232. os << std::setprecision(3) << (x[(j-1)*dim+k]*(1-t)+x[j*dim+k]*t);
  233. }
  234. os << ' ';
  235. }
  236. }
  237. if (j == 0 || j == edge.npoints - 1) {
  238. for (k = 0; k < dim; k++) {
  239. if (k != 0) os << ',';
  240. os << std::setprecision(3) << x[j * dim + k];
  241. }
  242. }
  243. }
  244. }
  245. static void genBundleInfo(const pedge &edge, std::ostream &os) {
  246. int k, j;
  247. const int dim = edge.dim;
  248. const std::vector<double> &x = edge.x;
  249. for (j = 0; j < edge.npoints; j++){
  250. if (j != 0) os << ':';
  251. for (k = 0; k < dim; k++) {
  252. if (k != 0) os << ',';
  253. os << std::setprecision(3) << x[j * dim + k];
  254. }
  255. if (j < edge.npoints - 1 && !edge.wgts.empty()) {
  256. os << ';' << std::setprecision(3) << edge.wgts[j];
  257. }
  258. }
  259. }
  260. static void genBundleColors(const pedge &edge, std::ostream &os,
  261. double maxwgt) {
  262. int k, j, r, g, b;
  263. double len, t, len_total0 = 0;
  264. const int dim = edge.dim;
  265. const std::vector<double> &x = edge.x;
  266. std::vector<double> lens(edge.npoints);
  267. for (j = 0; j < edge.npoints - 1; j++){
  268. len = 0;
  269. for (k = 0; k < dim; k++){
  270. len += (x[dim*j+k] - x[dim*(j+1)+k])*(x[dim*j+k] - x[dim*(j+1)+k]);
  271. }
  272. lens[j] = len = sqrt(len/k);
  273. len_total0 += len;
  274. }
  275. for (j = 0; j < edge.npoints - 1; j++){
  276. t = edge.wgts[j] / maxwgt;
  277. /* interpolate between red (t = 1) to blue (t = 0) */
  278. r = 255*t; g = 0; b = 255*(1-t);
  279. if (j != 0) os << ':';
  280. os << std::hex << std::setw(2) << std::setfill('0') << '#' << r << g << b
  281. << 85;
  282. if (j < edge.npoints - 2) {
  283. os << ';' << (lens[j] / len_total0);
  284. }
  285. }
  286. os << std::dec << std::setw(0); // reset stream characteristics
  287. }
  288. static void export_dot(FILE *fp, int ne, const std::vector<pedge> &edges,
  289. Agraph_t *g) {
  290. Agsym_t* epos = agattr(g, AGEDGE, const_cast<char*>("pos"), "");
  291. Agsym_t* esects = agattr(g, AGEDGE, const_cast<char*>("bundle"), "");
  292. Agsym_t* eclrs = nullptr;
  293. Agnode_t* n;
  294. Agedge_t* e;
  295. int i, j;
  296. double maxwgt = 0;
  297. /* figure out max number of bundled original edges in a pedge */
  298. for (i = 0; i < ne; i++){
  299. const pedge &edge = edges[i];
  300. if (!edge.wgts.empty()) {
  301. for (j = 0; j < edge.npoints - 1; j++){
  302. maxwgt = std::max(maxwgt, edge.wgts[j]);
  303. }
  304. }
  305. }
  306. std::ostringstream buf;
  307. for (n = agfstnode (g); n; n = agnxtnode (g, n)) {
  308. for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
  309. const pedge &edge = edges[ED_idx(e)];
  310. genBundleSpline(edge, buf);
  311. agxset(e, epos, buf.str().c_str());
  312. buf.str("");
  313. genBundleInfo(edge, buf);
  314. agxset(e, esects, buf.str().c_str());
  315. buf.str("");
  316. if (!edge.wgts.empty()) {
  317. if (!eclrs) eclrs = agattr(g, AGEDGE, const_cast<char*>("color"), "");
  318. genBundleColors(edge, buf, maxwgt);
  319. agxset(e, eclrs, buf.str().c_str());
  320. buf.str("");
  321. }
  322. }
  323. }
  324. agwrite(g, fp);
  325. }
  326. /// a hash derivation function for int pairs
  327. struct PointHash {
  328. public:
  329. size_t operator()(const std::pair<int, int> &x) const {
  330. return std::hash<int>{}(x.first) ^ std::hash<int>{}(x.second);
  331. }
  332. };
  333. using PointMap = std::unordered_map<std::pair<int, int>, int, PointHash>;
  334. static int bundle(Agraph_t *g, const opts_t &opts) {
  335. double *x = nullptr;
  336. int dim = 2;
  337. int i;
  338. if (checkG(g)) {
  339. agerrorf("Graph %s (%s) contains loops or multiedges\n", agnameof(g), fname);
  340. return 1;
  341. }
  342. initDotIO(g);
  343. SparseMatrix A = SparseMatrix_import_dot(g, dim, &x, FORMAT_CSR);
  344. if (!A){
  345. agerrorf("Error: could not convert graph %s (%s) into matrix\n", agnameof(g), fname);
  346. return 1;
  347. }
  348. if (x == nullptr) {
  349. agerr (AGPREV, " in file %s\n", fname);
  350. return 1;
  351. }
  352. A = SparseMatrix_symmetrize(A, true);
  353. if (opts.fmt == FMT_GV) {
  354. PointMap pm; // map from node id pairs to edge index
  355. Agnode_t* n;
  356. Agedge_t* e;
  357. int idx = 0;
  358. const int *ia = A->ia;
  359. const int *ja = A->ja;
  360. for (i = 0; i < A->m; i++){
  361. for (int j = ia[i]; j < ia[i+1]; j++){
  362. if (ja[j] > i){
  363. pm[std::pair(i, ja[j])] = idx++;
  364. }
  365. }
  366. }
  367. for (i = 0, n = agfstnode(g); n; n = agnxtnode(g,n)) {
  368. setDotNodeID(n, i++);
  369. }
  370. for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
  371. for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
  372. i = getDotNodeID (agtail(e));
  373. int j = getDotNodeID (aghead(e));
  374. if (j < i) {
  375. std::swap(i, j);
  376. }
  377. const int k = pm[std::pair(i, j)];
  378. agbindrec (e, "info", sizeof(etoi_t), true);
  379. ED_idx(e) = k;
  380. }
  381. }
  382. }
  383. const int *ia = A->ia;
  384. const int *ja = A->ja;
  385. int nz = A->nz;
  386. std::vector<double> xx(nz * 4);
  387. nz = 0;
  388. dim = 4;
  389. for (i = 0; i < A->m; i++){
  390. for (int j = ia[i]; j < ia[i+1]; j++){
  391. if (ja[j] > i){
  392. xx[nz*dim] = x[i*2];
  393. xx[nz*dim+1] = x[i*2+1];
  394. xx[nz*dim+2] = x[ja[j]*2];
  395. xx[nz*dim+3] = x[ja[j]*2+1];
  396. nz++;
  397. }
  398. }
  399. }
  400. if (Verbose)
  401. std::cerr << "n = " << A->m << " nz = " << nz << '\n';
  402. SparseMatrix B = nearest_neighbor_graph(nz, std::min(opts.nneighbors, nz), xx);
  403. SparseMatrix_delete(A);
  404. A = B;
  405. free(x);
  406. std::vector<pedge> edges =
  407. edge_bundling(A, 2, xx, opts.outer_iter, opts.K, opts.method,
  408. opts.nneighbors, opts.compatibility_method,
  409. opts.max_recursion, opts.angle_param, opts.angle);
  410. if (opts.fmt == FMT_GV) {
  411. export_dot(outfile, A->m, edges, g);
  412. }
  413. else {
  414. pedge_export_gv(outfile, A->m, edges);
  415. }
  416. return 0;
  417. }
  418. int main(int argc, char *argv[])
  419. {
  420. Agraph_t *g;
  421. Agraph_t *prev = nullptr;
  422. ingraph_state ig;
  423. int rv = 0;
  424. opts_t opts;
  425. init(argc, argv, opts);
  426. newIngraph(&ig, Files);
  427. while ((g = nextGraph(&ig)) != 0) {
  428. if (prev)
  429. agclose(prev);
  430. prev = g;
  431. fname = fileName(&ig);
  432. if (Verbose)
  433. std::cerr << "Process graph " << agnameof(g) << " in file " << fname << '\n';
  434. rv |= bundle(g, opts);
  435. }
  436. graphviz_exit(rv);
  437. }
  438. /**
  439. * @dir .
  440. * @brief fast edge bundling
  441. */