如何更改d3图例条目间距/对齐

时间:2017-03-14 16:08:01

标签: d3.js svg

我有这个传说:

enter image description here

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

这是我的代码:

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

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

P.S。我正在使用d3 v3.5。

1 个答案:

答案 0 :(得分:3)

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

下面的代码段将所有内容置于彼此之外,然后使用svg计算每个图例的g宽度getBBox。然后,使用d3.sum计算在其之前附加的每个元素的宽度(因此应该在其左侧)并相应地将translate值设置为这些宽度的总和。

它可能可能会被清理一下,它有点快。如果在元素正确定位之前存在滞后,透明地附加它们然后在它们被定位后将它们淡入可能是一个优雅的(在视觉上,不那么编程)解决方案(或者最初将它们附加到视图框之外)。

d3v4:

&#13;
&#13;
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)";
})
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
&#13;
&#13;
&#13;

D3V3:

&#13;
&#13;
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)";
})
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;