| 1 | /* $Id$ $Revision$ */ |
| 2 | /* vim:set shiftwidth=4 ts=8: */ |
| 3 | |
| 4 | /************************************************************************* |
| 5 | * Copyright (c) 2011 AT&T Intellectual Property |
| 6 | * All rights reserved. This program and the accompanying materials |
| 7 | * are made available under the terms of the Eclipse Public License v1.0 |
| 8 | * which accompanies this distribution, and is available at |
| 9 | * http://www.eclipse.org/legal/epl-v10.html |
| 10 | * |
| 11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
| 12 | *************************************************************************/ |
| 13 | |
| 14 | |
| 15 | #include "config.h" |
| 16 | #include <limits.h> |
| 17 | #include <sfdp.h> |
| 18 | #include <neato.h> |
| 19 | #include <adjust.h> |
| 20 | #include <pack.h> |
| 21 | #include <assert.h> |
| 22 | #include <ctype.h> |
| 23 | #include <spring_electrical.h> |
| 24 | #include <overlap.h> |
| 25 | #include <uniform_stress.h> |
| 26 | #include <stress_model.h> |
| 27 | |
| 28 | static void sfdp_init_edge(edge_t * e) |
| 29 | { |
| 30 | agbindrec(e, "Agedgeinfo_t" , sizeof(Agedgeinfo_t), TRUE); //node custom data |
| 31 | common_init_edge(e); |
| 32 | } |
| 33 | |
| 34 | static void sfdp_init_node_edge(graph_t * g) |
| 35 | { |
| 36 | node_t *n; |
| 37 | edge_t *e; |
| 38 | #if 0 |
| 39 | int nnodes = agnnodes(g); |
| 40 | attrsym_t *N_pos = agfindnodeattr(g, "pos" ); |
| 41 | #endif |
| 42 | |
| 43 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 44 | neato_init_node(n); |
| 45 | #if 0 |
| 46 | FIX so that user positions works with multiscale |
| 47 | user_pos(N_pos, NULL, n, nnodes); |
| 48 | #endif |
| 49 | } |
| 50 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 51 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) |
| 52 | sfdp_init_edge(e); |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | static void sfdp_init_graph(Agraph_t * g) |
| 57 | { |
| 58 | int outdim; |
| 59 | |
| 60 | setEdgeType(g, ET_LINE); |
| 61 | outdim = late_int(g, agfindgraphattr(g, "dimen" ), 2, 2); |
| 62 | GD_ndim(agroot(g)) = late_int(g, agfindgraphattr(g, "dim" ), outdim, 2); |
| 63 | Ndim = GD_ndim(agroot(g)) = MIN(GD_ndim(agroot(g)), MAXDIM); |
| 64 | GD_odim(agroot(g)) = MIN(outdim, Ndim); |
| 65 | sfdp_init_node_edge(g); |
| 66 | } |
| 67 | |
| 68 | /* getPos: |
| 69 | */ |
| 70 | static real *getPos(Agraph_t * g, spring_electrical_control ctrl) |
| 71 | { |
| 72 | Agnode_t *n; |
| 73 | real *pos = N_NEW(Ndim * agnnodes(g), real); |
| 74 | int ix, i; |
| 75 | |
| 76 | if (agfindnodeattr(g, "pos" ) == NULL) |
| 77 | return pos; |
| 78 | |
| 79 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 80 | i = ND_id(n); |
| 81 | if (hasPos(n)) { |
| 82 | for (ix = 0; ix < Ndim; ix++) { |
| 83 | pos[i * Ndim + ix] = ND_pos(n)[ix]; |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | return pos; |
| 89 | } |
| 90 | |
| 91 | static void sfdpLayout(graph_t * g, spring_electrical_control ctrl, |
| 92 | int hops, pointf pad) |
| 93 | { |
| 94 | real *sizes; |
| 95 | real *pos; |
| 96 | Agnode_t *n; |
| 97 | int flag, i; |
| 98 | int n_edge_label_nodes = 0, *edge_label_nodes = NULL; |
| 99 | SparseMatrix D = NULL; |
| 100 | SparseMatrix A; |
| 101 | |
| 102 | if (ctrl->method == METHOD_SPRING_MAXENT) /* maxent can work with distance matrix */ |
| 103 | A = makeMatrix(g, Ndim, &D); |
| 104 | else |
| 105 | A = makeMatrix(g, Ndim, NULL); |
| 106 | |
| 107 | if (ctrl->overlap >= 0) { |
| 108 | if (ctrl->edge_labeling_scheme > 0) |
| 109 | sizes = getSizes(g, pad, &n_edge_label_nodes, &edge_label_nodes); |
| 110 | else |
| 111 | sizes = getSizes(g, pad, NULL, NULL); |
| 112 | } |
| 113 | else |
| 114 | sizes = NULL; |
| 115 | pos = getPos(g, ctrl); |
| 116 | |
| 117 | switch (ctrl->method) { |
| 118 | case METHOD_SPRING_ELECTRICAL: |
| 119 | case METHOD_SPRING_MAXENT: |
| 120 | multilevel_spring_electrical_embedding(Ndim, A, D, ctrl, NULL, sizes, pos, n_edge_label_nodes, edge_label_nodes, &flag); |
| 121 | break; |
| 122 | case METHOD_UNIFORM_STRESS: |
| 123 | uniform_stress(Ndim, A, pos, &flag); |
| 124 | break; |
| 125 | case METHOD_STRESS:{ |
| 126 | int maxit = 200; |
| 127 | real tol = 0.001; |
| 128 | int weighted = TRUE; |
| 129 | |
| 130 | if (!D){ |
| 131 | D = SparseMatrix_get_real_adjacency_matrix_symmetrized(A);/* all distance 1 */ |
| 132 | weighted = FALSE; |
| 133 | } else { |
| 134 | D = SparseMatrix_symmetrize_nodiag(D, FALSE); |
| 135 | weighted = TRUE; |
| 136 | } |
| 137 | if (hops > 0){ |
| 138 | SparseMatrix DD; |
| 139 | DD = SparseMatrix_distance_matrix_khops(hops, D, weighted); |
| 140 | if (Verbose){ |
| 141 | fprintf(stderr,"extracted a %d-neighborhood graph of %d edges from a graph of %d edges\n" , |
| 142 | hops, (DD->nz)/2, (D->nz/2)); |
| 143 | } |
| 144 | SparseMatrix_delete(D); |
| 145 | D = DD; |
| 146 | } |
| 147 | |
| 148 | stress_model(Ndim, A, D, &pos, TRUE, maxit, tol, &flag); |
| 149 | } |
| 150 | break; |
| 151 | } |
| 152 | |
| 153 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 154 | real *npos = pos + (Ndim * ND_id(n)); |
| 155 | for (i = 0; i < Ndim; i++) { |
| 156 | ND_pos(n)[i] = npos[i]; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | free(sizes); |
| 161 | free(pos); |
| 162 | SparseMatrix_delete (A); |
| 163 | if (D) SparseMatrix_delete (D); |
| 164 | if (edge_label_nodes) FREE(edge_label_nodes); |
| 165 | } |
| 166 | |
| 167 | #if UNUSED |
| 168 | static int |
| 169 | late_mode (graph_t* g, Agsym_t* sym, int dflt) |
| 170 | { |
| 171 | char* s; |
| 172 | int v; |
| 173 | int rv; |
| 174 | |
| 175 | if (!sym) return dflt; |
| 176 | s = agxget (g, sym); |
| 177 | if (isdigit(*s)) { |
| 178 | if ((v = atoi (s)) <= METHOD_UNIFORM_STRESS) |
| 179 | rv = v; |
| 180 | else |
| 181 | rv = dflt; |
| 182 | } |
| 183 | else if (isalpha(*s)) { |
| 184 | if (!strcasecmp(s, "spring" )) |
| 185 | rv = METHOD_SPRING_ELECTRICAL; |
| 186 | else if (!strcasecmp(s, "maxent" )) |
| 187 | rv = METHOD_SPRING_MAXENT; |
| 188 | else if (!strcasecmp(s, "stress" )) |
| 189 | rv = METHOD_STRESS; |
| 190 | else if (!strcasecmp(s, "uniform" )) |
| 191 | rv = METHOD_UNIFORM_STRESS; |
| 192 | else { |
| 193 | agerr (AGWARN, "Unknown value \"%s\" for mode attribute\n" , s); |
| 194 | rv = dflt; |
| 195 | } |
| 196 | } |
| 197 | else |
| 198 | rv = dflt; |
| 199 | return rv; |
| 200 | } |
| 201 | #endif |
| 202 | |
| 203 | static int |
| 204 | late_smooth (graph_t* g, Agsym_t* sym, int dflt) |
| 205 | { |
| 206 | char* s; |
| 207 | int v; |
| 208 | int rv; |
| 209 | |
| 210 | if (!sym) return dflt; |
| 211 | s = agxget (g, sym); |
| 212 | if (isdigit(*s)) { |
| 213 | #if (HAVE_GTS || HAVE_TRIANGLE) |
| 214 | if ((v = atoi (s)) <= SMOOTHING_RNG) |
| 215 | #else |
| 216 | if ((v = atoi (s)) <= SMOOTHING_SPRING) |
| 217 | #endif |
| 218 | rv = v; |
| 219 | else |
| 220 | rv = dflt; |
| 221 | } |
| 222 | else if (isalpha(*s)) { |
| 223 | if (!strcasecmp(s, "avg_dist" )) |
| 224 | rv = SMOOTHING_STRESS_MAJORIZATION_AVG_DIST; |
| 225 | else if (!strcasecmp(s, "graph_dist" )) |
| 226 | rv = SMOOTHING_STRESS_MAJORIZATION_GRAPH_DIST; |
| 227 | else if (!strcasecmp(s, "none" )) |
| 228 | rv = SMOOTHING_NONE; |
| 229 | else if (!strcasecmp(s, "power_dist" )) |
| 230 | rv = SMOOTHING_STRESS_MAJORIZATION_POWER_DIST; |
| 231 | #if (HAVE_GTS || HAVE_TRIANGLE) |
| 232 | else if (!strcasecmp(s, "rng" )) |
| 233 | rv = SMOOTHING_RNG; |
| 234 | #endif |
| 235 | else if (!strcasecmp(s, "spring" )) |
| 236 | rv = SMOOTHING_SPRING; |
| 237 | #if (HAVE_GTS || HAVE_TRIANGLE) |
| 238 | else if (!strcasecmp(s, "triangle" )) |
| 239 | rv = SMOOTHING_TRIANGLE; |
| 240 | #endif |
| 241 | else |
| 242 | rv = dflt; |
| 243 | } |
| 244 | else |
| 245 | rv = dflt; |
| 246 | return rv; |
| 247 | } |
| 248 | |
| 249 | static int |
| 250 | late_quadtree_scheme (graph_t* g, Agsym_t* sym, int dflt) |
| 251 | { |
| 252 | char* s; |
| 253 | int v; |
| 254 | int rv; |
| 255 | |
| 256 | if (!sym) return dflt; |
| 257 | s = agxget (g, sym); |
| 258 | if (isdigit(*s)) { |
| 259 | if ((v = atoi (s)) <= QUAD_TREE_FAST && v >= QUAD_TREE_NONE){ |
| 260 | rv = v; |
| 261 | } else { |
| 262 | rv = dflt; |
| 263 | } |
| 264 | } else if (isalpha(*s)) { |
| 265 | if (!strcasecmp(s, "none" ) || !strcasecmp(s, "false" ) ){ |
| 266 | rv = QUAD_TREE_NONE; |
| 267 | } else if (!strcasecmp(s, "normal" ) || !strcasecmp(s, "true" ) || !strcasecmp(s, "yes" )){ |
| 268 | rv = QUAD_TREE_NORMAL; |
| 269 | } else if (!strcasecmp(s, "fast" )){ |
| 270 | rv = QUAD_TREE_FAST; |
| 271 | } else { |
| 272 | rv = dflt; |
| 273 | } |
| 274 | } else { |
| 275 | rv = dflt; |
| 276 | } |
| 277 | return rv; |
| 278 | } |
| 279 | |
| 280 | |
| 281 | /* tuneControl: |
| 282 | * Use user values to reset control |
| 283 | * |
| 284 | * Possible parameters: |
| 285 | * ctrl->use_node_weights |
| 286 | */ |
| 287 | static void |
| 288 | tuneControl (graph_t* g, spring_electrical_control ctrl) |
| 289 | { |
| 290 | long seed; |
| 291 | int init; |
| 292 | |
| 293 | seed = ctrl->random_seed; |
| 294 | init = setSeed (g, INIT_RANDOM, &seed); |
| 295 | if (init != INIT_RANDOM) { |
| 296 | agerr(AGWARN, "sfdp only supports start=random\n" ); |
| 297 | } |
| 298 | ctrl->random_seed = seed; |
| 299 | |
| 300 | ctrl->K = late_double(g, agfindgraphattr(g, "K" ), -1.0, 0.0); |
| 301 | ctrl->p = -1.0*late_double(g, agfindgraphattr(g, "repulsiveforce" ), -AUTOP, 0.0); |
| 302 | ctrl->multilevels = late_int(g, agfindgraphattr(g, "levels" ), INT_MAX, 0); |
| 303 | ctrl->smoothing = late_smooth(g, agfindgraphattr(g, "smoothing" ), SMOOTHING_NONE); |
| 304 | ctrl->tscheme = late_quadtree_scheme(g, agfindgraphattr(g, "quadtree" ), QUAD_TREE_NORMAL); |
| 305 | /* ctrl->method = late_mode(g, agfindgraphattr(g, "mode"), METHOD_SPRING_ELECTRICAL); */ |
| 306 | ctrl->method = METHOD_SPRING_ELECTRICAL; |
| 307 | ctrl->beautify_leaves = mapBool (agget(g, "beautify" ), FALSE); |
| 308 | ctrl->do_shrinking = mapBool (agget(g, "overlap_shrink" ), TRUE); |
| 309 | ctrl->rotation = late_double(g, agfindgraphattr(g, "rotation" ), 0.0, -MAXDOUBLE); |
| 310 | ctrl->edge_labeling_scheme = late_int(g, agfindgraphattr(g, "label_scheme" ), 0, 0); |
| 311 | if (ctrl->edge_labeling_scheme > 4) { |
| 312 | agerr (AGWARN, "label_scheme = %d > 4 : ignoring\n" , ctrl->edge_labeling_scheme); |
| 313 | ctrl->edge_labeling_scheme = 0; |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | void sfdp_layout(graph_t * g) |
| 318 | { |
| 319 | int doAdjust; |
| 320 | adjust_data am; |
| 321 | int hops = -1; |
| 322 | sfdp_init_graph(g); |
| 323 | doAdjust = (Ndim == 2); |
| 324 | |
| 325 | if (agnnodes(g)) { |
| 326 | Agraph_t **ccs; |
| 327 | Agraph_t *sg; |
| 328 | int ncc; |
| 329 | int i; |
| 330 | expand_t sep; |
| 331 | pointf pad; |
| 332 | spring_electrical_control ctrl = spring_electrical_control_new(); |
| 333 | |
| 334 | tuneControl (g, ctrl); |
| 335 | #if (HAVE_GTS || HAVE_TRIANGLE) |
| 336 | graphAdjustMode(g, &am, "prism0" ); |
| 337 | #else |
| 338 | graphAdjustMode(g, &am, 0); |
| 339 | #endif |
| 340 | |
| 341 | pad.x = PS2INCH(DFLT_MARGIN); |
| 342 | pad.y = PS2INCH(DFLT_MARGIN); |
| 343 | |
| 344 | if ((am.mode == AM_PRISM) && doAdjust) { |
| 345 | doAdjust = 0; /* overlap removal done in sfdp */ |
| 346 | ctrl->overlap = am.value; |
| 347 | ctrl->initial_scaling = am.scaling; |
| 348 | sep = sepFactor(g); |
| 349 | if (sep.doAdd) { |
| 350 | pad.x = PS2INCH(sep.x); |
| 351 | pad.y = PS2INCH(sep.y); |
| 352 | } |
| 353 | } |
| 354 | else { |
| 355 | /* Turn off overlap removal in sfdp if prism not used */ |
| 356 | ctrl->overlap = -1; |
| 357 | } |
| 358 | |
| 359 | if (Verbose) |
| 360 | spring_electrical_control_print(ctrl); |
| 361 | |
| 362 | ccs = ccomps(g, &ncc, 0); |
| 363 | if (ncc == 1) { |
| 364 | sfdpLayout(g, ctrl, hops, pad); |
| 365 | if (doAdjust) removeOverlapWith(g, &am); |
| 366 | spline_edges(g); |
| 367 | } else { |
| 368 | pack_info pinfo; |
| 369 | getPackInfo(g, l_node, CL_OFFSET, &pinfo); |
| 370 | pinfo.doSplines = 1; |
| 371 | |
| 372 | for (i = 0; i < ncc; i++) { |
| 373 | sg = ccs[i]; |
| 374 | nodeInduce(sg); |
| 375 | sfdpLayout(sg, ctrl, hops, pad); |
| 376 | if (doAdjust) removeOverlapWith(sg, &am); |
| 377 | setEdgeType(sg, ET_LINE); |
| 378 | spline_edges(sg); |
| 379 | } |
| 380 | packSubgraphs(ncc, ccs, g, &pinfo); |
| 381 | } |
| 382 | for (i = 0; i < ncc; i++) { |
| 383 | agdelete(g, ccs[i]); |
| 384 | } |
| 385 | free(ccs); |
| 386 | spring_electrical_control_delete(ctrl); |
| 387 | } |
| 388 | |
| 389 | dotneato_postprocess(g); |
| 390 | } |
| 391 | |
| 392 | static void sfdp_cleanup_graph(graph_t * g) |
| 393 | { |
| 394 | } |
| 395 | |
| 396 | void sfdp_cleanup(graph_t * g) |
| 397 | { |
| 398 | node_t *n; |
| 399 | edge_t *e; |
| 400 | |
| 401 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
| 402 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) { |
| 403 | gv_cleanup_edge(e); |
| 404 | } |
| 405 | gv_cleanup_node(n); |
| 406 | } |
| 407 | sfdp_cleanup_graph(g); |
| 408 | } |
| 409 | |
| 410 | |