123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include <assert.h>
- #include <limits.h>
- #include <time.h>
- #include <dotgen/dot.h>
- #include <pack/pack.h>
- #include <dotgen/aspect.h>
- #include <stdbool.h>
- #include <stddef.h>
- #include <stdint.h>
- #include <util/agxbuf.h>
- #include <util/alloc.h>
- #include <util/streq.h>
- static void
- dot_init_subg(graph_t * g, graph_t* droot)
- {
- graph_t* subg;
- if ((g != agroot(g)))
- agbindrec(g, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
- if (g == droot)
- GD_dotroot(agroot(g)) = droot;
-
- for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
- dot_init_subg(subg, droot);
- }
- }
- static void
- dot_init_node(node_t * n)
- {
- agbindrec(n, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true); //graph custom data
- common_init_node(n);
- gv_nodesize(n, GD_flip(agraphof(n)));
- alloc_elist(4, ND_in(n));
- alloc_elist(4, ND_out(n));
- alloc_elist(2, ND_flat_in(n));
- alloc_elist(2, ND_flat_out(n));
- alloc_elist(2, ND_other(n));
- ND_UF_size(n) = 1;
- }
- static void
- dot_init_edge(edge_t * e)
- {
- char *tailgroup, *headgroup;
- agbindrec(e, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true); //graph custom data
- common_init_edge(e);
- ED_weight(e) = late_int(e, E_weight, 1, 0);
- tailgroup = late_string(agtail(e), N_group, "");
- headgroup = late_string(aghead(e), N_group, "");
- ED_count(e) = ED_xpenalty(e) = 1;
- if (tailgroup[0] && (tailgroup == headgroup)) {
- ED_xpenalty(e) = CL_CROSS;
- ED_weight(e) *= 100;
- }
- if (nonconstraint_edge(e)) {
- ED_xpenalty(e) = 0;
- ED_weight(e) = 0;
- }
- {
- int showboxes = late_int(e, E_showboxes, 0, 0);
- if (showboxes > UCHAR_MAX) {
- showboxes = UCHAR_MAX;
- }
- ED_showboxes(e) = (unsigned char)showboxes;
- }
- ED_minlen(e) = late_int(e, E_minlen, 1, 0);
- }
- void
- dot_init_node_edge(graph_t * g)
- {
- node_t *n;
- edge_t *e;
- for (n = agfstnode(g); n; n = agnxtnode(g, n))
- dot_init_node(n);
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- for (e = agfstout(g, n); e; e = agnxtout(g, e))
- dot_init_edge(e);
- }
- }
- static void
- dot_cleanup_node(node_t * n)
- {
- free_list(ND_in(n));
- free_list(ND_out(n));
- free_list(ND_flat_out(n));
- free_list(ND_flat_in(n));
- free_list(ND_other(n));
- free_label(ND_label(n));
- free_label(ND_xlabel(n));
- if (ND_shape(n))
- ND_shape(n)->fns->freefn(n);
- agdelrec(n, "Agnodeinfo_t");
- }
- static void free_virtual_edge_list(node_t * n)
- {
- edge_t *e;
- for (size_t i = ND_in(n).size - 1; i != SIZE_MAX; i--) {
- e = ND_in(n).list[i];
- delete_fast_edge(e);
- free(e->base.data);
- free(e);
- }
- for (size_t i = ND_out(n).size - 1; i != SIZE_MAX; i--) {
- e = ND_out(n).list[i];
- delete_fast_edge(e);
- free(e->base.data);
- free(e);
- }
- }
- static void free_virtual_node_list(node_t * vn)
- {
- node_t *next_vn;
- while (vn) {
- next_vn = ND_next(vn);
- free_virtual_edge_list(vn);
- if (ND_node_type(vn) == VIRTUAL) {
- free_list(ND_out(vn));
- free_list(ND_in(vn));
- free(vn->base.data);
- free(vn);
- }
- vn = next_vn;
- }
- }
- static void
- dot_cleanup_graph(graph_t * g)
- {
- int i;
- graph_t *subg;
- for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
- dot_cleanup_graph(subg);
- }
- if (! agbindrec(g, "Agraphinfo_t", 0, true)) return;
- free (GD_clust(g));
- free (GD_rankleader(g));
- free_list(GD_comp(g));
- if (GD_rank(g)) {
- for (i = GD_minrank(g); i <= GD_maxrank(g); i++)
- free(GD_rank(g)[i].av);
- if (GD_minrank(g) == -1)
- free(GD_rank(g)-1);
- else
- free(GD_rank(g));
- }
- if (g != agroot(g)) {
- free_label (GD_label(g));
- }
- }
- /* delete the layout (but retain the underlying graph) */
- void dot_cleanup(graph_t * g)
- {
- node_t *n;
- edge_t *e;
- free_virtual_node_list(GD_nlist(g));
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
- gv_cleanup_edge(e);
- }
- dot_cleanup_node(n);
- }
- dot_cleanup_graph(g);
- }
- #ifdef DEBUG
- int
- fastn (graph_t * g)
- {
- node_t* u;
- int cnt = 0;
- for (u = GD_nlist(g); u; u = ND_next(u)) cnt++;
- return cnt;
- }
- #if DEBUG > 1
- static void
- dumpRanks (graph_t * g)
- {
- int i, j;
- node_t* u;
- rank_t *rank = GD_rank(g);
- int rcnt = 0;
- for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
- fprintf (stderr, "[%d] :", i);
- for (j = 0; j < rank[i].n; j++) {
- u = rank[i].v[j];
- rcnt++;
- if (streq(agnameof(u),"virtual"))
- fprintf (stderr, " %x", u);
- else
- fprintf (stderr, " %s", agnameof(u));
-
- }
- fprintf (stderr, "\n");
- }
- fprintf (stderr, "count %d rank count = %d\n", fastn(g), rcnt);
- }
- #endif
- #endif
- static void
- remove_from_rank (Agraph_t * g, Agnode_t* n)
- {
- Agnode_t* v = NULL;
- int j, rk = ND_rank(n);
- for (j = 0; j < GD_rank(g)[rk].n; j++) {
- v = GD_rank(g)[rk].v[j];
- if (v == n) {
- for (j++; j < GD_rank(g)[rk].n; j++) {
- GD_rank(g)[rk].v[j-1] = GD_rank(g)[rk].v[j];
- }
- GD_rank(g)[rk].n--;
- break;
- }
- }
- assert (v == n); /* if found */
- }
- /* removeFill:
- * This removes all of the fill nodes added in mincross.
- * It appears to be sufficient to remove them only from the
- * rank array and fast node list of the root graph.
- */
- static void
- removeFill (Agraph_t * g)
- {
- Agnode_t* n;
- Agnode_t* nxt;
- Agraph_t* sg = agsubg (g, "_new_rank", 0);
- if (!sg) return;
- for (n = agfstnode(sg); n; n = nxt) {
- nxt = agnxtnode(sg, n);
- delete_fast_node (g, n);
- remove_from_rank (g, n);
- dot_cleanup_node (n);
- agdelnode(g, n);
- }
- agdelsubg (g, sg);
- }
- #define agnodeattr(g,n,v) agattr(g,AGNODE,n,v)
- static void
- attach_phase_attrs (Agraph_t * g, int maxphase)
- {
- Agsym_t* rk = agnodeattr(g,"rank","");
- Agsym_t* order = agnodeattr(g,"order","");
- Agnode_t* n;
- agxbuf buf = {0};
- for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
- if (maxphase >= 1) {
- agxbprint(&buf, "%d", ND_rank(n));
- agxset(n, rk, agxbuse(&buf));
- }
- if (maxphase >= 2) {
- agxbprint(&buf, "%d", ND_order(n));
- agxset(n, order, agxbuse(&buf));
- }
- }
- agxbfree(&buf);
- }
- static void dotLayout(Agraph_t * g)
- {
- int maxphase = late_int(g, agfindgraphattr(g,"phase"), -1, 1);
- setEdgeType (g, EDGETYPE_SPLINE);
- setAspect(g);
- dot_init_subg(g,g);
- dot_init_node_edge(g);
- dot_rank(g);
- if (maxphase == 1) {
- attach_phase_attrs (g, 1);
- return;
- }
- dot_mincross(g);
- if (maxphase == 2) {
- attach_phase_attrs (g, 2);
- return;
- }
- dot_position(g);
- if (maxphase == 3) {
- attach_phase_attrs (g, 2); /* positions will be attached on output */
- return;
- }
- if (GD_flags(g) & NEW_RANK)
- removeFill (g);
- dot_sameports(g);
- dot_splines(g);
- if (mapbool(agget(g, "compound")))
- dot_compoundEdges(g);
- }
- static void
- initSubg (Agraph_t* sg, Agraph_t* g)
- {
- agbindrec(sg, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
- GD_drawing(sg) = gv_alloc(sizeof(layout_t));
- GD_drawing(sg)->quantum = GD_drawing(g)->quantum;
- GD_drawing(sg)->dpi = GD_drawing(g)->dpi;
- GD_gvc(sg) = GD_gvc (g);
- GD_charset(sg) = GD_charset (g);
- GD_rankdir2(sg) = GD_rankdir2 (g);
- GD_nodesep(sg) = GD_nodesep(g);
- GD_ranksep(sg) = GD_ranksep(g);
- GD_fontnames(sg) = GD_fontnames(g);
- }
- /* attachPos:
- * the packing library assumes all units are in inches stored in ND_pos, so we
- * have to copy the position info there.
- */
- static void
- attachPos (Agraph_t* g)
- {
- node_t* np;
- double* ps = gv_calloc(2 * agnnodes(g), sizeof(double));
- for (np = agfstnode(g); np; np = agnxtnode(g, np)) {
- ND_pos(np) = ps;
- ps[0] = PS2INCH(ND_coord(np).x);
- ps[1] = PS2INCH(ND_coord(np).y);
- ps += 2;
- }
- }
- /* resetCoord:
- * Store new position info from pack library call, stored in ND_pos in inches,
- * back to ND_coord in points.
- */
- static void
- resetCoord (Agraph_t* g)
- {
- node_t* np = agfstnode(g);
- double* sp = ND_pos(np);
- double* ps = sp;
- for (np = agfstnode(g); np; np = agnxtnode(g, np)) {
- ND_pos(np) = 0;
- ND_coord(np).x = INCH2PS(ps[0]);
- ND_coord(np).y = INCH2PS(ps[1]);
- ps += 2;
- }
- free (sp);
- }
- static void
- copyCluster (Agraph_t* scl, Agraph_t* cl)
- {
- int nclust, j;
- Agraph_t* cg;
- agbindrec(cl, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
- GD_bb(cl) = GD_bb(scl);
- GD_label_pos(cl) = GD_label_pos(scl);
- memcpy(GD_border(cl), GD_border(scl), 4*sizeof(pointf));
- nclust = GD_n_cluster(cl) = GD_n_cluster(scl);
- GD_clust(cl) = gv_calloc(nclust + 1, sizeof(Agraph_t*));
- for (j = 1; j <= nclust; j++) {
- cg = mapClust(GD_clust(scl)[j]);
- GD_clust(cl)[j] = cg;
- copyCluster (GD_clust(scl)[j], cg);
- }
- /* transfer cluster label to original cluster */
- GD_label(cl) = GD_label(scl);
- GD_label(scl) = NULL;
- }
- /* copyClusterInfo:
- * Copy cluster tree and info from components to main graph.
- * Note that the original clusters have no Agraphinfo_t at this time.
- */
- static void copyClusterInfo(size_t ncc, Agraph_t **ccs, Agraph_t *root) {
- int j, nclust = 0;
- Agraph_t* sg;
- Agraph_t* cg;
- for (size_t i = 0; i < ncc; i++)
- nclust += GD_n_cluster(ccs[i]);
- GD_n_cluster(root) = nclust;
- GD_clust(root) = gv_calloc(nclust + 1, sizeof(Agraph_t*));
- nclust = 1;
- for (size_t i = 0; i < ncc; i++) {
- sg = ccs[i];
- for (j = 1; j <= GD_n_cluster(sg); j++) {
- cg = mapClust(GD_clust(sg)[j]);
- GD_clust(root)[nclust++] = cg;
- copyCluster (GD_clust(sg)[j], cg);
- }
- }
- }
- /* doDot:
- * Assume g has nodes.
- */
- static void doDot (Agraph_t* g)
- {
- Agraph_t **ccs;
- Agraph_t *sg;
- pack_info pinfo;
- int Pack = getPack(g, -1, CL_OFFSET);
- pack_mode mode = getPackModeInfo (g, l_undef, &pinfo);
- getPackInfo(g, l_node, CL_OFFSET, &pinfo);
- if (mode == l_undef && Pack < 0) {
- /* No pack information; use old dot with components
- * handled during layout
- */
- dotLayout(g);
- } else {
- /* fill in default values */
- if (mode == l_undef)
- pinfo.mode = l_graph;
- else if (Pack < 0)
- Pack = CL_OFFSET;
- assert(Pack >= 0);
- pinfo.margin = (unsigned)Pack;
- pinfo.fixed = NULL;
- /* components using clusters */
- size_t ncc;
- ccs = cccomps(g, &ncc, 0);
- if (ncc == 1) {
- dotLayout(g);
- } else if (GD_drawing(g)->ratio_kind == R_NONE) {
- pinfo.doSplines = true;
- for (size_t i = 0; i < ncc; i++) {
- sg = ccs[i];
- initSubg (sg, g);
- dotLayout (sg);
- }
- attachPos (g);
- packSubgraphs(ncc, ccs, g, &pinfo);
- resetCoord (g);
- copyClusterInfo (ncc, ccs, g);
- } else {
- /* Not sure what semantics should be for non-trivial ratio
- * attribute with multiple components.
- * One possibility is to layout nodes, pack, then apply the ratio
- * adjustment. We would then have to re-adjust all positions.
- */
- dotLayout(g);
- }
- for (size_t i = 0; i < ncc; i++) {
- free (GD_drawing(ccs[i]));
- dot_cleanup_graph(ccs[i]);
- agdelete(g, ccs[i]);
- }
- free(ccs);
- }
- }
- void dot_layout(Agraph_t * g)
- {
- if (agnnodes(g)) doDot (g);
- dotneato_postprocess(g);
- }
- Agraph_t * dot_root (void* p)
- {
- return GD_dotroot(agroot(p));
- }
- /**
- * @defgroup engines Graphviz layout engines
- * @{
- * @dir lib/dotgen
- * @brief [hierarchical or layered](https://en.wikipedia.org/wiki/Layered_graph_drawing) layout engine, API dotgen/dotprocs.h
- *
- * [Dot layout user manual](https://graphviz.org/docs/layouts/dot/)
- *
- * Other @ref engines
- * @}
- */
|