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