2012-09-07 19 views
6

Creé un Árbol en D3.js basado en el Node-link Tree de Mike Bostock. El problema que tengo y que también veo en Mike's Tree es que la etiqueta de texto se superpone/solape a los nodos circulares cuando no hay suficiente espacio en lugar de extender los enlaces para dejar algo de espacio.¿Cómo evitar la superposición de elementos de texto en TreeMap cuando se abren elementos secundarios en D3.js?

Como nuevo usuario, no tengo permitido subir imágenes, así que aquí hay un link en Mike's Tree, donde puede ver las etiquetas de los nodos precedentes solapando los siguientes nodos.

He intentado varias cosas para solucionar el problema mediante la detección de la longitud de píxeles del texto con:

d3.select('.nodeText').node().getComputedTextLength(); 

Sin embargo, esto sólo funciona después de que procesa la página cuando necesito la longitud del elemento de texto más largo antes de que yo hacer.

conseguir el artículo texto más largo antes de que el procesamiento con:

nodes = tree.nodes(root).reverse(); 

var longest = nodes.reduce(function (a, b) { 
    return a.label.length > b.label.length ? a : b; 
}); 

node = vis.selectAll('g.node').data(nodes, function(d, i){ 
    return d.id || (d.id = ++i); 
}); 

nodes.forEach(function(d) { 
    d.y = (longest.label.length + 200); 
}); 

sólo se devuelve la longitud de la cadena, mientras que el uso de

d.y = (d.depth * 200); 

hace que todos los eslabones de una longitud estática y no cambia de tamaño tan hermoso cuando nuevos nodos se abren o cierran.

¿Hay alguna manera de evitar esta superposición? Si es así, ¿cuál sería la mejor manera de hacer esto y mantener la estructura dinámica del árbol?

Hay 3 posibles soluciones que puede llegar a, pero no son tan sencillas:

  1. La detección de longitud de la etiqueta y el uso de una elipse donde se desborda nodos secundarios. (lo que haría que las etiquetas sean menos legibles)
  2. escalando el diseño dinámicamente al detectar la longitud de la etiqueta y diciendo a los enlaces que se ajusten en consecuencia. (lo cual sería mejor pero parece realmente difícil
  3. escala el elemento svg y utiliza una barra de desplazamiento cuando las etiquetas comienzan a ejecutarse. (No estoy seguro de que esto sea posible ya que he estado trabajando suponiendo que el SVG necesita tener un establecer alto y ancho)

Respuesta

7

Por lo tanto, el siguiente enfoque puede dar diferentes niveles del diseño a diferentes "alturas" .Tiene que tener cuidado de que con un diseño radial corra el riesgo de no tener suficiente propagación para pequeños círculos para hinchar su texto sin superposiciones, pero vamos a ignorar eso por ahora.

La clave es darse cuenta de que la disposición del árbol simplemente asigna las cosas a un espacio arbitrario de ancho y alto y que el diagono al proyección asigna el ancho (x) al ángulo y la altura (y) al radio. Además, el radio es una función simple de la profundidad del árbol.

Así here es una manera de reasignar las profundidades en base a las longitudes de texto:

En primer lugar, yo uso el siguiente (jQuery) para calcular los tamaños de texto máximos para:

var computeMaxTextSize = function(data, fontSize, fontName){ 
    var maxH = 0, maxW = 0; 

    var div = document.createElement('div'); 
    document.body.appendChild(div); 
    $(div).css({ 
     position: 'absolute', 
     left: -1000, 
     top: -1000, 
     display: 'none', 
     margin:0, 
     padding:0 
    }); 

    $(div).css("font", fontSize + 'px '+fontName); 

    data.forEach(function(d) { 
     $(div).html(d); 
     maxH = Math.max(maxH, $(div).outerHeight()); 
     maxW = Math.max(maxW, $(div).outerWidth()); 
    }); 

    $(div).remove(); 
    return {maxH: maxH, maxW: maxW}; 
} 

Ahora recursivamente construir una matriz con una matriz de cadenas por nivel:

var allStrings = [[]]; 
var childStrings = function(level, n) { 
    var a = allStrings[level]; 
    a.push(n.name); 

    if(n.children && n.children.length > 0) { 
     if(!allStrings[level+1]) { 
      allStrings[level+1] = []; 
     } 
     n.children.forEach(function(d) { 
      childStrings(level + 1, d); 
     }); 
    } 
}; 
childStrings(0, root); 

Y luego calcular la longitud máxima del texto en cada nivel.

var maxLevelSizes = []; 
allStrings.forEach(function(d, i) { 
    maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif')); 
}); 

Entonces calcular el ancho total del texto para todos los niveles (añadiendo el espaciamiento de los iconos circulares pequeños y algo de relleno para que se vea bonito). Este será el radio del diseño final. Tenga en cuenta que usaré este mismo importe de relleno nuevamente más adelante.

var padding = 25; // Width of the blue circle plus some spacing 
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding}); 

var diameter = totalRadius * 2; // was 960; 

var tree = d3.layout.tree() 
    .size([360, totalRadius]) 
    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2)/a.depth; }); 

Ahora podemos llamar al diseño como de costumbre. Hay una última pieza: para calcular el radio para los diferentes niveles necesitaremos una suma acumulativa de los radios de los niveles anteriores. Una vez que tenemos eso, simplemente asignamos los nuevos radios a los nodos calculados.

// Compute cummulative sums - these will be the ring radii 
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) { 
    prev.push(prev[index] + curr.maxW + padding);     
    return prev; 
},[0]);              

var nodes = tree.nodes(root); 

// Assign new radius based on depth 
nodes.forEach(function(d) { 
    d.y = newDepths[d.depth]; 
}); 

Eh listo! Esta quizás no sea la solución más limpia, y quizás no aborde todas las inquietudes, pero debería ayudarlo a comenzar. ¡Que te diviertas!

+0

Wow, lo siento, tardó tanto tiempo en responder, pero no lo vi hasta ahora. Al final, nunca terminé solucionando este problema, pero gracias por su respuesta. Veré si puedo encontrar el código anterior e intento solucionar el problema. – alengel

Cuestiones relacionadas