Как избежать перекрытия текстовых элементов на TreeMap при открытии дочерних элементов в D3.js?

Я создал дерево в D3.js на основе дерева узлов связи Майка Бостока. Проблема, с которой я сталкиваюсь и которую я также вижу в дереве Майка, заключается в том, что текстовая метка перекрывает/не перекрывает узлы круга, когда места недостаточно, а не расширяет ссылки, чтобы оставить немного места.

Мне как новому пользователю не разрешено загружать изображения, поэтому вот ссылка на дерево Майка, где вы можете видеть метки предыдущих узлов, перекрывающих следующие узлы.

Я пробовал разные вещи, чтобы решить проблему, определяя длину текста в пикселях с помощью:

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

Однако это работает только после рендеринга страницы, когда мне нужна длина самого длинного текстового элемента перед рендерингом.

Получение самого длинного текстового элемента перед визуализацией с помощью:

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);
});

возвращает только длину строки, при использовании

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

делает каждую ссылку статической длины и не изменяет размер так красиво, когда открываются или закрываются новые узлы.

Есть ли способ избежать этого дублирования? Если да, то как лучше всего это сделать и сохранить динамическую структуру дерева?

Есть 3 возможных решения, которые я могу придумать, но они не так просты:

  1. Определение длины метки и использование многоточия там, где она выходит за пределы дочерних узлов. (что сделало бы этикетки менее читаемыми)
  2. динамическое масштабирование макета, определяя длину метки и сообщая ссылкам о соответствующей корректировке. (что было бы лучше, но кажется действительно сложным
  3. масштабируйте элемент svg и используйте полосу прокрутки, когда метки начинают переполняться. (не уверен, что это возможно, поскольку я работал с предположением, что SVG должен иметь заданную высоту и ширину).

person alengel    schedule 07.09.2012    source источник


Ответы (1)


Таким образом, следующий подход может дать разным уровням макета разную «высоту». Вы должны позаботиться о том, чтобы при радиальной компоновке у вас не было достаточного разброса для маленьких кругов, чтобы развернуть ваш текст без перекрытий, но давайте пока проигнорируем это.

Ключ в том, чтобы понять, что древовидная компоновка просто сопоставляет вещи с произвольным пространством ширины и высоты и что диагональная проекция сопоставляет ширину (x) с углом, а высоту (y) с радиусом. Более того, радиус является простой функцией глубины дерева.

Итак, здесь показан способ переназначения глубины на основе длины текста:

Прежде всего, я использую следующее (jQuery) для вычисления максимальных размеров текста для:

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};
}

Теперь я рекурсивно создам массив с массивом строк на уровне:

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);

А затем вычислить максимальную длину текста на уровне.

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

Затем я вычисляю общую ширину текста для всех уровней (добавляя интервалы для значков маленьких кругов и немного отступов, чтобы это выглядело красиво). Это будет радиус окончательного макета. Обратите внимание, что позже я снова буду использовать это же количество отступов.

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; });

Теперь мы можем вызывать макет как обычно. И еще одна последняя деталь: чтобы вычислить радиус различных уровней, нам понадобится кумулятивная сумма радиусов предыдущих уровней. Получив это, мы просто назначаем новые радиусы вычисляемым узлам.

// 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];
});

Эх вуаля! Это, возможно, не самое чистое решение и, возможно, не решает всех проблем, но оно должно помочь вам начать работу. Повеселись!

person Superboggly    schedule 13.12.2012
comment
Вау, извините, что так долго не отвечал, но я не видел этого до сих пор. В конце концов, я так и не решил эту проблему, но спасибо за ваш ответ. Я посмотрю, смогу ли я найти старый код и попытаюсь решить проблему. - person alengel; 28.10.2013