【问题标题】:How to change d3 legend entry spacing/alignment如何更改 d3 图例条目间距/对齐方式
【发布时间】:2017-03-14 16:08:00
【问题描述】:

我有这个传说:

如您所见,每个图例条目的宽度相同。相反,我希望每个图例条目的宽度根据条目符号和文本的宽度而变化。最终,我希望前导条目文本的末端和下一个条目符号的开头之间的距离相同。换句话说,我希望“OA”和加号之间的距离与“OI”和菱形以及“RAC”和正方形之间的距离相同。我需要它基于像素(字符串长度不够)。我一直在尝试各种东西,但都没有成功。

这是我的代码:

        var legendData = [["OA", "yellow", "circle"], ["OI", "blue", "cross"], ["RARC", "green", "diamond"], ["CAPE", "red", "square"], ["Other", "black", "triangle-down"]];

        this.svg.selectAll('.legend').remove() //remove remnants of previous legend so new legend has clean slate...eliminates overlays during resizing

        var legend = this.svg.append('g')
            .attr("class", "legend")
            .attr("height", 0)
            .attr("width", 0)
            .attr('transform', 'translate(' + (ScatterChart.Config.margins.left + (width * .008)) + ',' + (height += .40 * ScatterChart.Config.margins.bottom) + ')');

        var legendRect = legend
            .selectAll('g')
            .data(legendData)
            ;

        var labelLength = 0
        var labelLengthPrevious = 0

        var legendRectE = legendRect.enter()
            .append("g")
            .attr("transform", function (d, i) {
                //labelLength = labelLengthPrevious //Need to figure out pixel lengths
                //labelLengthPrevious += (d[0].length) + 50
                //return 'translate(' + labelLength + ', ' + 0 + ' )';  // y is constant and x growing
                return 'translate(' + (i * (.15 * width)) + ', ' + 0 + ' )';  // y is constant and x growing
            })
            ;

        legendRectE
            .append('path')
            .attr("d", d3.svg.symbol().type((d) => {
                return d[2]
            }
            ).size((d3.min([height, width]) * ScatterChart.Config.axisFontMultiplier) * (d3.min([height, width]) * ScatterChart.Config.symbolSizeMultiplier)))
            .style("fill", function (d) {
                return d[1];
            })
            .attr('stroke', 'black')
            ;

        //This asserts legendRectE as a node...I think. I do this so I can use the width and height measurements of legendRectE.
        var node: SVGElement = <SVGElement>legendRectE.node()

        legendRectE
            .append("text")
            .attr("x", function (d) {
                return node.getBoundingClientRect().width
            })
            .attr("y", function (d) {
                return node.getBoundingClientRect().height / 2.25
            })
            .text(function (d) {
                return d[0];
            })
            .style('font-size', function () { return d3.min([height, width]) * ScatterChart.Config.axisFontMultiplier + "px" })
            ;

我认为答案与这一行有关:return 'translate(' + (i * (.15 * width)) + ', ' + 0 + ' )'; // y is constant and x growing。现在,它只是通过将索引乘以图表宽度的 15% 来向右移动。我想我需要以某种方式替换legendRectE(或legendRectlegend)的宽度来代替(I * (.15 * width))。我不知道该怎么做。

你可以看到我在后面的代码中使用了下面的来获取legendRectE的宽度:var node: SVGElement = &lt;SVGElement&gt;legendRectE.node(),然后是node.getBoundingClientRect().width

node.getBoundingClientRect().width 给了我一个你现在看到的宽度值,但是当我使用相同的方法来确定我提到的翻译的值时,它会窒息;当我使用legendRectlegend 而不是legendRectE 时,我只得到'0'。

我想我可以像这样编辑转换函数:

    var legendRectE = legendRect.enter()
        .append("g")
        .attr("transform", function (d, i) {
             var node: SVGElement = <SVGElement>legendRectE.node()
             return 'translate(' + node.getBoundingClientRect().width + ', ' + 0 + ' )';  // y is constant and x growing
        })
        ; 

显然,我错了。有什么想法/建议吗?

附言我正在使用 d3 v3.5。

【问题讨论】:

    标签: d3.js svg


    【解决方案1】:

    挑战在于(据我所知)最初添加元素时很难确定变换,因为宽度未知。但是您可以在所有图例条目全部附加后返回并计算它们的宽度,然后相应地重新定位图例条目。

    下面的 sn-p 将所有内容置于彼此之上以开始,然后使用 getBBox 计算每个图例 gsvg 宽度。然后,使用 d3.sum 计算附加在它之前的每个元素的宽度(因此应该在它的左侧),并将平移值相应地设置为这些宽度的总和。

    大概可以清理一下,有点快。如果在元素被正确定位之前存在延迟,则透明地附加它们,然后在它们定位后淡入它们可能是一种优雅的(视觉上,较少以编程方式)解决方案(或最初将它们附加到视图框之外)。

    d3v4:

    var data = ['short text','much longer text','the longest text passage','short text'];
    
    var svg = d3.select('body')
      .append('svg')
      .attr('width',800)
      .attr('height',200);
      
    var groups = svg.selectAll('g')
     .data(data)
     .enter()
     .append('g');
     
    var rect = groups.append('rect')
      .attr('fill',function(d,i) { return d3.schemeCategory10[i];})
      .attr('height',30)
      .attr('width',30);
    
    var text = groups.append('text')
      .attr('y', 20)
      .attr('x', 35)
      .text(function(d) { return d; });
      
    // Now space the groups out after they have been appended:
    var padding = 10;
    groups.attr('transform', function(d,i) { 
      return "translate("+(d3.sum(data, function(e,j) {
        if (j < i) { return groups.nodes()[j].getBBox().width; } else return 0; }) + padding * i) + ",0)";
    })
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"&gt;&lt;/script&gt;

    d3v3:

    var data = ['short text','much longer text','the longest text passage','short text'];
    
    var svg = d3.select('body')
      .append('svg')
      .attr('width',800)
      .attr('height',200);
      
    var groups = svg.selectAll('g')
     .data(data)
     .enter()
     .append('g');
     
    var color = ["orange","red","purple","green"];
     
    var rect = groups.append('rect')
      .attr('fill',function(d,i) { return color[i];})
      .attr('height',30)
      .attr('width',30);
    
    var text = groups.append('text')
      .attr('y', 20)
      .attr('x', 35)
      .text(function(d) { return d; });
      
    // Now space the groups out after they have been appended:
    var padding = 10;
    groups.attr('transform', function(d,i) { 
      return "translate("+(d3.sum(data, function(e,j) {
        if (j < i) { return groups[0][j].getBBox().width; } else return 0; }) + padding * i) + ",0)";
    })
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 感谢您的帮助。您建议的编码对我有用,除了最后一部分。它在nodes() 上窒息。我收到一个错误:[ts] Property 'nodes' does not exist on type 'Selection&lt;string[]&gt;. 知道为什么以及如何使它工作吗?我认为这是因为我使用的是 TypeScript,但我不确定。
    • 我的错,忘了v3和v4在这方面有一些区别。立即更新答案 - 请参阅第二个 sn-p。
    • 谢谢@Andrew。我能够让它与您最近的更改一起工作......通过一个小调整:return (&lt;any&gt;groups)[0][j].getBBox().width
    猜你喜欢
    • 2014-04-16
    • 1970-01-01
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-25
    相关资源
    最近更新 更多