Muy buenas, me llamo Luis y para hoy les traigo otro artículo.
Este artículo explica cómo hacer que los gráficos de Graphviz sean fáciles y rápidos mediante el uso del preprocesador C.
El punto clave es que el lenguaje de gráficos de puntos que Graphviz (software de visualización de gráficos) utiliza es preprocesable en su sintaxis. Esto es lo que pretendían los desarrolladores de Graphviz. Gracias a su previsión, al describir gráficos, podemos usar las siguientes características (estoy citando Wikipedia de memoria):
- sustituyendo los correspondientes dígrafos y trígrafos con los símbolos equivalentes “#” y “”;
- eliminar caracteres de salto de línea de escape;
- Reemplazo de comentarios en línea y en bloque con líneas vacías (eliminando espacios y pestañas circundantes);
- insertar (incluido) el contenido de un archivo arbitrario (#include);
- sustitución de macros (#define);
- compilación condicional (#if, #ifdef, #elif, #else, #endif).
Ahora demostremos las nuevas oportunidades en la práctica. Como ejemplo, tomemos el gráfico de mi artículo sobre MediaStreamer2, está en la siguiente figura.
El gráfico es lo suficientemente grande y si lo describe «de frente» necesitará mucho trabajo manual sin errores. Si observa de cerca, puede aislar elementos duplicados con una diferencia solo en el contenido de algunos campos. Así es como se ven los nodos del gráfico como m1, m2, m2_1, m2_2, m3, m4. Estos son los primeros candidatos para la mecanización a pequeña escala mediante macros. Cree un archivo de encabezado para nuestro archivo de puntos principal. Llamémoslo common.dot:
// The common.dot file contains definitions for the main graph file. #define Q(x) #x // Auxiliary macro for "quoting" a string inside a macro. #define QUOTE(x) Q(x) // A macro to "quote" a string within another macro. // We define a macro for a conditional image of the internal // fields of the mblk_t structure. // The macro will only show some of the structure fields. #define msg_staff <p> *prev |<n> *next |<c> *cont |<d> *data | othernstuff // We define a macro for the conditional image of // the mblk_t structure itself. #define msg(name, ... ) name[xlabel=mblk_t label=QUOTE(<h> name | msg_staff) ]; // We define a macro for a conditional image of // the dblk_t structure itself. #define dbuf(name ...) name[label=QUOTE(<h> name) xlabel="dblk_t"]; // We define a macro for a conditional image of // the queue_t structure. // Some of the fields of this structure coincide with // the fields of the mblk_t structure, // therefore, the msg_staff macro is substituted // into the definition. #define queue(name, ...) name[ xlabel="queue_t" label=QUOTE(<h> name | msg_staff)];
Ahora es el momento de escribir el archivo de gráfico principal. Llamémoslo my_graph.dot:
// file my_graph.dot // Including mocro definitions file. #include "common.dot" digraph queue_demo { rankdir=LR; node[shape=Mrecord]; // Using the macros we defined above, // we create nodes that will symbolize data blocks on the graph. dbuf(d1); dbuf(d2); dbuf(d2_1); dbuf(d2_2); dbuf(d3); dbuf(d4); // We create nodes that will symbolize messages on the graph, // to which the data blocks are bound. msg(m1); msg(m2); msg(m2_1); msg(m2_2); msg(m3); msg(m4); // We create an instance of the control queue structure. // The node will be named q1. queue(q1); // We describe the connections of data blocks // with message nodes. m1:d->d1; m2:d->d2; m2_1:d->d2_1; m2_2:d->d2_2; m3:d->d3; m4:d->d4; // We Describe the connections of messages to each other. m1:n -> m2:h; m1:p -> q1:h; m2:n -> m3:h; m2:c->m2_1:h; m2_1:c->m2_2:h; m3:n -> m4:h; m2:p -> m1:h; m3:p -> m2:h; m4:p -> m3:h; // Describe the connection of messages to the queue structure. q1:n->m1:h; q1:p->m4:h; m4:n -> q1:h[color=blue]; // Color the edge blue. }
Procesamos el archivo con un preprocesador:
$ cpp my_graph.dot -o my_graph_res.dot
El resultado se colocará en el archivo my_graph_res.dot. Como resultado de la operación del preprocesador, el archivo de descripción del gráfico tendrá la forma:
# 1 "<built-in>" # 1 "<command-line>" # 1 "my_graph.dot"# 1 "common.dot" 1 # 3 "my_graph.dot" 2digraph queue_demo { rankdir=LR; node[shape=Mrecord]; d1[label="<h> d1" xlabel="dblk_t"];; d2[label="<h> d2" xlabel="dblk_t"];; d2_1[label="<h> d2_1" xlabel="dblk_t"];; d2_2[label="<h> d2_2" xlabel="dblk_t"];; d3[label="<h> d3" xlabel="dblk_t"];; d4[label="<h> d4" xlabel="dblk_t"];; m1[xlabel=mblk_t label="<h> m1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; m2[xlabel=mblk_t label="<h> m2 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; m2_1[xlabel=mblk_t label="<h> m2_1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; m2_2[xlabel=mblk_t label="<h> m2_2 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; m3[xlabel=mblk_t label="<h> m3 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; m4[xlabel=mblk_t label="<h> m4 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];; q1[ xlabel="queue_t" label="<h> q1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff"];; m1:d->d1; m2:d->d2; m2_1:d->d2_1; m2_2:d->d2_2; m3:d->d3; m4:d->d4; m1:n -> m2:h; m1:p -> q1:h; m2:n -> m3:h; m2:c->m2_1:h; m2_1:c->m2_2:h; m3:n -> m4:h; m2:p -> m1:h; m3:p -> m2:h; m4:p -> m3:h; q1:n->m1:h; q1:p->m4:h; m4:n -> q1:h[color=blue]; }
Como puede ver, tod0 el código de las macros se expanden y sustituyen. Las líneas son más largas y complejas. Queda por transferir el archivo para renderizarlo a una de las utilidades del paquete Graphviz (por ejemplo dot) o renderizar en el sitio web: https://sketchviz.com/new
El resultado será así:
El gráfico difiere en la disposición de los nodos del original al comienzo del artículo, debido al hecho de que, en aras de la coherencia, cambié el orden de declaración de los nodos.
Si lo desea, puede ir más allá y mover la conexión de los nodos de la cola a la macro.
Gracias por leer.
Añadir comentario