Ajustes visuales

Figura
Figura. Ajustes visuales

Los ajustes visuales modifican la presentación del grafo pero no su estructura de datos, es decir, su matriz de adyacencia. Se accede con el botón , con acciones como cambiar el tamaño, mover elementos y otros muchos tal como se observa en la Figura. En los siguientes apartados se explican cada uno de ellos.

Con el botón accedemos a información en este sitio. Con el botón "Restablecer" deshacemos la acción del ajuste visual.

Otra forma de ajustar visualmente un grafo es con estilos CSS sobre el SVG que veremos en un apartado al final.

Figura
Figura. Ajustes visuales sobre enlaces

Como se observa en la Figura, uno de los ajustes visuales que podemos ejecutar es quitar los marcadores invertidos que se configuran con la propiedad markerReverse. Veamos un ejemplo.

Figura
Figura. Propiedad markerReverse en arista "2🡒1"

La Figura presenta un grafo dirigido con el siguiente JSON:

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":0},{"index":2,"markerReverse":true}]},
    {"index":2,"parent":[{"index":0}]}
],
"config":{
    "markerEnd":"arrow"
}}

La arista "2🡒1" se incluye en el "parent" del nodo "1" con {"index":2, "markerReverse":true}, lo que quiere decir que con markerReverse la dirección "1🡒2" se invierte a "2🡒1". Ejecutando el ajuste visual para quitar todos los markerReverse vemos que esa arista se traslada al nodo "2" con {"index":1} en su "parent".

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":0}]},
    {"index":2,"parent":[{"index":0},{"index":1}]}
],
"config":{
    "markerEnd":"arrow"
}}
Figura
Figura. Grafo dirigido

Veamos ahora la opción intercambiar enlaces con el ejemplo del grafo de la Figura.

[[0, 0, 0],
[1, 0, 0],
[1, 0, 0]]

Esta es su matriz de adyacencia con el siguiente JSON.

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":0}]},
    {"index":2,"parent":[{"index":0}]}
],
"config":{
    "markerEnd":"arrow"
}}
Figura
Figura. Grafo dirigido

Aplicando intercambiar enlaces obtenemos el grafo de la Figura, que tiene la misma matriz de adyacencia pero se modifica el JSON, pues antes la propiedad "markerEnd":"arrow" hacía que los "parent" en los nodos "0" y "1" con {"index":0} apuntarán al nodo "0" y ahora tenemos "markerStart":"arrow" con los enlaces en el nodo "0" con {"index":2}, {"index":1} haciendo que los nodos "1" y "2" apunten al "0".

{"nodes":[
    {"index":0,"parent":[{"index":2},{"index":1}]},
    {"index":1,"parent":[{"index":-1}]},
    {"index":2,"parent":[{"index":-1}]}
],
"config":{
    "markerStart":"arrow"
}}

En un tema siguiente sobre la direccionalidad en grafos exponemos algo más sobre intercambiar enlaces.

Figura
Figura. Reducir enlaces

Otro ajuste visual que he incorporado es reducir enlaces. En la Figura vemos el mismo grafo de la Figura, pero donde la arista "2🡒0" en lugar de incluirse como {"index":0} en el "parent" del nodo "2" pues "markerEnd" tiene el valor "arrow" en la configuración general, aparece como {"index":2, "markerStart":"arrow", "markerEnd":"none"} en el "parent" del nodo "0" invirtiendo la configuración general:

{"nodes":[
    {"index":0,"parent":[{"index":-1},{"index":2,"markerStart":"arrow","markerEnd":"none"}]},
    {"index":1,"nodeOffset":"-16.8 3.6","parent":[{"index":0}]},
    {"index":2,"nodeOffset":"21.2 -21.6","parent":[{"index":1}]}
],
"config":{
    "markerEnd":"arrow"
}}

Con el ajuste para reducir enlaces eliminamos {"index":2, "markerStart":"arrow", "markerEnd":"none"} en el "parent" del nodo "0" y lo llevamos como {"index":0} en el nodo "2", lo cual reduce y clarifica el JSON:

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"nodeOffset":"-16.8 3.6","parent":[{"index":0}]},
    {"index":2,"nodeOffset":"21.2 -21.6","parent":[{"index":1},{"index":0}]}
],
"config":{
    "markerEnd":"arrow"
}}

Cambiar tamaño del SVG

Figura
Figura. Cambiar tamaño del grafo

El tamaño del SVG es el rectángulo señalado con la línea con puntos de la Figura. La mejor forma de configurar el ancho y alto tanto del SVG como del ViewBox es con la opción ajustar SVG activada, pues se ajustarán de forma automática esas medidas para albergar los elementos del SVG. Si tuviéramos que modficar el ancho y alto que se establece automáticamente, podríamos hacerlo cambiando los valores directamente en los controles. Pero es un engorro dada la relación de dependencia que hay entre ancho o alto del SVG y Viewbox, afectando a la altura de línea y otras cosas, especialmente cuando hay desplazamiento de nodos (propiedad nodeOffset).

El siguiente JSON es el que configura el grafo anterior, donde se observa que se pasa la configuración "adjust":false para que no ajuste el SVG, pues por defecto ese valor es verdadero.

{"nodes":[
    {"index":0,"value":1,"parent":[{"index":-1}],"nodeOffset":"-17.5 1.5"},
    {"index":1,"value":"2","parent":[{"index":0}],"nodeOffset":"-1.47 -14.25"},
    {"index":2,"value":"3","parent":[{"index":1},{"index":0}],"nodeOffset":"-1.47 -20.75"},
    {"index":3,"value":4,"parent":[{"index":2},{"index":1}],"nodeOffset":"-17.5 -36.5"},
    {"index":4,"value":5,"parent":[{"index":3},{"index":2},{"index":0}],"nodeOffset":"-33.53 -70.75"},
    {"index":5,"value":6,"parent":[{"index":4},{"index":0},{"index":1},{"index":3}],"nodeOffset":"-33.53 -114.25"}
],
"config":{
    "adjust":false
}}
Figura
Figura. Cambiar tamaño del grafo (2)

Para modificar el ancho o alto del SVG es mejor utilizar el ajuste visual para cambiar el tamaño como se observa en la Figura, donde hemos reducido el ancho y alto actuando sobre los botones, lo que se comprueba en la línea de puntos que indica el área del SVG. Obtenemos este JSON, donde se observa que ha actuado también sobre los nodeOffset, para reubicar los nodos y que aparezcan en su posición anterior:

{"nodes":[
    {"index":0,"value":1,"parent":[{"index":-1}],"nodeOffset":"-1.5 1.5"},
    {"index":1,"value":"2","parent":[{"index":0}],"nodeOffset":"14.53 -14.25"},
    {"index":2,"value":"3","parent":[{"index":1},{"index":0}],"nodeOffset":"14.53 -20.75"},
    {"index":3,"value":4,"parent":[{"index":2},{"index":1}],"nodeOffset":"-1.5 -36.5"},
    {"index":4,"value":5,"parent":[{"index":3},{"index":2},{"index":0}],"nodeOffset":"-17.53 -70.75"},
    {"index":5,"value":6,"parent":[{"index":4},{"index":0},{"index":1},{"index":3}],"nodeOffset":"-17.53 -114.25"}
],
"config":{
    "wv":68,
    "hv":68,
    "width":170,
    "height":170,
    "adjust":false,
    "wvIni":68
}}
Figura
Figura. Cambiar tamaño del grafo (3)

Ese ajuste visual se encarga de rellenar los controles de medidas del SVG (with, height) y del Viewbox (wv, hv). Observe como la altura de línea sigue conservando el valor inicial 25 = 250/10 pues inicialmente es 1/10 de la altura inicial 250. El ancho inicial ViewBox (propiedad wvIni) se utiliza para aplicar el zoom, modificándose cuando cambiamos las medidas del ViewBox, sirviéndonos para referenciar el valor 100% del zoom.

Mover elementos

Figura
Figura. Mover elementos del grafo

Con el grafo del apartado anterior hemos seleccionado el nodo inferior y le aplicamos movimiento hacia abajo, como se observa en la Figura. Con esta funcionalidad se pueden mover también aristas y si no hay nada seleccionado se moverá todo el conjunto.

Con el boton las separaciones de nodos (nodeOffset), de aristas (lineOffset) y de etiquetas de aristas (lineTextOffset) serán borrados, bien de todos los elementos, o si hay una selección, sólo del elemento seleccionado.

Borrar separaciones

Figura
Figura. Grafo con separaciones

En la Figura vemos un grafo dirigido donde hemos aplicado separación del nodo con la propiedad nodeOffset, separación de arista con la propiedad lineOffset y separación de texto de arista con la propiedad lineTextOffset que desplaza el texto "B" a la derecha de su lugar por defecto que es sobre la arista "1🡒0". En el JSON siguiente de ese grafo resaltamos en color azul estas propiedades:

{"nodes":[
    {"index":0, "parent":[{"index":-1}]},
    {"index":1, "nodeOffset":"-6 22",
        "parent":[{"index":0, "lineOffset":-1.59, "value":"B","lineTextOffset":1}]},
    {"index":2, "parent":[{"index":0}, {"index":1}]}
],
"config":{
    "markerEnd":"arrow"
}}
Figura
Figura. Posicionar nodos del grafo
Figura
Figura. Grafo sin separaciones

Con el ajuste visual borrar separaciones que vemos en la Figura borramos separaciones de nodos, aristas y textos de aristas, tanto todas las del grafo como seleccionando un elemento. Al borrarlas todas el grafo queda como la Figura, con el siguiente JSON donde se observa que no hay nodeOffset, lineOffset o lineTextOffset.

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":0,"value":"B"}]},
    {"index":2,"parent":[{"index":0},{"index":1}]}
],
"config":{
    "markerEnd":"arrow"
}}

También podemos borrar separaciones con el botón para borrar separaciones en el ajuste visual mover elementos que vimos en el apartado anterior.

Posicionar nodos

Figura
Figura. Posicionar nodos del grafo

He agregado la funcionalidad posicionar nodos como se observa en la Figura. Se trata de mover el nodo "4" seleccionado a una posición absoluta x,y que están en unidades de ViewBox, reposicionando ese nodo con valor inicial y=51 a la final y=61. Usamos este JSON inicialmente:

{"nodes":[
    {"index":0,"value":1,"parent":[{"index":-1}],"nodeOffset":"-1.5 1.5"},
    {"index":1,"value":"2","parent":[{"index":0}],"nodeOffset":"14.53 -14.25"},
    {"index":2,"value":"3","parent":[{"index":1},{"index":0}],"nodeOffset":"14.53 -20.75"},
    {"index":3,"value":4,"parent":[{"index":2},{"index":1}],"nodeOffset":"-1.5 -36.5"},
    {"index":4,"value":5,"parent":[{"index":3},{"index":2},{"index":0}],"nodeOffset":"-17.53 -70.75"},
    {"index":5,"value":6,"parent":[{"index":4},{"index":0},{"index":1},{"index":3}],"nodeOffset":"-17.53 -114.25"}
],
"config":{}}
[[48.5, 14], [64.53, 23.25],
[64.53, 41.75], [48.5, 51],
[32.47, 41.75], [32.47, 23.25]]

Si no hay ningún nodo seleccionado aparecerán desactivados los controles x e y mientras que se activa el área de texto posiciones y el botón con tres puntos "···". Pulsando este botón se recuperan las posiciones de todos los nodos, pudiendo modificarlas ahí. La lista adjunta obtenida sigue el orden de los índices de los nodos, así que si modificamos la posición del nodo "4" que es [48.5, 51] por [48.5, 61] obtendremos el mismo resultado que seleccionando ese nodo y cambiándolo en el control y.

Si la lista se pasa con menos posiciones que el total de nodos, se irán aplicando siguiendo el orden natural de los nodos "0, 1, 2, ...".

Aplicar zoom

Figura
Figura. Aplicar zoom al grafo

En la Figura puede ver como aplicamos un zoom del 50%. El zoom actua sobre la altura de línea, partiendo de que el valor 100% corresponde al ancho inicial del ViewBox (vwIni = 68), que para ese grafo era de 25, mientras que al aplicar el zoom del 50% la altura de línea se reduce a 12.5.

El zoom no modifica las medidas del SVG, solo actua sobre la altura de línea, pues a partir de ella se configuran los tamaños de los elementos SVG. Si necesita cambiar el tamaño del SVG sin afectar a los elementos debe usar esa funcionalidad que comentamos en un apartado anterior.

Rotar

Figura
Figura. Grafo original

Usaremos para hacer pruebas el grafo que se muestra en la Figura, que puede encontrar en las muestras de la aplicación o bien importar con el siguiente JSON:

{"nodes":[
    {"index":0,"value":"A","parent":[{"index":-1}]},
    {"index":1,"value":"B","parent":[{"index":0,"value":10}]},
    {"index":2,"value":"C","parent":[{"index":0,"value":20},{"index":1,"value":30}]},
    {"index":3,"value":"D","parent":[{"index":1,"value":40},{"index":4,"value":"X"}]},
    {"index":4,"value":"E","parent":[{"index":1,"value":50},{"index":2,"value":60}]}
],
"config":{
    "markerEnd":"arrow"
}}
Figura
Figura. Aplicar rotación de 90° al grafo
Figura
Figura. Grafo rotado 90°

En la Figura puede ver como lo rotamos 90° quedando como la Figura. El algoritmo busca el centro de los nodos del grafo y rota cada nodo el ángulo que se especifique con respecto a ese centro, modificándose la posición de los nodos mediante la propiedad nodeOffset. Así es como quedaría el JSON:

{"nodes":[
    {"index":0,"value":"A","parent":[{"index":-1}],"nodeOffset":"30 30"},
    {"index":1,"value":"B","parent":[{"index":0,"value":10}],"nodeOffset":"21.67 -11.67"},
    {"index":2,"value":"C","parent":[{"index":0,"value":20},{"index":1,"value":30}],"nodeOffset":"-11.67 21.67"},
    {"index":3,"value":"D","parent":[{"index":1,"value":40},{"index":4,"value":"X"}],"nodeOffset":"-3.33 -36.67"},
    {"index":4,"value":"E","parent":[{"index":1,"value":50},{"index":2,"value":60}],"nodeOffset":"-36.67 -3.33"}
],
"config":{
    "markerEnd":"arrow"
}}

Reflejar

Figura
Figura. Reflejar grafo en vertical

Usando el mismo grafo anterior, vemos en Figura como lo reflejamos verticalmente resultando el grafo de la Figura. La disposición no es como rotar, sino que se divide el plano con una línea al centro horizonal y se reflejan los nodos en el subplano opuesto. Y para el reflejo horizontal se divide el plano con una línea al centro vertical y se reflejan los nodos en los subplanos opuestos.

Figura
Figura. Grafo reflejado en vertical

Vea que el orden original de los nodos es "A,B,C,D,E", los reflejados es "D,E,B,C,A ", mientras que si hubiésemos rotado 180° el orden sería "E,D,C,B,A". Actúa sobre los nodeOffset, como se observa en el JSON resultante, viendo que los nodos "B,C" no se mueven pues están sobre la línea divisoria horizontal, mientras que "A,D,E" cambian de subplano verticalmente:

{"nodes":[
    {"index":0,"value":"A","parent":[{"index":-1}],"nodeOffset":"0 50"},
    {"index":1,"value":"B","parent":[{"index":0,"value":10}]},
    {"index":2,"value":"C","parent":[{"index":0,"value":20},{"index":1,"value":30}]},
    {"index":3,"value":"D","parent":[{"index":1,"value":40},{"index":4,"value":"X"}],"nodeOffset":"0 -50"},
    {"index":4,"value":"E","parent":[{"index":1,"value":50},{"index":2,"value":60}],"nodeOffset":"0 -50"}
],
"config":{
    "markerEnd":"arrow"
}}

Aplicar relleno

Figura
Figura. Aplicar relleno al grafo

Se añade el estilo CSS al elemento SVG con style="padding: 10 10 10 10", donde el orden es "TRBL" top, right, bottom, left tal como usualmente se utiliza en propiedades agrupadas de CSS, recordando que cuando no se especifican las longitudes como píxeles en el CSS de un SVG, entonces se refieren a unidades del ViewBox.

En la Figura vemos que se añade un espacio de 10 píxeles alrededor del área interna del SVG, que queda delimitado por la línea de puntos.

{"nodes":[
    {"index":0,"value":1,"nodeOffset":"-1.5 1.5","parent":[{"index":-1}]},
    {"index":1,"value":"2","nodeOffset":"14.53 -14.25","parent":[{"index":0}]},
    {"index":2,"value":"3","nodeOffset":"14.53 -20.75","parent":[{"index":1},{"index":0}]},
    {"index":3,"value":4,"nodeOffset":"-1.5 -36.5","parent":[{"index":2},{"index":1}]},
    {"index":4,"value":5,"nodeOffset":"-17.53 -70.75","parent":[{"index":3},{"index":2},{"index":0}]},
    {"index":5,"value":6,"nodeOffset":"-17.53 -114.25","parent":[{"index":4},{"index":0},{"index":1},{"index":3}]}
],
"config":{
    "wv":68,
    "hv":68,
    "width":170,
    "height":170,
    "adjust":false,
    "wvIni":68,
    "style":"padding: 10 10 10 10"
}}

Esparcir elementos

Figura
Figura. Esparcir elementos de un grafo

La funcionalidad para esparcir elementos se refiere a desplazar radialmente los nodos desde su centro. En las dos capturas siguientes se observa la primera que es la situación inicial y la segunda tras aplicarle un esparcido positivo con espaciado 1, pulsando dos veces el botón "+" que se observa en la Figura.

Figura
Figura. Grafo sin esparcir
Figura
Figura. Grafo esparcido

El grafo inicial sin esparcir tiene este JSON:

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":-1}]},
    {"index":2,"parent":[{"index":-1}]},
    {"index":3,"parent":[{"index":0},{"index":1},{"index":2}]},
    {"index":4,"parent":[{"index":0},{"index":1},{"index":2}]},
    {"index":5,"parent":[{"index":0},{"index":1},{"index":2}]}
],
"config":{
    "wv":87.5,
    "hv":50,
    "width":218.75,
    "height":125,
    "adjust":false,
    "wvIni":87.5
}}

Para esparcir elementos primero tenemos que encontrar el centro geométrico de los nodos o vértices, lo que hacemos con este algoritmo que recorre todos los elementos CIRCLE recopilando las posiciones de sus centros y radios. El objeto nodes proviene del JSON anterior, extrayendo el índice node.index y buscando en el SVG un elemento CIRCLE con data-node igual a ese índice. Los atributos cx, cy, r son los que tiene el CIRCLE para su centro y radio:

for (let node of nodes) {
    let circle = document.querySelector(`#${ui.id} div[data-view] svg circle[data-node="${node.index}"]`);
    let [x, y, r] = [+circle.getAttribute("cx"), +circle.getAttribute("cy"), +circle.getAttribute("r")];
    points.push([x, y]);
    radius = r;
}
let xm = 1/n * points.reduce((p,v) => (p += v[0], p), 0);
let ym = 1/n * points.reduce((p,v) => (p += v[1], p), 0);
    

El punto (xm, ym) podemos denominarlo centro geométrico como promedio de las posiciones de los elementos:

xm = 1/n ∑i=0..n xi
ym = 1/n ∑i=0..n yi

Modificamos radialmente cada posición de los elementos con este código:

let sign = action==="plus" ? 1 : -1;
let gap = spacing * sign / radius;
for (let i=0; i<nodes.length; i++) {
    let node = nodes[i];
    let nodeOffset = graph.getProperty(node, "nodeOffset", config).toString();
    if (!/\s+/.test(nodeOffset.trim())) nodeOffset += " 0";
    let [xo, yo] = nodeOffset.split(/\s+/).map(v => +v);
    let [x, y] = points[i];
    let angle = Math.atan2(y-ym, x-xm);
    let r = Math.sqrt((x-xm)**2 + (y-ym)**2) * gap;
    let xe = xo + r * Math.cos(angle);
    let ye = yo + r * Math.sin(angle);
    let xn = wxG.redondear(xe, config.precision);
    let yn = wxG.redondear(ye, config.precision);
    let offset = `${xn} ${yn}`;
    node.nodeOffset = offset;
}

En (xo, yo) obtenemos el desplazamiento de cada nodo (nodeOffset). Calculamos el ángulo que forma cada nodo en (x, y) con el centro (xm, ym). Aplicamos el radio modificado r = Math.sqrt((x-xm)**2 + (y-ym)**2) * gap, donde gap es una proporción con respecto al radio de los círculos de los nodos (radius). Finalmente obtenemos los nuevos desplazamientos (xn, yn) con xo + r * Math.cos(angle) y yo + r * Math.sin(angle) tras aplicarle el redondeo a los decimales especificado en la configuración.

En el ejemplo no hay desplazamientos de nodos previamente, como se observa en el JSON más arriba, con lo que este caso xo=0, yo=0. Tras aplicar el esparcido el JSON queda así, donde se observan los nodeOffset que desplazan los nodos respecto a su posición original:

{"nodes":[
    {"index":0,"parent":[{"index":-1}],"nodeOffset":"-7.56 -4.32"},
    {"index":1,"parent":[{"index":-1}],"nodeOffset":"0 -4.32"},
    {"index":2,"parent":[{"index":-1}],"nodeOffset":"7.56 -4.32"},
    {"index":3,"parent":[{"index":0},{"index":1},{"index":2}],"nodeOffset":"-7.56 4.32"},
    {"index":4,"parent":[{"index":0},{"index":1},{"index":2}],"nodeOffset":"0 4.32"},
    {"index":5,"parent":[{"index":0},{"index":1},{"index":2}],"nodeOffset":"7.56 4.32"}
],
"config":{
    "wv":87.5,
    "hv":50,
    "width":218.75,
    "height":125,
    "adjust":false,
    "wvIni":87.5
}}

Ajustar a rejilla

Figura
Figura. Ajuste a rejilla en grafos

La funcionalidad para ajustar a rejilla un grafo puede verse en la Figura. Se trata de construir una rejilla de nodos con tantas columnas como se especifiquen. Si un grafo tiene 15 nodos y especificamos 5 columnas, tendremos una rejilla de 3 filas con 5 nodos en cada fila. Por ejemplo, el grafo de Kneser 6,2 tiene la siguiente matriz de adyacencia:

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
[0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1],
[0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0],
[1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0],
[1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
[1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0]]
Figura
Figura. Grafo Kneser 6,2

Podemos crear el grafo con esta matriz poniéndola en el área de texto de la parte superior, en el control denominado Nodos o matriz de adyacencia, y luego actualizar el grafo con el botón , obteniendo el que se observa en la Figura.

Esa es la disposición inicial de los nodos en niveles, con el JSON obtenido a continuación. Cada nodo tiene una colección parent que son los nodos con los que está relacionado mediante una arista. Así en el primer nivel están los nodos que apuntan a la raíz -1. Y en el segundo están los que apuntan a estos y entre sí. Si hubiese un nodo que apuntase a alguno del segundo nivel pero no al primero, se agregaría un tercer nivel disponiendo ese nodo en una tercera fila y así sucesivamente.

{"nodes":[
    {"index":0,"parent":[{"index":-1}]},
    {"index":1,"parent":[{"index":-1}]},
    {"index":2,"parent":[{"index":-1}]},
    {"index":3,"parent":[{"index":-1}]},
    {"index":4,"parent":[{"index":-1}]},
    {"index":5,"parent":[{"index":2},{"index":3},{"index":4}]},
    {"index":6,"parent":[{"index":1},{"index":3},{"index":4}]},
    {"index":7,"parent":[{"index":1},{"index":2},{"index":4}]},
    {"index":8,"parent":[{"index":1},{"index":2},{"index":3}]},
    {"index":9,"parent":[{"index":0},{"index":3},{"index":4},{"index":7},{"index":8}]},
    {"index":10,"parent":[{"index":0},{"index":2},{"index":4},{"index":6},{"index":8}]},
    {"index":11,"parent":[{"index":0},{"index":2},{"index":3},{"index":6},{"index":7}]},
    {"index":12,"parent":[{"index":0},{"index":1},{"index":4},{"index":5},{"index":8},{"index":11}]},
    {"index":13,"parent":[{"index":0},{"index":1},{"index":3},{"index":5},{"index":7},{"index":10}]},
    {"index":14,"parent":[{"index":0},{"index":1},{"index":2},{"index":5},{"index":6},{"index":9}]}
],
"config":{}}

Esa disposición inicial es automática y no se puede modificar. La disposición por niveles es óptima para representar gráficamente los árboles. Esa fue la primera necesidad que tuve cuando inicié esta aplicación. Pero un árbol también es un grafo, por lo que la aplicación no tiene ningún problema tratando con grafos que no son árboles, pues en cualquier caso siempre podemos modificar posteriormente la posición de los nodos.

Figura
Figura. Grafo Kneser 6,2 en rejilla

O bien utilizar esta funcionalidad ajustando a una rejilla de 5 columnas, disponiéndose entonces como se observa en la Figura. Se van ubicando los nodos en filas completando esas columnas con un espaciado entre nodos que especificamos en la herramienta. Podemos obtener el JSON siguiente donde se observan los desplazamientos de nodos (nodeOffset) que se aplica con respecto a la disposición inicial en niveles que comentamos antes.

{"nodes":[
    {"index":0,"parent":[{"index":-1}],"nodeOffset":"-1.67 2.5"},
    {"index":1,"parent":[{"index":-1}],"nodeOffset":"6.67 2.5"},
    {"index":2,"parent":[{"index":-1}],"nodeOffset":"15 2.5"},
    {"index":3,"parent":[{"index":-1}],"nodeOffset":"23.33 2.5"},
    {"index":4,"parent":[{"index":-1}],"nodeOffset":"31.67 2.5"},
    {"index":5,"parent":[{"index":2},{"index":3},{"index":4}],"nodeOffset":"5.91 2.5"},
    {"index":6,"parent":[{"index":1},{"index":3},{"index":4}],"nodeOffset":"21.82 2.5"},
    {"index":7,"parent":[{"index":1},{"index":2},{"index":4}],"nodeOffset":"37.73 2.5"},
    {"index":8,"parent":[{"index":1},{"index":2},{"index":3}],"nodeOffset":"53.64 2.5"},
    {"index":9,"parent":[{"index":0},{"index":3},{"index":4},{"index":7},{"index":8}],"nodeOffset":"69.55 2.5"},
    {"index":10,"parent":[{"index":0},{"index":2},{"index":4},{"index":6},{"index":8}],"nodeOffset":"-39.55 27.5"},
    {"index":11,"parent":[{"index":0},{"index":2},{"index":3},{"index":6},{"index":7}],"nodeOffset":"-23.64 27.5"},
    {"index":12,"parent":[{"index":0},{"index":1},{"index":4},{"index":5},{"index":8},{"index":11}],"nodeOffset":"-7.73 27.5"},
    {"index":13,"parent":[{"index":0},{"index":1},{"index":3},{"index":5},{"index":7},{"index":10}],"nodeOffset":"8.18 27.5"},
    {"index":14,"parent":[{"index":0},{"index":1},{"index":2},{"index":5},{"index":6},{"index":9}],"nodeOffset":"24.09 27.5"}
],
"config":{}}

Ajustar a círculo

Figura
Figura. Ajuste a círculo en grafos

Podemos ajustar a círculo un grafo, como se observa en la Figura. Especificamos un radio para el círculo y un espaciado entre nodos. Podemos disponerlos en varios círculos concéntricos y posicionar un nodo en el centro del círculo. Al igual que para rejilla, aquí también podemos disponer un orden de aparición de los nodos.

Figura
Figura. Grafo Kneser 6,2 en círculo

Tomando la matriz de adyacencia del grafo Kneser 6,2 que vimos en el apartado anterior, el ajuste a círculo con radio 50 y espaciado 1 se observa en la Figura.

Figura
Figura. Grafo Kneser 6,2 en círculo con orden de nodos

Aunque la parte de ejecución de algoritmos aún no la he explicado, podemos ejecutar el algoritmo Ciclo Hamiltoniano con vuelta atrás, que en caso de que exista, nos devoverá un array, que para este grafo es [0,9,3,5,2,7,4,6,11,12,8,10,13,1,14,0]. Se trata del camino cerrado o ciclo que recorre todos los nodos. Si ponemos esa lista de nodos en el control orden de nodos obtendremos el grafo de la Figura, donde se observa que ahora hay una arista entre cada uno de los nodos recorriendo el círculo, que es precisamente ese ciclo Hamiltoniano.

Figura
Figura. Ciclo Hamiltoniano en Grafo Kneser 6,2

En la ejecución del algoritmo ese ciclo aparece resaltado en rojo, como se ve en Figura.

Figura
Figura. Grafo Kneser 6,2 en círculos concéntricos con un nodo en el centro

Por último, podemos disponer los nodos en 2 círculos concéntricos y ubicar el nodo 0 en el centro, como se observa en la Figura.

Ajustes visuales varios

Figura
Figura. Tamaño fuente en grafos

En los ajustes visuales varios encontramos opciones para cambiar el tamaño de fuente de texto, como se observa en la Figura. Por ahora sólo está esa opción aunque podría incorporar otras posteriormente.

El tamaño de la fuente de los textos se obtiene como 1/4 de la altura de línea, que a su vez es 1/10 del alto del SVG. En este caso se refiere al alto de líneas visuales donde se ubican los nodos, algo parecido al line-height de CSS. Así con un alto del SVG de 250, la altura de línea será 250/10=25 y el tamaño de la fuente será 25/4=6.25. En la Figura hemos cambiado el tamaño de la fuente a 10px, de tal forma que todos los elementos TEXT tiene una propiedad SVG font-size="6.25" y otra propiedad CSS style="font-size: 10px", sobrescribiendo esta última a la anterior, con lo que el texto se renderiza a 10px. En font-size el tamaño es el correspondiente a unidades relativas de ViewBox. Mientras que con CSS son unidades absolutas en píxeles, entendiéndolas absolutas respecto al mismo monitor donde se reproduce.

Ajustes con estilos CSS

Figura
Figura. Colores en grafos

Aparte de lo que vimos en el apartado anterior sobre ajustes visuales varios, veamos ahora que otras posibilidades hay para modificar el estilo visual de los elementos SVG. Ese grafo de la Figura, que también puede encontrar en las muestras de la aplicación, nos servirá para ver como configurar estilos a los elementos del SVG, tomando como ejemplos los colores de los elementos.

El siguiente JSON es la exportación de ese grafo:

{"nodes":[
    {"index":0,"parent":[{"index":-1}],"nodeOffset":"-1 0"},
    {"index":1,"parent":[{"index":0}],"nodeOffset":"-4 0"},
    {"index":2,"parent":[{"index":0}],"nodeOffset":"2 0"}
],
"config":{
    "wv":57,
    "hv":54,
    "width":190,
    "height":180,
    "adjust":false,
    "wvIni":57,
    "nodeColor":"cyan",
    "lineColor":"red",
    "markerEnd":"arrow"
}}

En el código SVG generado a continuación se observa que los PATH tiene color de línea stroke="red" tal como se especifica en la configuración con "lineColor":"red". Los POLYGON que son las flechas que forman parte de las aristas o líneas también tienen ese color. Los CIRCLE tiene color de fondo fill="cyan" como la configuración "nodeColor":"cyan", mientras que el borde es stroke="currentcolor" que es el color negro actual del contenedor, al igual que los TEXT que tienen fill="currentcolor".

<svg viewBox="0 0 57 54" width="190" height="180" xmlns="http://www.w3.org/2000/svg">
    <path d="M15 37.5 L27.5 12.5" stroke-width="0.3" stroke="red" fill="none" data-line="1,0"></path>
    <path d="M40 37.5 L27.5 12.5" stroke-width="0.3" stroke="red" fill="none" data-line="2,0"></path>
    <circle cx="27.5" cy="12.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="0"></circle>
    <text x="27.5" y="12.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="0">
        0
    </text>
    <circle cx="15" cy="37.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="1"></circle>
    <text x="15" y="37.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="1">
        1
    </text>
    <polygon points="24.7 18.09 21.88 20.33 24.61 21.69" fill="red" data-line="1,0"></polygon>
    <circle cx="40" cy="37.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="2"></circle>
    <text x="40" y="37.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="2">
        2
    </text>
    <polygon points="30.3 18.09 30.39 21.69 33.12 20.33" fill="red" data-line="2,0"></polygon>
</svg>
Figura
Figura. Estilo CSS en grafo

Podemos agregar color magenta al SVG en la configuración CSS estilo SVG como se observa en Figura quedando con el estilo CSS color: magenta, con lo que todos los currentcolor heredan ahora el color magenta, que aplica a los textos y los bordes de los círculos.

El SVG queda así con el estilo style="color: magenta" en su cabecera, siendo el resto igual:

<svg viewBox="0 0 57 54" width="190" height="180" style="color: magenta"
        xmlns="http://www.w3.org/2000/svg">
    ...
</svg>

En la Figura vemos la configuración CSS reglas elementos para incorporar estilo a los elementos del SVG que veremos a continuación. Se trata de incorporar estilo a los elementos sobrescribiendo los que especifiquen en la configuración o en el style del SVG.

Figura
Figura. Estilo CSS en grafos con style en SVG

Si ahí ponemos circle{stroke: green; stroke-width: 2} obtendremos el grafo de la Figura, dando color verde con grueso 2 a los bordes de todos los CIRCLE del SVG, agregando style="stroke: green; stroke-width: 2;" a esos elementos.

Figura
Figura. Estilo CSS en grafos con rules CSS

Si ahí ponemos circle[data-node="0"]{stroke: green; stroke-width: 2} path[data-line="2,0"]{stroke: blue; stroke-width: 1; stroke-dasharray: 1} obtendremos el grafo de la Figura, dando color verde con grueso 2 al borde exclusivamente del primer CIRCLE del nodo con índice 0 (data-node="0"). Y al PATH con color línea azul con puntos de la arista "2,0" (data-line="2,0").

El estilo CSS del SVG y las reglas CSS podrían incorporarse en un elemento <style> externo al SVG. En el siguiente ejemplo tomamos el grafo inicial sin nada de lo que hemos añadido y le ponemos la clase example_ a la configuración CSS clase SVG, que debe llevar obligatoriamente un guion bajo al final, siendo añadido si no lo lleva, con objeto de no interferir con otras clases de la página.

<style>
    svg.example_ {
        color: magenta;
    }
    svg.example_ circle[data-node="0"]{
        stroke: green;
        stroke-width: 2
    }
    svg.example_ path[data-line="2,0"]{
        stroke: blue;
        stroke-width: 1;
        stroke-dasharray: 1
    }
}</style>
<svg viewBox="0 0 57 54" width="190" height="180" class="example_"
    xmlns="http://www.w3.org/2000/svg">
    <path d="M15 37.5 L27.5 12.5" stroke-width="0.3" stroke="red" fill="none" data-line="1,0"></path>
    <path d="M40 37.5 L27.5 12.5" stroke-width="0.3" stroke="red" fill="none" data-line="2,0"></path>
    <circle cx="27.5" cy="12.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="0"></circle>
    <text x="27.5" y="12.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="0">
        0
    </text>
    <circle cx="15" cy="37.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="1"></circle>
    <text x="15" y="37.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="1">
        1
    </text>
    <polygon points="24.7 18.09 21.88 20.33 24.61 21.69" fill="red" data-line="1,0"></polygon>
    <circle cx="40" cy="37.5" r="6.25" stroke-width="0.3" stroke="currentcolor" fill="cyan" data-node="2"></circle>
    <text x="40" y="37.5" font-size="6.25" stroke-width="0.3" font-weight="normal" font-style="normal" font-family="Arial" 
        fill="currentcolor" text-anchor="middle" dominant-baseline="central" data-node="2">
        2
    </text>
    <polygon points="30.3 18.09 30.39 21.69 33.12 20.33" fill="red" data-line="2,0"></polygon>
</svg>
0 1 2
Figura. Estilo CSS en grafos con class en SVG

El código SVG debe exportarse usando la opción identificar elementos SVG para que agregue los data-node y data-line que identifican los elementos. Incorporamos ese código directamente en este punto de la página obteniendo lo mismo en la Figura que la vez anterior.

Figura
Figura. Estilo en grafos con configuraciones

En los ejemplos anteriores hemos agregado estilo CSS de colores de líneas, bordes y textos, así como anchos de líneas y anchos de bordes. Pero esos estilos también pueden agregarse con la aplicación. En la Figura se observa esto con un resultado similar a los anteriores, donde lo único que aplicamos con CSS es el subrayado del texto "2" de ese nodo. Este es el JSON resultante:

{"nodes":[
    {"index":0,"parent":[{"index":-1}],"nodeOffset":-1,
        "nodeBorderColor":"green","nodeBorderWidth":7},
    {"index":1,"parent":[{"index":0}],"nodeOffset":-4},
    {"index":2,"parent":[{"index":0,"lineColor":"blue",
        "lineDash":1,"lineWidth":2.5}],"nodeOffset":"2"}
],
"config":{
    "wv":57,
    "hv":54,
    "width":190,
    "height":180,
    "adjust":false,
    "wvIni":57,
    "rulesCss":"text[data-node=2]{text-decoration: underline}",
    "nodeColor":"cyan",
    "nodeFontColor":"magenta",
    "lineColor":"red",
    "markerEnd":"arrow"
}}

Se observan las propiedades nodeBorderColor y nodeBorderWidth que configuran color verde y ancho grueso para el borde del nodo "0". La arista "2-0" tiene color línea azul con lineColor, línea punteada con lineDash y ancho con lineWidth.

En la configuración general, aparte de nodeColor y lineColor que ya aparecía en los ejemplos anteriores, ahora agregamos nodeFontColor para el color magenta de los textos y la regla CSS text[data-node=2]{text-decoration: underline} que configura subrayado de texto para el "2", estilo que no es posible aplicar con la configuración de la aplicación.