Graphviz 15.1.1~dev.20260630.1303
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 v2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.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/debug.h>
23#include <util/exit.h>
24#include <util/prisize_t.h>
25#include <vector>
26
27#include <sparse/DotIO.h>
30
31typedef enum {
34} fmt_t;
35
36typedef struct {
38 int idx;
39} etoi_t;
40
41#define ED_idx(e) (((etoi_t*)AGDATA(e))->idx)
42
43typedef struct {
45 int method;
47 double K;
52 double angle;
53} opts_t;
54
55static char *fname;
56static FILE *outfile;
57static char **Files;
58
59static const char use_msg[] =
60"Usage: mingle <options> <file>\n\
61 -a t - max. turning angle [0-180] (40)\n\
62 -c i - compatibility measure; 0 : distance, 1: full (default)\n\
63 -i iter: number of outer iterations/subdivisions (4)\n\
64 -k k - number of neighbors in the nearest neighbor graph of edges (10)\n\
65 -K k - the force constant\n\
66 -m method - method used. 0 (force directed), 1 (agglomerative ink saving, default), 2 (cluster+ink saving)\n\
67 -o fname - write output to file fname (stdout)\n\
68 -p t - balance for avoiding sharp angles\n\
69 The larger the t, the more sharp angles are allowed\n\
70 -r R - max. recursion level with agglomerative ink saving method (100)\n\
71 -T fmt - output format: gv (default) or simple\n\
72 -v - verbose\n";
73
74static void
76{
77 std::cerr << use_msg;
79}
80
81/* checkG:
82 * Return non-zero if g has loops or multiedges.
83 * Relies on multiedges occurring consecutively in edge list.
84 */
85static int
87{
88 Agedge_t* e;
89 Agnode_t* n;
90 Agnode_t* h;
91 Agnode_t* prevh = nullptr;
92
93 for (n = agfstnode (g); n; n = agnxtnode (g, n)) {
94 for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
95 if ((h = aghead(e)) == n) return 1; // loop
96 if (h == prevh) return 1; // multiedge
97 prevh = h;
98 }
99 prevh = nullptr; // reset
100 }
101 return 0;
102}
103
104static void init(int argc, char *argv[], opts_t &opts) {
105 int c;
106 char* cmd = argv[0];
107 double s;
108 int i;
109
110 opterr = 0;
111 opts.outer_iter = 4;
114 opts.K = -1;
115 opts.fmt = FMT_GV;
116 opts.nneighbors = 10;
117 opts.max_recursion = 100;
118 opts.angle_param = -1;
119 opts.angle = 40.0/180.0*M_PI;
120
121 while ((c = getopt(argc, argv, ":a:c:i:k:K:m:o:p:r:T:v:?")) != -1) {
122 switch (c) {
123 case 'a':
124 if (sscanf(optarg, "%lf", &s) > 0 && s >= 0)
125 opts.angle = M_PI * s / 180;
126 else
127 std::cerr << "-a arg " << optarg << " must be positive real - ignored\n";
128 break;
129 case 'c':
130 if (sscanf(optarg, "%d", &i) > 0 && 0 <= i && i <= COMPATIBILITY_FULL)
132 else
133 std::cerr << "-c arg " << optarg << " must be an integer in [0,"
134 << COMPATIBILITY_FULL << "] - ignored\n";
135 break;
136 case 'i':
137 if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
138 opts.outer_iter = i;
139 else
140 std::cerr << "-i arg " << optarg << " must be a non-negative integer - "
141 "ignored\n";
142 break;
143 case 'k':
144 if (sscanf(optarg, "%d", &i) > 0 && i >= 2)
145 opts.nneighbors = i;
146 else
147 std::cerr << "-k arg " << optarg << " must be an integer >= 2 - ignored\n";
148 break;
149 case 'K':
150 if (sscanf(optarg, "%lf", &s) > 0 && s > 0)
151 opts.K = s;
152 else
153 std::cerr << "-K arg " << optarg << " must be positive real - ignored\n";
154 break;
155 case 'm':
156 if (sscanf(optarg, "%d", &i) > 0 && 0 <= i && i <= METHOD_INK)
157 opts.method = i;
158 else
159 std::cerr << "-k arg " << optarg << " must be an integer >= 2 - ignored\n";
160 break;
161 case 'o':
162 outfile = openFile(cmd, optarg, "w");
163 break;
164 case 'p':
165 if (sscanf(optarg, "%lf", &s) > 0)
167 else
168 std::cerr << "-p arg " << optarg << " must be real - ignored\n";
169 break;
170 case 'r':
171 if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
173 else
174 std::cerr << "-r arg " << optarg << " must be a non-negative integer - "
175 "ignored\n";
176 break;
177 case 'T':
178 if (!strcmp(optarg, "gv"))
179 opts.fmt = FMT_GV;
180 else if (!strcmp(optarg,"simple"))
182 else
183 std::cerr << "-T arg " << optarg << " must be \"gv\" or \"simple\" - "
184 "ignored\n";
185 break;
186 case 'v':
187 Verbose = 1;
188 if (sscanf(optarg, "%d", &i) > 0 && i >= 0)
189 Verbose = static_cast<unsigned char>(i);
190 else
191 optind--;
192 break;
193 case ':':
194 if (optopt == 'v')
195 Verbose = 1;
196 else {
197 std::cerr << cmd << ": option -" << optopt << " missing argument\n";
198 usage(1);
199 }
200 break;
201 case '?':
202 if (optopt == '\0' || optopt == '?')
203 usage(0);
204 else {
205 std::cerr << cmd << ": option -" << optopt << " unrecognized\n";
206 usage(1);
207 }
208 break;
209 default:
210 break;
211 }
212 }
213 argv += optind;
214 argc -= optind;
215
216 if (argc > 0)
217 Files = argv;
218 if (!outfile) outfile = stdout;
219 if (Verbose) {
220 std::cerr
221 << "Mingle params:\n"
222 << " outer_iter = " << opts.outer_iter << '\n'
223 << " method = " << opts.method << '\n'
224 << " compatibility_method = " << opts.compatibility_method << '\n'
225 << " K = " << std::setprecision(2) << opts.K << '\n'
226 << " fmt = " << (opts.fmt ? "simple" : "gv") << '\n'
227 << " nneighbors = " << opts.nneighbors << '\n'
228 << " max_recursion = " << opts.max_recursion << '\n'
229 << " angle_param = " << std::setprecision(2) << opts.angle_param << '\n'
230 << " angle = " << std::setprecision(2) << (180 * opts.angle / M_PI) << '\n';
231 }
232}
233
234/* genBundleSpline:
235 * We have ninterval+1 points. We drop the ninterval-1 internal points, and add 4 points to the first
236 * and last intervals, and 3 to the rest, giving the needed 3*ninterval+4 points.
237 */
238static void genBundleSpline(const pedge &edge, std::ostream &os) {
239 int k, j;
240 const int dim = edge.dim;
241 const std::vector<double> &x = edge.x;
242
243 for (j = 0; j < edge.npoints; j++){
244 if (j != 0) {
245 os << ' ';
246 const std::vector<double> tt1{0.15, 0.5, 0.85};
247 const std::vector<double> tt2{0.15, 0.4, 0.6, 0.85};
248 const std::vector<double> &tt = (j == 1 || j == edge.npoints - 1) ? tt2 : tt1;
249 for (const double t : tt){
250 for (k = 0; k < dim; k++) {
251 if (k != 0) os << ',';
252 os << std::setprecision(3) << (x[(j-1)*dim+k]*(1-t)+x[j*dim+k]*t);
253 }
254 os << ' ';
255 }
256 }
257 if (j == 0 || j == edge.npoints - 1) {
258 for (k = 0; k < dim; k++) {
259 if (k != 0) os << ',';
260 os << std::setprecision(3) << x[j * dim + k];
261 }
262 }
263 }
264}
265
266static void genBundleInfo(const pedge &edge, std::ostream &os) {
267 int k, j;
268 const int dim = edge.dim;
269 const std::vector<double> &x = edge.x;
270
271 for (j = 0; j < edge.npoints; j++){
272 if (j != 0) os << ':';
273 for (k = 0; k < dim; k++) {
274 if (k != 0) os << ',';
275 os << std::setprecision(3) << x[j * dim + k];
276 }
277
278 if (j < edge.npoints - 1 && !edge.wgts.empty()) {
279 os << ';' << std::setprecision(3) << edge.wgts[j];
280 }
281 }
282}
283
284static void genBundleColors(const pedge &edge, std::ostream &os,
285 double maxwgt) {
286 int k, j, r, g, b;
287 double len, t, len_total0 = 0;
288 const int dim = edge.dim;
289 const std::vector<double> &x = edge.x;
290 std::vector<double> lens(edge.npoints);
291
292 for (j = 0; j < edge.npoints - 1; j++){
293 len = 0;
294 for (k = 0; k < dim; k++){
295 len += (x[dim*j+k] - x[dim*(j+1)+k])*(x[dim*j+k] - x[dim*(j+1)+k]);
296 }
297 lens[j] = len = sqrt(len/k);
298 len_total0 += len;
299 }
300 for (j = 0; j < edge.npoints - 1; j++){
301 t = edge.wgts[j] / maxwgt;
302 /* interpolate between red (t = 1) to blue (t = 0) */
303 r = 255*t; g = 0; b = 255*(1-t);
304 if (j != 0) os << ':';
305 os << std::hex << std::setw(2) << std::setfill('0') << '#' << r << g << b
306 << 85;
307 if (j < edge.npoints - 2) {
308 os << ';' << (lens[j] / len_total0);
309 }
310 }
311 os << std::dec << std::setw(0); // reset stream characteristics
312}
313
314static void export_dot(FILE *fp, size_t ne, const std::vector<pedge> &edges,
315 Agraph_t *g) {
316 Agsym_t* epos = agattr_text(g, AGEDGE, const_cast<char*>("pos"), "");
317 Agsym_t* esects = agattr_text(g, AGEDGE, const_cast<char*>("bundle"), "");
318 Agsym_t* eclrs = nullptr;
319 Agnode_t* n;
320 Agedge_t* e;
321 int j;
322 double maxwgt = 0;
323
324 /* figure out max number of bundled original edges in a pedge */
325 for (size_t i = 0; i < ne; i++){
326 const pedge &edge = edges[i];
327 if (!edge.wgts.empty()) {
328 for (j = 0; j < edge.npoints - 1; j++){
329 maxwgt = std::max(maxwgt, edge.wgts[j]);
330 }
331 }
332 }
333
334 std::ostringstream buf;
335 for (n = agfstnode (g); n; n = agnxtnode (g, n)) {
336 for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
337 const pedge &edge = edges[ED_idx(e)];
338
339 genBundleSpline(edge, buf);
340 agxset(e, epos, buf.str().c_str());
341 buf.str("");
342
343 genBundleInfo(edge, buf);
344 agxset(e, esects, buf.str().c_str());
345 buf.str("");
346
347 if (!edge.wgts.empty()) {
348 if (!eclrs) eclrs = agattr_text(g, AGEDGE, const_cast<char*>("color"), "");
349 genBundleColors(edge, buf, maxwgt);
350 agxset(e, eclrs, buf.str().c_str());
351 buf.str("");
352 }
353 }
354 }
355 agwrite(g, fp);
356}
357
359struct PointHash {
360public:
361 size_t operator()(const std::pair<int, int> &x) const {
362 return std::hash<int>{}(x.first) ^ std::hash<int>{}(x.second);
363 }
364};
365
366using PointMap = std::unordered_map<std::pair<int, int>, int, PointHash>;
367
368static int bundle(Agraph_t *g, const opts_t &opts) {
369 double *x = nullptr;
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 (size_t i = 0; i < A->m; i++){
396 for (int j = ia[i]; j < ia[i+1]; j++){
397 if (ja[j] > (int)i){
398 pm[std::pair((int)i, ja[j])] = idx++;
399 }
400 }
401 }
402 int i = 0;
403 for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
404 setDotNodeID(n, i++);
405 }
406 for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
407 for (e = agfstout (g, n); e; e = agnxtout (g, e)) {
408 i = getDotNodeID (agtail(e));
409 int j = getDotNodeID (aghead(e));
410 if (j < i) {
411 std::swap(i, j);
412 }
413 const int k = pm[std::pair(i, j)];
414 agbindrec (e, "info", sizeof(etoi_t), true);
415 ED_idx(e) = k;
416 }
417 }
418 }
419
420 const int *ia = A->ia;
421 const int *ja = A->ja;
422 int nz = A->nz;
423 std::vector<double> xx(nz * 4);
424 nz = 0;
425 const int dim = 4;
426 for (size_t i = 0; i < A->m; i++){
427 for (int j = ia[i]; j < ia[i+1]; j++){
428 if (ja[j] > (int)i){
429 xx[nz*dim] = x[i*2];
430 xx[nz*dim+1] = x[i*2+1];
431 xx[nz*dim+2] = x[ja[j]*2];
432 xx[nz*dim+3] = x[ja[j]*2+1];
433 nz++;
434 }
435 }
436 }
437 GV_DEBUG("n = %" PRISIZE_T " nz = %d", A->m, nz);
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 GV_INFO("Process graph %s in file %s", agnameof(g), fname);
476 rv |= bundle(g, opts);
477 }
478
479 graphviz_exit(rv);
480}
481
int getDotNodeID(Agnode_t *n)
Definition DotIO.c:639
SparseMatrix SparseMatrix_import_dot(Agraph_t *g, double **x, int format)
Definition DotIO.c:91
void initDotIO(Agraph_t *g)
Definition DotIO.c:633
void setDotNodeID(Agnode_t *n, int v)
Definition DotIO.c:637
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:42
helpers for verbose/debug printing
#define GV_DEBUG(...)
Definition debug.h:40
#define GV_INFO(...)
Definition debug.h:15
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, size_t 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:271
static double len(glCompPoint p)
Definition glutils.c:138
static bool Verbose
Definition gml2gv.c:26
void free(void *)
edge
Definition gmlparse.y:246
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:333
int agxset(void *obj, Agsym_t *sym, const char *value)
Definition attr.c:521
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:28
#define agtail(e)
Definition cgraph.h:977
#define aghead(e)
Definition cgraph.h:978
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:43
void agerrorf(const char *fmt,...)
Definition agerror.c:167
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:157
@ AGPREV
Definition cgraph.h:946
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:97
int agwrite(Agraph_t *g, void *chan)
Return 0 on success, EOF on failure.
Definition write.c:669
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:50
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:43
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
@ 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:91
static opts_t opts
Definition gvgen.c:415
static const char * usage
Definition gvpr.c:54
#define B
Definition hierarchy.c:120
$2 prev
Definition htmlparse.y:291
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)
static void export_dot(FILE *fp, size_t ne, const std::vector< pedge > &edges, Agraph_t *g)
fmt_t
@ FMT_SIMPLE
@ FMT_GV
static char ** Files
static void init(int argc, char *argv[], opts_t &opts)
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
#define PRISIZE_T
Definition prisize_t.h:25
graph or subgraph
Definition cgraph.h:424
implementation of Agrec_t
Definition cgraph.h:172
string attribute descriptor symbol in Agattr_s.dict
Definition cgraph.h:640
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:90