【问题标题】:How to avoid the overlapping of text elements on the TreeMap when child elements are opened in D3.js?在 D3.js 中打开子元素时,如何避免 TreeMap 上的文本元素重叠?
【发布时间】:2012-09-01 03:20:28
【问题描述】:

我基于 Mike Bostock 的节点链接树在 D3.js 中创建了一个树。我遇到的问题以及我在 Mike's Tree 中也看到的问题是,当空间不足时,文本标签会重叠/低于圆形节点,而不是扩展链接以留出一些空间。

作为一个新用户,我不允许上传图片,所以这里有一个 link 到 Mike's Tree,您可以在其中看到前面节点的标签与后面的节点重叠。

我尝试了各种方法来通过检测文本的像素长度来解决问题:

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 需要设置高度和宽度)。

【问题讨论】:

    标签: svg d3.js label overlap treemap


    【解决方案1】:

    所以下面的做法可以给不同层次的布局不同的“高度”。您必须注意,如果使用径向布局,您可能没有足够的散布来让小圆圈散开您的文本而不会重叠,但现在让我们忽略这一点。

    关键是要认识到,树布局只是将事物映射到具有宽度和高度的任意空间,并且对角线投影将宽度 (x) 映射到角度,将高度 (y) 映射到半径。此外,半径是树深度的简单函数。

    所以here 是一种根据文本长度重新分配深度的方法:

    首先,我使用以下 (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];
    });
    

    哎呀!这可能不是最干净的解决方案,也可能无法解决所有问题,但它应该可以帮助您入门。玩得开心!

    【讨论】:

    • 哇,抱歉花了这么长时间才回复,但我直到现在才看到。最后我从来没有最终解决这个问题,但感谢您的回答。我会看看我是否能找到旧代码并尝试解决问题。
    猜你喜欢
    • 2020-01-24
    • 2020-11-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    • 1970-01-01
    • 2014-03-31
    • 2023-03-13
    • 1970-01-01
    相关资源
    最近更新 更多