\section{A sample program: {\tt simple.c}}
\label{sec:simple}
-This following code illustrates an application which only uses \gviz\ to
-position a graph. The application will either provide its own
-rendering, perhaps in an interactive setting, or, as in this
-case, it does not render the graph at all.
+This following code illustrates an application which uses \gviz\ to
+position a graph using the \dot\ layout and then write the output
+using the {\tt plain} format.
+An application can replace the call to {\tt gvRender} with its own
+function for rendering the graph, using the layout information
+encoded in the graph structure (cf. Section~\ref{sec:layout_info}).
\pagebreak[4]
\begin{verbatim}
-#include <dotneato.h>
+#include <gvc.h>
-int main(int argc, char** argv)
+int main(int argc, char **argv)
{
- Agraph_t* g;
- Agnode_t* n;
- FILE* fp;
- char buf[BUFSIZ];
- point p;
+ GVC_t *gvc;
+ graph_t *g;
+ FILE *fp;
- aginit ();
+ gvc = gvContext();
if (argc > 1)
- fp = fopen (argv[1],"r");
- else
+ fp = fopen(argv[1], "r");
+ else
fp = stdin;
- g = agread (fp);
+ g = agread(fp);
- dot_layout(g);
-
- agnodeattr(g, "pos", "");
- for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
- p = ND_coord_i(n);
- sprintf(buf,"%d,%d", p.x, p.y);
- agset(n,"pos",buf);
- printf ("node %s at position (%s)\n", n->name, buf);
- }
- agwrite(g, stdout);
+ gvLayout(gvc, g, "dot");
- dot_cleanup(g);
- return 0;
-}
+ gvRender(gvc, g, "plain", stdout);
+
+ gvFreeLayout(gvc, g);
+
+ agclose(g);
+
+ return (gvFreeContext(gvc));
+}
\end{verbatim}
\section{A sample program: {\tt dot.c}}
handling, its specific declaration of
the {\tt Info} data (cf. Section~\ref{sec:info}), and a few other
minor details.
-If someone desired to write a new layout algorithm, this
-code could be copied directly, merely changing the algorithm-specific
-layout and cleanup functions \verb+dot_layout+ and \verb+dot_cleanup+.
-A more detailed and realistic example is provided in Appendix~\ref{sec:demo}.
-
\begin{verbatim}
-#include <dotneato.h>
-
-int main(int argc, char** argv)
-{
- Agraph_t *g, *prev=NULL;
+#include <gvc.h>
+
+int main(int argc, char **argv)
+{
+ graph_t *g, *prev = NULL;
GVC_t *gvc;
gvc = gvContext();
-
- dotneato_initialize(gvc,argc,argv);
- while ((g = next_input_graph())) {
+ gvParseArgs(gvc, argc, argv);
+
+ while ((g = gvNextInputGraph(gvc))) {
if (prev) {
- dot_cleanup(prev);
+ gvFreeLayout(gvc, prev);
agclose(prev);
}
+ gvLayoutJobs(gvc, g);
+ gvRenderJobs(gvc, g);
prev = g;
-
- gvBindContext(gvc, g);
-
- dot_layout(g);
- dotneato_write(gvc);
}
- dotneato_terminate(gvc);
- return 0;
+ return (gvFreeContext(gvc));
}
+
\end{verbatim}
\section{A sample program: {\tt demo.c}}
relies on the \gviz\ renderers, but now it creates the graph dynamically
rather than reading the graph from a file.
\begin{verbatim}
-#include <dotneato.h>
+#include <gvc.h>
-int main(int argc, char** argv)
+int main(int argc, char **argv)
{
Agraph_t *g;
- Agnode_t *n,*m;
+ Agnode_t *n, *m;
Agedge_t *e;
- Agsym_t *a;
- GVC_t *gvc;
+ Agsym_t *a;
+ GVC_t *gvc;
- /* set up renderer context */
+ /* set up a graphviz context */
gvc = gvContext();
- /* Accept -T and -o options like dot.
- * Input files are ignored in this demo. */
- dotneato_initialize(gvc, argc, argv);
+ /* parse command line args - minimally argv[0] sets layout engine */
+ gvParseArgs(gvc, argc, argv);
/* Create a simple digraph */
- g = agopen("g",AGDIGRAPH);
- n = agnode(g,"n");
- m = agnode(g,"m");
- e = agedge(g,n,m);
+ g = agopen("g", AGDIGRAPH);
+ n = agnode(g, "n");
+ m = agnode(g, "m");
+ e = agedge(g, n, m);
/* Set an attribute - in this case one that affects the visible rendering */
- if (!(a = agfindattr(g->proto->n, "color")))
- a = agnodeattr(g, "color", "");
- agxset(n, a->index, "red");
-
- /* bind graph to GV context - currently must be done before layout */
- gvBindContext(gvc,g);
+ agsafeset(n, "color", "red", "");
- /* Compute a layout */
- neato_layout(g);
+ /* Compute a layout using layout engine from command line args */
+ gvLayoutJobs(gvc, g);
/* Write the graph according to -T and -o options */
- dotneato_write(gvc);
+ gvRenderJobs(gvc, g);
- /* Clean out layout data */
- neato_cleanup(g);
+ /* Free layout data */
+ gvFreeLayout(gvc, g);
/* Free graph structures */
agclose(g);
- /* Clean up output file and errors */
- dotneato_terminate(gvc);
-
- return 0;
-}
+ /* close output file, free context, and return number of errors */
+ return (gvFreeContext(gvc));
+}
\end{verbatim}
multiple components. Most of the time, the obvious approach is used:
draw each component separately and then assemble the drawings into a single
layout. The only place this is not done is in \neato\ when the mode is
-{\tt MODE\_KK} and {\tt pack} is false (cf. Section~\ref{sec:neato}).
+{\tt "KK"} and {\tt pack="false"} (cf. Section~\ref{sec:neato}).
For the \dot\ algorithm, its layered drawings make the merging simple:
the nodes on the highest rank of each component are all put
packing arbitrary graph drawings together quickly, aesthetically and
with efficient use of space. The following code indicates how the
library can be integrated with the
-basic algorithm (cf. Section~\ref{sec:twopi}).
+basic layout algorithms given an input graph {\tt g} and a \gvc\ value
+{\tt gvc}.
\begin{verbatim}
- Agnode_t* c = NULL;
- Agraph_t** ccs; /* array of connected components */
- Agraph_t* sg;
- Agnode_t* c = NULL;
- int ncc; /* number of connected components */
- int i;
+ graph_t *sg;
+ FILE *fp;
+ graph_t** cc;
+ int i, ncc;
- ccs = ccomps (g, &ncc, (char*)0);
- if (ncc == 1) {
- circleLayout (g,ctr);
- adjustNodes (g);
- spline_edges(g);
- }
- else {
- pack_info pinfo;
- pack_mode pmode = getPackMode (g,l_node);
+ cc = ccomps(g, &ncc, (char*)0);
- for (i = 0; i < ncc; i++) {
- sg = ccs[i];
- if (ctr && agcontains (sg, ctr)) c = ctr;
- else c = 0;
+ for (i = 0; i < ncc; i++) {
+ sg = cc[i];
nodeInduce (sg);
- circleLayout (sg,c);
- adjustNodes (sg);
- }
- spline_edges(g);
- pinfo.margin = getPack (g, CL_OFFSET, CL_OFFSET);
- pinfo.doSplines = 1;
- pinfo.mode = pmode;
- pinfo.fixed = 0;
- packSubgraphs (ncc, ccs, g, &pinfo);
+ gvLayout(gvc, sg, "neato");
}
+ pack_graph (ncc, cc, g, 0);
+
+ gvRender(gvc, g, "ps", stdout);
+
for (i = 0; i < ncc; i++) {
- agdelete (g, ccs[i]);
+ sg = cc[i];
+ gvFreeLayout(gvc, sg);
+ agdelete(g, sg);
}
\end{verbatim}
-The call to {\tt ccomps} splits the graph into its connected
+The call to {\tt ccomps} splits the graph {\tt g} into its connected
components. {\tt ncc} is set to the number of components.
The components are represented by subgraphs of the input graph, and are
stored in the returned array. The function gives names to the components
the component containing these nodes is the first one in the returned array.
Continuing with the example,
-if there is only one component, we can revert to the base implementation
-for simplicity. If there are multiple components,
-we take one at a time, using {\tt nodeInduce} to create the
-corresponding node-induced subgraph, laying out the component with
-{\tt circleLayout}, and removing node overlaps, if necessary, by
-calling {\tt adjustNodes}. As a technical point, if a center node
-is provided, it is only used with the component containing it. A
-better implementation would allow a center node to be specified on
-a per component basis.
-
-After the nodes of each component are positioned, with coordinates
-stored in the attributes {\tt pos[0]} and {\tt pos[1]}, the code
-calls {\tt spline\_edges} to generate the edge representations.
-
-Next, it uses the \pack\ function {\tt packSubgraphs} to reassemble
+we take one component at a time, use {\tt nodeInduce} to create the
+corresponding node-induced subgraph, and then lay out the component
+with {\tt gvLayout}. Here, we use \neato\ for each layout, but it
+is possible to use a different layout for each component.\footnote{
+At present, the \dot\ layout has a limitation that it only works on
+a root graph. Thus, to use \dot\ for a component, one needs to create
+a new copy of the subgraph, apply \dot\, and then copy the position
+attributes back to the component.}
+
+Next, we use the \pack\ function {\tt pack\_graph} to reassemble
the graph into a single drawing. To position the components, \pack\
uses the polyomino-based approach described by
Freivalds et al\cite{pack}. The first three arguments to the
-function are clear. The fourth argument sets various parameters
-used in the packing. The {\tt pinfo.margin} field specifies the
-margin, in points, maintained between components. The value
-{\tt CL\_OFFSET} is defined by \gviz; it is the same amount of
-space used by \dot\ around clusters. Here, we employ the auxiliary
-function {\tt getPack}. This uses the graph attribute {\tt "pack"} to
-determine this value, which can take boolean and integer values.
-If the attribute evaluates to a non-negative integer, this value is
-returned. If the attribute evaluates to true, the third argument is
-returned. Otherwise, the function returns the second value.
-
-The {\tt pinfo.doSplines} field, if non-zero,
-tells the function that edge representations have already been
-computed for the graph and should be used in determining the packing.
-Otherwise, the packing will treat edges as line segments connecting
-the centers of the two endpoints.
+function are clear. The fourth argument indicates whether or not
+there are fixed components.
-The {\tt pinfo.mode} field specifies how the packing should be done. At
+The {\tt pack\_graph} function uses the graph's {\tt packmode}
+attribute to determine how the packing should be done. At
present, packing uses the single algorithm mentioned above, but allows
-three varying granularities, represented by the values {\tt l\_node},
-{\tt l\_clust} and {\tt l\_graph}. In the first case, packing is done at
+three varying granularities, represented by the values {\tt "node"},
+{\tt "clust"} and {\tt "graph"}. In the first case, packing is done at
the node and edge level. This provides the tightest packing, using the
least area, but also allows a node of one component to lie between
-two nodes of another component. The second value, {\tt l\_clust},
+two nodes of another component. The second value, {\tt "clust"},
requires that the packing treat top-level clusters with a set
bounding box {\tt GD\_bb} value like a large node. Nodes and edges not
entirely contained within a cluster are handled as in the previous
is treated as one large node, whose size is determined by its
bounding box.
-In our example, we use another library function, {\tt getPackMode},
-to set the mode value. This function uses the graph's {\tt "packmode"}
-attribute to determine the value. If this is {\tt "node"},
-{\tt "cluster"} or {\tt "graph"}, the function returns
-{\tt l\_node}, {\tt l\_clust} and {\tt l\_graph}, respectively.
-Otherwise, the function returns its second argument.
-
-The last field, {\tt pinfo.fixed}, is used to constrain the placement
-of certain components. If non-NULL, this field should point to an
-array of {\tt ncc} booleans, where {\tt pinfo.fixed[i]} is true if
-component {\tt i} should be left at its current position. If the
-application specifies fixed components, these are placed first. Then
-the remaining components are packed into the unoccupied space, respecting
-the {\tt pinfo.mode} field. It is the application's responsibility to
-make sure that the fixed components do not overlap each other, if that
-is desired.
-
Note that the library automatically computes the bounding box of
each of the components. Also,
-as a side-effect, {\tt packSubgraphs} finishes by recomputing and
+as a side-effect, {\tt pack\_graph} finishes by recomputing and
setting the bounding box attribute {\tt GD\_bb} of the graph.
The final step is to free the component subgraphs.